C++11/14/17常用新特性

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这个值是没有一个变量来存储的,&num也是在寄存器中的,是没有主存地址的

此时,如果想对上面的一些变量进行引用,则至少需要知道它们在主存中的地址,也就是是一个左值

int& a = num;	//√
int& a = 1;		//×
int*& p = pnum;	//√
int*& p = &num; //×

后两个是非常好的例子,pnum是存储在内存中的变量,其值为num的地址,所以可以被引用;&num是临时变量(右值),不在主存中被分配空间,所以不能被引用

也就是说,pnum和&num的值都是num变量的地址,但是它们存在的方式取决了其能否被引用

如果一定要引用临时变量,就需要先将其拷贝到内存中(就像 i n t ∗ p = & n u m int* p =\&num intp=&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

本文参考现代 C++ 教程:高速上手 C++ 11/14/17/20

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值