C++11新特性

1.引入 nullptr

如果 NULL 被定义为 ((void*)0),C++ 不允许直接将 void * 隐式转换到其他类型,那么当编译char *ch = NULL时,无法编译通过,因此NULL一般被定义为 0。

而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,为了解决这个问题,引入nullptr。考虑以下:

void foo(char *);
void foo(int);

foo(NULL);     // 此处调用哪个函数呢?可能会违反代码本意?

2. 新增类型推导关键字auto、​​decltype

  • 类型推导auto
vector<string> data;
auto iter = data.begin();
  • 模版函数中使用auto:
//传统写法
template <typename Product, typename Creator>  
void processProduct(const Creator& creator) {  
    Product* val = creator.makeObject();  
}         

//如果使用auto,则可以这样写: 可以减少 template 模板的使用 
template <typename Creator>  
void processProduct(const Creator& creator) {  
    auto val = creator.makeObject();  
 }
  •  decltype有点像auto的反函数,auto可以让你声明一个变量,而decltype则可以从一个变量或表达式中得到类型。
auto x = 1;
auto y = 2;
decltype(x+y) z;
  • 拖尾返回类型(auto 与 decltype 配合)
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
    return x+y;
}

// 从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法:
template<typename T, typename U>
auto add(T x, U y) {
    return x+y;
}

3.区间遍历

类似python遍历方法,引入以下for循环语法:

map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};  
for (auto p : m){  
    cout<<p.first<<" : "<<p.second<<endl;  

4. 初始化列表

//原来版本中,只能这样初始化数组
int arr[3] = {1, 2, 3}  
vector<int> v(arr, arr + 3); 

//在C++11中,我们可以使用以下语法来进行替换:

int arr[3]{1, 2, 3};

map<int, string>{{1, "a"}, {2, "b"}};

string str{"Hello World"};

vector<int> iv{1, 2, 3}; 
vector<int> data_int = {1,2,3};
vector<string> data_string = {"1", "2", "3"};

5. 模板增强

  • 使用using关键字更加直观的定义别名(typedef 可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称,因为模板不是类型):
typedef int (*fun)(int *);   //以前c++的做法,声明一个参数为int *,返回值为int的函数指针,名字叫fun
using fun = int (*)(int *);  //c++11,这样更加直观

template <typename T>
using newType = std::pair<T, T>;
  • 变长模板和默认模板参数
//c++11可以在定义模板时,给与模板一个默认的类型,这样可以在不设置类型的时候使用默认类型:
template <typename T = int, typename U = std::string>

//同时c++11可以设置模板参数为任意个数:
template <typename T1, typename... TS>      //可以接受至少一个模板类型
template <typename... TS>                   //至少0个

6. 显式缺省(=default)、显式删除(=delete)

我们知道在没有指定的情况下,c++会对类设置默认的构造函数、拷贝构造函数、赋值函数以及析构函数,但是有时候我们并不需要这些默认函数,因此在C++11中引入了对这些特性进行精确控制的特性:default指定生成默认函数,delete指定禁用默认函数。如果禁用了默认的构造函数和析构函数,必须指定一个自定义的函数。

class Test {
public:
    Test() = default;       //指定为Test类生成默认构造函数,如果设置为delete,就是禁用默认构造函数,如果禁用了
    ~Test() = default;      //默认析构函数
    Test(const Test&) = delete;    //禁用拷贝构造函数
    Test& operator=(const Test&) = delete;  //禁用类赋值函数
};

Test a;
Test b(a);      //error,因为已经被禁用
Test c = a;     //error,因为已经被禁用

7.构造函数特性

C++11提供了两种新的构造函数特性,用于提升类构造的效率,分别是委托构造和继承构造,前者主要用于多构造函数的情况,而后者用在类继承方面: 

  • 委托构造 

委托构造的本质为了简化函数代码,做到复用其他构造函数代码的目的。

class Test {
public:
    Test() {
        a = 1;
    }
    Test(int _b) : Test() {
        b = _b;
    }
    int a,b;
};

Test t(2);    //会调用Test()将a赋值为1
  • 继承构造 

c++在继承的时候,需要将构造函数的参数逐个传递到积父类的构造函数中完成父类的构造,这种效率是很低下的,因此c++11引入了继承构造的特性,使用using关键字:

class Test {
public:
    Test(int _a, int _b) : a(_a), b(_b) {};
    int a,b;
};

class Test2 : public Test {
    using Test::Test;
}

Test2 t(2, 3);      //会调用父类的构造函数

8.显式控制虚函数重载

由于虚函数的特性,可能会被意外进行重写,为了做到精确对虚函数重载的控制,c++11使用了overridefinal关键字完成对这一特性的实现,下面看例子:

//override显式声明对虚函数进行重载

class Test {
public:
    Test() {};
    virtual int fun(int);
};

class Test2 : public Test {
    using Test::Test;
    int fun(int) override;      //显式声明对虚函数进行重载
    int fun(float) override;    //错误,父类没有这个虚函数
}
//final关键字是为了显式禁止类的继承和虚函数的重载使用:

class Test {
public:
    virtual int fun(int) final;
};

class Test2 final: public Test {
    int fun(int) override;      //非法,因为该虚函数已经设置为finale,禁止重载 
};

class Test3 : public Test2 {};  //非法,Test2已经设置为final,禁止作为父类

9. Lambda 表达式

lambda表达式类似Javascript中的闭包,它可以用于创建并定义匿名的函数对象,以简化编程工作。Lambda的语法如下:

vector<int> iv{5, 4, 3, 2, 1};  
int a = 2, b = 1;  

for_each(iv.begin(), iv.end(), [b](int &x){cout<<(x + b)<<endl;}); // (1)  
for_each(iv.begin(), iv.end(), [=](int &x){x *= (a + b);});     // (2)  
for_each(iv.begin(), iv.end(), [=](int &x)->int{return x * (a + b);});// (3) 
/*
1、[]内的参数指的是Lambda表达式可以取得的全局变量,(1)函数中的b就是指函数可以得到在Lambda表达式外的全局变量;
2、如果在[]中传入=的话,即是可以取得所有的外部变量,如(2)和(3)Lambda表达式。
3、()内的参数是每次调用函数时传入的参数。
4、->后加上的是Lambda表达式返回值的类型,如(3)中返回了一个int类型的变量。
*/

//以下adder是lambda函数
//int (* p)(int, int) = [](int p1, int p2) {return p1 + p2; };  以前的写法,使用auto扩展性更强
auto adder = [](auto op1, auto op2) {return op1 + op2; };
cout << "int result:" << std::accumulate(data_int.begin(), data_int.end(), 0, adder) << endl;
cout << "string result:" << std::accumulate(data_string.begin(), data_string.end(), string(""), adder) << endl;
cout << "sum is:" << adder(1, 3) << endl;

10.强枚举类型

c++11引入了enum class来保证枚举不会被隐式转换:

enum class test : int {
    v1 = 0,
    v2 = 1
};

if (test::v1 == 0)          //错误,不能把test类型与int做隐式转换
if (test::v1 == test(0))    //正确,显示转换后进行比较

11. 新增容器

  • std::array

std::array 保存在栈内存中,相比堆内存中的 std::vector,我们能够灵活的访问这里面的元素,从而获得更高的性能。

std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array只需指定其类型和大小即可:

std::array<int, 4> arr= {1,2,3,4};

int len = 4;
std::array<int, len> arr = {1,2,3,4}; // 非法, 数组大小参数必须是常量表达式

当我们开始用上了 std::array 时,难免会遇到要将其兼容 C 风格的接口,这里有三种做法:

void foo(int *p, int len) {
    return;
}

std::array<int 4> arr = {1,2,3,4};

// C 风格接口传参
// foo(arr, arr.size());           // 非法, 无法隐式转换
foo(&arr[0], arr.size());
foo(arr.data(), arr.size());

// 使用 `std::sort`
std::sort(arr.begin(), arr.end());

  • std::forward_list

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

  • 无序容器

C++11 引入了两组无序容器: 
std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

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

  • 元组 std::tuple

元组的使用有三个核心的函数:

std::make_tuple: 构造元组 
std::get: 获得元组某个位置的值 
std::tie: 元组拆包

#include <tuple>
#include <iostream>

auto get_student(int id)
{
    // 返回类型被推断为 std::tuple<double, char, std::string>
    if (id == 0)
        return std::make_tuple(3.8, 'A', "张三");
    if (id == 1)
        return std::make_tuple(2.9, 'C', "李四");
    if (id == 2)
        return std::make_tuple(1.7, 'D', "王五");
    return std::make_tuple(0.0, 'D', "null");   
    // 如果只写 0 会出现推断错误, 编译失败
}

int main()
{
    auto student = get_student(0);
    std::cout << "ID: 0, "
    << "GPA: " << std::get<0>(student) << ", "
    << "成绩: " << std::get<1>(student) << ", "
    << "姓名: " << std::get<2>(student) << '\n';

    double gpa;
    char grade;
    std::string name;

    // 元组进行拆包
    std::tie(gpa, grade, name) = get_student(1);
    std::cout << "ID: 1, "
    << "GPA: " << gpa << ", "
    << "成绩: " << grade << ", "
    << "姓名: " << name << '\n';

    //合并两个元组,可以通过 std::tuple_cat 来实现。
    auto new_tuple = std::tuple_cat(get_student(1), std::move(t));
}

12. 正则表达式

考虑下面的正则表达式:

[a-z]+.txt: 在这个正则表达式中, [a-z] 表示匹配一个小写字母, + 可以使前面的表达式匹配多次,因此 [a-z]+ 能够匹配一个及以上小写字母组成的字符串。在正则表达式中一个 . 表示匹配任意字符,而 \. 后则表示匹配字符 . ,最后的 txt 表示严格匹配 txt 这三个字母。因此这个正则表达式的所要匹配的内容就是“文件名为纯小写字母的文本文件” 。

  • 使用形式1:
#include <iostream>
#include <string>
#include <regex>

int main() {
    std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
    // 在 C++ 中 `\` 会被作为字符串内的转义符,为使 `\.` 作为正则表达式传递进去生效,需要对 `\` 进行二次转义,从而有 `\\.`
    std::regex txt_regex("[a-z]+\\.txt");
    for (const auto &fname: fnames)
        std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
}
  • 使用形式2:
std::regex base_regex("([a-z]+)\\.txt");
std::smatch base_match;
for(const auto &fname: fnames) {
    if (std::regex_match(fname, base_match, base_regex)) {
        // sub_match 的第一个元素匹配整个字符串
        // sub_match 的第二个元素匹配了第一个括号表达式
        if (base_match.size() == 2) {
            std::string base = base_match[1].str();
            std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;
            std::cout << fname << " sub-match[1]: " << base << std::endl;
        }
    }
}

13. 语言级线程支持

std::thread 
std::mutex/std::unique_lock 
std::future/std::packaged_task 
std::condition_variable

代码编译需要使用 -pthread 选项

14. 右值引用和move语义

  • 在 C++03 中的引用类型是只绑定左值的,C++11 引入一个新的引用类型叫右值引用类型,它是绑定到右值的,如临时对象或字面量,增加右值引用的主要原因是为了实现 move 语义。与传统的拷贝不同,move 的意思是目标对象“窃取”原对象的资源,并将源置于“空”状态。当拷贝一个对象时,其实代价昂贵且无必要,move 操作就可以替代它。如在 string 交换的时候,使用 move 意义就有巨大的性能提升,如下所示:
//原方案很慢,因为需要申请内存,然后拷贝字符;
void naiveswap(string &a, string & b)  
{  
    string temp = a;  
    a=b;  
    b=temp;  
}  
 
//使用move就只需要交换两个数据成员,无须申请、释放内存和拷贝字符数组;
void moveswapstr(string& empty, string & filled)  
{  
    //pseudo code, but you get the idea  
    size_t sz=empty.size();  
    const char *p= empty.data();  
    //move filled's resources to empty  
    empty.setsize(filled.size());  
    empty.setdata(filled.data());  
    //filled becomes empty  
    filled.setsize(sz);  
    filled.setdata(p);  
} 

要实现支持 move 的类,需要声明move 构造函数和 move 赋值操作符,如下:

class Movable  
{  
    Movable (Movable&&); //move constructor  
    Movable&& operator=(Movable&&); //move assignment operator  
}; 
  • 转移左值 

有时候,我们可能想转移左值,也就是说,有时候我们想让编译器把左值当作右值对待,以便能使用转移构造函数,即便这有点不安全。出于这个目的,C++ 11在标准库的头文件< utility >中提供了一个模板函数std::move。实际上,std::move仅仅是简单地将左值转换为右值,它本身并没有转移任何东西。它仅仅是让对象可以转移。

unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);              // still an error
unique_ptr<Shape> c(std::move(a));   // okay

请注意,第三行之后,a不再拥有Triangle对象 

15.新的智能指针

C++98 定义的唯一的智能指针类 auto_ptr 已经被弃用,C++11 引入了新的智能针对类 shared_ptr 和 unique_ptr。它们都是标准库的其它组件兼容,可以安全地把智能指针存入标准容器,也可以安全地用标准算法“倒腾”它们。

一旦你用 unique_ptr 关键字定义了一个对象,那么下列事件只要发生一个,对象就会被销毁并释放内存:
    • unique_ptr 管理的对象被销毁。
    • unique_ptr 管理的对象通过赋值操作符指向另一个指针,或调用了reset()方法。
对于不想了解太多细节的用户来说,这就意味着如果你使用了 unique 指针的语义,那么在跳出作用域之前,你就不用手动回收对象的内存了。
以前,我们需要这么写代码:
    YourObject * obj = new YourObject();
然后在程序的最后你一定要记得释放内存:
    delete(obj);
否则你可就造成内存泄露了。而现在,
    std::unique_ptr<YourObject> obj(new YourObject());
当 obj 跳出作用域范围之外的时候,内存将会被自动回收。

16.新的算法

主要是 all_of()、any_of() 和 none_of(),下面是例子:

//all_of()、any_of() 和 none_of()函数
#include <algorithm>  
//C++11 code  
//are all of the elements positive?  
all_of(first, first+n, ispositive()); //false  
//is there at least one positive element?  
any_of(first, first+n, ispositive());//true  
// are none of the elements positive?  
none_of(first, first+n, ispositive()); //false  
 
 
//新的 copy_n:
#include <algorithm>  
int source[5]={0,12,34,50,80};  
int target[5];  
//从 source 拷贝 5 个元素到 target  
copy_n(source,5,target);  
 
 
//iota() 算法可以用来创建递增序列,它先把初值赋值给 *first,然后用前置 ++ 操作符增长初值并赋值到给下一个迭代器指向的元素,如下
#include <numeric>  
int a[5]={0};  
char c[3]={0};  
iota(a, a+5, 10); //changes a to {10,11,12,13,14}  
iota(c, c+3, 'a'); //{'a','b','c'}  

参考文章:

https://blog.youkuaiyun.com/jiange_zh/article/details/79356417

https://blog.youkuaiyun.com/caogenwangbaoqiang/article/details/79438279

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值