C++面试常见问题

C++面试常见问题

针对《程序员面试笔记——C/C++、算法、数据结构篇》一书的读书记录整理

文章目录

1.C++程序设计基础

简述#include<>与#include""区别

答:#include<>从编译器指定路径处搜索,#include""首先在程序当前目录中进行搜索,然后再从编译器指定的路径进行搜索。

‘#’与‘##’在define中作用

答:宏定义中#运算符将其后面的参数转换成字符串,宏定义中的##运算符前后的参数进行字符串拼接

简述assert断言的概念

答:assert用于在程序的DEBUG版本中检测条件表达式,如果为假,则输出诊断信息并终止程序运行。

简述++i与i++的区别

前者先加1后赋值,后者先赋值后加1

简述C++类型转换操作符
  • static_cast:基本类型转换(父类指针指向父类对象转换为子类指针的时候可以成功转换,但是不会检查对象是不是真的子类对象,所以运行结果不安全)
  • dynamic_cast:用于对象指针之间的类型转换(将父类指针转换为子类指针的过程中,需要对其背后的真实对象进行类型检查,不匹配会转换失败返回空指针或者抛出异常)
  • const_cast:在转换过程中增加或者删除const属性volatile属性
  • reinterpret_cast:可以将一种类型的指针直接转换为另一种类型的指针,不论两个类型是否有继承关系。还可以把整数与指针互相转换。不安全。
静态全局变量的概念

静态全局变量的作用域为仅限于定义位置的文件内部。如果放在头文件里,其他文件包含以后,会对此静态全局变量进行拷贝,每个拷贝相互独立。

简述宏定义与内联函数的区别
对比宏定义内联
替换阶段预处理阶段编译阶段
替换方式简单字符串替换函数内嵌
调试模式不可调试可调式
参数检查无参数检查有参数检查
作为类内成员函数无法访问类内所有成员可以访问类内所有成员
缺点没有参数检查代码膨胀

注意:在定义内联函数的时一定要在函数定义时使用inline关键字,在函数声明中使用inline时没有效果的。

sizeof计算结构体时内存对齐问题

数据对齐指在处理结构体中的成员时,成员在内存中的起始地址编码必须时成员类型所占字节数的整数倍。
结构体sizeof的计算结果必须是结构体中所占空间最多的成员所占空间的整数倍。
在数据对齐时,要以结构体最深层的基本数据类型为准。

简述malloc/free与new/delete的区别
对比malloc/freenew/delete
定义C语言的库函数C++的运算符
应用只能用于基本类型可以用于基本类型以及用户自定义类型
返回值返回void*,需要显式转换成所需要的类型直接指明对象类型
操作只负责在堆上申请空间,返回首地址申请空间后会调用对象的构造函数/析构函数
作为类内成员函数无法访问类内所有成员可以访问类内所有成员
简述delete与delete[]的区别

当new[]中的数组元素是基本类型时,通过delete和delete[]都可以释放数组空间
当new[]中的数组元素时自定义类型时,只能通过delete[]释放数组空间

不使用临时变量交换两个数

一个异或操作的交换律公式,a ^ b ^ a == a ^ a ^ b == b
一个数与其自身异或操作结果为0,一个数与0进行异或操作结果还是这个数本身。

  • 方法1:
    a=a-b;
    b=b+a;
    a=b-a;
  • 方法2:
    a=a^b;
    b=a^b;
    a=a^b;
计算二进制中1的个数

将原数字每次右移1位,与1按位与操作,返回的是1,计数+1
或者把1按位左移进行按位与。

找出数组中唯一不成对出现的数字

所有数字异或一次,最后与0异或,返回值就是不成对的数字

简述main函数执行前后发生什么

main函数第一行代码执行前会调用全局对象和静态对象的构造函数,初始化全局变量和静态变量,main函数最后一行代码执行之后会调用atexit注册的函数,并且调用顺序与注册顺序相反。

2.指针和引用

区分数组和指针

数组名等价于数组首元素的地址,当数组名作为参数时,相当于传递了数组首元素的地址,而且只要实参是地址,那么形参一定是指针。
数组名是不能进行自增操作的,而指针可以。

简述指针和句柄的区别

句柄是一个32位的无符号证数,表示一个内存地址列表的整数索引,是分配给资源的唯一标识。句柄是间接指向资源对象的。有句柄的原因是因为如果资源对象在系统中一直处于空闲状态,那么操作系统的内存模块会将其内存回收,如果重新访问这个资源,系统会再重新分配内存,这个操作可能导致资源对象的物理地址放生改变。
实际上句柄中记录着资源对象列表中某个成员对象的缩影。
指针就是内存地址。

指针常量和常量指针的常见问题

常量地址不能初始化普通指针
指针常量定义时必须初始化
常量指针不能修改指向的内容
指针常量不能修改指针的值

指针常量与字符串常量的冲突

字符串常量是存在常量区中,如果定义一个const指针获取常量的指针,是不可以通过该定义的指针去修改常量的,此时这个指针相当于指向常量的常量指针。

简述数组指针与二维数组的区别

数组名始终等价于数组首元素的地址

指针作为参数的常见错误

如果想通过指针在被调函数中修改主调函数的变量,必须将主调函数变量的地址作为参数,在被调函数中修改指针指向的内容。

函数指针的概念

指针变量可以指向任意类型的数据,也可以指向一个函数,每个函数在内存中都占用一段存储单元,这段存储单元的首地址称为函数的入口地址,指向这个函数入口地址的指针称为函数指针。函数名等价于函数的入口地址。

空指针和野指针的概念

空指针是一种特殊的指针:处于空闲状态,没有指向任何变量。
野指针不是空指针,而是指向不明或不当的内存地址。出现野指针的原因

  • 指针未初始化
  • 指针指向的变量被free或者delete后没有置空
  • 指针操作超过所指向变量的生存期
简述指针跟引用的区别
  • 指针是变量的地址,引用是变量的别名
  • sizeof指针为指针本身所占空间,32位返回4字节,引用sizeof返回原变量占有的空间
  • 自增:指针自增指向下一地址空间,引用自增对原变量自增
  • 初始化:指针可以不初始化或者初始化为空,引用必须初始化
  • 修改:指针可以本身可以修改,引用本身不能修改
  • 指针可以定义为二重,引用不可以
  • 指针需要先解引用,引用直接使用

常量引用的初始化操作实际上分为两步:1将常量存放在一个临时变量里面,然后使用临时变量初始化常量引用

3.内存管理

简述栈空间跟堆空间的关系

栈空间存储函数参数局部变量,空间由操作系统自动分配回收。
堆空间用于动态分配内存块,由程序员分配跟释放
堆空间的频繁分配与释放会造成空间碎片化
栈空间默认在windows下都是1MB,堆空间理论有几G
生长方向:栈是自上而下,内存地址逐渐减小。堆是自下而上,内存地址逐渐增大

简述递归程序潜在的风险

在递归调用过程中,每次递归都会保留现场,把当前的上下文压入函数栈,调用过多,压栈过多会造成栈溢出,可以考虑使用循环代替递归调用。

预防内存泄露的方法

使用智能指针代替普通指针
保证malloc和free,new和delete成对出现
检查释放内存时有没有提前return的情况

访问vector元素时的越界问题

通过下标访问vector 的元素时不会进行边界检查,程序不会报错。
如果想在访问vector中的元素时首先进行边界检查,可以使用at函数

4.字符串

简述memcpy和strcpy的区别

两者都是C标准库函数
memcpy时C语言的内存拷贝函数,提供的是内存拷贝功能,不是只针对字符串
strcpy专为字符串定义的拷贝函数,用于标准字符串拷贝(有字符串结束标志\0)

5.面向对象

简述面向过程和面向对象的区别

面向过程是分析问题的解决步骤,明确步骤的输入输出跟流程,结构化自上而下的程序设计方法。
面向过程是把构成问题的事务分解成对象,从局部着手,通过迭代的方式逐步构建整个程序,以数据为核心,类设计为主要工作的程序设计方法。
面向过程使程序性能更高,系统开销更小。
面向过程使程序有更好的可扩展可复用可维护性。

简述面向对象的基本特征

抽象:对象抽象成类。
继承:继承一个类使得继承的类有被继承类的特性。
封装:把属性方法隐藏起来,只暴露有限的信息。
多态:不用对象对于同一消息的不同响应。

简述类和结构体的关系

类的默认访问控制是private,struct的默认访问控制是public
C++在C语言上对struct进行了拓展,使得struct也可以包含方法成员。

类中的静态数据成员与静态成员函数

静态数据成员属于整个类,不属于某个对象,必须在类内声明,类外初始化
静态成员函数也是属于整个类,没有this指针,所以不能访问非静态成员变量。
访问静态成员时可以通过类访问,也可以通过对象访问。

简述const修饰符在类中的用法

修饰成员变量代表成员变量初始化之后不可变,需要在初始化列表中进行初始化
修饰成员函数代表该成员函数不能改变成员变量的值
常量对象只能调用类的常量成员函数

简述友元函数和友元类的概念。

友元函数不是类的成员函数,而是类外部的函数,能够访问类的非公有成员。
友元类的所有成员函数都是另一个类的友元函数。
友元关系是单方向的,且不能被继承。

构造函数和析构函数的执行顺序

自上而下构造,自下而上析构

C语言不支持函数重载的原因

C++在编译过程中对函数重命名,而C语言保留原始函数名

extern C作用

告诉编译器使用C函数编译,不进行重命名

简述函数重载和函数覆盖的区别

函数覆盖发生在子类和父类之间,父类定义虚函数,子类重新实现这个函数,函数原型相同,根据对象的选择调用的函数。
函数重载是同一类不同方法,参数列表不同,根据参数类型选择调用的函数。

名字隐藏的问题

父类中有一组重载函数,子类在继承时如果覆盖了这组重载函数的任意一个,则其他没有被覆盖的同名函数在子类中是不可见的。

继承和组合的区别

继承是is-a关系,组合是has-a的关系

简述公有继承,私有继承和保护继承的区别

公有继承可以访问父类的公有成员保护成员
保护继承可以访问共有成员保护成员,继承过来的共有成员都变为保护成员
私有继承可以可以访问公有成员保护成员,继承过来的共有成员保护成员都变成私有成员

父类构造函数和子类构造函数的关系

创建一个子类对象时,系统在执行子类构造函数的函数体前,首先调用父类的构造函数。

虚继承中构造函数的调用

在菱形继承中存在访问二义性的问题,使用虚继承保证了子类只有被菱形继承的爷爷类只有一份拷贝,虚继承保证继承关系中的虚基类只被初始化一次

空类的大小

1字节,占位符

简述虚函数表的概念

如果一个类中有虚函数,那么这个类就会对应一个虚函数表,虚函数表中的元素是一组指向函数的指针,每个指针指向一个虚函数的入口地址,在访问虚函数的时候通过虚函数表进行函数调用。
在含有虚函数的类对象中,除了对象的数据成员之外,还有一个指向虚函数表的指针,位于顶部。

函数调用的匹配规则

先找参数完全匹配的普通函数
寻找模板参数完全匹配的函数模板,并实例化一个模板函数
通过隐式转换匹配普通函数
都失败编译失败

简述vector容器空间增长的原理

如果向一个已满的vector插入元素,会重新分配一块内存空间,并将原有元素和新插入的元素拷贝到新空间中。内存增长大小一般为1.5-2倍

简述vector容器中size和capacity函数的用途

size返回容器中已经保存的元素个数
capacity返回容器当前容量大小

手工调整vector容器空间的方式

reserve函数可以让容器重新分配指定大小的空间
shrink_to_fit函数可以回收所有尚未使用的剩余空间
resize函数可以强制调整容器中已保存的元素个数

简述deque容器的插入删除原理

双端队列deque是一种双向开口的存储空间分段连续的数据结构,每段数据空间内部是连续的,而每段数据空间不一定连续。在向deque删除和插入元素的过程中,会根据数据空间的状态,动态分配和释放空间,数据空间段的数量会发生变化。

迭代器失效原因

对容器进行删除操作时,容器中元素的数量发生变化,这种变化可能会导致某些元素的物理地址发生变化,使指向这些元素的迭代器失效。

简述环状引用问题以及其解决方案

有可能出现环状引用的地方使用weak_ptr弱指针代替shared_ptr共享指针可以有效地避免环状引用的问题

unique_ptr优于auto_ptr的原因

由于unique_ptr在内存安全性,充当容器元素和支持动态数组方面优于auto_ptr,因此C++11使用unique_ptr代替auto_ptr

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值