C++编译原理
Preprocessing + Compilation + Assembly + Linking
-E 预编译 生成.i文件
-S 预编译+编译 生成.s文件
-c 预编译+编译+汇编 生成.o文件
-l
-g 生成调试时间
-W 使能警告
符号表
internal链接属性
const全局变量
static变量
inline
class类内定义
https://zybuluo.com/uuprince/note/81709
http://lxwei.github.io/posts/262.html
变量的初始化
全局变量的初始化顺序
同一个编译单元内的全局变量,初始化的顺序与他们声明的顺序是一致,销毁的顺序则相反
对于不同编译单元间的全局变量,c++ 标准并没有明确规定它们之间的初始化(销毁)顺序
解决方案
Construct On First Use
引用计数
http://www.cnblogs.com/catch/p/4314256.html
thread_local
每个线程开始时进行初始化
各个线程独自拥有这个变量
errno变量其实就是个thread_local的例子
引用与指针
语言层面上,引用不是指针,只是变量的别名
从底层实现上,引用是指针实现的
顶层const与底层const
底层const--const int *ptr; 可以改变ptr,指向类型为const int *
顶层const--int * const ptr; 不可以改变ptr,指向类型为int *
函数指针与指针函数
函数指针的本身是一个指针,指针指向的是一个函数
指针函数的本身是一个函数,其函数的返回值是一个指针
“*”的优先级低于“()”的优先级
父类指针与子类指针
父类指针可以指向子类指针,但只能访问父类方法
子类指针也可以指向父类对象,前提是必须要做类型转换
如果子类隐藏了父类方法,调用的方法版本由指针类型决定(与虚函数正好相反,虚函数由对象类型决定)
https://blog.csdn.net/u010355144/article/details/45115321
覆盖与隐藏
覆盖
函数参数都相同
隐藏
函数相同
父类中所有同名函数都会被隐藏
调用被隐藏函数
object.Base::func
using Base::func
多态三要素
父类中有虚函数。
子类覆盖父类中的虚函数
通过己被子类对象赋值的父类指针,调用该虚函数
作用:
隐藏实现细节
接口重用
虚函数与多态
虚函数的特性:动态绑定(只能由引用或指针触发)
多态:
1. 引用或指针的静态类型与动态类型不一致
2. 由动态类型决定真实执行的版本
override与final
override 标记此函数覆盖了基类的虚函数(避免发生隐藏),也只有虚函数可以覆盖
final 标记子类不可以覆盖此虚函数
默认实参:使用引用或指针的静态类型,而不是动态类型
绕过动态绑定:使用作用域运算符
不能设为虚函数的有:
1. 构造函数(构造函数的顺序,先构造父类)
2. inline成员函数(不存在函数调用,编译期确定方法内容)
3. static成员函数(没必要)
4. 友元函数(没有继承的概念)
private与virtual
子类拥有父类的完整拷贝,private只是阻止子类直接访问父类的实现
private与virtual没有任何关系
虚函数表
虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址
每个类都会维护一张虚表,编译器根据类的声明创建出虚表
对象被构造时,虚表的地址就会被写入这个对象内存的起始位置(保证性能)
虚函数表将被该类的所有对象共享
https://songlee24.github.io/2014/09/02/cpp-virtual-table/
https://blog.csdn.net/haoel/article/details/1948051
菱形继承
出现二义性问题直接报错
这是class-type语言中一种天生的缺陷,由虚继承解决
三/五法则
如果一个类需要析构函数,那么也需要拷贝构造函数和拷贝赋值构造函数。
如: 析构函数中需要释放一个构造函数中分配的动态内存,默认的拷贝会使得所有对象都指向同一个动态内存。
内存泄露
definitely lost:
1. 多态中,父类的析构函数不是虚函数
2. 智能指针,循环引用(weak解锁)
3. 内存覆盖,strncpy
indirectly lost:
1. longjmp
possibly lost:
1. 由于抛出异常导致没有释放内存
(char *) 0 、(char *) 9 :
(char *) 0:C语言中,produce a null-pointer value of type char *;其他语言可能执行其他地址
(char *) 9:大多数语言中执行地址9
宏定义的作用域:
C语言标准中宏定义的作用域:
从定义位置开始,到其当前所在作用域结束,即宏定义只属于当前这个文件,其他文件如果没有通过#include包含这个文件,那就不能使用这个宏定义
宏定义作用域不受函数等作用域影响
std::atomic与std::memory_order
六种memory_order
relaxed:没有顺序一致性的要求,也就是说同一个线程的原子操作还是按照happens-before关系,但不同线程间的执行关系是任意
https://www.zhihu.com/question/24301047
https://en.cppreference.com/w/cpp/atomic
内存屏障:
保证屏障前后的执行顺序
http://www.cnblogs.com/Mainz/p/3556430.html
volatile:
与const类似,可以实现类似底层const、顶层const的功能
作用:
1. 不允许被优化(具体的内容和机器相关,通常不允许寄存器访问)
2. 于序列上在另一个对 volatile 对象的访问之前,只适用于单线程
volatile 只在三种场合下是合适的:
1. 和信号处理(signal handler)相关的场合
2. 和内存映射硬件(memory mapped hardware)相关的场合
3. 和非本地跳转(setjmp 和 longjmp)相关的场合
https://liam0205.me/2018/01/18/volatile-in-C-and-Cpp/
SegmentFault:
enable: ulimit -c unlimited
save path: /cores/
new | operator new | placement new
new
操作符
分配足够的空间,并调用相关对象的构造函数
operator new
函数
只分配所要求的空间,不调用相关对象的构造函数
支持new_handler
placement new
重载 operator new 的一个标准、全局的版本
在一个已经分配好的内存中(栈或者堆中)构造一个新的对象
https://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html
序列式容器
array
静态空间,大小固定
vector
动态空间
每次容量不足,则扩充至两倍;如仍然不足,则扩充至足够大的容量
每次容量扩充,都会进行内存拷贝
list
环状双向链表,尾端以一个空白节点标示
forward_list
与list主要在迭代器上的区别
dequeue
双向开口,逻辑上连续的线性空间
动态地以分段连续内存组合而成,内部维护各个分段
容器配接器
stack
无iterator,不允许遍历
默认以deque实现
queue
无iterator,不允许遍历
默认以deque实现
priority_queue
无iterator,不允许遍历
max-heap实现,默认以vector表现为一个完全二叉树
关联式容器
set
基于RB-tree实现
key即value
不支持写入操作
multiset
同set,支持重复键值
map
基于RB-tree实现
基于pair管理
multimap
同map,支持重复键值
unordered系列
基于hash实现
http://en.cppreference.com/w/cpp/container
智能指针
默认初始化为空指针
unique_ptr
不能拷贝或赋值,但将要被销毁的指针除外
可以通过release或reset转移指针所有权
删除器导致shared_ptr和unique_ptr的区别
shared_ptr的删除器指针是运行时绑定;
unique_ptr的删除器指针是编译时绑定;
shared_ptr与control block
1. 对象指针
2. 两个引用计数(shared/weak)
3. 删除器
4. 配置器
支持原子访问
weak_ptr也会保持control block不被释放
https://heleifz.github.io/14696398760857.html
https://en.cppreference.com/w/cpp/memory/shared_ptr
仿函数
不是函数,但可以像函数一样调用对象(重载了operator())
refs:
Makefile https://seisman.github.io/how-to-write-makefile/introduction.html