C++面试知识点
@TOC
前言
C++面试知识点。
一、C和C++的区别?
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
关键字、后缀名、返回值:
C语言中,如果一个函数没有指定返回值类型,默认返回int类型;
C++中,如果一个函数没有返回值则必须指定为void。
二、C++里面的指针和引用的区别?
指针 | 引用 |
---|---|
指针是地址、有存储空间 | 就是别名 |
指针可以有多级,可以指向NULL | 引用只能是一级,不能指向NULL |
指针可以在定义的时候不初始化,在初始化之后可以再改变指向 | 引用必须在定义的时候初始化,在初始化之后不可以再改变指向 |
sizeof的结果是4或8 | sizeof的结果是被引用对象的大小 |
指针作为参数传递时,要检查是否为空 | 引用作为参数传递时,不需要检查是否为空 |
指针自加1是将指针指向向后偏移一个存储单元 | 引用自加1是将对应的值加1 |
引用比指针多了类型检查 |
【注】引用在底层是怎么实现的?
引用的底层是通过指针实现的,引用的本质就是所引用对象的地址。
int * const aref = &a; *aref = 20;
【注】引用能不能引用空类型(void类型)?
引用为对象起了另外的一个名字,该对象是已经存在的对象,引用必须初始化,有类型;
void类型没有分配内存,而引用必须是另一个固定内存变量的别名,所以不能指向void。
三、C++里面的static关键字?
一、C++里面的static关键字的作用?
两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。
面向过程:全局静态变量、局部静态变量、静态函数。
面向对象:类的静态数据成员(对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。)
类的静态成员函数: 非静态成员函数可以任意地访问静态成员函数和静态数据成员; 静态成员函数不能访问非静态成员函数和非静态数据成员; 没有this指针的额外开销。
【注】静态的成员变量放在内存的哪个区?
全局数据区。
二、静态的成员函数只能调用静态的成员方法,为什么呢?或者说为什么不能在静态成员函数中使用非静态变量?
程序最终都将在内存中执行,变量只有在内存中占有一席之地时才能被访问。
因为静态是针对类的,而成员变量为对象所有。
静态成员函数不属于任何一个类对象,没有this指针,而非静态成员必须随类对象的产生而产生,所以静态成员函数”看不见”非静态成员,自然也就不能访问了。
类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;
非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
在一个类的静态成员中去访问其非静态成员之所以会出错,是因为在类的非静态成员不存在的时候类的静态成员就已经存在了,访问一个内存中不存在的东西当然会出错。
【注】静态的局部变量,是一个数据成员,放在内存的哪个区?
全局数据区(静态存储区)。
四、C++里面的const有什么作用?假如说这个const修饰的是类的成员函数,会怎么样?
1、const用于定义常量:const定义的常量,编译器可以对其进行数据静态类型安全检查。
2、const修饰函数形式参数:当输入参数为用户自定义类型和抽象数据类型时,应该将值传递改为const &传递,可以提高效率。
【注】用引用传递不需要产生临时对象,节省了临时对象的构造、复制、析构过程消耗的时间。但是只有引用有可能会改变a,所以加const。
3、const修饰函数的返回值:如给指针传递的函数返回值加const,则返回值不能被直接修改,且该返回值只能被赋值给加const修饰的同类型的指针。
4、const修饰类的成员函数(函数定义体):任何不会修饰数据成员的函数都应用const修饰,这样,当它不小心修改了数据成员或调用了非const成员函数时,编译器都会报错。
【注】const修饰类的成员函数形式为:int GetCount(void) const;
【注】const和static这两个关键字可以同时使用吗?
不可以。const要在定义的时候进行初始化。
假设在类里面定义了一个const static的类型,这样的话会有什么问题吗?或者考虑另外一个问题:类里面的静态成员变量应该怎么初始化呢?
类内定义,类外初始化。
五、const和#define有什么区别?一般推荐使用哪种?或者说在某个场景下用两个都可以,那你会用哪种?
define和const都是定义作用:
1、#define不带类型,const带类型;
2、#define不占内存,const占内存;
【注】#define只在预处理阶段起作用,它定义的宏在编译后消失了,不占用内存,但是const定义的常变量本质上是一种变量,在编译运行阶段使用的,有类型、占用存储单元。
3、#define只是简单的字符替换,可能会出现问题,不如const安全。
【注】typedef
用来定义一种数据类型的别名,增强程序的可读性。
typedef
是编译过程的一部分,有类型检查的功能。
typedef
有作用域限定。
【注】尽量用编译器而不用预处理:即尽量用const和inline而不用#define。
六、C++里面的inline关键字知道吗?这个inline关键字有什么地方是不能使用的?
在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline
修饰符,表示为内联函数。
inline的使用是有所限制的: inline只适合函数体内代码简单的函数使用, 不能包含复杂的结构控制语句例如while、switch, 并且不能内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数)。
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
【注】如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。
【注】建议:inline函数的定义放在头文件中:
编译器必须随处可见内联函数的定义,所以,这要求每个调用了内联函数的文件都出现了该内联函数的定义。
七、C++里面的函数指针了解吗?一般在什么场景下会用到这个?
每一个函数都占用一段内存单元,它们有一个起始地址,指向函数入口地址的指针称为函数指针。
指向函数的指针变量的一般定义形式为:
数据类型 (*指针变量名)(参数表);
int (*p)(int a, int b); //p是一个指向函数的指针变量,所指函数的返回值类型为整型 int *p(int a, int b); //p是函数名,此函数的返回值类型为整型指针
特点:
函数指针可任意指向与其返回类型和参数列表相同的函数。
函数指针不支持+1, ++, --等操作。
使用typedef可以创建函数指针的别名。
函数指针常用的用法/场景:将函数作为参数传递。
函数指针的应用场景:回调(callback)。我们调用别人提供的 API函数(Application Programming Interface,应用程序编程接口),称为Call;如果别人的库里面调用我们的函数,就叫Callback。
八、C++里面的多线程有了解吗?多线程里面的锁都有哪些类型?
互斥锁、读写锁、条件变量等。
九、vector的底层是怎么实现的?
vector同样是一个模板,并且其底层实现用的是三个指针,然后利用这三个指针相互加减,达到存储效果。
而vector和string类似,本质是都是一个顺序表。
1、尾插push_back()
该接口的实现操作一般是先检查容量是否充足,然后把数据放进去,最后size大小加一。
2、尾删pop_back()
实现该接口的操作一般是先检查是否还存在数据,然后size大小减一。
3、某一位置插入 insert()
同样的道理,一般先检查容量是否充足,如果不够,需要警惕迭代器失效问题,然后移动该位置及以后的所有数据,最后插入数据;
官方文档定义其返回值为新插入数据的位置。
4、某一位置删除 erase()
该接口的操作一般是从pos后位置开始,所有数据前挪一单位,但是在挪之前,需要检查是否还存在数据;
官方文档定义其返回值为删除数据的下一位置。
十、迭代器在什么时候会失效?为什么要用这个迭代器?就是这个迭代器的作用是什么?vector有迭代器吗?
【注】指针一定是迭代器,但是迭代器不一定是指针。
vector在push_back
的时候当容量不足时会触发扩容,导致整个vector重新申请内存,并且将原有的数据复制到新的内存中,并将原有内存释放,这自然是会导致迭代器失效的,因为迭代器所指的内存都已经被释放。
insert导致的迭代器失效的情况:
(1)插入操作导致vector
扩容,迭代器失效原因和push_back
相同;
(2)插入操作引起vector
内元素移动,导致被移动部分的迭代器失效;
(3)删除操作引起vector
内元素移动,导致被移动部分的迭代器失效。
十一、map和vector的应用场景?
如果你需要高效的随机存取,而不在乎插入和删除的效率,使用vector
;
如果你要存储一个数据字典,并要求方便地根据key找value,那么map
是较好的选择;
如果你需要大量的插入和删除,而不关心随机存取,则应使用list
;
如果你需要随机存取,而且关心两端数据的插入和删除,则应使用deque
;
如果你要查找一个元素是否在某集合内存中,则使用set
存储这个集合比较好。
十二、动态库中导出类和导出函数的区别?
只需要在class后加上__declspec(dllexport)
即可实现导出类。
在DLL中有一张导出表,其中有一系列函数,这些函数叫做导出函数。这些函数可供外部程序调用,即这些函数都是该DLL的入口点(类似main函数)。
不在导出表中的函数,为该DLL私有的函数,外部程序不能调用它们。
【注】没有__declspec(dllexport)
,将生成的测试lib库添加到项目中,直接调用,会报错。
十三、关于内存泄漏
出现内存泄漏的原因:一个是忘了释放,一个是重复释放。
智能指针一定不会造成内存泄漏吗?使用的时候要注意什么?
两个智能指针相互指向,就会循环,造成内存泄漏。
十四、关于类的一个问题
有一个类A,其中有一个函数,函数功能只是打印“hello world”,如果:
A p = new A();
之后delete了这个p
然后使用p指向这个函数会发生什么?
可以正常打印“hello world”,
因为函数不属于对象,但是如果这个函数中用到了成员变量就会出错。
如果直接定义一个p = nullptr的指针,指向这个函数,也可以执行。
delete之后,sizeof这个对象是多大?1,要在内存中分配初始化所需的空间位置,为1。
如果在这个函数前面加上virtual,还是用这个p指向这个函数会发生什么?
访问出错,对象没了,虚函数表没了,访问不到了。
这个时候sizeof这个对象是多大?4,因为virtual有虚函数表,类中会有虚函数指针,指向这个表,即指针大小4。
十五、QT中的信号与槽的一些优点?
QT是基于消息触发机制实现的很多功能。
激发信号的Qt对象无须知道是哪个对象的哪个槽需要接收它发出的信号,它只需要做的是在适当的时间发送适当的信号就可以了,而不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收到了信号。
同样地,对象的槽也不知道是哪些信号关联了自己,而一旦关联信号和槽,Qt就保证了适合的槽得到了调用。
即使关联的对象在运行时被删除。应用程序也不会崩溃。
十六、怎么解决粘包问题?
1.利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。
2.一般可能会在头加一个数据长度之类的包,以确保接收。
互联网知识点