C++11新特性

  • 1.列表初始化
  • 2. 变量类型推导
  • 3. 范围for循环
  • 4. final与override
  • 5. 智能指针
  • 6. 新增加容器---静态数组array、forward_list以及unordered系列
  • 7. 默认成员函数控制
  • 8. 右值引用
  • 9. lambda表达式
  • 10. 线程库​​​​​​​

1.列表初始化

1.1、C++98中{}的初始化问题

在C++98中,标准允许使用花括号{}对数组元素进行统一的列表初始值设定。比如:
       

int array1[] = {1,2,3,4,5};
int array2[5] = {0};

对于一些自定义的类型,却无法使用这样的初始化。比如

vector<int> v{1,2,3,4,5};

1.2、内置类型的列表初始化
 

int main()
{
// 内置类型变量
int x1 = {10};
int x2{10};
int x3 = 1+2;
int x4 = {1+2};
int x5{1+2};
// 数组
int arr1[5] {1,2,3,4,5};
int arr2[]{1,2,3,4,5};
// 动态数组,在C++98中不支持
int* arr3 = new int[5]{1,2,3,4,5};
// 标准容器
vector<int> v{1,2,3,4,5};
map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};
return 0;
}
class Point
{
public:
Point(int x = 0, int y = 0): _x(x), _y(y)
{}
private:
int _x;
int _y;
};
int main()
{
Pointer p{ 1, 2 };
return 0;
}

1.3、自定义类型的列表初始化

    标准库支持单个对象的列表初始化
            多个对象想要支持列表初始化,需给该类(模板类)添加一个带有initializer_list类型参数的构造函数即
            可。

变量类型推导

在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎
么给,或者类型写起来特别复杂,比如

#include <map>
#include <string>
int main()
{
    std::map<std::string, std::string> m{{"apple", "苹果"}, {"banana","香蕉"}};
    std::map<std::string, std::string>::iterator it = m.begin();
    while(it != m.end())
    {
        cout<<it->first<<" "<<it->second<<endl;
        ++it;
    }
    return 0;
}

C++11中,可以使用auto来根据变量初始化表达式类型推导变量的实际类型,可以给程序的书写提供许多方
便。将程序中c与it的类型换成auto,程序可以通过编译,而且更加简洁

decltype类型推导

auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有
时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就
无能为力
 

template<class T1, class T2>
T1 Add(const T1& left, const T2& right)
{
return left + right;
}

3.范围for循环

4.final与override

由于虚函数的特性,可能会被意外进行重写,为了做到精确对虚函数重载的控制,c++11使用了override和final关键字完成对这一特性的实现.
override关键字 : 显式声明对虚函数进行重载
final关键字 : 显式终结类的继承和虚函数的重载使用

5.智能指针

 下节分享

6.新增加容器---静态数组array、forward_list以及unordered系列
 

(1)std::array
std::array 保存在栈内存中,相比堆内存中的 std::vector,我们能够灵活的访问这里面的元素,从而获得更高的性能。
std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array只需指定其类型和大小即可. std::array<int, 4> arr= {1,2,3,4};

(2)std::forward_list
std::forward_list 是一个列表容器,使用方法和 std::list 基本类似。
和 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。

(3)无序容器unordered_map、unordered_set
C++11 引入了两组无序容器,无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant)。

std::unordered_map/std::unordered_multimap
td::map使用的数据结构为二叉树,而std::unordered_map内部是哈希表的实现方式,哈希map理论上查找效率为O(1)。但在存储效率上,哈希map需要增加哈希表的内存开销。

std::unordered_set/std::unordered_multiset
std::unordered_set的数据存储结构也是哈希表的方式结构,除此之外,std::unordered_set在插入时不会自动排序,这都是std::set表现不同的地方

(4)元组std::tuple
tuple是一个固定大小的不同类型值的集合,是泛化的std::pair,其中的元素个数不再限于两个,而且功能更加丰富.
元组的使用有三个核心的函数:
std::make_tuple: 构造元组
std::get: 获得元组某个位置的值
std::tie: 元组拆包
std::tuple_cat:合并两个元组,可以通过 std::tuple_cat 来实现

7.默认成员函数控制
 

在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构
函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生
成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带
参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程
序员可以控制是否需要编译器生成。

显式缺省函数:在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的指示编译器生成该函数的默认版
本,用=default修饰的函数称为显式缺省函数
 

删除默认函数:如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他
人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对
应函数的默认版本,称=delete修饰的函数为删除函数

 


8.右值引用

C++98中提出了引用的概念,引用即别名,引用变量与其引用实体公共同一块内存空间,而引用的底层是通
过指针来实现的,因此使用引用,可以提高程序的可读性。

为了提高程序运行效率,C++11中引入了右值引用,右值引用也是别名,但其只能对右值引用
 

int Add(int a, int b)
{
    return a + b;
}
int main()
{
    const int&& ra = 10;
    // 引用函数返回值,返回值是一个临时变量,为右值
    int&& rRet = Add(10, 20);
    return 0;
}

1. 不能简单地通过能否放在=左侧右侧或者取地址来判断左值或者右值,要根据表达式结果或变量的性质
判断,比如上述:c常量
       2. 能得到引用的表达式一定能够作为引用,否则就用常引用

普通引用只能引用左值,不能引用右值,const引用既可引用左值,也可引用右值

C++11中右值引用:只能引用右值,一般情况不能直接引用左值

值的形式返回对象的缺陷:如果一个类中涉及到资源管理,用户必须显式提供拷贝构造、赋值运算符重载以及析构函数,否则编译器将会自动生成一个默认的,如果遇到拷贝对象或者对象之间相互赋值,就会出错。

class String
{
 public:
 String(char* str = "")
{
    if (nullptr == str)
    str = "";
    _str = new char[strlen(str) + 1];
    strcpy(_str, str);
}
String(const String& s)
    : _str(new char[strlen(s._str) + 1])
{
    strcpy(_str, s._str);
}

C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式,可以有效缓解该问题

在C++11中如果需要实现移动语义,必须使用右值引用

C++98中引用作用:因为引用是一个别名,需要用指针操作的地方,可以使用指针来代替,可以提高代码的

可读性以及安全性

C++11中右值引用主要有以下作用:

          1. 实现移动语义(移动构造与移动赋值)
                  2. 给中间临时变量取别名:
                  3. 实现完美转发
 

完美转发:是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。函数模板在向                           其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相
                         应实参是右值,它就应该被转发为右值。这样做是为了保留在其他函数针对转发而来的参数的左右值属性进
                         行不同处理

int main()
{
    string s1("hello");
    string s2(" world");
    string s3 = s1 + s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象
    stirng&& s4 = s1 + s2; // s4就是s1和s2拼接完成之后结果的别名
    return 0;
}


9.lambda表达式

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法
       如果待排序元素为自定义类型,需要用户定义排序时的比较规则

struct Goods
{
    string _name;
    double _price;
};
struct Compare
{
        bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price <= gr._price;
    }
};
int main()
{
    Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
    sort(gds, gds+sizeof(gds) / sizeof(gds[0]), Compare());
    return 0;
}


       随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法, 都要重新去
写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了
极大的不便。因此,在C11语法中出现了Lambda表达式

int main()
{
    Goods gds[] = { { "苹果", 2.1 }, { "相交", 3 }, { "橙子", 2.2 }, {"菠萝", 1.5} };
    sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& l, const Goods& r)
    ->bool
    {
        return l._price < r._price;
    });
return 0;
}


lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
        []{};
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
        int a = 3, b = 4;
        [=]{return a + 3; };
    // 省略了返回值类型,无返回值类型
        auto fun1 = [&](int c){b = a + c; };
        fun1(10)
        cout<<a<<" "<<b<<endl;
    // 各部分都很完善的lambda函数
        auto fun2 = [=, &b](int c)->int{return b += a+ c; };
        cout<<fun2(10)<<endl;
    // 复制捕捉x
        int x = 10;
        auto add_x = [x](int a) mutable { x *= 2; return a + x; };
        cout << add_x(10) << endl;
    return 0;
}


捕获列表说明
捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针

注意:
a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值
传递方式捕捉变量a和this,引用方式捕捉其他变量 c. 捕捉列表不允许变量重复传递,否则就会导致编
译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都
会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同

 

10.线程库

(1)std::thread
std::thread为C++11的线程类,使用方法和boost接口一样,非常方便,同时,C++11的std::thread解决了boost::thread中构成参数限制的问题,我想着都是得益于C++11的可变参数的设计风格。

(2)std::atomic
std::atomic为C++11分装的原子数据类型.
从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。

(3)std::condition_variable
std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一样,可以让线程休眠,直到被唤醒,现在在从新执行。线程等待在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。

std::condition_variable cv;
while (!ready) cv.wait(lck); //调用cv.wait(lck)的时候,线程将进入休眠
cv.notify_all(); //休眠结束
 












 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值