您现在的位置是:首页 >其他 >C++ 知识点总结 面经网站首页其他
C++ 知识点总结 面经
总结C++面试常问的知识点总结
注意事项
1.不要着急,先想一下,组织语言
2.对简单问题回答要有自己的理解,把细节做好
3.对于相对复杂的问题,整理好逻辑思路,以及问题大致描述的顺序。需要跟面试官沟通,不要自顾自滔滔不绝
4.对于不知道的内容,不要只说不知道,可以回答其他相关的,引导提问,体现自己擅长的地方
5.你还有什么问题?如果能得到工作将来在公司会用到那些技术,对面试做出点评
回答问题,饱满有层次
C++11 新特性
容器、智能指针、lambda表达式
程序内存布局
高地址向低地址
栈区、堆区 数据区、代码区
栈区
栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量等。栈区的内存分配和释放是由系统自动完成的,速度快。
堆区
堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。在C++中,可以使用new在堆区分配内存,使用delete释放内存。堆区的内存分配和释放需要手动完成,速度相对较慢。
数据区
数据区通常包括静态存储区和常量区。
静态存储区包括.DATA段和.BSS段
数据区是程序内存中的一部分,用于存储全局变量和静态变量。它分为两个部分:DATA段和BSS段。DATA段(全局初始化区)存放初始化的全局变量和静态变量;BSS段(全局未初始化区)存放未初始化的全局变量和静态变量。程序运行结束时自动释放。
静态变量和局部变量
静态局部变量和局部变量的主要区别在于它们的生命周期和作用域。
局部变量是在函数内部定义的变量,它的生命周期仅限于函数的执行期间。当函数执行完毕后,局部变量就会被销毁。局部变量只能在定义它的函数内部访问。
静态局部变量也是在函数内部定义的,但是它在程序执行期间一直存在,即使定义它的函数已经执行完毕。静态局部变量只能在定义它的函数内部访问,但是它的值会在函数调用之间保持不变。
C++程序编译过程
C++程序的编译过程通常包括以下四个步骤:
-
预处理:预处理器将源代码文件中的预处理指令(如#include和#define)进行替换和展开,生成一个预处理后的文件。
-
编译:编译器将预处理后的文件翻译成汇编语言代码,并对代码进行优化。然后,汇编器将汇编语言代码翻译成机器语言代码,生成目标文件。
-
链接:链接器将多个目标文件和库文件链接在一起,生成可执行文件。链接器解决了目标文件之间的相互引用问题,并将库函数与程序代码链接在一起。
-
加载:当程序运行时,操作系统负责将可执行文件加载到内存中,并为程序分配运行所需的资源。然后,操作系统将控制权转交给程序,程序开始执行。
C++ 三大特性
继承、多态、封装
继承:允许派生类使用基类变量,减少代码量
多态:动态多态(基类指针指向派生类),静态多态(函数、操作符重载)
封装:模块化,通过访问控制方式(public、protected、private)控制对成员的访问
继承
1.代码的复用
2.通过继承,再基类里面给所有派生类可以保留统一的纯虚函数接口,等待派生类进行重写,通过使用多态,可以通过基类的指针访问不同派生类对象同名覆盖方法
C++继承多态
多态:
静态多态(编译时期):函数重载和模板。模板是一种静态多态,是一种编译时的多态(类似函数重载)
动态多态(运行时期):虚函数,指针指向派生类对象,
在C++中,隐藏、重载和重写是三个不同的概念:
隐藏(Hiding):当派生类中定义了一个与基类中同名的成员时,派生类中的成员将隐藏基类中的成员。这意味着,如果我们使用派生类对象访问该成员,将访问派生类中定义的成员,而不是基类中的成员。
重载(Overloading):当在同一作用域内定义了多个同名函数,但它们的参数列表不同时,这些函数被称为重载函数。编译器根据调用函数时提供的实参来确定应调用哪个函数。
重写(Overriding):当派生类中定义了一个与基类中虚函数同名、同参数列表、同返回类型的函数时,派生类中的函数将重写基类中的虚函数。这意味着,如果我们使用基类指针或引用指向派生类对象并调用该虚函数,将调用派生类中定义的函数,而不是基类中的函数。
这些概念之间的主要区别在于它们所涉及的上下文和目的。隐藏涉及到派生类和基类之间的关系;重载涉及到同一作用域内多个同名函数之间的关系;重写涉及到派生类和基类之间虚函数的关系。
this指针
对于一个类型定义的多个成员变量,它们共享成员函数。当调用成员函数的时候,通过传入this指针来区别到底是哪个成员调用的成员函数
对齐
使用预处理命令#pragma pack(n)
编译器会按照n来进行对齐,小于n的将会升到n,大于n的为n的倍数
sizeof问题
new 和delete什么时候使用new[]申请可以使用delete释放
delet有两步,第一步先调用析构函数,然后再释放内存
如果是自定义类型,而且提供了析构函数,那么new[] 一定需要匹配delete[]
空间配置器
给容器使用的,主要的作用把对象的内存开辟和对象构造分开,把对象析构和内存释放分开
vector和list区别
数组和链表的区别
vector底层是可以扩容的数组,随机访问多
list底层是双向链表deque,增加删除多
map和多重map multimap
map映射表[key - value],底层实现是红黑树
multimap 允许key重复
红黑树
5个性质
插入的3种情况(最多旋转2次)
删除4种情况(最多旋转3次)
防止内存泄漏?智能指针
分配的堆内存没有释放,也再也没有释放机会了
智能指针利用栈上指针出作用域自动析构
unique_ptr/scoped_ptr/auot_ptr
shared_ptr/weak_ptr
C++调用C语言语句
C和C++生成符号方式不同
C语言的函数声明必须阔在 extern"C"{ /* code */} 中
C++类的初始化列表
可以指定对象成员变量的初始化方式,尤其是指定成员对象的构造方式
C和C++区别,内存分布有什么区别
1.引用
2.函数重载
3.new/delete malloc/free
4.const,inline,带默认参数值的函数
5.模板
6.OOP
7.STL
8.智能指针
int * const p和const int* p 区别
int * const p,修饰的是p,p不能修改,*p可以修改
const int* p,修饰的是int,p可以修改,*p不能修改
malloc和new
1.malloc按字节开辟内存,new底层也是通过malloc开辟内存,new还可以提供初始化
2.malloc开辟失败 nullptr,new开辟失败抛出异常
3.malloc是库函数,new是操作符
map和set容器实现原理
set集合,只存储key
map映射表,存储key-value键值对
底层都是红黑树
shared_ptr引用计数在哪
堆上分配的
迭代器失效
迭代器不允许一边读一边修改
当通过一个迭代器插入一个元素,所有迭代器都失效
当通过一个迭代器插入一个元素,当前删除位置之后的所有元素的迭代器就都失效了
当通过迭代器更新容器元素亦幻,要及时对迭代器进行更新,
struct和class区别
1.定义类的时候的区别,struct默认public,class默认private
2.继承时
3.c++中 struct空类sizeof是1
4.class还可以定义模板类型参数
宏和内联函数
#define 字符串替换,预编译阶段
inline 编译阶段,在函数调用点,把函数代码直接展开调用,节省了函数的调用开销
宏没有办法调试,inline可调试,在debug模式下跟普通函数没有区别
局部变量存放在栈上
stack ebp指针偏移来一定访问,不产生符号.text,属于指令的一部分
静态全局或静态局部在数据段上.data .bss
拷贝构造传引用而不传值
因为如果传值则需要再调用拷贝构造函数产生一个临时变量,这样会递归下去,产生错误,会一直调用拷贝构造函数
编译器会检查这个错误,直接产生编译错误
不可被继承的类
派生类构造时会先调用基类的构造函数,因此把基类的构造函数私有化可以实现以不可被继承的类
什么是纯虚函数?为什么要有纯虚函数?虚函数表放在哪里?
virtual void func() = 0; 纯虚函数 =》 抽象类(不能被实例化对象的,可以定义指针和引用)
一般定义在基类里面,基类不代表任务实体,它的主要作用之一就是给所有的派生类保留统一的纯虚函数接口,让派生类进行重写,方便使用多态。
虚函数表在编译阶段产生,运行是加载到.rodata段(常量区,只读)
单例模式
饿汉式单例模式:还没有获取实例对象,实例对象就已经产生了
懒汉式单例模式:唯一的实例对象,直到第一次获取它的时候才产生
const static volatile
const
const 定义的叫常量,编译方式为:编译过程中,把出现常量名字的地方,用常量的值进行替换
const int a = 10;
int* p = (int*)& a;
*p = 20;
cout << *p << " " << a << endl;
最终输出为20 10
即使把a内存中存放的值改为20,a也是10
const还可以定义常成员方法
static关键字作用
static修饰全局变量,在符号表中符号的作用域从g(global)变成l(local),其它文件不可见
static修饰局部变量,(数据区)放在.data和.bss段,如果初始化了放在.data段,如果未初始化或初始化未0放在.bss段
static修饰成员变量:这个成员变量从对象私有变为对象共享
static修饰成员方法:不再产生this指针,成员方法不再使用成员对象进行调用,可直接使用作用域调用。 静态成员变量属于类,而不属于类的任何一个实例
const和static区别
面向过程:
const:修饰全局变量、局部变量、形参变量
static:修饰全局变量、局部变量
面向对象
const:常方法/成员变量 Test *this -> const Test *this 依赖对象
static:静态方法/成员变量 Test *this -> 没有,没有this指针 不依赖对象,通过类作用域访问
四种强制类型转换
const_cast
static_cast
reinterpret_cast:C风格的类型转换,没有安全可言,可以随便转换为任意类型
dynamic_cast:支持RTTI信息识别的类型转换
deque的底层原理
deque双端队列,两端都有队头和队尾,两端都可以插入删除O(1)
动态开辟的二维数组
#define MAP_SIZE 2
#define QUE_SIZE(T) 4096/sizeof(T)
开始时
第一维数组大小为MAP_SIZE(T*)
第二维数组默认开辟的大小为QUE_SIZE(T)
扩容:把第一维数组按照2倍的方式进行扩容,扩容以后,会把原来的第二维的数组,从新一维数组的第oldsize/2 开始存放
虚函数,多态
一个类存在虚函数是,在编译阶段就会产生一张虚函数表,运行时虚函数表加载到.rodata段。
在使用指针或者引用时,调用虚函数,指针访问对象的头四个字节vfptr获取指针,再到vftable中取到虚函数的地址,进行动态绑定调用
多态:基类指针指向不同派生类,然后调用不同派生类的同名覆盖方法。
设计函数接口的时候,可以使用基类的指针或者引用来接收不同派生类对象
虚析构函数
当基类指针指向派生类对象的时候,在调用析构函数的时候会只调用基类的析构,没有调用派生类的析构,会产生内存的泄露
智能指针
管理资源的生命周期
构造函数和析构函数
构造函数不能为虚函数
虚函数的调用必须首先对象得存在,只有对象存在才能获取对象前四个字节的vfptr然后在从vftable中找到对应的虚函数进行调用。构造函数没调用完,理论上是没有对象产生的
析构函数可以为虚函数
异常机制
try{
/* 可能会抛出异常的代码 */
}catch(const string &err) {
/* 捕获想应的异常类型对象,进行处理,完成后,代码继续向下执行 */
}
早绑定 晚绑定
早绑定(静态绑定):编译时期的绑定,普通函数的调用,用对象调用虚函数
晚绑定(动态绑定):用指针/引用调用虚函数的时候都是动态绑定
指针和引用的区别
指针可以不初始化,引用必须初始化
野指针
指针未初始化但已被使用
指针指向的内存已被释放或删除,但指针未被重置
指针越界访问
智能指针交叉引用问题
定义对象的时候用强智能指针shared_ptr,而引用对象的时候用弱智能指针weak_ptr,当通过weak_ptr访问对象成员时,需要先调用weak_ptr的lock提升方法,把weak_ptr提升成shared_ptr强智能指针,再进行对象成员的调用
重载的底层实现,虚函数的底层实现
重载,因为在C++生成函数符号是依赖 名字+参数列表
编译到函数调用点时,根据函数明在和传入的实参(个数和类型),和某一个函数重载匹配的话,那么就直接调用想应的函数重载版本(静态的多态,都是在编译阶段处理的)
虚函数
虚函数的底层实现是通过虚函数表和虚表指针来实现的。
每个含有虚函数的类都有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。
每个含有虚函数的类对象都有一个隐藏成员,即虚表指针vfptr,它指向该类的虚函数表vftable。
当基类指针指向一个子类对象时,通过这个指针调用同名的虚函数时,会根据虚表指针找到对应的子类或基类的虚函数地址,并执行该函数。
sizeof
sizeof是一个编译时运算符,它用于计算给定类型或表达式的大小(以字节为单位)
如计算sizeof(string) 的时候,输出的是string类的大小,而不是str的长度
strlen
输出字符串长度,到’ ’之前
string str = "12345";
std::cout << sizeof(str) << std::endl; // 输出 6,此时加上了'