1、C++this指针干什么用的?
- 1个类型定义的对象都有各自的成员变量,但是是共享一套成员方法,在成员方法里,具体访问谁的成员变量,这个是靠this指针区分的。
- 1个类型定义了很多对象,都是私有的成员变量,但是共享一套成员方法,在成员方法里面访问谁的成员变量?通过this指针区分。
- C++的类的普通成员方法在被编译的时候都会多出来一个this指针。
2、C++的new和delete,什么时候用new[]申请,可以用delete释放?
- new和delete本质上是一个运算符重载。operator new,operator delete
- 当我们用new[]来开辟一个数组内存的时候,相应的在释放这个内存的时候得使用delete[]ptr;
- delete相当于free有两件事情要做:第一个是调用析构函数,第二个是释放内存。
- 如果是自定义类型而且提供了析构函数,那么用new[]开辟,就一定需要用delete[]ptr释放。
- delete[]知道是要释放一个数组内存,就要把数组的每个对象进行析构,然后调用析构函数的时候要传入每个对象的起始地址,就要知道这个内存上有多少个对象,需要把ptr指针-4,才能取到这块内存存储了多少个对象的值。
- 比如说,new Test[10]; 实际上除了开辟10个Test对象的内存,还需要额外开辟4字节的内存,记录开辟对象的个数。new底层调用malloc开辟内存。
- 其他的情况,都可以用delete释放。
3、C++的static关键字的作用(我从elf结构,链接过程来回答)?
3.1、从面向过程来说
static可以修饰全局变量,函数,局部变量。
static修饰全局变量和函数,本来是整个工程可见,现在变为仅当前文件可见,全局变量或者函数被static修饰以后,在符号表中, 符号的作用域从g(global)变成l(local)了。
局部变量是指令, 本身不产生符号,通过ebp-偏移量
来访问的,加上static修饰局部变量,变成数据段,要产生符号了,local。
static修饰局部变量的话, 这个变量的内存位置就变到.data段(存储初始化且初始化不为0的数据)或者.bss段(存储未初始化或者初始化为0的数据)了。因为在数据段,程序一开始就开辟内存,第一次运行到它才初始化,只初始化1次。
3.2、从面向对象的角度来说
static可以修饰成员变量,也可以修饰成员方法,也可以在成员方法里定义静态的局部变量。
static修饰成员变量,这个成员变量就从对象私有变成对象所共享的。
static修饰成员方法也一样,变成对象共有的,相当于成员方法不再产生this指针,也就是说,这些方法不需要用对象来调用,用类的作用域来调用。
4、C++的继承?
继承(a kind of)是属于类和类之间的关系,类与类之间常见的关系还有 组合(a part of)。
继承有2大好处:
- 1、代码的复用,通过1个简单的继承,就可以把基类的成员复用到派生类。
- 2、通过继承,在基类里面给所有派生类可以保留统一的纯虚函数接口,等待派生类进行重写,通过使用多态(基类指针或者引用指向从这个基类派生的不同的派生类对象,这个基类指针或者引用指向谁,就可以访问谁的同名覆盖方法),可以通过基类指针访问不同派生类对象的重写方法(同名覆盖方法)!
- 只要持有基类的指针,不管这个指针传进来是哪个派生类对象,都可以访问这个派生类对象的同名覆盖方法。
- 基本上可以做到开闭原则,添加新的功能可以添加一个额外的派生类从基类继承而来,重写基类的纯虚函数接口就可以了。、
5、C++的继承多态
多态:
- 静多态(编译时期的多态)
- 动多态(运行时期的多态)
静多态(编译时期的多态): 函数重载和模板。
函数重载: 一个函数名展现出来很多不同的状态,因为参数列表不同,到底是调用哪个函数重载的版本,这个调用点在编译时就要确定的。
模板: 可以通过不同类型的实例化展现出来。用什么类型从模板实例化代码?这个从编译时确定的。
动多态(运行时期的多态): 虚函数 ,指针/引用指向派生类对象
动多态的好处: 是可以用统一的基类指针或者引用通过指向不同的派生类对象,访问不同派生类对象的同名覆盖方法。 我们在写接口时,全部都是基类的指针或者引用就可以,不放具体的派生类。
6、空间配置器
空间配置器allocator: 给容器使用的;
主要作用: 把内存开辟和对象构造分开,把对象析构和内存释放分开。
- 原本我们使用new,new不仅开辟空间还会构造对象,delete不仅释放空间还对象析构。
我们为什么把它们分开呢?
- 当我们去初始化1个容器的时候,这些容器理应是空的,底层只有内存,不应该有对象;
- 如果在容器构造的时候直接new,不仅仅开辟内存还会构造很多无用、我们不需要的对象。
- 当我们从容器删除元素的时候,只是需要把对象析构掉,并不需要释放对象的内存,那个内存是容器的内存,容器以后还要使用,因此只需要调用析构函数即可!
- 如果用delete,会把容器底层所有位置上的都当做对象析构掉,还会释放内存!
7、vector 和list的区别
7.1、vector
- vector是数组,底层是可以2倍扩容的数组,提供的尾部的增加和删除,push_back,pop_back,O(1)的操作。
- 适合随机访问多的场合(O(1))(优先级队列是基于vector实现的,底层是大根堆),数组在中间这些位置增加或者删除都是时间复杂度O(N)的操作
7.2、list
- list是循环的双向链表,节点内存不是连续的,每一个节点都是new出来的,适合于增加和删除操作多的场景,首尾增加和删除的时间复杂度都是O(1),其他节点不会改动。
8、map,multimap?
- map(不允许key重复的):映射表[key-value],底层实现是红黑树(二叉排序树),通过快速找到key,来找到key对应value。
- multimap(允许key重复的):映射表[key-value],底层实现是红黑树(二叉排序树),通过快速找到key,来找到key对应value。
红黑树: 5个性质,插入3种情况(最多旋转2次),删除(最多旋转3次)4种情况。
9、C++如何防止内存泄露?智能指针详述?
内存泄漏: 分配的堆内存(没有名字,只能用指针来指向)没有释放,也再没有机会释放了,也就是指向堆内存的指针指向其他地方去了,找不着原来的内存,也就没有机会释放。
int *p = new int[10000];
if(xxx)
return;//运行抛异常了
delete []p;
使用智能指针来防止内存泄漏(智能指针利用栈上对象出作用域自动析构的特点,在智能指针的析构函数就把资源释放了)
unique_ptr pre(new int[100000]);
//不管代码从哪里return,只要出函数栈帧,对象就要析构,在对象析构函数中释放资源
auto_ptr / scoped_ptr / unique_ptr / shared_ptr / weak_ptr