11.关于const 指针
(1)常量指针(指向常量的指针:底层const)
如:
const int *pi=&a;
此处const限制的是指针pi解引用操作,即*pi不能修改,但pi自身可以修改。同时,const对*p的限定不会影响到所指向的值的变更。如果指向的a不是const int a,则直接对a修改仍然是可以的,只是不能通过解引用来修改a。(初学者最难理解的地方)
(2)指针常量(指针自身为常量:顶层const)
如:
char* const ptr="abc";
此处const 限定的是指针本身,即ptr自身的值(所指向变量的地址)不可修改,但不限定解引用*ptr对指向的变量的修改。
如:
*(ptr+1)='c';//ok:此时*ptr=="acc"
*ptr++;//error:不能对ptr做自增运算
(3)指向常量的指针常量(同时限定指针本身和解引用操作)
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:直接构造强制转化,如:
char *pStr="hello world!";
string s1=string(pStr);
string 转c-style字符串:调用string的c_str成员,如:
string s1("hello world!");
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)中的错误,一个好习惯是数组形参后往往跟一个表达数组长度的形参,如:
void print(const int argv[],size_t size);
int main(int argc,char *argv[]);
16.返回指向数组的指针或引用
如:
int (*func(int i))[10];
为便于阅读,常用typedef简化代码,如:
typedef int arr[10];
arr* func(int i);
17.关于函数指针
(1)声明方法:若函数原型为:
bool lengthCompare(const string &,const string &);
则指向它的函数指针为:
bool (*pFunc)(const string &,const string &);
pFunc=lengthCompare;//指向函数入口地址
(2)函数指针作参数(C/C++中回调思想的实现方法)
如:
void useBigger(const string &,const string &,bool (*pFunc)(const string &,const string &));//参3为函数指针,回调中参3的实参被称为回调函数
useBigger(s1,s2,lengthCompare);//调用示例,函数名被自动转化成函数指针
(3)函数指针作返回值
如:
int (*foo(int))(int *,int );
//同理,可结合typedef或using来简化代码
using Func=int (int*,int);//右边为函数标号,没有函数名,与typedef int Func(int *,int);等价
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)