nullptr&NULL
传统C++把NULL(void* or int)和0视为同一种东西,引发了很多问题。C++11引入nullptr,其类型为nullptr_t,可以隐式转化为任何指针类型,可以和0完全区别开。
结论:空指针无脑使用nullptr
constexpr&const
传统C++中const为常数,区别于常量表达式。带来的一些麻烦,比如不能用于初始化数组长度。C++11引入constexpr,可以显示声明常量表达式。
常量表达式的值一定在编译期计算出
也可以修饰函数,表示其返回值为常量表达式
C++17引入constexpr if,可以进行编译时分支判断
// 编译前
template<typename T>
auto print_type_info(const T& t) {
if constexpr (std::is_integral<T>::value) {
return t + 1;
} else {
return t + 0.001;
}
}
// 编译后
int print_type_info(const int& t) {
return t + 1;
}
double print_type_info(const double& t) {
return t + 0.001;
}
强化if
C++17中提出了一种强化if,可以在if中初始化临时变量(简直和Go一模一样)
if(int i = 0;
i < 1){
...
}
强化for
这个就用的很多了,C++11提出来的
for(auto& e: nums){
...
}
初始化列表
平时一直在用,没想到这个也是C++11的新特性
typedef struct Foo{int a, int b} Foo;
Foo foo1 {1, 2};
Foo foo2 = {1, 2};
foo.someFunc({1, 2});
结构化绑定
C++17的新东西,从C++11的tuple发展而来,可以很方便地实现多返回值函数
std::tuple<int, double, std::string> f() {
return std::make_tuple(1, 2.3, "456");
}
auto [x, y, a] = f();
auto&delctype
auto我一般都是变量类型太长懒得写的时候用一下,C++20中可以用于函数传参,C++14开始可以作为返回值推导
我理解的delctype就是typeof,可以:1. 直接用于变量声明。2. 用is_same<type1, type2>::value判断两个类型是否一致
delctype(auto)没太搞懂
template
将很多运行时处理的问题,丢到编译期进行。是C++黑魔法。
- 外部模板
传统C++会在每个文件中实例化模板,C++11引入外部模板(extern),可以显式通知编译期不再当前文件中实例化,而是使用外部实例
template class std::vector<bool>; // 强行实例化
extern template class std::vector<double>; // 不在该当前编译文件中实例化模板
还有很多特性我没写进来,因为模板编程我也没接触过很多,以后有机会再学
OOP
- 委托构造
就是可以在一个构造函数中使用另一个构造函数
Base() {
value1 = 1;
}
Base(int value) : Base() { // 委托 Base() 构造函数
value2 = value;
}
- 继承构造
using Base::Base;
- final&override
就是override和final,这个用的也挺多的
override自带虚函数检查,final禁止类被继承or虚函数被重载
- default&delete
C++会给代码增加一些默认的函数,比如构造、析构等
可以用=default字段显示声明默认函数
以及=delete字段显示声明删除默认函数
枚举类
可以理解为带强制类型的枚举类型
enum class new_enum : unsigned int {
value1,
value2,
value3 = 100,
value4 = 100
};
lambda
2333,写力扣的时候经常随手套一个lambda
[捕获列表](参数列表) (参数) 异常属性 -> 返回类型 {
函数体
}
引用捕获 [ & ] [\&] [&]
值捕获 [ = ] [=] [=]
捕获变量 [ v 1 , v 2 , . . . ] [v1, v2, ...] [v1,v2,...]
表达式捕获(C++14):即捕获的成员可以用表达式初始化(可以用右值捕获)
- 泛型lambda
形参可以用auto
-
std::function
-
std::bind&placeholder
bind用于绑定函数参数
placeholder是占位符,表示当前参数先不设置
合起来用类似参数默认值?可能用placeholder就对默认参数的位置没有要求了吧
左值引用&右值引用
我一度认为是否会用左值/右值引用能够判断出一个人是否了解modern C++
左值:理解为等号左边的值(能取地址)
右值:理解为等号右边的值,即表达式结束后不再存在的临时对象(不能取地址),比如常量表达式,函数返回值,和存在寄存器中的临时值等
纯右值:纯粹的字面量,比如非引用的返回值、表达式(包括lambda)等
将亡值:可以被移动的临时值(第一次看到可能比较难理解,不妨先往后看)
引用:一个变量的别名(没去了解过底层原理,感觉像是指针的某种高级封装)
对引用的操作就相当于对地址中值的操作,也就是给引用变量赋值的前提是,被引用的变量是可以取址的(比如存在寄存器中的变量)。举个例子
int num = 10; // num是左值,存在内存中
int num1 = num+1; // num+1是一个表达式,计算的结果肯定是存在寄存器中的
int* pnum = # // 虽然这个地址是主存中的地址,但是&num这个值是没有一个变量来存储的,&num也是在寄存器中的,是没有主存地址的
此时,如果想对上面的一些变量进行引用,则至少需要知道它们在主存中的地址,也就是是一个左值
int& a = num; //√
int& a = 1; //×
int*& p = pnum; //√
int*& p = # //×
后两个是非常好的例子,pnum是存储在内存中的变量,其值为num的地址,所以可以被引用;&num是临时变量(右值),不在主存中被分配空间,所以不能被引用
也就是说,pnum和&num的值都是num变量的地址,但是它们存在的方式取决了其能否被引用
如果一定要引用临时变量,就需要先将其拷贝到内存中(就像 i n t ∗ p = & n u m int* p =\&num int∗p=&num),然后再去引用内存中的变量,从而间接引用之前的临时变量
以上提到的引用均为左值引用(T&),也是通常提到“引用”时所指的引用
这里不得不提到常量左值引用(const T&),常量左值引用没有左值引用的限制,可以接受右值初始化,但是限定了只读
但是因为右值是用临时变量存储的,赋给左值的时候是做了深拷贝的,然后再销毁临时变量。深拷贝必然带来资源的消耗,所以就需要一种能够直接引用右值的方法
右值引用则是对&num这种右值的引用,不能用左值初始化,一定要用的话需要std::move将左值参数转换为右值
简单来说,右值引用就是延长右值的声明周期,不再做深拷贝,仅仅去引用寄存器中的临时变量。从这个角度来看,右值引用的速度是比左值引用快很多的
移动和拷贝
拷贝:通过一个对象,生成与其相同的另一个对象
移动:把一个对象移动到另一个位置
传统C++在移动时,必须使用先复制再析构的方式,非常反人类。一种更理想的移动方式,就是将旧对象的“使用权限”交给新对象,这就可以通过一个移动构造函数来实现
A(A&& a): p(a.p){
a.p = nullptr;
}
简单来说,就是通过右值引用拿到旧对象的使用权限,然后解除旧对象自身的引用
也就是说如果打算把一个对象赋值给另一个对象,但是这个对象之后不再打算使用了,就可以用移动的方式减少资源消耗,举个例子
vector<string> v;
string str;
v.push_back(move(str)); // 这步操作之后str为空
完美转发
引用坍塌规则:对引用进行引用时,函数形参 T & & T\&\& T&&不一定能进行右值引用,当传入的是左值时,会被推导为左值。也就是说只有传入右值引用才是真的右值引用
参数转发存在一个问题,引用类型本身是一个左值,所以当传递一个右值引用参数的时候,会被当做左值处理,举个例子
template<typename T>
void pass(T&& v){
reference(v);
}
void reference(int& v); // 传递左值
void reference(int&& v);// 传递右值
pass(1);
int l = 1;
pass(l);
// pass直接写int&&是不能接收左值参数的,这个模板的写法我也不太熟悉,但是用decltype发现传入左值时v被解释为左值引用了
无论是 p a s s ( 1 ) pass(1) pass(1)还是 p a s s ( l ) pass(l) pass(l),虽然传入pass的一个是左值,一个是右值,但是由于v本身是一个引用,意味着v是一个左值,所以只会调用左值引用的reference
forward函数可以解决引用坍塌的问题,在传递参数时会保持其原有类型
move只是把左值转化为右值,forward只是做类型转换
智能指针
指针永远是C++最难的地方之一,所以引入了智能指针来更自动地管理内存
智能指针其实包含很多种类型:shared_ptr/unique_ptr/weak_ptr
- shared_ptr
sp可以自动对变量引用计数,当显示调用delete的时候,可能并不会立即释放对象,而是等待引用计数为0之后才会释放
可以用make_share<T>来构造
- unique_ptr
如其名,up指向的对象不能被其他的智能指针共享,但是可以用move转移给其他的up
- weak_ptr
shared_ptr仍然可能引起内存泄漏,可以使用weak_ptr去检查shared_ptr是否存在
临界区&锁
- mutex
- lock_guard
- unique_guard
期望
- future
- wait
- packaged_task
条件变量
- condition_variable
- wait
- notify_one
- notify_all
原子操作
- atomic