C/C++常见概念辨析

Mostly Referencehere,  slightly change

1. 指针

 int (*p)[5]  指针,指向含5个元素数组

 int *p[5]  数组,长度为5,数组中每一个元素指向一个整型变量。

 int *f( int i, int j)和 int (*p)( int i ,int j)

 前者是返回指针的函数,它是一个函数的声明,后者是指向函数的指针,它定义了一个指针。

2. 指针/引用

1). 指针是一个实体,而引用仅是个别名;
2). 引用使用时无需解引用(*),指针需要解引用;
3). 引用只能在定义时被初始化一次,之后不可变;指针可变;
4). 引用没有 const,指针有 const;
5). 引用不能为空,指针可以为空;
6). “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
7). 指针和引用的自增(++)运算意义不一样;
8). 从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。

3. new/ delete -> malloc/free

int *p = (int *) malloc(sizeof(int) * length);   void free( void * memblock );

int *p2 = new int[length];   delete []objects;

new 内置了sizeof、类型转换和类型安全检查功能;非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作

malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。对于非内部数据类对象而言执行对象构造析构的时候会调用new/delete, 却不会调用malloc/free

1)new自动计算需要分配的空间,而malloc需要手工计算字节数
2)new是类型安全的,而malloc不是,比如:

<span style="font-family:Microsoft YaHei;"><span style="font-family:KaiTi_GB2312;">    int* p = new float[2]; // 编译时指出错误 
    int* p = malloc(2*sizeof(float)); // 编译时无法指出错误 </span></span>
     new operator 由两步构成,分别是 operator new 和 construct
3)operator new对应于malloc,但operator new可以重载,可以自定义内存分配策略,甚至不做内存分配,甚至分配到非内存设备上。而malloc无能为力
4)new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
5)malloc/free要库文件支持,new/delete则不要。

4. 堆/栈

一个由c/C++编译的程序占用的内存分为以下几个部分
1)栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2)堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3)全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放 
4)文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5)程序代码区—存放函数体的二进制代码。

int a = 0; 全局初始化区  
char *p1; 全局未初始化区  
main()  
{  
int b; 栈  
char s[] = "abc"; 栈  
char *p2; 栈  
char *p3 = "123456"; 123456\0在常量区,p3在栈上。  
static int c =0; 全局(静态)初始化区  
p1 = (char *)malloc(10);  
p2 = (char *)malloc(20);  
分配得来得10和20字节的区域就在堆区。  
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。  
}

5. static

主要控制存储方式以及可见性

• 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

• 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访 问,但不能被模块外其它函数访问。它是一个本地的全局变量。

• 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是 ,这个函数被限制在声明它的模块的本地范围内使用。

在C语言中,static有一点稍微的不同。

static 变量: 
1).变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。  
2).变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。

static函数

1).静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。

2).static函数(也叫内部函数)只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。


在类里面

类体中的数据成员的声明前加上static关键字,该数据成员就成为了该类的静态数据成员。和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。同时,静态数据成员: 
1).静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中;

2).静态数据成员被 类 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员;

3).静态数据成员可以成为成员函数的可选参数,而普通数据成员则不可以;

4).★静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的 指针或引用;

5).★const函数中可以改变类的static成员。不能改变普通成员;

静态成员函数:

静态成员函数是为类的全体对象服务的即用事例化该类就可以访问静态成员函数了。这样就不需要定义一个全局函数了,有下面的特点

1).静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存;

2).静态成员函数不可以调用类的非静态成员。因为静态成员函数不含this指针;

3).静态成员函数不可以同时声明为 virtual、const、volatile函数;

内存分配

类的静态成员变量和函数中的静态变量一样,在编译的时候就分配内存了,存放在data段,直到程序退出才释放,并不是随着对象的删除而释放的。

如下例子所示:

static变量内存中仅有一份,为所有类Objects 共享,声明在外面

static函数没有this指针,所有只能访问static变量,不可以访问类中其他变量

#include <iostream>
 
using namespace std;

class Box
{
   public:
      static int objectCount;
      // Constructor definition
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // Increase every time object is created
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;
ll
int main(void)
{
  
   // Print total number of objects before creating object.
   cout << "Inital Stage Count: " << Box::getCount() << endl;

   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects after creating object.
   cout << "Final Stage Count: " << Box::getCount() << endl;

   return 0;
}

output as follows

Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2

6.extern "C" 的作用。

实现C++与C及其它语言的混合编程.

以int func(int, int)为例,C的编译器会将名字改编为_func, 而C++的编译器会改编为_func_int_int 或_funcii(各编译器不同)。
如果这个函数在C中编译成库,目标文件中函数名为_func,当这个函数中C++中被调用时,C++的编译器就会到目标文件中寻找_funcii,结果找不到,出错。
所以为了防止这种问题,在C++调用时,将函数声明前加个extern "C" 告诉C++的编译器,不要对名字再进修饰,而直接去找_func。

7.volitale关键字

如果一个基本变量被volatile修饰,编译器将不会把它保存到寄存器中,而是每一次都去访问内存中实际保存该变量的位置上。这一点就避免了没有volatile修饰的变量在多线程的读写中所产生的由于编译器优化所导致的灾难性问题。所以多线程中必须要共享的基本变量一定要加上volatile修饰符。当然了,volatile还能让你在编译时期捕捉到非线程安全的代码。

8.explicit关键字

explicit 可以有效得防止构造函数的隐式转换带来的错误或者误解。

class String {
public:
    String(int n); // allocate n bytes to the String object
    String(const char *p); // initializes object with char *p
};

上面的定义,下面char 类型会自动转换为int

String mystring = 'x';
而如果声明为 explicite,则不会有这种自动转换

class String {
public:
    explicit String (int n); //allocate n bytes
    String(const char *p); // initialize sobject with string p
};

9. const

const修饰类的数据成员:表示成员常量,不能被修改,同时它只能在初始化列表中赋值。

函数使用const:onst 放在函数后面,只有成员函数声明为const 才有意义,const函数里面才有const this 指针。表示不会改变类的成员(除非该class 为 mutable).

const 放在函数的前面,表示是返回一个const 指针或者引用,此种用法不要轻易使用。

const类对象/指针/引用:只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用

10.C++中四种类型转换符。

const_cast,字面上理解就是去const属性。
static_cast,命名上理解是静态类型转换。如int转换成char。
dynamic_cast,命名上理解是动态类型转换。如子类和父类之间的多态类型转换。
reinterpreter_cast,仅仅重新解释类型,但没有进行二进制的转换。

11.STL中各种容器底层实现和特点及使用场合.

容器:

vector  底层数据结构为数组 ,支持快速随机访问;vector 容量永远大于等于其大小,动态空间,相比array空间灵活性好
list       底层数据结构为双向链表,支持快速增删
deque 底层数据结构为一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,也支持随机访问 

适配器:对容器的再封装 
stack   底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
queue 底层一般用23实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时 

priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现 



hashmap 以 hashtable 为底层机制。运用map, 为的是能够根据键值快速搜寻元素。因为map底层为RB-tree, 有自动排序功能(begin()到end()是有序的)而hashtable没有, 反应出来的结果就是map的元素有自动排序功能而hash_map没有。 map的特性是, 每一个元素都同时拥有一个实值(value)和一个键值(key), 这点在hash_map中也是一样的。

hashmap在查询较多而插入删除较少的情况下会有很高的效率。因为非顺序的map在插入删除操作所需的消耗几乎是线性增长的,不比map有递减趋势的增长;


你需要“可以在容器的任意位置插入一个新元素”的能力吗?如果是,你需要序列容器,关联容器做不到。
你关心元素在容器中的顺序吗?如果不,散列容器就是可行的选择。否则,你要避免使用散列容器。
你需要哪一类迭代器?如果必须是随机访问迭代器,在技术上你就只能限于vector、deque和string。
当插入或者删除数据时,是否非常在意容器内现有元素的移动?如果是,你就必须放弃连续内存容器。
容器中的数据的内存布局需要兼容C吗?如果是,你就只能用vector。
查找速度很重要吗?如果是,你就应该看看散列容器(优于)排序的vector(优于)标准的关联容器大概是这个顺序。
你需要插入和删除的事务性语义吗?也就是说,你需要有可靠地回退插入和删除的能力吗?如果是,你就需要使用基于节点的容器。如果你需要多元素插入的事务性语义,你就应该选择list,因为list是唯一提供多元素插入事务性语义的标准容器。事务性语义对于有兴趣写异常安全代码的程序员来说非常重要。
你要把迭代器、指针和引用的失效次数减到最少吗?如果是,你就应该使用基于节点的容器,因为在这些容器上进行插入和删除不会使迭代器、指针和引用失效(除非它们指向你删除的元素)。一般来说,在连续内存容器上插入和删除会使所有指向容器的迭代器、指针和引用失效。

12.strcpy/strncpy

都是字符串的拷贝函数,首先看函数原型

char * strcpy ( char * destination, const char * source );

src和dest所指内存区域不可以重叠且,dest必须有足够的空间来容纳src的字符串。返回指向dest的指针。把src所指由'\0'结束的字符串的前n个字节复制到dest所指的数组中。

char * strncpy ( char * destination, const char * source, size_t num );

第三个参数表示从source处拷贝的最大字符串数。


如果src的前n个字节不含NULL字符,则结果不会以NULL字符结束。
如果src的长度小于n个字节,则以NULL填充dest,直到n个字节。

image
src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
返回指向dest的指针。

char * strcpy ( char * destination, const char * source ) 
{ 
if(destination == NULL || source == NULL) 
return NULL; 
char *temp = destination;
while((*temp ++ = *source++) != '\0'); 
return destination;     
} 


if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

string 类实现

string::String(const char *str)
{
   if(str == NULL)
  {
     m_data = new char[1];
     m_data[0] = ' ';
  }
 else
 {
   m_data = new char[strlen(str) + 1];  //strlen 不计算结尾的空字符
   strcpy(m_data, str);
 }
}
String::String(const string &other)
{
  m_data = new char[strlen(other.m_data) +1]
  strcpy(m_data, other.m_data);
}
String&::operator = (const String& that)
{
  if(this != that)
      return *this;
  delete []m_data;
  m_data = new char[strlen(other.m_data)+1];
  strcpy(m_data,that.m_data);
  return *this;
}

      


13.深拷贝/ 浅拷贝

以下情况都会调用拷贝构造函数:
(1)一个对象以值传递的方式传入函数体 
(2)一个对象以值传递的方式从函数返回 
(3)一个对象需要通过另外一个对象进行初始化。

缺省拷贝构造函数在拷贝过程中是按字节复制的,对于指针型成员变量只复制指针本身,而不复制指针所指向的目标--浅拷贝。

如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

几种考虑较周期的写法:

String& operator=(const String& that) {  
if(&that != this) {  
         String str (that);  
char *psz = m_psz;  
         m_psz = str.m_psz;  
         str.m_psz = psz;  
     }  
return *this;  
}

String& operator=(cosnt String& that) {   
     if(&that != this) {   
         char *psz = strcpy (new char[strlen(that.m_psz)+1],that.m_psz);//如果失败会抛出异常,m_psz最后在析构函数里释放   
         delete[] m_psz;   
         m_psz = psz;      
     }   
     return *this;   
 }  

14.类模板,函数模板

template<class  形参名,class 形参名,…>   class 类名

    { ... };

 template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)

 {

    函数体

 }


15.静态多态/动态多态

函数重载 是同一个类中实现的静态多态, 而虚函数是基类和派生类共同达到的 动态多态

16. 宏定义 / inline 函数

使用宏定义:一是它避开了类型检查,在某些情况下,会导致不同类型参数之间的比较,引起错误;二是可能在不该替换的地方进行了替换

对于单纯的变量,最好以 const对象或enums 替换# defines

对于行似函数的宏(macros), 最好改用inline 函数替换 #defines.



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不负初心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值