Part1
1.深拷贝是什么?浅拷贝是什么?
2.new与malloc有什么区别?
3. 重载是什么?重定义(隐藏)是什么?重写是什么?
4.协变是什么?
5.const修饰成员函数的作用是什么?
6.拷贝构造函数会造成内存泄漏吗?
7.指针和引用的区别?
8.基本数据类型在32和64位操作系统中所占据的字节数有什么不同?
9.定义对象有哪些方法?
10.哪些函数不能为虚函数?
11.序列式容器的迭代器会在什么情况下失效?
12.关联式容器的迭代器会在什么情况下失效?
13. struct的大小如何计算?
14. 空类的大小是多少?当一个派生类继承了空类,计算派生类大小时,需要加入空类的大小吗?
15.计算class大小时需要考虑哪些成员?
16.当存在继承、多继承、虚继承的情况时,如何计算class的大小?
17.#pragma pack(n)如何设定对齐方式?
18.map的底层实现是什么?
19.unordered_map的底层实现是什么?
20.虚函数表是属于类的还是属于类对象?虚函数表指针是属于类的还是属于类对象?
21.一个类多继承N个类,该类中有多少个虚函数表指针?
22.虚函数的作用是什么?
23.派生类中声明基类的同名虚函数时,需要加virtual吗?
24.public、protected和private的访问权限是什么?
Part2
1.深拷贝是什么?浅拷贝是什么?
深拷贝:原来的对象和拷贝对象相互独立,会复制变量指向的动态分配的内存,修改其中一个对象,不会影响另一个对象。
浅拷贝:原来的对象和拷贝对象共用一份实体,不会复制变量指向的动态分配的内存,修改其中一个对象,会影响另一个对象。
系统默认为一个类生成的拷贝构造函数,执行的是浅拷贝
2.new与malloc有什么区别?
new | malloc |
操作符 | 函数 |
不需要指定内存的大小(字节数),根据类自动生成 | 需要指定内存的大小(字节数) |
失败时抛出异常 | 失败时返回NULL |
先申请内存,再调用构造函数 | 只申请内存 |
底层可以用malloc实现 | 不能用new实现 |
不可以二次分配内存 | 可以用realloc二次分配内存(扩大或缩小) |
自由存储区 | 堆 |
返回具体类的类型的指针 | 返回void* |
允许重载 | 不允许重载 |
针对数组,有new A[10] | 不特别处理数组 |
3. 重载是什么?重定义(隐藏)是什么?重写是什么?
重载:在同一作用域下,函数名相同,函数参数不同。编译器根据函数的参数类型,来决定调用哪个函数。(返回值类型不构成重载)。
重定义(隐藏):不同作用域下(父类和子类中),函数名相同,当子类的对象调用同名函数时,默认调用子类的函数,隐藏父类的函数,除非加上了父类的域作用符 :: 。
重写:父类的函数用virtual修饰,子类中一函数,函数名相同、函数参数类型相同、返回值相同,则构成重写。(唯一的例外是返回值协变)。如果父类函数是const,子类函数可以是const,也可以不是const。如果如果父类函数不是const,子类函数只能不是const在。
4.协变是什么?
子类重写父类的函数,当父类函数的返回值类型是指针或引用时,子类的返回值可以是父类函数返回值类型的子类。
5.const修饰成员函数的作用是什么?
const成员函数不能修改成员变量 (mutable除外),只能调用其他const成员函数
6.拷贝构造函数会造成内存泄漏吗?
默认的拷贝构造函数是浅拷贝,会导致析构时,同一资源被释放两次。
7.指针和引用的区别?
指针 | 引用 |
指针是一个变量,占据内存 空间 | 引用是一个别名,不占据内存空间 |
可以为NULL,且非const指针可以改变值 | 不能为NULL,必须初始化,初始化后不能改变值 |
指针++,是指针地址自增 | 引用++,是引用绑定的对象的自增 |
sizeof(指针),得到指针大小 | sizeof(引用),得到引用绑定的对象的大小 |
作为函数参数传递时,指针实际上是传值,对指针形参的改变,不会改变指针实参的值。需要通过*指针来改变指针指向的内存的值。 | 作为函数参数传递时,引用实际上是引用传递,传的是实参,对形参的改变会改变实参。 |
8.基本数据类型在32和64位操作系统中所占据的字节数有什么不同?
32位操作系统 | 64位操作系统 | |
int | 4 | 4 |
long | 4 | 8 |
long long | 8 | 8 |
float | 4 | 4 |
double | 8 | 8 |
指针 | 4 | 8 |
short | 2 | 2 |
char | 1 | 1 |
9.定义对象有哪些方法?
在栈上定义,A a; 由编译器管理内存,当超出作用域,对象被自动销毁。
在堆上定义,A* a = new A(); 由程序员管理内存,允许对象的生命周期超出变量所在的作用域,需要程序员手动释放内存。
10.哪些函数不能为虚函数?
静态函数、非成员函数、构造函数、友元函数
11.序列式容器的迭代器会在什么情况下失效?
插入或删除元素后,指向插入或删除元素位置之后的迭代器全部失效。(需要更新迭代器,比如 it = nums.erase(it))
容器的容量capacity变化时(比如插入元素,原有的capacity不足,重新分配内存),迭代器会失效
12.关联式容器的迭代器会在什么情况下失效?
插入或删除元素后,指向插入或删除元素位置的迭代器失效。其他迭代器不受影响,因为关联式容器的底层结构是红黑树。(为了解决当前迭代器失效的问题,可以在删除或插入前自增迭代器)
13. struct的大小如何计算?
计算结构体的大小要考虑到内存对齐,主要是两个原则:
1)每个成员变量的偏移量是成员变量大小的整数倍,比如int是4字节,可以放在0,4,8,12等地址,但不能放在2,3,5等地址。
2)结构体的大小必须是所有成员变量的大小的整数倍,也就是所有成员变量大小的最小公倍数。
14. 空类的大小是多少?当一个派生类继承了空类,计算派生类大小时,需要加入空类的大小吗?
空类的大小为1。
当一个派生类继承了空类,计算派生类大小时,不需要加入空类的大小。
但当空类作为非静态成员变量时,需要计算大小(为1)。
15.计算class大小时需要考虑哪些成员?
考虑非静态成员变量。
不考虑静态成员和成员函数。
16.当存在继承、多继承、虚继承的情况时,如何计算class的大小?
当存在继承时,若父类中有虚函数,计算class大小时,需要加父类的大小,和一个虚表指针(4字节),指向包含了父类和子类所有虚函数的虚表。
当存在多继承,例如继承了N个类,且父类中都有虚函数,那么需要加N个父类的大小,和N个虚表指针,各指向包含了父类所有虚函数的虚表,其中第一个虚表包含了第一个继承的父类的虚函数和子类的虚函数。
当存在虚继承,需要加父类的大小,和一个指向派生类的虚表的虚表指针,一个指向基类的虚表的虚基表指针。
17. #pragma pack(n)如何设定对齐方式?
n可以是2的幂:1,2,4,8...
成员对象对齐时,取成员变量大小和n中的较小值来对齐,偏移量是min(成员变量大小,n)的倍数
计算class的整体大小时,取最大的成员变量的大小和n中的较小值来对齐,class大小是min(最大的成员变量大小,n)的倍数。
18.map的底层实现是什么?
红黑树。红黑树是平衡二叉查找树的一种变体,并不严格保证左右子树高度差<=1,但是平衡的代价更低,平均性能强于平衡二叉树。
红黑树是一棵二叉查找树,且满足以下性质:
- 结点是黑色或红色。
- 根节点是黑色。
- 叶子结点为NULL,是黑色。
- 红色结点的子节点颜色为黑色。
- 任意结点到其叶子结点都有相同数量的黑色结点。
关键性质:从根结点到叶子结点的最长路径不会大于最短路径的两倍。
19.unordered_map的底层实现是什么?
哈希表。哈希表实现为一个动态数组,每个元素是一个桶,存储着一个链表或者红黑树。
20.虚函数表是属于类的还是属于类对象?虚函数表指针是属于类的还是属于类对象?
虚函数表是属于类。
虚函数表指针是属于类对象。
21.一个类多继承N个类,该类中有多少个虚函数表指针?
若N个类中有M个类有虚函数,那么派生类中会有M个虚函数表指针,指向M个不同的虚函数表。
22.虚函数的作用是什么?
允许在派生类中重新定义与基类同名的函数,并通过基类的指针或引用来访问派生类中的同名函数。
23.派生类中声明基类的同名虚函数时,需要加virtual吗?
可加可不加,因为派生类中与基类同名的函数会默认是virtual
24.public、protected和private的访问权限是什么?
public:类内外和派生类都可以访问,在类的对象中可见。
protected:类内和派生类可以访问,在类的对象中不可见。
private:只有类内可以访问,在类的对象中不可见。