13、对象的构造函数
- 构造函数是一种特殊的成员函数。
- 构造函数不需要用户调用,而是在建立对象时自动执行。
- 构造函数的名字必须与类名相同,而不能由用户任意命名。
- 够赞函数的功能是由用户定义的,用户根据初始化的要求设计函数体和函数参数。
14、不同对象执行析构函数的动机
- 对于函数中定义的自动局部对象,当函数被调用结束时,对象释放,在对象释放前自动执行析构函数。
- static局部对象只在main()函数结束或调用exit函数结束程序时,调用static局部对象的析构函数。
- 对于全局对象,在程序的流程离开其作用域时(如main函数结束或调用exit函数)时,调用该全局对象的析构函数。
- 只要对象的生命周期结束,程序就自动执行实现设计好的析构函数来完成相关工作。
15、派生类的构造函数和析构函数
- 派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,派生类中新增的成员在派生类中构造函数中初始化。
- 如果基类中没有不带参数的构造函数,那么在派生类的构造函数中必须调用基类构造函数,以初始化积累成员。
- 派生类中构造函数的执行程序:
- 先执行基类构造函数,调用顺序按照他们被继承时声明的顺序。
- 再执行内嵌成员对象的构造函数,调用顺序按照他们在类中声明的顺序。
- 最后执行派生类构造函数。
- 派生类析构函数的执行顺序
- 先执行派生类的析构函数。
- 再执行内嵌成员对象的析构函数,调用顺序按照他们在类中声明的反顺序。
- 再执行其基类的析构函数,调用顺序按照他们被继承时声明的顺序。
16、类的封装
- 将有关的代码和数据封装在一个对象中,每个对象间相互独立,互不干扰。
- 将对象中的某些部分对外隐蔽,隐蔽内部细节,只留下少量接口。
- 类具有信息隐藏的能力,能够有效的把类的内部数据隐藏起来,使外部函数只有只有通过类的公有成员才能访问类的内部数据。
- 封装使类成为一个具有内部数据的自我隐藏能力,功能独立的软件模块。
- 类的公有成员也称为类的接口,外部函数要访问类的内部成员,只有通过类的公有成员才能实现。
17、形参带默认值的函数
- 给默认值的时候,从右向左给。
- 调用效率的问题。
- 定义出可以给形参默认值,声明也可以给形参默认值形参给默认值的时候,不管是定义处给,还是声明处给,形参默认值只能出现一次。
18、智能指针
- unique_ptr
- 表示互斥权,拷贝构造函数和拷贝赋值运算符都delete了。
- 一个unique_ptr拥有一个对象,在某一时刻,只能由一个unique_ptr指向一个给定的对象。当unique_ptr被销毁,所致的对象也被销毁。
- unique_ptr不能拷贝,不能赋值,可以移动。
- 为动态分配的内存提供异常安全,unique_ptr可以理解为一个简单的指针(指向一个对象)或一对指针(包含释放器deleter的情况)。
- 将动态分配内存的所有权传递给函数。
- 从函数返回动态分配的内存。
- shared_ptr:
- shared_ptr表示共享所有权,可以共享一个对象。当两段代码都没有独享所有权(负责销毁对象)时,可以使用shared_ptr。shared_ptr是一种计数指针,当计数变为0时释放所指向的对象。
- 其实就是一个指针套上了释放器,套上了计数器,拷贝的时候增加了引用,赋值也增加了引用,相应的也会有递减了引用计数。
- weak_ptr:
- 是一种不控制所指向对象生存期的智能指针,指向由一个shared_ptr管理的对象。
- weak_ptr,不影响shared_ptr的引用计数。一旦shared_ptr被销毁,那么对象也会被销毁,即使weak_ptr还指向这个对象,这个对象也会被销毁。
- auto_ptr:
被 c++11 弃用,原因是缺乏语言特性如 “针对构造和赋值” 的 std::move
语义,以及其他瑕疵。
- auto_ptr 与 unique_ptr 比较
- auto_ptr 可以赋值拷贝,复制拷贝后所有权转移;unqiue_ptr 无拷贝赋值语义,但实现了
move
语义;
- auto_ptr 对象不能管理数组(析构调用
delete
),unique_ptr 可以管理数组(析构调用 delete[]
);
19、纯虚函数和虚函数的区别
- 虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。
- 虚函数可以直接被使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类只有声明而没有定义。
- 虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。
- 虚函数和纯虚函数通常存在于抽象基类之中,被继承的子类重载,目的是提供一个统一的接口。
- 虚函数和纯虚函数的定义中不能有static标识符,被static修饰的函数在编译时候要求前期绑定,然而虚函数却是动态绑定,而且被两者修饰的函数声明周期也不一样。
- 虚函数必须实现,如果不实现,编译器将报错。
- 虚函数,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
- 实现了纯虚函数的子类,改纯虚函数在子类中就变成了虚函数,子类的子类可以覆盖。该虚函数,由多态方式调用的时候动态绑定
- 虚函数是C++中用于实现多态的机制。核心理念就是通过基类访问派生类定义的。
- 包含纯虚函数的类叫做抽象类(也称为接口类),抽象类不能实例化处对象。
20、哈希函数
- 哈希冲突的产生原因:哈希是通过对数据进行再压缩,提高效率的一种解决方法。但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的值。这时候就产生了哈希冲突。
- Hash哈希冲突发生的场景:当关键字值域远大于哈希表的长度,而且事先并不知道关键字的具体取值时。hash冲突就会发生。
- Hash溢出发生的场景:当关键字的实际取值大于哈希表的长度时,而且表中已装满了记录,如果插入一个新纪录,不仅发生冲突,而且还会发生溢出。
解决哈希冲突的方法主要有:开放地址法和拉链法。
- 开放地址法:
- 线性探测:按顺序决定值时,如果数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。
- 再平方探测:按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等。直至不发生哈希冲突。
- 伪随机探测:按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值得基础上加上随机数,直至不发生哈希冲突。
- 链式地址法:
对于相同的值,使用链表进行连接。使用数组存储每一个链表
- 优点:
- 拉链法处理冲突简单,且无堆积现象,即非同义词绝不会发生冲突,因此平均查找长度较短。
- 由于拉链中个链表的节点空间时动态申请的,故它更适合于造表前无法确定表长的情况。
- 开放定址法为减少冲突,要求装填因子较小,故当节点规模较大时会浪费很多空间。而拉链法中可取a>=1,且节点较大时,拉链法中增加的指针域可忽略不计,因此节省空间。
- 再用拉链法构造的散列表中,删除节点的操作易于实现,只要简单地山区链表上相应的节点即可。
- 缺点:
- 指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。
- 建立公共溢出区存储所有哈希冲突的数据。
- 对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。
21、C++强制转换
- static_cast
- static_cast 用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换。另外,如果对象所属的类重载了强制类型转换运算符 T(如 T 是 int、int* 或其他类型名),则 static_cast 也能用来进行对象到 T 类型的转换。
- static_cast 不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。因为这些属于风险比较高的转换。
- reinterpret_cast
reinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。
- const_cast
const_cast 运算符仅用于进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。
- dynamic_cast
用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但是这种转换不检查安全性,即不检查转换后的指针是否确实指向一个派生类对象。dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针。
22、构造函数为什么不能是虚函数
虚函数对应一个虚指针,虚指针其实是存储在对象的内存空间的。如果构造函数是虚函数,就需要通过虚函数表中对应的虚函数指针(编译期间生成属于类)来调用,可对象目前还没有实例化,也即是还没有内存空间,何来的虚指针,所以构造函数不能是虚函数。
虚函数的作用在于通过父类的指针或者引用来调用它的成员函数的时候,能够根据动态类型来调用子类相应的成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,所以构造函数不能是虚函数。
23、虚函数可以是内联函数吗
- 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
- 内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
inline virtual
唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()
),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
24、strcat、strcpy、strcmp区别
-
strcat函数原型char* strcat(char* dest,const char* src);
进行字符串的拼接,将第二个字符串连接到第一个字符串中第一个出现\\0开始的地方。返回的是拼接后字符的首地址。并不检查第一个数组的大小是否可以容纳第二个字符串。如果第一个数组的已分配的内存不够容纳第二个字符串,则多出来的字符将会溢出到相邻的内存单元。
-
strncat函数原型:strncat(dest,src,maxsize)
功能跟strcat一致,不过它带有一个maxsize的参数,设置容纳的最大的字符长度。如在遇到\\0之前达到了最大字符长度,则会只连接最大字符长度个数的字符。
-
*strcpy函数原型 char strcpy(char dest,const char src);
将第二个字符串\\0之前的字符复制到第一个内存地址内。返回的是复制后的字符串的首地址。有char*返回值是为了函数能够支持链式表达式,增加了函数的“附加值”。char a[7]="abcdef",char b[5]="xyz";
strcpy(a,b)函数 当将后面的数组赋值给前面那个时侯 除去五个元素后,从下标为5开始的元素仍旧是之前a[5]的元素。
-
strncpy(str1,str2,numbe)
是将str2中的前number个字符赋给str1,或是将\\0之前的字符赋给str1.
-
**strcmp函数原型 **int strcmp(const char src1,const char src2);
进行两个字符串中从第一个开始的ASCII码的比较 。遇到\\0或者不一致时退出,如果前者大于后者返回1,小于返回-1 如果在两个中的任何一个的\\0之前都保持一致,则返回0. 当src或src遇到\\0时即停止比较.strcmp比较的是字符串,不是字符,字符之间的比较可以直接用==
-
strncmp(str1,str2,numbe)
函数在strcmp的基础上多了一个int参数,即指定比较前几个字符是否相等。
25、虚函数和静态函数的区别
静态函数在编译时就已经确定,而虚函数在运行时动态绑定。虚函数是实现多态重要手段,在函数前加virtual关键字即可。
26、静态函数与静态变量的区别
- 类静态数据成员在编译时创建并初始化:在该类的任何对象建立之前就存在,不属于任何对象,而非静态类成员变量则是属于对象所有的。类静态数据成员只有一个拷贝,为所有此类的对象所共享。
- 类静态成员函数属于整个类,不属于某个对象,由该类所有对象共享。
- static 成员变量实现了同类对象间信息共享。
- static 成员类外存储,求类大小,并不包含在内。
- static 成员是命名空间属于类的全局变量,存储在 data 区的rw段。
- static 成员只能类外初始化。
- 可以通过类名访问(无对象生成时亦可),也可以通过对象访问。
27、public、protected、private区别
- public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问。
- protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问。
- private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问。