目录
6. 类成员初始化方式?构造函数的执行顺序 ?为什么用成员初始化列表会快一些?
15. 如何在共享内存上使用STL(Standard Template Library)标准库?
16. vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?
18. C/C++的内存分配,详细说一下栈、堆、静态存储区?
21. define分别与const、typedef、inline的区别
35. 在成员函数中调用delete this会出现什么问题?对象还可以使用吗?
42. 空类会默认添加哪些东西?怎么写?空类的大小是多少?为什么?
56. 为什么要用static_cast转换而不用c语言中的转换?
57. 成员函数里memset(this,0,sizeof(*this))会发生什么
1. 指针和引用的区别?
(1) 指针是一个实体,需要分配内存空间。引用只是变量的别名,不需要分配内存空间。
(2) 指针在定义的时候不一定要初始化,并且指向的空间可变。引用在定义的时候必须进行初始化,并且不能够改变。(注:引用的值不能为nullptr)
(3) 有多级指针,但是没有多级引用,只能有一级引用。
(4) 指针和引用的自增运算结果不一样。指针是指向下一个空间,引用时引用的变量值加1
(5) sizeof 指针得到的是指针本身的大小。而sizeof 引用得到的是所指向的变量(对象)的大小
(6) 而指针访问一个变量是间接访问。引用访问一个变量是直接访问。
2. 静态变量什么时候初始化
(1) 初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存。
(2) 在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化。
(3) 而在C++中,初始化时在执行相关代码时才会进行初始化。
主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。所以C++标准定为全局或静态对象是有首次用到时才会进行构造,并通过atexit()来管理。在程序结束,按照构造顺序反方向进行逐个析构。所以在C++中是可以使用变量对静态局部变量进行初始化的。
3. mutable
如果需要在const成员方法中修改一个成员变量的值,那么需要将这个成员变量修饰为mutable。即用mutable修饰的成员变量不受const成员方法的限制;
4. C++模板是什么,底层怎么实现的?
(1) 编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译:在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
(2) 在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。
5. 虚函数可以声明为inline吗?
可以,但编译器不一定会这么做
(1) 虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。而内联函数用于提高效率。内联函数的原理是,在编译期间,对调用内联函数的地方的代码替换成函数代码。内联函数对于程序中需要频繁使用和调用的小函数非常有用。
(2) 虚函数要求在运行时进行类型确定,而内敛函数要求在编译期完成相关的函数替换;
如果virtual可以在编译期决定调用什么函数,那么就可以被inline
6. 类成员初始化方式?构造函数的执行顺序 ?为什么用成员初始化列表会快一些?
(1) 赋值初始化,通过在函数体内进行赋值初始化;列表初始化,在冒号后使用初始化列表进行初始化。
(2) 一个派生类构造函数的执行顺序如下:
①基类(虚基类)按照继承的顺序执行构造函数;
②类成员对象按照初始化顺序执行构造函数;
③派生类自己的构造函数。
(3) 赋值初始化时在构造函数当中做赋值的操作,而初始化列表是做纯粹的初始化操作。C++的赋值操作是会产生临时对象的。临时对象的出现会降低程序的效率。
7. 必须使用成员列表初始化的情况
(1) 必须使用成员初始化的四种情况
①当初始化一个引用成员时;
②当初始化一个常量成员时;
③当调用一个基类的构造函数,而它拥有一组参数时;
④当调用一个成员类的构造函数,而它拥有一组参数时;
(2) 成员初始化列表做了什么
①编译器会一一操作初始化列表,以适当的顺序在构造函数之内安插初始化操作,并且在任何显示用户代码之前;
②list中的项目顺序是由类中的成员声明顺序决定的,不是由初始化列表的顺序决定的;
8. 在C++中析构函数可以是纯虚的吗
可以,但是如果类包含纯虚析构函数,则必须为纯虚析构函数提供函数体。因为
派生类
的析构函数里面隐含调用了基类的析构函数。如果缺少~Base()
的函数体,当然会出现错误。
9. 构造函数析构函数可以调用虚函数吗?
可以
在构造函数和析构函数中最好不要调用虚函数;在执行基类构造函数的时候,派生类的成员变量完全未初始化,那么如果此时调用的是派生类的虚函数,如果里面调用了派生类的成员变量,那么就导致出现错误
10. 构造函数析构函数可否抛出异常
(1) 构造函数可以抛出异常但不建议,如果在构造时发生问题,那么就不是一个完整的对象,因此析构函数也不会被调用,只会处理异常发生前的变量。
(2) 析构函数不可以抛出异常,通常异常发生时,C++机制会调用已经构造对象的析构函数来释放资源,此时如析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
ps: 如果控制权基于异常的因素离开析构函数,而此时正有另一个异常处于作用状态,C++会调用terminate函数让程序结束;
11. 类如何实现只能静态分配和只能动态分配
ps: 静态分配发生在程序编译和连接的时候。动态分配则发生在程序调入和执行的时候。
前者是把new、delete运算符重载为private属性。后者是把构造、析构函数设为protected属性,再用子类来动态创建
12. 什么情况会自动生成默认构造函数?
(1) 类中有一个类成员含有默认构造函数的,编译器会为该类自动生成默认构造函数。
(2) 基类中含有默认构造函数,而派生类中没有定义默认构造函数,则编译器会为派生类提供一个上一层基类的默认构造函数。
(3) 类中含有虚函数时或基类为虚基类时,由于编译器要为该类生成虚函数表vtable,并为该类的对象生成指向该vtable的vptr,所以需要为该类合成默认构造函数
13. 什么是组合?
组合就是一个类里面的数据成员是另一个类的对象,即内嵌其他类的对象作为自己的成员;
ps: 创建组合类对象,构造函数的执行顺序:按照内嵌对象成员在组合类中的定义顺序调用内嵌对象的构造函数,与组合类构造函数的初始化列表顺序无关。然后执行组合类构造函数的函数体,析构函数调用顺序相反。
14. 继承机制中对象之间如何转换?
(1) 向上类型转换
将派生类指针或引用转换为基类的指针或引用被称为向上类型转换,向上类型转换会自动进行,而且向上类型转换是安全的。
(2) 向下类型转换
将基类指针或引用转换为派生类指针或引用被称为向下类型转换,向下类型转换不会自动进行,因为一个基类对应几个派生类,所以向下类型转换时不知道对应哪个派生类,所以在向下类型转换时必须加动态类型识别技术。如RTTI (Run-Time Type Identification) 技术,用dynamic_cast进行向下类型转换。
15. 如何在共享内存上使用STL(Standard Template Library)标准库?
(1) 考虑到要将STL容器放到共享内存中,而容器却自己在堆上分配内存。一个最笨拙的办法是在堆上构造STL容器,然后把容器复制到共享内存,并且确保所有容器的内部分配的内存指向共享内存中的相应区域,这基本是个不可能完成的任务。
(2) 一个方法就是进程A把容器放在共享内存中的确定地址上(fixed offsets),则进程B可以从该已知地址上获取容器。另外一个改进点的办法是,进程A先在共享内存某块确定地址上放置一个map容器,然后进程A再创建其他容器,然后给其取个名字和地址一并保存到这个map容器里。进程B知道如何获取该保存了地址映射的map容器,然后同样再根据名字取得其他容器的地址。
16. vector越界访问下标,map越界访问下标?vector删除元素时会不会释放空间?
(1) 通过下标访问vector中的元素时不会做边界检查,即便下标越界,程序也不会报错,而是返回这个地址中存储的值。如果想在访问vector中的元素时首先进行边界检查,可以使用vector中的at函数。通过使用at函数不但可以通过下标访问vector中的元素,而且在at函数内部会对下标进行边界检查。
(2) map的下标运算符[]的作用是:将key作为下标去执行查找,并返回相应的值;如果不存在这个key,就将一个具有该key和value的某人值插入这个map。
(3) erase()函数,只能删除内容,不能改变容量大小; clear()函数,只能清空内容,不能改变容量大小;如果要想在删除内容的同时释放内存,那么可以选择deque容器。