C++11 新特性

 原文 https://www.cnblogs.com/linuxAndMcu/p/11600553.html 

1. nullptr 

(1) 作用:nullptr 的类型为 nullptr_t,能够隐式地转换为任何指针的类型,能和他们进行相等或者不等的比较。

     简单说,nullptr目的是为了区分 空指针NULL 和 0。

(2) 解释:传统C++ 把NULL和0视为同一种东西,这样会导致出现违反直观的情况。比如:

void foo(char *); //构造函数char
void foo(int);    //构造函数int
main() {
    char* ch = NULL;    //NULL被视为0
    foo(ch);    //调用 构造函数int,而不是调用 构造函数char,违反直观。
}

     c++11 改用 char* ch = nullptr 之后,就会调用构造函数char,避免违法直观。

2. 类型推导

2.1 auto关键字

(1) 作用:可以让编译器自动分析初始值来判断变量所属的类型。必须确定初始值类型。

(2) 解释:典型的使用例子是迭代器:

//c++98:
for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)
//c++11: 
for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);
2.2 decltype关键字

(1) 作用:自动分析表达式判断它的类型,但不会去计算表达式的值。

(2) 解释:

auto x = 1;    //自动推导为 int
auto y = 2;
decltype(x+y) z;    //也自动推导为 int
2.3 auto 和 decltype 的区别

(1) 执行时间不同:

auto: 执行在程序运行期间。

decltype:执行在程序编译期间。(与sizeof()一样)

(2) 推导逻辑不同:

auto:通过编译器计算变量的初始值来推导类型;如果初始化语句中存在多个表达式且类型不相同时,会推导出“最宽泛”的类型。 

decltype:分析表达式的结果来推导类型,但又不会执行将表达式的值计算出来。

(3) 顶层const处理逻辑不同:

auto: 会忽略顶层const,比如:const int m = 10; auto d = m; //d 的类型是int 不带 const

decltype:不会忽略顶层const。

(4) 推导获得引用类型逻辑不同:

auto:不会推导获得引用类型。

decltype:多层括号decltype((表达式)) ,返回的就是引用。 表达式是左值也会获得引用类型。

(5) 使用场景不同:

auto:适合简单的类型推导,比如迭代器 和 模板编程中简化模板函数或模板类的定义。

decltype:复杂表达式的类型推导,避免不必要的类型转换,特别是涉及到const和引用。

(6) 参考文章:

C++11中auto与decltype的区别与联系深入解析_auto和decltype区别-CSDN博客

auto 和 decltype的区别_decltype和auto的区别-CSDN博客

2.4  拖尾返回类型、auto 与 decltype 配合

(1) 作用:推导函数模板的返回类型。

(2) 解释: decltype(x+y) add(T x, U y); 这种自动推导函数返回类型的写法,会编译错误。因为编译器在读到 decltype(x+y) 时,x 和 y 尚未被定义,所以编译错误。

   所以c++11引入了拖尾返回类型“ -> ” ,如下代码所示:

template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {    //拖尾返回类型
    return x+y;
}

     这样就 避免了 编译期间需要确定模板参数的类型,获得自动推导能力。

3. 区间迭代 - 基于范围的 for 循环

(1) 作用:引入了基于范围的迭代写法,:   key : array

(2) 解释: 代码如下:

c++98:
for(std::vector<int>::iterator i = arr.begin(); i != arr.end(); ++i) {
    std::cout << *i << std::endl;
}
c++11:
for(auto i : arr) {    
    std::cout << i << std::endl;
}

4. 列表 初始化

(1) 作用:统一普通数组、POD(plain old data)、类对象的初始化格式: A a = {x}  或 A a {x}。 有没有 赋值符号 = 都可以。 

(2) 解释:代码如下:

// 普通数组
int i_arr[3] = { 1, 2, 3 };
// POD类型:结构体
struct A
{
    int x;
    struct B
    {
        int i;
        int j;
    } b;
};
A a = { 1, { 2, 3 } };
// 类对象
class Foo
{
    public:
    Foo(int) {}
    Foo(const Foo &);
}  
Foo a = {123}; //调用构造函数 Foo(int) {}

5. 模板增强

5.1 外部模板

(1) 作用:显式的告诉编译器何时进行模板的实例化,避免重复实例化而导致的编译时间增加。

(2) 解释:传统 C++ 中,只要在每个编译文件中遇到被完整定义的模板,都会实例化。这样会重复实例化,导致编译时间增加。

     C++11 引入了外部模板 extern,能够显式地告诉编译器何时进行模板的实例化。代码如下:

template class std::vector<bool>;           // 强行实例化
extern template class std::vector<double>;  // 不在该编译文件中实例化模板
5.2 尖括号 “>” 

(1) 作用:连续的右尖括号 >> 将不再 仅被当做右移运算符 来进行处理。

(2) 解释:代码如下:

//c++98: 
std::vector<std::vector<int>> wow; //编译不通过,需要改成 "int> > wow"
//c++11:
std::vector<std::vector<int>> wow; //编译通过
5.3 类型别名模板

(1) 作用:可以为模板定义一个新的名称,就和 typedef 结构体 一样。

(2) 解释:在传统 C++ 中,typedef 可以为类型定义一个新的名称,但却不能为模板定义一个新的名称。C++11 使用 using , 支持对模板定义一个新的名称。代码如下:

template <typename T>
typedef SuckType<int, T, 1> NewType; // 不合法
using NewType = SuckType<int, T, 1>; // 合法
5.4 默认模板参数

(1) 作用:可以为模板的参数,设定默认类型,使用时不需要每次都指定。

(2) 解释:代码如下:

template<typename T = int, typename U = int>  //指定默认的 int类型
auto add(T x, U y) -> decltype(x+y) {
    return x+y;
}

6. 构造函数

6.1 委托构造

(1) 作用:构造函数内部可以调用另一个构造函数 ,达到简化代码的目的。

(2) 解释:代码如下:

class Base {
public:
    int value1,value2;
    Base() { value1 = 1; }
    Base(int value) : Base() {  // 委托构造函数  Base()  
        value2 = 2;
    }
};

(3) 注意:先执行目标构造函数,再执行委托构造函数。 委托构造函数在后

6.2 继承构造

(1) 作用:派生类想要使用基类的构造函数,不再 需要在构造函数中显式声明。

(2) C++11 使用 using,让派生类直接继承基类的构造函数。

struct A
{
  A(int i) {}
  A(double d,int i) {}
};
//c++98:
struct B:A
{
  B(int i):A(i) {}
  B(double d,int i):A(d,i) {}
  // ......等等好多个和基类构造函数对应的构造函数
};
//c++11:
struct C:A
{
  using A::A;    //继承基类的构造函数,一句话搞定
};

7. Lambda表达式

7.1 作用:

        所谓 Lambda 表达式,就是提供了一个类似匿名函数的特性。

        匿名函数就是需要定义一个函数,但是又不想费力去命名这个函数。

7.2 常用组成结构:

     [capture list] (parameter list) { function body}

(1) capture list:表示捕获列表, 捕获 lambda 所在函数中的局部变量的列表,可以为空。(lambda表达式外部的变量)。 取以下值的意义分别是:

[] : 不捕获任何变量。

[&] : 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。

[=] : 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。

注意1: 值捕获的前提是变量可以拷贝

注意2: 拷贝的时间是在lambda表达式被创建时拷贝,不是调用时。

(2) parameter list:表示 lambda表达式(函数)的参数

(3) Function body: 表示函数体。

(4) 使用例子:

int a = 0;
auto f = [=] { return a; };    //定义lambda表达式 f, 拷贝捕获外部变量 a
cout << f() << endl; // 输出0  //调用lambda表达式 f,返回a的值并输出
a += 1;                        //修改变量a
cout << f() << endl; // 输出0  //由于捕获a是在创建lambda时,所以时候修改变量a,不会起作用。
7.3 完整组成结构:

        [capture list] (parameter list) option -> return type { function body}

option: 表示 函数选项;可以填 mutable,exception,attribute。(选填)

        mutable:lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。

        exception:lambda表达式是否抛出异常以及何种异常。

return type: 表示该 lambda 的返回类型

7.4 大致原理

(1) 定义一个 lambda 表达式后,编译器会自动生成一个匿名类,称为闭包类型。

(2) 运行时, lambda 表达式就返回一个匿名的闭包实例,是一个右值。

(3) 所以,lambda 表达式的结果就是一个个闭包。

(4) 对于传值方式捕获的变量,闭包中会添加这个变量的数据类型,作为非静态数据成员。

(5) 对于引用方式捕获的变量,闭包中是否添加这个变量的数据类型,与具体实现有关。

7.5 lambda 表达式是不能被赋值

    lambda 表达式不能被赋值修改,但可以拷贝生成一个副本。

    闭包类型禁用了赋值操作符,但是没有禁用复制构造函数

auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };
a = b;   // 非法,lambda无法赋值
auto c = a;   // 合法,生成一个副本
7.6 lambda 表达式可以赋值给函数指针

(1) lambda 表达式可以赋值给 类型匹配的 函数指针。

(2) 但使用函数指针并不是那么方便。所以 STL 定义在 < functional > 头文件提供了函数对象封装 std::function,类似于函数指针。它可以绑定任何类函数对象,只要参数与返回类型相同。

//返回 bool,且接收两个 int 的函数包装器:
std::function<bool(int, int)> wrapper = [](int x, int y) { return x < y; };
7.7 STL 算法搭配 lambda 表达式

        大部分 STL 算法,可以非常灵活地搭配 lambda 表达式来实现想要的效果。例如:

int value = 3;
vector<int> v {1, 3, 5, 2, 6, 10};
//如果数组元素的值大于value,就返回true,并统计个数。
//coun_if()算法的第三个参数,输入lambda表达式。
int count = std::count_if(v.beigin(), v.end(), [value](int x) { return x > value; });

8. 新增容器

8.1 std::array

(1) 作用:提供比 vector 性能更高的顺序容器。

(2) array 和 vector 的区别:

相同:都是顺序容器,都是利用连续的内存空间来存储元素,都能用下标来访问元素。

不同:

vector无需指定大小;array需要指定大小,并且只能用常量数值来指定大小

vector 容量大小可变;array容量大小不可变。

vector 作为函数参数时,可以被隐式转换成指针;array不能被隐式转换

vector 依赖动态分配/释放内存,比较耗时;array的内存大小固定,编译时直接分配一段栈上/静态存储区内存即可,速度更快

(3) 代码如下: std::array<int, 4> arr= {1,2,3,4};

8.2 std::forward_list

(1) 作用:单向链表

(2) 解释:std::forward_list 是单向链表, 使用方法和 std::list (双向链表)基本类似。 

    提供了 O(1) 复杂度的元素插入,不支持随机访问(这也是链表的特点),标准库容器中唯一一个不提供 size() 方法的容器。比 std::list 有更高的空间利用率。

8.3 无序容器

(1) 作用:C++11 引入了两组无序容器: 

std::unordered_set,std::unordered_multiset
std::unordered_map,std::unordered_multimap

(2) 解释: 无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(n)。

(3) unorderedmap和map的区别:

a. 底层实现方式: 

map:使用红黑树(自平衡二叉查找树)实现,map中的元素按照键的大小有序排列

unordered_map:使用哈希表实现,不保证元素的顺序。

b. 性能表现:

    map在插入和删除操作时需要维护红黑树的平衡,而unordered_map则只需要计算哈希值并将元素放入相应的桶中即可。

    需要快速查找特定的元素时,unordered_map通常比map更快,因为哈希表在平均情况下提供O(1)的查找时间复杂度,红黑树的查找时间复杂度是O(logN)。

map:进行有序地遍历元素时,性能更好。

unordered_map:插入操作和查找时,性能更好。

c. 综上所述:

map:用于 有序地关联数据

unordered_map:用于 快速查找大规模数据

8.4 元组 std::tuple

(1) 作用:把多个数据组合成一个结构体数据。可以构造元组,获取元素和拆解元组。

(2) 解释:

a. 构造元组:auto student = std::make_tuple(3.8, 'A', "张三");

b. 获得元组某个位置的值:std::get<0>(student);

c. 拆解元组:std::tie(gpa, grade, name) = student ;

9. 正则表达式

(1) 作用:C++11 提供了正则表达式库 ,用于操作 std::string 对象

(2) 解释: 具体内容包括 std::regex 和  std::smatch,std::regex_match(),代码如下:

std::regex regex_str("([a-z]+)\\.txt");    //匹配串
std::string source_arr[] = {"foo.txt", "bar.txt"};
for (const auto &source: source_arr)
    bool is_match = std::regex_match(source, regex_str); //成功返回true

std::smatch match_data;
for(const auto &source: source_arr) {
    if (std::regex_match(source, match_data, regex_str)) {
        if (match_data.size() == 2) {
            // match_data 的第一个元素是 整个字符串
            std::string match_full = match_data[0].str(); 
            // match_data 的第二个元素是 匹配串里第一个括号表达式
            std::string match_sub = match_data[1].str();
        }
    }
}

10. 语言级线程支持

10.1 std::thread

作用:线程

10.2 std::mutex/std::unique_lock

std::mutex 作用:互斥量

std::unique_lock 作用:用于管理对互斥量的锁定与解锁, 与std::lock_guard基本相同.

     另外还支持 延迟锁定、条件等待、尝试锁定、锁定时长、使用权转移、手动解锁。

10.3 std::future/std::packaged_task

std::packaged_task 作用:异步任务提供者

std::future 作用:异步返回对象

10.4 std::condition_variable

作用:条件变量,用于阻塞线程等待从而实现线程同步。和GO的条件变量一样。

11. 右值引用和move语义

        c++11 引入了右值引用移动语义,为了避免无谓的复制,提高程序性能。

11.1 左值、右值

(1) 左值:表达式结束后依然存在的持久化对象

(2) 右值:表达式结束时就不再存在的临时对象

(3) 典型特性:左值表达式能做 取址 操作,右值不能。

11.2 右值引用

(1) 作用: 右值本来在表达式语句结束后就该终结了,而通过右值引用又重获新生,其生命期延迟至 右值引用类型变量 的生命期一样。

     右值引用使用的符号是 &&。

(2) 目的:减少右值的内存拷贝。

(3) 解释:代码如下

class A {
  public:
    int a;
};
A getTemp()
{
    return A();
}
A && a = getTemp();   //getTemp()的返回值是右值, 通过右值引用 a ,延长了生命周期
11.3 std::move

(1) 作用:显示地 把左值转化为右值, 让右值引用可以指向左值。避免赋值。

(2) 解释:想“借用”一个对象的数据而不是复制它时,可以使用std::move触发移动语义,提升性能。比如在容器中实现元素转移操作, 代码如下:

MyClass myObj;
std::vector<MyClass> vec;
// 使用 std::move 将 myObj 以移动语义添加到 vector 中
vec.push_back(std::move(myObj));

12. std::bind 函数

(1) 作用:把一个可调用对象与一些参数绑定,生成一个新的可调用对象。可以节省每次调用该对象时,都输入一大堆参数。

(2) 解释:

double divFunc(double x, double y) {return x/y;} //定义可调用对象 divFunc
auto fn_half = std::bind (divFunc, _1, 2);  //绑定了第二个参数y,固定值为2
std::cout << divFunc(10) << '\n';    //只需输入第一个参数10即可,输出结果5 (10/2 = 5)

    如果绑定的是对象成员函数的话,就这样调用:

//Obj是具有divFunc成员函数的类对象
auto fn_half = std::bind (&Obj::divFunc, &obj divFunc, _1, 2);

13. std :: function 类模板

(1) 作用: std :: function 是通用的多态函数包装器,  它的实例可以存储,复制和调用任何可调用的目标 。

    可调用目标 包括函数,lambda 表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针。

(2) 解释: 简单说就是一个函数签名模版,代码如下:

int f(int a, int b)
{
  return a+b;
}
int main()
{
	std::function<int(int, int)>func = f;
	cout<<f(1, 2)<<endl; // 3
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值