C/C++重难点总结系列(二)

11.关于const 指针

(1)常量指针(指向常量的指针:底层const)   

如:

  1. const int *pi=&a;  
 此处const限制的是指针pi解引用操作,即*pi不能修改,但pi自身可以修改。同时,const对*p的限定不会影响到所指向的值的变更。如果指向的a不是const int a,则直接对a修改仍然是可以的,只是不能通过解引用来修改a。(初学者最难理解的地方)

(2)指针常量(指针自身为常量:顶层const)

如:

  1. charconst ptr="abc";  
此处const 限定的是指针本身,即ptr自身的值(所指向变量的地址)不可修改,但不限定解引用*ptr对指向的变量的修改。

如:

  1. *(ptr+1)='c';//ok:此时*ptr=="acc"                              
  1. *ptr++;//error:不能对ptr做自增运算  

(3)指向常量的指针常量(同时限定指针本身和解引用操作)

  1. const double *const p= &pi//前面定义:const double pi=3.14;  
此处两个const对p和*p进行了限定,即不能修改p的同时也不能修改*p。

12.关于string和C风格字符串

 (1)使用区别:

a.比较

c-style字符串:strcmp(s1,s2);

        string:!=,  ==,    >, <,    >=,<=

b.拷贝

c-style字符串:strcpy(s1,s2); strncpy(s1,s2,10);

string:string s1=s2;//拷贝构造 s1=s2;//赋值

c.连接

c-style字符串:strcat(s1,s2);        strncat(s1,s2,10);

string:string s3=s1+s2;         s1=s1+s2;

d.计算长度

c-style字符串:int length=strlen(s1);       int length=sizeof(s1)/sizeof(char)-1;

string:size_type size=ys1.size();

 (2)相互转化:

c-style字符串转string:直接构造强制转化,如:

  1. char *pStr="hello world!";  
  2. string s1=string(pStr);  

        string 转c-style字符串:调用string的c_str成员,如:

  1. string s1("hello world!");   
  2. char *pStr=s1.c_str();  

注:C++中尽量使用string来操作字符串,更加安全便捷。c-style字符串往往用于旧式函数接口,如文件流的open()函数参数文件名就是c-style字符串。

13.关于auto和decltype关键字(c++11)

(1)auto:编译器自动判断表达式类型,弱化类型识别。

(2)decltype:decltype()计算括号中的类型并用来定义一个新的对象。括号中可以是变量、引用、表达式。

14.关于容器和迭代器的理解

标准容器和迭代器采用模版类实现,泛型编程范式,理应从泛型编程的思想去理解。

容器:可理解成封装好的泛化的数据结构,vector作为容器的一种,以它为例,从c角度来看,vector实为不断malloc和free的“动态分配的不定长数组”,但是这种自动增长会带来效率上的折损,同时带来安全性和便捷性的提升。

迭代器:可理解成泛化的指针,专门用来访问与之对应容器元素的数据类型。并且,在泛型算法中迭代器作为统一的接口而方便算法的调用。内部实现时对大多数指针操作符(包括++,--,*等)进行重载,故可以利用指针的操作方式来访问专门的容器元素。

注:不是所有的指针操作符都会被重载,如==和!=只对vector和string的迭代器有效。

15.关于数组形参

数组不允许拷贝,所以传递数组参数时不能用值传递,数组作为形参会转化为指针的方式传递,指针指向数组首元素的地址

注:(1)数组传参时不会进行越界检查(C陷阱),若越界访问编译器不会报错,而是产生随机数。

       (2)为防止(1)中的错误,一个好习惯是数组形参后往往跟一个表达数组长度的形参,如:

  1. void print(const int argv[],size_t size);  
  2. int main(int argc,char *argv[]);  

16.返回指向数组的指针或引用

如:

  1. int (*func(int i))[10];  
 为便于阅读,常用typedef简化代码,如:

  1. typedef int arr[10];  
  2. arr* func(int i);  

17.关于函数指针

(1)声明方法:若函数原型为:

  1. bool lengthCompare(const string &,const string &);  

则指向它的函数指针为:

  1. bool (*pFunc)(const string &,const string &);  
  2. pFunc=lengthCompare;//指向函数入口地址  

(2)函数指针作参数(C/C++中回调思想的实现方法)
如:

  1. void useBigger(const string &,const string &,bool (*pFunc)(const string &,const string &));//参3为函数指针,回调中参3的实参被称为回调函数  
  2. useBigger(s1,s2,lengthCompare);//调用示例,函数名被自动转化成函数指针  
(3)函数指针作返回值

如:

  1. int (*foo(int))(int *,int );  
  2. //同理,可结合typedef或using来简化代码  
  3. using Func=int (int*,int);//右边为函数标号,没有函数名,与typedef int Func(int *,int);等价  
  4. Func* foo(int);  
(4)用函数指针调用函数

如:

bool res=(*pFunc)("hello","world");//等价于bool res=lengthCompare("hello","world");


18.关于常量成员函数

类的成员函数形参列表中都有一个隐藏的this指针常量,总是指向这个对象(相当于顶层const的效果,this指针本身是常量,不可变更指向),若在成员函数参数列表后加上const修饰,则this指针指向一个常量(相当于加入了底层const的效果),此时成员函数只可读而不可写(不可修改成员变量),这种成员函数被称为常量成员函数。

注:通过区分成员函数是否是常量成员函数可以重载!

19.关于字节对齐与存储空间问题

各大互联网公司常考题,现总结如下:

(1)结构体成员从结构体最大成员的整数倍开始存储;

(2)结构体总大小一定为最大成员的整数倍,不足的补齐。

(3)VS编译器默认按8字节对齐,gcc默认按4字节对齐,用#pragma pack()宏可以修改对齐方式,如#pragma pack(1)设为单字节对齐,则默认对齐规则失效。

(4)联合体(共用体Union)所占空间为最大成员的空间,同样需考虑字节对齐方式。

(5)enum变量实际上按整形(int)存储,故32位/64位机上占用空间固定为4个字节,其列表中的枚举值最多可达2^32个。

(6)指针本身的大小与指向的对象类型无关,与平台有关,因为指针存放的是地址,故32位机上指针大小固定为4字节,64位机上固定为8字节。

(7)有虚函数的类(多态类)在构造时会往对象内存中插入一个虚表指针,与普通指针所占空间相同(32位4字节,64位8字节)。

(8)虚继承时派生类也会产生一个虚基类指针,与普通指针所占空间相同(3位4字节,64位8字节)

(9)static数据成员属于类域(所有对象共享),存放在全局(静态)数据区,因此不算入某个对象所占的空间。

(10)无任何数据成员的空类,编译器会插入一个单字节变量(通常为char型),因此占用1字节空间。若将该空类作为基类,则其派生类不计算这1字节的额外空间(编译器优化:空基类继承优化)


20.关于何时会调用拷贝构造函数

(1)定义对象时用“=”初始化,如:string str="hello";

(2)函数传参时,用非引用/指针类型传参。

(3)函数返回时,返回一个非引用/指针类型的对象。

(4)花括号初始化对象数组中的对象元素。

注:自定义类的对象可以存放于内置数组中,但必须提供一个默认构造函数。当类中的构造函数带参数时,编译器不会合成一个默认构造函数,需要手动添加,如:MyClass()=default;(C++11)


阅读更多
文章标签: c c++
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭