编译内存相关
C++编译过程?
编译预处理 处理#开头的指令
编译优化 将源码翻译成汇编代码
汇编 将汇编代码翻译成机器代码
链接 将机器代码链接成一个整体,生成可执行文件
链接分为两种:
静态链接:静态链接库.a里的代码拷贝到最终可执行文件中
动态链接:动态链接库.so中的代码不会被拷贝到最终可执行文件中,而是将其内容映射到相应的虚拟地址空间。
静态链接:
优点:运行速度快 缺点:浪费空间,修改静态库后要对整体目标文件重新编译
动态链接:
优点:节省内存,更新方便 缺点:运行速度稍慢
内存管理
栈区:系统自动分配和释放 局部变量、函数参数、返回地址
堆区:程序员控制分配和释放 new或malloc
全局、静态存储区:存放全局变量和静态变量
常量存储区:存放常量,程序运行结束自动释放
代码区:存放编译后的二进制文件
静态全局变量和全局变量的区别
全局变量作用于所有文件,静态全局变量仅作用于本文件
对象创建限制在堆或栈
静态建立 编译器在栈上分配内存,直接调用类的构造函数创建对象
动态建立 使用new关键字在堆空间上创建对象,首先在堆空间上寻找合适的内存并分配,然后调用类的构造函数创建对象
限制对象只能建立在堆上(禁止使用A a)
构造函数要设置protected。将构造函数的访问权限设置为private或protected时,保证类外无权调用A a。如果设为private,无法继承
使用一个public静态函数创建对象,非静态函数只能通过对象调用,静态函数可以通过类名调用。protected禁止了类外创建对象,所以只能使用静态函数通过类名创建。
内存对齐
编译器将程序中每个数据单元安排在自己宽度的整数倍上
优点:便于不同平台之间的移植;提高内存的访问效率
内存泄漏
由于疏忽或错误导致本该释放的内存空间未能释放
比如指针指向一块申请到的内存空间后被重新赋值,导致申请的内存空间无法被找到释放
如何防止内存泄漏?内存泄漏检测工具的原理?
分配后记得释放、注意循环引用、使用智能指针
智能指针
include memory头文件
共享指针shared_ptr 多个指针指向同一个对象
独占指针unique_ptr 同一时刻只有一个指针指向该对象 move移交所有权
弱指针weak_ptr 指向shared_ptr指向的对象
使用智能指针可能出现的问题:循环引用
两个类分别定义了指向对方对象的共享指针,因此要释放a对象,首先要释放b对象;而要释放b对象,又要先释放a对象。
weak_ptr拥有临时所有权,当某个对象存在时才能被访问,需要获得所有权时将其转化为 shared_ptr,此时如果原来的 shared_ptr 被销毁,则该对象的生命期被延长至这个临时的 shared_ptr 同样被销毁。
语言对比
C++11的新特性
1. auto自动类型推导,编译器会在 编译期间 通过初始值推导出变量的类型
2. Decltype 声明类型自动推导,通过表达式推断要定义变量的类型,但又不想用该表达式初始化变量,此时使用decltype
3. Lambda表达式
4. for语法,遍历某个序列
5. 右值引用
6. 标准库move()函数
7. Delete和default
面向对象
什么是面向对象?面向对象的三大特性
对象是具体的事物,事物的抽象就是类,类中包含成员变量和成员函数
面向对象的三大特性
继承 子类拥有父类的成员函数和成员变量,对于非private方法或成员变量,子类可以重写父类方法,当父类中的成员变量、成员函数或者类本身被final修饰时,修饰的类、成员不能被修改
封装 将具体的实现过程和数据封装成一个函数,只能通过接口进行访问,降低耦合性
多态 父类指针指向子类对象,不同继承类的对象,对同一消息作出不同响应。
重载、重写、隐藏
重载:参数列表中类型、个数、顺序不同的同名函数
重写(覆盖):发生在类的继承中,重写的基类中被重写的函数使用 修饰,派生类的函数名、参数列表、返回值类型都同基类一致,只有函数体不同。
隐藏:派生类的函数屏蔽了与其同名的基类函数,此时只要同名,无论参数列表是否相同,基类函数均被隐藏
什么是多态?多态如何实现?
父类指针指向子类对象
通过虚函数实现。类中保存虚函数表,创建对象时产生指向虚函数表的指针。当该类被继承时,虚函数中的函数地址被覆盖。于是可以通过父类指针调用子类特有的方法。
关键字库函数
sizeof和strlen的区别
1 strlen是函数,在运行中计算长度;sizeof是运算符(同加减乘除),在编译时计算长度
2 strlen得到字符串长度,sizeof得到数组分配空间的大小
3 sizeof的参数可以是类型也可以是变量,strlen的参数必须是char*类型的变量。
为什么构造函数不能是虚函数,而基类的析构函数一般写成虚函数
因为构造函数是自动调用的,不可能通过父类指针去调用
基类的析构函数一般写成虚函数是因为若父类的析构函数不是虚函数,则只会清理父类内存,不回清理子类特有内存,造成部分内存泄露。
Lambda表达式
匿名函数,短小精悍
sort(arr.begin(), arr.end(), [](int a, int b) { return a > b; }); // 降序排序
explicit的作用
声明类构造函数必须要显式调用,可以阻止调用构造函数时进行隐式转换。
A ex=10相当于先将10转化为A类型的对象,然后将该对象赋值给ex。而使用explicit后该构造函数只能显式调用ex(100),不允许隐式调用构造函数再去赋值。
static关键字的作用
全局/局部静态变量,普通的静态变量,类的静态成员变量和函数。
全局静态变量只在本文件内可见,普通全局变量所有源文件可见;普通静态变量在程序运行结束后被释放,生命周期比普通变量长,类的静态成员变量和函数属于类不属于对象,且累的静态成员函数只能调用类的静态成员变量。
final关键字
在类名后加final,该类无法被继承
在成员函数后加final,子类中无法重写该函数
const作用和用法
作用:
1 const修饰变量 表示该变量是常量
2 const修饰函数形参 表示不能修改传递的参数值
3 const修饰类的成员函数 表示不能修改成员变量,不能调用非const函数
define和const的区别
define是做直接的替换,无法调试,且没有类型检查,所以不如const安全。
define和typedef的区别
#define表示直接替换,没有作用域的限制
typedef用来定义类型的别名
inline作用及使用方法
在编译阶段,程序中出现内联函数调用表达式会被内联函数的函数体进行替换,内联函数一般比较简短,使用内联函数可以提高运行效率
宏定义define和内联函数inline的区别
宏定义是直接替换,不会有安全检查;内联函数可以减少调用的开销
new和malloc如何判断是否申请到内存
malloc:申请到内存返回该对象的指针,否则返回null指针
new:申请到内存返回该对象的指针,否则抛出bad_alloc异常
delete实现原理?delete和delete[]的区别?
delete首先执行该对象所属类的析构函数,然后释放内存
delete和delete[]的区别
delete释放单个对象所占空间,只调用一次析构函数
delete[]释放数组空间,对数组中每一个成员都调用一次析构函数
new和malloc的区别,delete和free的区别
new和delete搭配,malloc和free搭配
new申请空间时无需指定分配空间的大小,编译器会根据类型自行计算,malloc申请空间时,需要确定所申请空间的大小
new会调用构造函数,delete会调用析构函数
new和delete是关键字,malloc和free是库函数
C和C++中struct的区别
C中struct定义数据,C++定义数据和函数
C++中struct可以有访问权设置
struct和union的区别
union是联合体,struct是结构体
使用时,联合体只有一个有效成员,对不同成员赋值,会覆盖其他成员的值,对结构体则不会。
联合体的大小为所有变量中的最大值,按最大类型的倍数分配内存,结构体分配内存的大小遵循内存对齐的原则
struct和class的异同
struct默认public,class默认private
volatile的作用?是否具有原子性,对编译器的影响
volatile:当对象的值可能被多个线程改变时,告知编译器不对要进行优化,不将变量从内存缓存到寄存器中,避免了该被改变的值未被改变
什么情况下一定要用volatile,能否和const一起使用
当多个线程用到某一变量,并且该变量的值可能发生改变,需要volatile进行修饰,不要将变量从内存缓存到寄存器中,导致寄存器里的值和内存中的值不一致
strcpy函数缺陷
strcpy不检查边界大小,可能导致其他变量被覆盖。
类相关
什么是虚函数?什么是纯虚函数
虚函数是virtual修饰的函数
在虚函数基础上=0就是纯虚函数;
纯虚函数需要子类重写基类纯虚函数,否则不能实例化
如何禁止构造函数的使用
构造函数=delete
构造函数、析构函数是否需要定义成虚函数?为什么
构造函数是自动调用的,不应该设置为被父类指针调用
析构函数在被调用释放内存时,如果未设置为虚函数,则只会释放父类的内存,子类特有的内存不会被释放,造成内存泄露
如何避免拷贝?
delete掉拷贝构造函数
将拷贝构造函数和赋值构造函数设为private
如何减少构造函数开销?
在构造函数中使用类初始化列表,因为对于非内置类型,少了一次调用默认构造函数的过程。
多重继承时会出现什么状况?如何解决
造成命名冲突
解决方案:
1. 声明出现冲突的成员变量来源于哪个类
2. 虚继承,在继承方式前面加上virtual关键字,保证命名冲突的成员变量在派生类中只保留一份
为什么拷贝构造函数必须为引用
拷贝构造函数必须以引用的方式传递参数,这是因为如果值传递的方式,会默认调用拷贝构造函数,这样会导致无限循环,最终导致内存溢出。
C++类对象的初始化顺序
先调用父类的构造函数,再调用子类自身的构造函数
如何禁止一个类被实例化
1 在类中定义一个纯虚函数
2 构造函数private
3 delete掉构造函数
实例化一个对象需要几个阶段
1 分配空间,存储在堆上的对象,在运行阶段分配内存
2 初始化
3 赋值
友元函数的作用
友元函数能够访问类的私有成员
友元类之间共享数据
深拷贝和浅拷贝的区别
深拷贝:开辟了新的内存空间
浅拷贝:未开辟新的内存空间
当类的成员变量有指针变量时,需要使用深拷贝,因为如果是浅拷贝,当一个对象删除后,这个对象内的指针所指内存也将释放,此时另一个对象指向的就是垃圾内存。
深拷贝需要在拷贝构造函数中定义。
编译时的多态和运行时的多态
编译时的多态:函数重载
运行时的多态:父类指针指向子类对象
语言特性相关
左值和右值得区别,左值引用和右值引用的区别,如何将左值转换为右值
看能不能对表达式取地址,如果能,就是左值,否则就是右值
右值引用可以延长临时值的生命周期,减少拷贝的次数,提高运行效率
move()函数的实现原理
1 独享指针所有权的转移,unique_ptr
2 右值引用,移动语义
什么是指针?指针的大小和用法
指向某个类型
大小8个字节空间
指向普通对象的指针
指向常量对象的指针
指向函数的指针
指向对象成员的指针
指向类的当前对象的指针常量this
什么是野指针和悬空指针
野指针是指向内存被释放的指针,指向的值是什么都可能
悬空指针是不确定指向的指针
Nullptr比NULL的优势
Nullptr本身是指针类型,而NULL本身是0;
指针和引用的区别
引用相当于取别名,给一个对象取别名后,不能将这个别名给另一个对象。
引用不占内存空间
指针可以为空,但引用一定要绑定对象
指针可以由多级,但引用只能一级
常量指针和指针常量的区别
const在*左边 常量指针,定值,值不能改变
const在*右边,指针常量 定向 指向不能改变
函数指针和指针函数
指针函数本质是函数,返回值是指针
函数指针本质是指针,这个指针指向一个函数
强制类型转换
static_cast 良性转换 原有的自动类型转换
const_cast 强制去掉常量属性
reinterpret_cast 改变指针或引用的类型 两个具体类型指针的转换
dynamic_cast 在类的继承层次之间进行类型转换 要求有虚函数
如何判断结构体是否相等?能否用memcmp函数判断结构体相等
需要重载操作符==,不能使用memcmp判断,因为memcmp是逐个字节进行比较的,而结构体在内存中存在字节对齐,补的字节是随机的
什么是模板?如何实现?
模板是创建类或者函数的蓝图,分为函数模板和类模板
函数模板和类模板的区别
实例化方式不同,函数模板自动推导类型,类模板需要显式
什么是模板特化,为什么特化?
某些情况下,通用模板的定义对特定类型不合适,所以进行模板特化
<> 包含的头文件为系统自带的头文件库;而 "" 包含的头文件为用户自定义的函数库