文章目录
主要参考:
https://github.com/chankeh/cpp-backend-reference/blob/master/back-end.md
https://www.cnblogs.com/inception6-lxc/p/8686156.html
https://blog.youkuaiyun.com/qq_34561506/article/details/82024989
https://blog.youkuaiyun.com/kuweicai/article/details/82779648
https://www.cnblogs.com/fangyukuan/archive/2010/09/18/1829871.html
https://www.cnblogs.com/fangyukuan/archive/2010/09/18/1830493.html
https://www.jianshu.com/p/6aafc59faa82
https://github.com/jxingm/Interview-Notes/blob/master/c%2B%2B/关键字 static const volatile extern.pdf
https://github.com/jxingm/Interview-Notes/blob/master/c%2B%2B/多态 虚函数 虚函数表.pdf
https://github.com/jxingm/Interview-Notes/blob/master/c%2B%2B/c%2B%2B其他常见问题.pdf
关键字
volatile
https://www.cnblogs.com/xing901022/p/7840684.html
https://www.cnblogs.com/lidabo/p/13182455.html
- 易变性: 当一个线程更新某个volatile声明的变量时,会通知其他的cpu使缓存失效,从而其他cpu想要做更新操作时,需要从 内存 重新读取数据,不会使用缓存或者寄存器中的值。
- 不可优化性: 告诉编译器所修饰的对象不应该执行优化
static
-
隐藏作用:修饰全局变量或函数或类,使其只能在文件作用域中可见,起到封装作用
-
改变存储区:修饰局部变量使其由栈区存储改为静态/全局存储区,生命周期改变,但作用域不变,且只能被初始化一次。
-
限定作用:在C++的类的定义中使用static声明静态函数或静态属性,可以通过
类名::变量名/函数名
直接引用,统一属于类的静态资源,是类实例之间共享的,一处变、处处变。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。但静态数据成员的初始化必须在类外进行。class Test { public: static int a; static void test() { cout << "test is running." << endl; } }; int Test::a = 0; int main(int argc, char **argv) { cout << Test::a << endl; Test::test(); return 0; }
const
https://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html
const 常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。常用来声明常量,并取代宏定义的常量。
修饰基本数据类型:
-
修饰变量: 可以用在类型说明符前
const int a = 2;
,也可以用在类型说明符后int const b = 3;
,其结果是一样的。该变量都是不可修改的。在声明时必须初始化:const char name[] = "JOY";
-
修饰指针变量和引用变量:如果const位于星号 * 的左侧,则指针所指向的变量
*p
不可修改;如果const位于星号 * 的右侧,即指针变量p
是常量,所指向的地址不可修改。int a = 2; const int *p = &a; // *p 不可修改 int const *q = &a; // *q 不可修改 int *const r = &a; // r 不可修改 const int &c = a; // c 不可修改 int const &d = a; // d 不可修改
在函数中的应用:
-
修饰函数参数:在函数体中const所修饰的部分进行常量化,不可被修改,保护原对象的属性。通常用于参数为指针或引用的情况。
-
修饰一般类型的函数返回值:一般不这样做。因为如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何作用。例如把函数
int GetInt(void)
写成const int GetInt(void)
是没有意义的,都可以传递给int
变量,是可以更改的。 -
修饰指针类型的函数返回值:C++ 允许从函数返回指针,但安全性考虑,必须指向静态存储区或堆区。该返回值
只能被赋给加 const 修饰的同类型指针
,且指针数据*p
不可修改。const int *func1() { static int a = 9; return &a; }
-
修饰引用类型的函数返回值:如果返回值不是内部数据类型,将函数
MyClass GetObj(void)
改写为const Myclass & GetObj(void)
的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟返回了一个对象的“拷贝”还是仅返回了对象的“别名”。返回引用,即返回的是“左值”,是可以修改的,但加了 const 后就不可修改了。int &mymin(int &i, int &j) { return i; } // 返回可修改的左值 int a = 9, b = 3; int x = mymin(a, b) = 1; cout << x << endl; // x = 1
事实上,函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。禁止返回局部变量的引用。
在类中的应用:
- 修饰类的成员函数: 限定该成员函数
void run() const;
不能修改任何数据成员。同样可以修饰成员函数的参数和返回值,作用同上。 - 修饰类的数据成员: 用于在类中定义常量,要注意const类型的数据成员的初始化方式:C++11支持使用类内初始值,或在构造函数的初始化列表中赋值。如果同时使用上面的两种方式,以初始化列表中的值为最终初始化结果。所以该常量的值类的定义时是未知的。那在定义其他数据成员的时候,就不能使用该常量,只能在成员函数(调用时已经完成初始化)中使用。
class Test { public: static int a; const int MAX_LEN = 10; vector<int> arr1(MAX_LEN, -1); // ERROR!!! int arr2[MAX_LEN]; // ERROR!!! Test() { cout << MAX_LEN << endl; vector<int> arr3(MAX_LEN, -1); // Right int arr4[MAX_LEN]; // ERROR 不能这样定义数组 } }
- 修饰类的静态数据成员: 要解决上述问题,只需要将常量声明为静态成员即可。注意在类外初始化。
- 常量对象: 常量对象
const Test t;
只能调用const类型的成员函数,别的成员函数都不能调用。
extern
修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”,一般用在头文件中,别处需要调用只需要包含这个头文件即可。
在C++中extern还有另外一种作用,用于指示C或者C++函数的调用规范。比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同,用此来解决名字匹配的问题。
配合使用
const修饰的全局常量据有跟static相同的特性,前者只能作用于文件作用域中,但经过extern引用后,在其他文件中也可使用;static和const配合使用时,只是为了“隐藏”;但extern和static不能配合使用。
编译阶段
http://www.cnblogs.com/chengxuyuancc/archive/2013/04/04/2999844.html
内联函数
增加了 inline
关键字的函数称为“内联函数”。内联函数和普通函数的区别在于:当编译器
处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,把整个函数体进行展开,在执行时减少了调用开销(压栈,跳转,返回)。很显然,使用内联函数会使最终可执行程序的体积增加。以时间换取空间,或增加空间消耗来节省时间,这是计算机学科中常用的方法。内联函数中的代码应该只是很简单、执行很快的几条语句。
宏
宏定义不是C/C++严格意义上的语句,所以定义后不加分号,是由预处理器
进行处理,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率。 但是宏定义不检查函数参数和返回值类型,直接展开,相对来说,内联函数会检查参数类型,所以更安全。
生成可执行程序的流程
https://zhuanlan.zhihu.com/p/45402323
https://www.cnblogs.com/mrsandstorm/p/5701530.html
源代码(.c) -> 预编译处理
(.i)-> 编译、优化
(.s)->汇编
(.obj、.o、.a、.ko) -> 链接
-> 可执行程序(.exe、.elf、.axf)
- 预处理:又称为预编译,是做些
代码文本替换
工作。编译器执行预处理指令(以#开头,例如#include),这个过程会得到不包含#指令的.i文件。这个过程会拷贝#include 包含的文件代码,进行#define 宏定义的替换 , 处理条件编译指令 (#ifndef #ifdef #endif)等。生成.i文件
- 编译优化:通过预编译输出的.i文件中,只有常量:数字、字符串、变量的定义,以及c语言的关键字:main、if、else、for、while等。这阶段要做的工作主要是,
对声明做语法检查
通过语法分析和词法分析,确定所有指令是否符合规则,之后翻译成汇编代码
。生成.s文件
- 汇编:把汇编语言翻译成
目标机器指令
的过程,生成目标文件(.obj .o等)。目标文件中存放的也就是与源程序等效的目标的机器语言代码。生成.o文件
- 链接:将有关的目标文件链接起来,
将定义链接起来
,包括库文件函数、其他目标代码等。生成可执行文件
智能指针
智能指针:实际指行为类似于指针的类对象 ,它的一种通用实现方法是采用引用计数的方法。
- 智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。
- 每次创建类的新对象时,初始化指针并将引用计数置为1;
- 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;
- 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;这是因为左侧的指针指向了右侧指针所指向的对象,因此右指针所指向的对象的引用计数+1;
- 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。
- 实现智能指针有两种经典策略:一是引入辅助类,二是使用句柄类。这里主要讲一下引入辅助类的方法
内存
https://blog.youkuaiyun.com/Augurlee/article/details/107350157
内存分区
https://blog.youkuaiyun.com/Augurlee/article/details/107350157#_7
不同编译环境下的数据类型差异
https://blog.youkuaiyun.com/Augurlee/article/details/107350157#_31
大端模式和小端模式
https://blog.youkuaiyun.com/Augurlee/article/details/107350157#_97
内存对齐
https://blog.youkuaiyun.com/Augurlee/article/details/107350157#_108
内存分配方式
内存分配方式有三种:
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
多态
https://blog.youkuaiyun.com/Augurlee/article/details/109332212
基本语法
当i是一个整数的时候i++和++i那个更快?它们的区别是什么?
几乎一样。i++返回的是i的值,++i返回的是i+1的值,即++i是一个确定的值,是一个可以修改的左值。
int a = 1;
int b = a++ = 1; // 语法错误:表达式不可赋值
int c = ++a = 2; // 正确
运算符重载
C++模板
https://blog.youkuaiyun.com/Augurlee/article/details/109330574
标准模板库 STL
vector的reserve和capacity的区别?
reserve()用于让容器预留空间,避免再次分配内存;capacity()返回在重新进行分配以前所能容纳的元素数量。