十八万字整理C/C++、嵌入式软开 常见面试题汇总24
十八万字吐血整理的C/C++、嵌入式常见面试题!!!!
文中很多资料避免不了从网上或是其他复习资料里收集整理,十分感谢前辈的辛勤付出,如果存在侵权请一定联系我进行删除。
系列文章PDF下载地址:《最全C_C++及嵌入式软开面试题宝典.pdf》
141、迭代器++it,it++哪个好,为什么
1.前置返回一个引用,后置返回一个对象
// ++i实现代码为:
int& operator++() { *this += 1; return *this; }
2.前置不会产生临时对象,后置必须产生临时对象,临时对象会导致效率降低
//i++实现代码为:
int operator++(int) { int temp = *this; ++*this; return temp; }
142、C++如何处理多个异常的?
1.C++中的异常情况:
- 语法错误(编译错误):比如变量未定义、括号不匹配、关键字拼写错误等等编译器在编译时能发现的错误,这类错误可以及时被编译器发现,而且可以及时知道出错的位置及原因,方便改正。
- 运行时错误:比如数组下标越界、系统内存不足等等。这类错误不易被程序员发现,它能通过编译且能进入运行,但运行时会出错,导致程序崩溃。为了有效处理程序运行时错误,C++中引入异常处理机制来解决此问题。
2.C++异常处理机制:
异常处理基本思想:执行一个函数的过程中发现异常,可以不用在本函数内立即进行处理,而是抛出该异常,让函数的调用者直接或间接处理这个问题。
C++异常处理机制由3个模块组成:try(检查)、throw(抛出)、catch(捕获)
抛出异常的语句格式为:throw 表达式;如果try块中程序段发现了异常则抛出异常。
try{可能抛出异常的语句;(检查)} catch(类型名[形参名])//捕获特定类型的异常 {//处理1; } catch(类型名[形参名])//捕获特定类型的异常 {//处理2; } catch(…)//捕获所有类型的异常{}
143、 模板和实现可不可以不写在一个文件里面?为什么?
不可以!因为在编译时模板并不能生成真正的二进制代码,而是在编译调用模板类或函数的CPP文件时才会去找对应的模板声明和实现,在这种情况下编译器是不知道实现模板类或函数的CPP文件的存在,所以它只能找到模板类或函数的声明而找不到实现,而只好创建一个符号寄希望于链接程序找地址。但模板类或函数的实现并不能被编译成二进制代码,结果链接程序找不到地址只好报错了。
《C++编程思想》第15章(第300页)说明了原因:模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
144、在成员函数中调用delete this会出现什么问题?对象还可以使用吗?
1.类对象空间
在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,让成员函数知道当前是哪个对象在调用它。当调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他任何函数调用,只要不涉及到this指针的内容,都能够正常运行。一旦涉及到this指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题。
2.为什么是不可预期的问题?
delete this之后不是释放了类对象的内存空间了么,那么这段内存应该已经还给系统,不再属于这个进程。照这个逻辑来看,应该发生指针错误,无访问权限之类的令系统崩溃的问题才对啊?这个问题牵涉到操作系统的内存管理策略。delete this释放了类对象的内存空间,但是内存空间却并不是马上被回收到系统中,可能是缓冲或者其他什么原因,导致这段内存空间暂时并没有被系统收回。此时这段内存是可以访问的,你可以加上100,加上200,但是其中的值却是不确定的。当你获取数据成员,可能得到的是一串很长的未初始化的随机数;访问虚函数表,指针无效的可能性非常高,造成系统崩溃。
3.如果在类的析构函数中调用delete this,会发生什么?
会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存”。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。(值传递的拷贝构造函数也是无限递归)
145、三个智能指针
1.shared_ptr共享的智能指针:
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。
注意事项:
- 不要用一个原始指针初始化多个shared_ptr。
- 不要再函数实参中创建shared_ptr,在调用函数之前先定义以及初始化它。
- 不要将this指针作为shared_ptr返回出来。
- 要避免循环引用。(两个类,互相包含对方。使用weak_ptr解决)
2.unique_ptr独占的智能指针:
<1>unique_ptr是一个独占的智能指针,他不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另外一个unique_ptr。
<2>unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再拥有原来指针的所有权了。
<3>如果希望只有一个智能指针管理资源或管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。
3.weak_ptr弱引用的智能指针:
弱引用的智能指针weak_ptr是用来监视shared_ptr的,不会使引用计数加一,它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命周期,更像是shared_ptr的一个助手。weak_ptr没有重载运算符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中关连的资源是否存在。 weak_ptr还可以用来返回this指针和解决循环引用的问题。
146、智能指针怎么用?智能指针出现循环引用怎么解决?
1.shared_ptr
调用一个名为make_shared的标准库函数,
shared_ptr<int> p = make_shared<int>(42);
通常用auto更方便,auto p = …;shared_ptr<int> p2(new int(2));
每个shared_ptr都有一个关联的计数器,通常称为引用计数,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象;shared_ptr的析构函数就会递减它所指的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
2.unique_ptr
一个unique_ptr拥有它所指向的对象。某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
3.weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象还是会被释放。
弱指针用于专门解决shared_ptr循环引用的问题,weak_ptr不会修改引用计数,即其存在与否并不影响对象的引用计数器。循环引用就是:两个对象互相使用一个shared_ptr成员变量指向对方。弱引用并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
5.循环引用问题
虽然智能指针会减少内存泄漏的可能性,但是如果使用智能指针的方式不对,一样会造成内存泄漏。比较典型的情况是循环引用问题,比如这段代码:
class B; // 前置声明 class A { public: shared_ptr<B> ptr; }; class B { public: shared_ptr<A> ptr; }; int main() { while(true) { shared_ptr<A> pa(new A()); shared_ptr<B> pb(new B()); pa -> ptr = pb; pb -> ptr = pa; } return 0; }
上图中,class A和class B的对象各自被两个智能指针管理,也就是A object和B object引用计数都为2,为什么是2?
分析class A对象的引用情况,该对象被main函数中的pa和class B对象中的ptr管理,因此A object引用计数是2,B object同理。
解决方法
解决方法很简单,把class A或者class B中的shared_ptr改成weak_ptr即可,由于weak_ptr不会增加shared_ptr的引用计数,所以A object和B object中有一个的引用计数为1,在pa和pb析构时,会正确地释放掉内存。
147、智能指针的作用
1.C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
2.智能指针在C++11版本之后提供,包含在头文件<memory>中,shared_ptr、unique_ptr、weak_ptr。shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
3.初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr<int> p4 = new int(1);的写法是错误的
4.拷贝和赋值。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象
5.unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
6.智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
7.weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
148、auto_ptr作用
1.auto_ptr的出现,主要是为了解决“有异常抛出时发生内存泄漏”的问题;抛出异常,将导致指针p所指向的空间得不到释放而导致内存泄漏;
2.auto_ptr构造时取得某个对象的控制权,在析构时释放该对象。我们实际上是创建一个auto_ptr<Type>类型的局部对象,该局部对象析构时,会将自身所拥有的指针空间释放,所以不会有内存泄漏;
3.auto_ptr的构造函数是explicit,阻止了一般指针隐式转换为 auto_ptr的构造,所以不能直接将一般类型的指针赋值给auto_ptr类型的对象,必须用auto_ptr的构造函数创建对象;
4.由于auto_ptr对象析构时会删除它所拥有的指针,所以使用时避免多个auto_ptr对象管理同一个指针;
5.Auto_ptr内部实现,析构函数中删除对象用的是delete而不是delete[],所以auto_ptr不能管理数组;
6.auto_ptr支持所拥有的指针类型之间的隐式类型转换。
7.可以通过*和->运算符对auto_ptr所有用的指针进行提领操作;
8.T* get(),获得auto_ptr所拥有的指针;T* release(),释放auto_ptr的所有权,并将所有用的指针返回。
149、class、union、struct的区别
1.C语言中,struct只是一个聚合数据类型,没有权限设置,无法添加成员函数,无法实现面向对象编程,且如果没有typedef结构名,声明结构变量必须添加关键字struct。
2.C++中,struct功能大大扩展,可以有权限设置(默认权限为public),可以像class一样有成员函数,继承(默认public继承),可以实现面对对象编程,允许在声明结构变量时省略关键字struct。
3.C与C++中的union:一种数据格式,能够存储不同的数据类型,但只能同时存储其中的一种类型。C++ union结构式一种特殊的类。它能够包含访问权限、成员变量、成员函数(可以包含构造函数和析构函数)。它不能包含虚函数和静态数据变量。它也不能被用作其他类的基类,它本身也不能有从某个基类派生而来。Union中得默认访问权限是public。union类型是共享内存的,以size最大的结构作为自己的大小。每个数据成员在内存中的起始地址是相同的。
4.在C/C++程序的编写中,当多个基本数据类型或复合数据结构要占用同一片内存时,我们要使用联合体;当多种类型,多个对象,多个事物只取其一时(我们姑且通俗地称其为“n 选1”),我们也可以使用联合体来发挥其长处。在某一时刻,一个union中只能有一个值是有效的。union的一个用法就是可以用来测试CPU是大端模式还是小端模式。
150、动态联编与静态联编
1.在C++中,联编是指一个计算机程序的不同部分彼此关联的过程。按照联编所进行的阶段不同,可以分为静态联编和动态联编;
2.静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的,又称为早期联编。要实现静态联编,在编译阶段就必须确定程序中的操作调用(如函数调用)与执行该操作代码间的关系,确定这种关系称为束定,在编译时的束定称为静态束定。静态联编对函数的选择是基于指向对象的指针或者引用的类型。其优点是效率高,但灵活性差。
3.动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现。这种联编又称为晚期联编,或动态束定。动态联编对成员函数的选择是基于对象的类型,针对不同的对象类型将做出不同的编译结果。C++中一般情况下的联编是静态联编,但是当涉及到多态性和虚函数时应该使用动态联编。动态联编的优点是灵活性强,但效率低。动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式为:指向基类的指针变量名->虚函数名(实参表)或基类对象的引用名.虚函数名(实参表)
4.实现动态联编三个条件:
必须把动态联编的行为定义为类的虚函数;
类之间应满足子类型关系,通常表现为一个类从另一个类公有派生而来;
必须先使用基类指针指向子类型的对象,然后直接或间接使用基类指针调用虚函数;
目前已整理十万字的C/C++、嵌入式常见面试题!!!!还在持续更新中!!! 这个专栏写完了,再po上自己亲手敲的笔试编程题整理。