static
修饰全局变量
- 加不加static都是静态存储方式
- 加了static,只初始化一次,防止在其他源文件中被引用
修饰局部变量
- 变成静态存储方式,变量的生命周期变长,作用范围不变
- 只初始化一次
修饰函数
- 加了static之后,函数在内存中只有一份
- 没加的话,普通函数在每个被调用中维持一份复制品
修饰类函数成员
- 表示该静态成员不属于类的实例
- 访问的时候,
类名::fun()
来访问 - 该函数内部不能访问类的其他数据成员,只能访问静态成员
const
修饰全局变量
- 限制全局变量的作用范围为当前源文件
1 2 3 |
//test.cpp int a = 1; const int b = 2; |
1 2 3 4 5 6 |
//main.cpp extern int a; int main() { cout << a << endl;//1 } |
1 2 3 4 5 6 |
//main.cpp extern const int b; int main() { cout << b << endl;//报错:无法解析的外部符号 } |
修饰局部变量
1 2 3 4 5 6 7 8 9 |
int main() { const int b = 2; int* p = (int*)(&b); *p = 3; cout << b << endl;//2 cout << *p << endl;//3 } //b里面存放的值被改变了,但使用变量输出的时候显示的却是初始值 |
1 2 3 4 5 6 7 8 9 |
int main() { const volatile int b = 2; int* p = (int*)(&b); *p = 3; cout << b << endl;//3 cout << *p << endl;//3 } //使用变量输出局部变量的真实值 |
修饰指针
指针自身是一个对象,它的值是所指向对象的地址,是一个整数。
- 顶层const
- 指针本身是个常量
- 底层const
- 指针指向的对象是个常量
口诀:
const星左,底被指
const星右,顶指针
解读:
1 2 3 4 5 |
int a = 1; int b = 2; const int * p1 = &a; int * const p2 = &b; //从内到外看 |
首先,p1是一个指针,指向一个int型对象,这个对象是个常量,所以p1是一个底层const
首先,p2是一个常量,然后发现p2是一个常量指针,它指向一个int型对象,所以p2是一个顶层const
修饰函数参数
- 函数参数为值传递
- 传递到函数内部是实参的一份拷贝,所以再内部再怎么改变这份拷贝的值,都不会影响实参的值
- 不需要将参数声明为const
- 函数参数为指针
- 只会进行浅拷贝,所以传递到函数内部的是一份拷贝的指针,不是原始对象
- 加上底层const来防止指向对象被篡改
- 加上顶层const来防止指针指向被篡改
- 函数参数为引用传递
- 引用就是一个别名,它不是对象。使用传递到函数内部的是就是原始对象本身
- 加上底层const来防止指向对象被修改
修饰函数返回值
令函数返回一个常量,可以有效防止因用户错误造成的意外。
修饰成员函数
- 防止成员函数修改类的数据成员的内容
mutable
修饰的数据成员可以在后置const的函数内部被修改值
C/C++调用约定
WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除??
__stdcall
- 被调用函数在返回前自己完成清除工作
- 函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定
__cdecl
- 调用者完成被调用函数的清除工作
- 函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。
- 实现可变参数的函数只能使用该调用约定。
__fastcall
- 被调用函数在返回前自己完成清除工作
- 用于对性能要求非常高的场合
- __fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送
thiscall
- 仅仅应用于"C++"成员函数
- this指针存放于CX寄存器,参数从右到左压
- thiscall不是关键词,因此不能被程序员指定
vector
vector
底层实现是封装了顺序表,是一块物理上连续的空间。它的初始化元素个数是size()
大小,分配的整个空间大小是capacity()
大小。- 由于是连续的内存,所以它支持随机访问,时间复杂度为
O(1)
- 连续的空间,所以它插入删除元素,就可能需要元素移动拷贝等操作
- 添加元素时,
vector
由于涉及空间的重新分配,所以指向容器的迭代器、指针以及引用会全部失效 - 删除元素时,
vector
是被删除元素之前的迭代器是有效
四种类型转换
static_cast
- 用来完成编译器认可的隐式类型转换。
- 基本数据类型之间的转换。
- 派生体系中向上转型:将派生类指针或引用转化为基类指针或引用。
- 任意类型指针与void*之间的转换。
dynamic_cast
- 用来完成派生类指针或引用与基类指针或引用之间的转换。
- 其他三种都是在编译时完成的,dynamic_cast是在运行时处理的,运行时要进行运行时类型检查。
- 基类中要有虚函数,因为运行时类型检查的类型信息存在于虚函数表中,而有虚函数才会有虚函数表。
- 它可以实现向上转型或向下转型,前提是继承关系是public或protected。
reinterpret_cast
- 用于任意指针或引用之间的转换。
- 它会产生一个新的值,这个值会与原始参数有完全相同的比特位。
- 指针类型 → 一个足够大的整数类型。
- 整数类型 || 枚举类型 → 指针类型。
- 指向函数的指针 → 另一个不同类型的指向函数的指针。
- 一个指向对象的指针 → 另一个不同类型的指向对象的指针。
- 一个指向类函数成员的指针 → 另一个不同类型的函数成员的指针。
- 一个指向类数据成员的指针 → 不同类型的数据成员的指针。
const_cast
- 只能对指针或引用去除或添加const属性
- 不能用于不同类型之间的转换,只能改变同种类型的const属性
- 函数形参是一个非const引用,要传一个const引用给它,可以用const_cast去除实参的常量性
- 一个const对象,想调用该const对象里的非const函数,可以用const_cast去除对象的常量性
空类大小
- 空类对象在内存中是有起始地址的,也就是说最少能存一个字节。所以一个空类的大小是1。
在纯堆和纯栈上的类对象
- 编译器在栈上创建对象时,如果类的析构函数是私有的,则无法回收栈上的内存,因此无法在栈上创建。
- 这样的类对象,可以通过new来创建,但是无法通过delete删除对象,因此需要提供额外的destroy函数来释放对象。
指针和引用的区别
- 指针首先是一个变量,这个变量存储的是一个地址,指向内存的一个存储单元。
- 引用和原变量实质上是同一个东西,只不过是原变量的一个别名。
- 可以有const指针,不能有const引用。
- 指针可以有多级,引用只能是一级。
- 指针可以为空,引用必须在定义的时候初始化。
- 对指针sizeof得到的是指针本身的大小,对引用sizeof得到的是引用所指向的原对象的大小。
lambda表达式
我们可以向一个算法传递任何类别的可调用对象
一个lambda表达式表示一个可调用的代码单元
可以将其理解为一个未命名的内联函数
与任何函数一样,lambda表达式也有返回类型,参数列表和函数体
与普通函数不同的是,lambda表达式可以定义在函数内容格式:
[捕获列表](参数列表)-> 返回类型 {函数体}
注意:
可以忽略参数列表和返回类型,但不能忽略捕获列表和函数体
1 2 |
auto f = [] {return 32;}; cout << f() << endl; |
关于捕获列表:
[] | lambda不能使用所在函数中的变量 |
[names] | names是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量 |
[&] | 隐式捕获列表,采用引用捕获的方式。lambda中所使用的和来自所在函数的实体都采用引用的方式使用 |
[=] | 隐式捕获列表,采用值捕获方式。lambda体将拷贝所使用的来自所在函数的实体的值 |
[&,identifier_list] | identifier_list是逗号分隔开的列表,包含0个或多个所占函数的变量。 这些变量采用值捕获的方式,而任何隐式捕获的变量都采用引用捕获的方式捕获。 identifier_list的名字前不能使用& |
[=,identifier_list] | identifier_list是逗号分开的列表,包含0个或多个所占函数的变量 这些变量采用引用捕获的方式,而任何隐式捕获的变量都采用值捕获的方式捕获。 identifier_list中的名字不能包括this,且这些名字之前必须使用& |
1 |
transform(v.begin(),v.end(),v.begin(),[](int i)->int {if(i < 0) return -i;else return i;}); |
智能指针
- 一定程度上,可以自动回收堆内存,可以避免野指针、重复手动释放、内存泄露等问题。
auto_ptr
- 已弃用,因为赋值操作会导致原来的智能指针失去对内存的所有权。
unique_ptr
- unique_ptr不可拷贝和赋值,初始化unique_ptr必须使用直接初始化的方式
- release会放弃对对象的所有权,并返回保存的指针。
- reset可以置空指针,或者先释放所指对象然后重置智能指针的值。
shared_ptr
- 引用计数机制。
- 可能引起对一块内存空间的循环引用而导致无法释放。
weak_ptr
- 为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
线程间通信方式
全局变量
- 首先需要对变量添加volatile关键字,这样会使编译器从内存中去读这个值,而不是因编译器优化从缓存的地方读取
互斥量
- 互斥量的功能和临界区很相似
- 区别是,Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象,可以跨进程使用。而且等待一个被锁住的Mutex可以设定为TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。
信号量
- 信号量是解决生产者/消费者问题的关键要素。
事件
- 用事件来同步线程是最具弹性的
- 一个事件具有两种状态:激发状态和未激发状态。也称为有信号状态和无信号状态。
- 事件种类
- 手动重置事件
被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态。直到程序把它设置为未激发状态。 - 自动重置事件
被设置为激发状态后,会唤醒一个等待中的线程,然后自动恢复为未激发状态。
- 手动重置事件
临界区
- CRITICAL_SECTION是最快的。其他的内核锁如互斥量事件,每进一次内核,都需要上千个CPU周期。
- 使用临界区时,最好不要长时间锁住一份资源。这里所说的长时间是相对的,视不同程序来定。
- 缺点:
- CRITICAL_SECTION不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。
try_lock
std::bind
- 内部实现是一个_Binder的偏特化,并把传进来的参数使用std::forward对参数进行了完美转发。
- 而_Binder里面重载了(),里面调用了_Call_binder。
- _Call_binder里面调用了_Call。、
define和typedef
- define用来定义宏,会在预处理的时候被展开。
- typedef用来定义一个标识符或关键字的别名,是语言编译过程中的一部分,不涉及实际内存的分配。
CreateThread和_beginthreadex
CreateThread
- 对CreateThread函数的调用,导致系统创建了一个线程内核对象。
- 该内核对象最初的使用计数为2,除非线程终止,而且从CreateThread返回的句柄被关闭,否则线程内核对象不会被销毁。
- 该线程内核对象的其他属性也被初始化:
- 暂停计数被设为1
- 退出代码被设为STILL_ACTIVE(0X103)
- 而且对象被设为未触发状态
- 一旦创建了内核对象,系统就会分配内存,供线程堆栈使用。这里的内存是从进程的地址空间分配的,线程是没有自己的地址空间的。
- 然后,系统将两个值写入新线程堆栈的最上端。(线程堆栈始终是从高位内存地址向地位内存地址构建的)
- 写入线程堆栈的第一个指是传给CreateThread函数的pvParam参数的值
- 第二个被写入线程堆栈的值是传给CreateThread函数的pfnStartAddr值
- 每个线程都有自己的CPU寄存器,称为线程的上下文。
上下文反应了当线程上一次执行时,线程的CPU寄存器的状态。
线程的CPU寄存器全部保存在一个CONTEXT结构(定义在WinNT.h中)
CONTEXT结构本身保存在线程内核对象中 - 指令指针寄存器和栈指针寄存器是线程上下文中最重要的两个寄存器。
因为线程始终在进程的上下文中运行。所以,这两个地址标识的内存都位于线程所在的进程的地址空间中。
当线程的内核对象被初始化的时候,CONTEXT结构体的堆栈指针寄存器被设为pfnStartAddr在线程堆栈中的地址。
而指令指针寄存器被设为RtlUserThreadStart函数的地址。(此函数是NTDLL.dll模块导出的)
_beginthreadex
- 每个线程都有自己的专用_tiddata内存块,它们是从C/C++运行库的堆上分配的。
- 传给_beginthreadex的线程函数的地址保存在_tiddata内存块中。
- _beginthreadex会在内部调用CreateThread,因为操作系统只知道用这种方式来创建一个线程。
- CreateThread在内部被调用时,传给它的函数地址是_threadstartex,而不是pfnStartAddr。参数地址是_tiddata结构的地址,而非pvParam。
- 如果成功创建,会返回线程的句柄,任何操作失败,会返回0。
Chrome的框架
chrome进程通信
chrome怎么关掉GPU渲染
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 2020_05_11_0205/14
- ♥ 2020_04_2905/01
- ♥ 2019_11_0511/07
- ♥ 2023_02_1502/20
- ♥ 2022_02_2602/26
- ♥ 2022_03_1403/17