C++11 新特性

从 C++98 到 C++11,C++11 标准经历了十几年的沉淀。尽管后来更新速度越来越快,又出现了 C++14、C++17 等等,但 C++11 是必学的经典标准。

主要特性目录(加粗的是实现了以前没有的重要功能,或者确实很好用的):

  1. 关键字及新语法
    1.1. auto 关键字及用法
    1.2. nullptr 关键字及用法
    1.3. for 循环语法
  2. STL 容器
    2.1. std::array
    2.2. std::forward_list
    2.3. std::unordered_map
    2.4. std::unordered_set
  3. 多线程
    3.1. std::thread
    3.2. std::atomic
    3.3. std::condition_variable
  4. 智能指针内存管理
    4.1. std::shared_ptr
    4.2. std::weak_ptr
  5. 其他
    5.1. std::function、std::bind 封装可执行对象
    5.2. lambda 表达式

1. 关键字及新语法

1.1. auto关键字及用法

auto 并没有让 C++ 成为弱类型语言,只是使用 auto 的时候,编译器根据上下文,确定 auto 变量的真正类型。

auto AddTest(int a, int b) {    // auto 可以作为函数的返回值类型
    return a + b;
}
int main() {
    auto index = 10;    // 自动识别类型为 int
    auto res = AddTest(1,2);
    std::cout << "index:" << index << std::endl;    // index:10
    std::cout << "res:" << ret << std::endl;    // res: 3
}

但是注意 auto 作为函数返回值时,必须用于定义函数,不能仅仅声明函数。

1.2. nullptr 关键字及用法

在 nullptr 出现之前存在什么问题呢?首先,NULL 不是一个关键字,而只是一个宏(macro):

#define NULL 0

考虑这样一个函数重载的情形:

void foo(int) {}    // #1
void foo(char*) {}    // #2
int main() {
    foo(NULL);    // 调用 #1 还是 #2 ?
}

为了解决这种二义性,C++11 引入了关键字 nullptr,它作为一种空指针常量。foo(nullptr); 会毫无异议地调用函数 #2。

1.3. for 循环语法

原先 C++ 的 for 循环是没有像 foreach 那样的用法的。现在 C++ 11 的 for 循环语法再结合 auto 关键字可以大大简化开发代码:

int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    for (auto number : numbers)
        std::cout << number << std::endl;
}

2. STL容器

2.1. std::array

相对于数组而言,std::array 增加了 STL 的各种迭代器、算法、操作方法等,更加安全、方便。

相对于 std::vector 而言,std::array 提供了静态数组,编译时确定大小、更轻量、更效率,当然也比 std::vector 有更多局限性。

std::array 差不多就是 std::vector 和 普通数组的中和版本。

#include <array>
int main() {
    std::array<int, 4> arrayDemo = {1, 2, 3, 4};
    for (auto it : arrayDemo)
        std::cout << it << std::endl;
    int arrayDemoSize = sizeof(arrayDemo);
    std::cout << "arrayDemo size:" << arrayDemoSize << std::endl;    // 16
    return 0;
}

2.2. std::forward_list

std::forward_list 为新增的线性表,与 list 区别在于它是单向链表。forward_list 可以看作是对 C 语言风格的单链表的封装,仅提供有限的接口,和 C 中它的实现相比,基本上不会有任何开销。当不需要双向迭代的时候,与 std::list 相比,该容器具有更高的空间利用率。

#include <forward_list>
int main() {
    std::forward_list<int> numbers = {1, 2, 3, 4, 5, 4, 4};
    for (auto number : numbers)
        std::cout << number << " ";    // 1 2 3 4 5 4 4
    numbers.remove(4);    // 移除值为 4 的节点
    std::cout << "numbers after remove:" << std::endl;
    for (auto number : numbers)
        std::cout << number << " ";        // 1 2 3 5
    return 0;
}

2.3. std::unordered_map

C++ unordered_map

2.4. std::unordered_set

std::unordered_set 的数据存储结构也是 hashtable+list 的方式,此外,std::unordered_set 在插入时不会像 std::set 一样自动排序。

#include <unordered_set>
#include <set>
int main() {
    std::unordered_set<int> unorder_set;
    unorder_set.insert(7);
    unorder_set.insert(5);
    unorder_set.insert(3);
    unorder_set.insert(4);
    unorder_set.insert(6);
    for (auto itor : unorder_set)
        std::cout << itor << " ";    // 7 5 3 4 6

    std::set<int> set;
    set.insert(7);
    set.insert(5);
    set.insert(3);
    set.insert(4);
    set.insert(6);
    for (auto itor : set)
        std::cout << itor << " ";    // 3 4 5 6 7
}

3. 多线程

在 C++11 以前,C++ 的多线程编程均需依赖系统或第三方接口实现,一定程度上影响了代码的移植性。C++11 中,引入了 boost 库中多线程的部分内容,形成标准后的接口与 boost 库基本没有变化,这样方便了使用者切换使用 C++ 标准接口。

3.1. std::thread

C++11 的 std::thread 解决了 boost::thread 中参数限制的问题,这大概都是得益于 C++11 的可变参数的设计风格。

thread 类内的三个方法:

  • join:等待线程完成其执行。当 a 线程调用此方法时,主线程就被停止执行,直到 a 线程执行完毕。
  • detach:容许线程从线程句柄独立开来执行。当此方法被调用后,执行的线程从线程对象中分离,已不再被一个线程对象所表达。C++ 线程对象可以被销毁,同时 OS 执行的线程可以继续。
  • swap:交换 2 个 thread 对象。

3.2. std::atomic

当我们在编程中想对共享资源进行保护时,很自然地就会想到加锁,但是锁机制会大大增加时间开销。

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

下面例子中,我们使用 100 个线程, 模拟一万次网页点击:

#include <atomic> 
#include <thread>
#include <iostream>
#include <list>

// 用原子数据类型作为共享资源的数据类型
std::atomic_int total(0);
//long total = 0;
 
void click(){
    for(int i=0; i<100;++i)
        // 仅仅是数据类型的不同而已,对其的访问形式与普通数据类型的资源并无区别
        total += 1;
}

int main()
{
    // 创建100个线程模拟点击统计
    std::list<std::thread> threads;
    for(int i=0; i<100;++i)
        threads.push_back(std::thread(click));

    for (auto& th : threads)
        th.join();

    std::cout<<"result:"<<total<<std::endl;    // result:10000
    return 0;
}

3.3. std::condition_variable

线程休眠在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex)来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

#include <iostream>    // std::cout
#include <thread>      // std::thread
#include <mutex>       // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable

std::mutex mtx;                // 全局互斥锁
std::condition_variable cv;    // 全局条件变量
bool ready = false;            // 全局标志位

void do_print_id(int id) {
    std::unique_lock <std::mutex> lck(mtx);    // 进入休眠
    while (!ready)        // 如果标志位不为 true, 则等待
        cv.wait(lck);     // 进入休眠, 当前线程被阻塞, 当全局标志位变为 true 之后,
    // go() 函数内调用 notify_all() 函数后, 线程被唤醒, 继续往下执行打印线程编号 id
    std::cout << "thread " << id << '\n';
}

void go() {
    std::unique_lock <std::mutex> lck(mtx);
    ready = true;       // 设置全局标志位为 true
    cv.notify_all();    // 唤醒所有线程
}

int main() {
    std::thread threads[10];
    for (int i = 0; i < 10; ++i)
        threads[i] = std::thread(do_print_id, i);

    std::cout << "10 threads ready to race...\n";
    go();

    for (auto & th:threads)
        th.join();
}

输出结果(顺序是不一定的):

10 threads ready to race...
thread 9
thread 2
thread 3
thread 8
thread 7
thread 6
thread 5
thread 4
thread 1
thread 0

4. 智能指针内存管理

智能指针及其作用
简单画一下指针、智能指针对象和计数器之间的关系:

5. 其他

5.1. std::function、std::bind 封装可执行对象

std::bind 用来将可调用对象与其参数一起进行绑定。绑定后可以使用 std::function 进行保存,并延迟到我们需要的时候调用:

  • 将可调用对象与其参数绑定成一个仿函数;
  • 可绑定部分参数。

在绑定部分参数的时候,通过使用 std::placeholders 来决定空位参数将会是调用发生时的第几个参数。

#include <iostream>       // std::cout
#include <functional>     // std::function

class A {
public:
    int i_ = 0;    // C++11 允许非静态(non-static)数据成员在其声明处(在其所属类内部)进行初始化
    void output(int x, int y) {
        std::cout << x << " " << y << std::endl;
    }  
};

int main() {
    A a;
    // fr 保存了指代的函数 output(),可以在之后的程序过程中调用
    // std::bind 第一个参数为对象函数指针,表示函数相对于类的首地址的偏移量
    // 第二个参数为对象指针
    // 最后两个为参数占位符,表示 std::bind 封装的可执行对象可以接受两个参数
    std::function<void(int, int)> fr = std::bind(&A::output, &a, std::placeholders::_1, std::placeholders::_2);
    // 调用成员函数
    fr(1, 2);    // 1 2

    // 绑定成员变量
    std::function<int&(void)> fr2 = std::bind(&A::i_, &a);
    fr2() = 100;    // 对成员变量进行赋值
    std::cout << a.i_ << std::endl;    // 100
}

5.2. lambda 表达式

lambda 表达式用于定义并创建匿名的函数对象,以简化编程工作。可以拿来当作 inline 函数使用。lambda 的语法形式如下:
                [...] (...) ... {...}

  • [] 内是一个 capture,可以在 lambda 内部访问的"nonstatic 外部变量",如果没有要访问的变量,可以为空。static 变量是可以直接被访问的。
  • () 内是参数,和函数参数一样。
  • ... 是 mutable 或 exception 声明或者返回类型。如果其中之一出现,那么必须出现 ()。
  • {} 内是函数体,在这里面写明 lambda 要完成的工作。
int main() {
    int xxx= 10;
    auto f = [xxx] (int a) { cout << "hello, world " << a << xxx << endl; };
    f(12);
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容