20210531-C++面试

1. sizeof(void)=1;
windows:sizeof(void)编译不通过
C++:sizeof(空的结构体/类)=1
C:sizeof(空的结构体)=0

2.右值引用就是将寄存器中的值进行引用
如: int&& i = a+1;
int& i = a+1编译时通不过的,因为a+1是寄存器中的值,无法作为左值,如果要用,就要先把寄存器中的值保存到内存中
如:int c = a+1;
iint& i = c;
右值引用为了效率考虑,少了寄存器的值到内存的拷贝。使用move可以将左值变为右值。
使用场景:函数返回一个临时变量,想减少一次复制时(数据很大),可以使用右值引用。

3.移动构造:
减少不必要的复制,带来性能提升。就是将临时对象它原来的内存空间转移给构造出来的对象,而不是进行复制。
使用场景:临时对象即将消亡,但是它里面的资源需要再利用。移动构造函数(使用右值引用)。

4.虚函数是怎么实现的?
虚函数主要是实现多态的功能。它是通过虚函数表和虚表指针的方式来实现的。编译器在编译时,会给所有虚函数的类创建一张虚函数表。构建派生类的流程:
1)将实例化的虚表指针先指向基类的虚函数表。
2)构造基类的成员变量。
3)调用基类的构造函数
4)将实例化的虚表指针指向派生类的虚函数表
5)构造派生类成员变量
6)调用派生类的构造函数

5.C++中的智能指针?三种指针解决的问题以及区别?
1)shared_ptr智能指针,利用引用计数和栈区对象释放调用析构函数的特性,创建的智能指针。
为了解决内存泄露的问题。但是shared_ptr不能很好的解决指针循环引用的问题。
2)weak_ptr是为了解决这个问题而设计的。weak_ptr只能从shared_ptr和weak_ptr中构造,引用时不增加引用计数,但是不能单独使用,需要调用lock。将weak_ptr转成shared_ptr才能使用。使用前需要判断对象是不是已经被释放了。
3)unique_ptr:不能共享指针,某个时刻只能有一个unique_ptr指向一个对象,当unique_ptr被销毁时,它指向的对象也将被释放。可以使用拷贝和赋值一个即将被销毁的unique_ptr,如函数返回一个unique_ptr
unique_ptr get_result()
{
unique_ptr up(new int(100));
return up;
}
4)shared_ptr要重载哪些? =号操作符,拷贝构造函数,->符号

6.C++中4中强制转化?
const_cast为了移除变量const或volatile限定符
值得注意的是,const_cast后使用指针改变常量的值,内存中常量的值被改变了,但是由于是const类型,编译期已经转换成指令了,常量的值没有改变。

static_cast明确隐式转换;如float转Int

dynamic_cast:利用运行时类型检测,识别错误的转化。因为内部需要调用检测函数,需要额外的开销,所以一般只有父类转子类才会用。

7.C++内存分为哪几块?有什么作用?
栈区:由编译器自动分配释放,存放局部变量。操作和栈类似。
堆区:由程序员分配释放
静态存储区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域。未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束之后由系统释放。
文字常量区:常量字符串放在这,程序结束之后由系统释放。
程序代码区:存放函数体的二进制代码。
全局变量初始化在main函数之前,静态变量初始化在第一次函数调用这个静态局部变量之前。

8.多态是C++里边的,然后如果让你在C语言中实现一个多态,你怎么做呢?
首先创建需要的函数指针,创建一个只有 函数指针的结构体,里面存放之前创建的函数指针,模拟成虚函数表。
创建一个基类结构体,包含之前创建的存放函数指针的结构体和其它数据类型。创建子类结构体,包含基类结构体和自己的数据。
创建子类结构体时,使基类结构体中的函数指针指向自己的函数实现。 就可以实现多态了。

9.C++和golang,这方面有了解吗?
开发环境简单,编译好找错误,开发迅速
系统库比较全,c++需要较多的第三方或者自己开发
跨平台的特性,C++各个平台代码有差异,库也不一样

10.C++和python的区别?
C++是编译型语言,python是解释型语言。所以C++的运行效率高,python的运行效率很低。
python中所以的变量都是对象,C++有基础类型。C++需要自己管理内存,python有垃圾回收机制。
解释性语言和编译型语言的区别
计算机不能直接理解高级语言,所以最终都是要将高级语言翻译成机器语言。翻译的方式有两种,一个是编译,一个是解释。

编译型语言通常会对代码进行编译,生成可执行的二进制代码,执行的是编译后的结果。
优点:
1、预编译过程可以做代码优化
2、只编译一次,运行时不需要翻译,效率高
3、可以脱离语言环境独立运行。
缺点:
1、修改后需要重新编译
2、不同环境下不可移植,需要针对操作系统环境编译不同的可以执行文件。
解释性语言不对源码这行编译,通过解释器载入脚本后运行,每个语句都是执行的时候才进行解释翻译。每执行一次就要翻译一次。,效率低。
优点:1、有良好的平台兼容性,在安装了解释器的任何环境都可以运行。
2、灵活&快速部署,修改代码可直接运行,不用停机维护。缺点:边运行边翻译,性能不行。

11.抽象类和接口的区别
含有纯虚函数的类是抽象类,只有纯虚函数的类是接口类
接口带来的最大好处就是避免了多继承带来的复杂性和低效性,并且同时可以提供多重继承的好处。接口和抽象类都可以体现多态性,但是抽象类对事物进行抽象,更多的是为了继承,为了扩展,为了实现代码的重用,子类和父类之间体现的是is-a关系;

接口则更多的体现一种行为约束,一种规则,一旦实现了这个接口,就要给出这个接口中所有方法的具体实现,也就是说实现类对于接口中所有的方法都是有意义的。
在设计类的时候,首先考虑用接口抽象出类的特性,当你发现某些方法可以复用的时候,可以使用抽象类来复用代码。简单说,接口用于抽象事物的特性,抽象类用于代码复用。

12.构造函数里能否调用虚成员方法?
可以,如果在基类调用虚成员方法,会调用基类的虚函数,如果在子类调用会调用子类的虚函数 (实验结果)
如果是纯虚函数,在基类中不能调用虚函数,因为这时候这个纯虚函数还没有实体 (实验结果)
对象构造过程:
1)首先会按对象的大小申请一块内存
2)把指向这块内存的指针作为this指针来调用类的构造函数,对这块内存进行初始化。
3)如果有父类就会先调用父类构造,多个父类一次构造,并会适当调整this指针的位置
4)最后执行自己的构造

13.C++如何实现运行时类型信息?
typeid,dynamic_cast是c++运行时类型信息RTTI重要组成部分。运行信息,来自于多态,所以这些运算符只用于基于多态的继承体系中。
static_cast明确隐式转换。如果类型不能做隐式转换编译报错。
int n = 5; float f = 10.0f;
f = n; 等价于 f = static_cast(n);
void* p = nullptr; 等价于 int* pN = static_cast<int*>§;
const_cast用来移除const和volatile限定符,经过转换后,const变量的值也会被改变
const int a = 5; int* k = const_cast<int*>(&a); *k = 123; //a被改为123 实现现象有点奇怪,变量是变123了,cout是5,给其他变量赋值也是5
const_cast只能对指针和引用使用
reinterpret_cast 与强制类型转换相同

dynamic_cast利用运行时类别检测,识别错误的转换,因为内部要调用检测函数需要额外的开销,所以一般只有父类转子类的时候才会用,要使用dynamic_cast,类中必须要有虚函数(多态)
如pSon=dynamic_cast<Son*>(pfather); 如果pfather是指向子类的基类指针,返回指向子类的子类指针。如果不是,返回nullptr

要实现dynamic_cast,编译器会在每个含有虚函数的对象的虚函数表的前四个字节存放一个指向_RTTICompleteObjectLocator结构的指针,当然还要额外空间存放_RTTICompleteObjectLocator及其相关结构的数据。_RTTICompleteObjectLocator就是实现dynamic_cast的关键结构,里面存放了vfptr相对this指针的偏移,构造函数偏移(针对虚拟继承),type_info指针,以及类层次结构中其它类的相关信息。
type_info是C++ Standard所定义的类型描述器,type_info中放置着每个类的类型信息。
如:My_slider* pd1 = dynamic_cast<My_slider*>(pb);
My_slider的type_info会被编译器产生出来。pb指向的对象的type_info会在运行时通过vptr取得。这两个type_info会被交给runtime library函数,比较之后告诉我们是否吻合。如果吻合,返回转换后的My_slider*;否则返回nullptr。
typeid()返回指针或引用指向的对象的类型信息type_info

函数调用规约入栈 顺序

13.delete和delete[]的区别
如果是int* p = new int[10];两者结果是一样的
但是下面这种, delete a;只会释放a指向的所有对象的内存空间,但是却只会执行a[0]的析构函数,其他对象里的buffer就得不到释放。
class A
{
private:
char* buffer;
public:
A(){buffer = new char[1024];}
~A(){delete[] buffer;}
}
A* a = new A[10];

14.栈溢出问题如何解决?
1).递归问题可以使用尾递归的方法进行优化
2).递归函数中,使用static替代非static局部变量。不仅降低每次递归时,产生和释放变量对象的开销,且,static对象还能保存递归的中间状态。
3).创建线程时,增加栈区的大小。
4).创建数组之类的,减小创建的大小。

15.内联函数和宏定义的区别
宏定义在编译前进行展开,做字符串替换,不会检查错误,只有在展开之后编译的时候才检查错误;
内联函数必须是简单的函数体(不能有for,while, if, switch等),内联函数是内嵌到程序代码中的,减少函数在调用时的时空开销(调用函数时,要保留现场,然后转入被调用函数去执行,调用完,再返回主函数,再恢复现场)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值