c++11 新特性

c++11的新特性

1.1 lambda表达式

lambda表达式讲解

[capture list] (params list) mutable exception-> return type { function body };
[capture list] (params list) -> return type {function body};  //1
[capture list] (params list) {function body};       //2
[capture list] {function body};     //3

例子1:

vector<int> v({1,5,2,7,8});
sort(v.begin(), v.end(), [] (const int &a, const int &b) { return a < b; });
for(int i = 0; i < v.size(); ++i)
   cout<<v[i]<<endl;
输出:
1
2
5
7
8

例子2:

// 值捕获
int main()
{
    int a = 123;
    auto f = [a] { cout << a << endl; }; 
    a = 321;
    f(); // 输出:123
}

// 引用捕获
int main()
{
    int a = 123;
    auto f = [&a] { cout << a << endl; }; 
    a = 321;
    f(); // 输出:321
}

// 隐式值捕获
int main()
{
    int a = 123;
    auto f = [=] { cout << a << endl; };    // 值捕获
    f(); // 输出:123
}

// 隐式引用捕获
int main()
{
    int a = 123;
    auto f = [&] { cout << a << endl; };    // 引用捕获
    a = 321;
    f(); // 输出:321
}

C++11中的Lambda表达式捕获外部变量主要有以下形式:

捕获形式 说明
[] 不捕获任何外部变量
[变量名, …] 默认以值得形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符)
[this] 以值的形式捕获this指针
[=] 以值的形式捕获所有外部变量
[&] 以引用形式捕获所有外部变量
[=, &x] 变量x以引用形式捕获,其余变量以传值形式捕获
[&, x] 变量x以值的形式捕获,其余变量以引用形式捕获

1.2 自动类型推导和 decltype

自动类型推导:
auto x = 0; //0 是 int 类型,所以 x 也是 int 类型  
auto c = 'a'; //char  
auto d = 0.5; //double  
auto national_debt = 14400000000000LL;//long long  
vector<int> vi;
auto ci=vi.begin();

auto作为函数返回值时,只能用于定义函数,不能用于声明函数。
#pragma once
class test
{
public:
    auto testWork(int a, int b); // 声明函数(错误)
    // 在引用头文件的调用testWork函数是,编译无法通过。
}

class test
{
public:
    auto testWork(int a, int b) // 定义函数(正确)
    {
        return a+b;
    }
    // 但如果把实现写在头文件中,可以编译通过,
    // 因为编译器可以根据函数实现的返回值确定auto的真实类型。 
    
}

C++11 也提供了从对象或表达式中“俘获”类型的机制,
新的操作符 decltype 可以从一个表达式中“俘获”其结果的类型并“返回”:
decltype使用:

const vector<int> vi;  
typedef decltype (vi.begin()) CIT;  
CIT another_const_iterator; 

const int ci = 0, &cj = ci;
decltype(ci) x = 0;
decltype(cj) y = x;
decltype(cj) z; //报错,因为cj是一个引用,因此作为引用的 z 必须要进行初始化


int i = 0;
decltype((i)) a; //报错,因为a类型为 int&,必须进行初始化
decltype(i) b; //正确

需要注意的是,decltype((variable))的结果永远是引用,而decltype(variable)结果只有当variable本身就是一个引用时才是引用,其实就是根据它的类型决定;

1.3 deleted 函数和 defaulted 函数

=default; 指示编译器生成该函数的默认实现。这有两个好处:一是让程序员轻松了,少敲键盘,二是有更好的性能。
与 defaulted 函数相对的就是 deleted 函数, 实现 non copy-able 防止对象拷贝,要想禁止拷贝,用 =deleted 声明一下两个关键的成员函数就可以了:

int func()=delete;  
//防止对象拷贝的实现
struct NoCopy  
{  
    NoCopy & operator =(const NoCopy &) = delete;  
    NoCopy(const NoCopy &) = delete;  
};  
NoCopy a;  
NoCopy b(a); //编译错误,拷贝构造函数是 deleted 函数 

1.4 nullptr

nullptr 是一个新的 C++ 关键字,它是空指针常量,它是用来替代高风险的 NULL 宏和 0 字面量的。
nullptr 是强类型的,所有跟指针有关的地方都可以用 nullptr,包括函数指针和成员指针:

void f(int); //#1  
void f(char *);//#2  
//C++03  
f(0); //调用的是哪个 f?  
//C++11  
f(nullptr) //毫无疑问,调用的是 #2  
 
const char *pc=str.c_str(); //data pointers  
if (pc != nullptr)  
  cout << pc << endl;  
int (A::*pmf)()=nullptr; //指向成员函数的指针  
void (*pmf)()=nullptr; //指向函数的指针  

1.5 右值引用

右值引用讲解

左值右值的区别:

  • 左值:在赋值号左边,可以被赋值的值,可以取地址;

  • 右值:在赋值号右边,取出值赋给其他变量的值;

  • 左值引用:type & 引用名 = 左值表达式

  • 右值引用:type && 引用名 = 右值表达式

有一个可以区分左值和右值的便捷方法:
看能不能对表达式取地址,如果能,则为左值,否则为右值。

int main() {
    int i    = 1;          //i为常规左值
    int &r   = i;          //正确:r绑定到i上,r是一个引用
    int &&rr = i;          //错误:不能将一个右值引用绑定到左值i上
    int &r2  = i * 1;      //错误:等号右边是一个右值,但左值引用只能绑定到左值上
    int &&rr2 = i * 1;     //正确:右值引用绑定到右值上
    const int &r3 = i * 1; //正确:可以将一个const的左值引用绑定到右值上
    return 0;
}

关于右值引用的两个应用:
1.移动构造
2.移动赋值

MyString(const MyString& str) // 拷贝构造函数
MyString(MyString&& str) // 移动构造函数
MyString& operator=(const MyString& str) // 拷贝赋值函数 =号重载
MyString& operator=(MyString&& str) // 移动赋值函数 =号重载

// 我们可以销毁一个move后源对象,也可以赋予它新值,但不能使用一个move后源对象的值

// 移动构造函数,参数 "arg.member" 是左值
A(A&& arg) : member(std::move(arg.member))
{
} 
 
// 移动赋值函数
A& operator=(A&& other) {
     member = std::move(other.member);
     return *this;
}

移动构造函数与拷贝构造函数的区别是,拷贝构造的参数是const MyString& str,是常量左值引用,而移动构造的参数是MyString&& str,是右值引用,而MyString("hello")是个临时对象,是个右值,优先进入移动构造函数而不是拷贝构造函数。而移动构造函数与拷贝构造不同,它并不是重新分配一块新的空间,将要拷贝的对象复制过来,而是"偷"了过来,将自己的指针指向别人的资源,然后将别人的指针修改为nullptr,这一步很重要,如果不将别人的指针修改为空,那么临时对象析构的时候就会释放掉这个资源,"偷"也白偷了。

对于一个左值,肯定是调用拷贝构造函数了,但是有些左值是局部变量,生命周期也很短,能不能也移动而不是拷贝呢?C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。我觉得它其实就是告诉编译器,虽然我是一个左值,但是不要对我用拷贝构造函数,而是用移动构造函数吧。。。

vector<MyString> vecStr2;
    vecStr2.reserve(1000); //先分配好1000个空间
    for(int i=0;i<1000;i++){
        MyString tmp("hello");
        vecStr2.push_back(std::move(tmp)); //调用的是移动构造函数
    }

1.6 智能指针

shared_ptr    
weak_ptr 
unique_ptr

1.7 多线程

在C++11以前,C++的多线程编程均需依赖系统或第三方接口实现,一定程度上影响了代码的移植性。C++11中,引入了boost库中的多线程部分内容,形成C++标准,形成标准后的boost多线程编程部分接口基本没有变化,这样方便了以前使用boost接口开发的使用者切换使用C++标准接口,把容易把boost接口升级为C++接口。我们通过如下几部分介绍C++11多线程方面的接口及使用方法。

1.7.1 std::thread

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

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <thread>
void threadfun1()
{
    std::cout << "threadfun1 - 1\r\n" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "threadfun1 - 2" << std::endl;
}

void threadfun2(int iParam, std::string sParam)
{
    std::cout << "threadfun2 - 1" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "threadfun2 - 2" << std::endl;
}

int main()
{
    std::thread t1(threadfun1);
    std::thread t2(threadfun2, 10, "abc");
    t1.join();
    std::cout << "join" << std::endl;
    t2.detach();
    std::cout << "detach" << std::endl;
}

输出:

threadfun1 - 1
threadfun2 - 1

threadfun1 - 2
join
detach

有以上输出结果可以得知,t1.join()会等待t1线程退出后才继续往下执行(当thread::join()函数被调用后,调用它的线程会被block,直到线程的执行被完成。);t2.detach()此时 子线程和main thread 完全分离,两个线程自顾自的运行,main thread可以不等子线程运行完,就提前结束。detach字符输出后,主函数退出,threadfun2还未执行完成,但是在主线程退出后,t2的线程也被已经被强退出。

1.7.2 std::atomic

std::atomic为C++11分装的原子数据类型。

  • 什么是原子数据类型?
    从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。

下面例子中,我们使用10个线程,把std::atomic_int类型的变量iCount从100减到1。

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <thread>
#include <atomic>
#include <stdio.h>
std::atomic_bool bIsReady = false; // std::atomicM<bool>
std::atomic_int iCount = 100;
void threadfun1()
{
    while(!bIsReady) {
        std::this_thread::yield();
    }
    while (iCount > 0)
    {
        printf("iCount:%d\r\n", iCount--);
    }
}

int main()
{
    std::atomic_bool b;
    std::list<std::thread> lstThread;
    for (int i = 0; i < 10; ++i)
    {
        lstThread.push_back(std::thread(threadfun1));
    }
    for (auto& th : lstThread)
    {
        th.join();
    }
}

1.7.3 std::condition_variable

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

// webset address: http://www.cplusplus.com/reference/condition_variable/condition_variable/%20condition_variable
// condition_variable example
#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 print_id(int id) {
    std::unique_lock<std::mutex> lck(mtx);
    while (!ready) cv.wait(lck);
    // ...
    std::cout << "thread " << id << '\n';
}

void go() {
    std::unique_lock<std::mutex> lck(mtx);
    ready = true;
    cv.notify_all();
}

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

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

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

    return 0;
}

上面的代码,在14行中调用cv.wait(lck)的时候,线程将进入休眠,在调用33行的go函数之前,10个线程都处于休眠状态,当22行的cv.notify_all()运行后,14行的休眠将结束,继续往下运行,最终输出如上结果。

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

std::bind和std::function也是从boost中移植进来的C++新标准,这两个语法使得封装可执行对象变得简单而易用。此外,std::bind和std::function也可以结合我们一下所说的lamda表达式一起使用,使得可执行对象的写法更加“花俏”。

我们下面通过实例一步步了解std::function和std::bind的用法:
Test.h

//Test.h 示例代码
class Test
{
public:
    void Add()
    {
        
    }
};


//main.cpp 示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
#include <functional>
#include <iostream>
#include "Test.h"
int add(int a,int b)
{
    return a + b;
}

int main()
{
    Test test;
    test.Add();
    return 0;
}

假如我们的需求是让Test里面的Add由外部实现,如main.cpp里面的add函数,有什么方法呢?

//修改Test.h
class Test
{
public:
    typedef int(*FunType)(int, int);
    void Add(FunType fun,int a,int b)
    {
        int sum = fun(a, b);
        std::cout << "sum:" << sum << std::endl;
    }
};

//main.cpp
int add(int a,int b)
{
    return a + b;
}
....
....
Test test;
test.Add(add, 1, 2);
....

到现在为止,完美了吗?如果你是Test.h的提供者,你觉得有什么问题?我们把问题升级,假如add实现是在另外一个类内部,如下代码:

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html
class TestAdd
{
public:
    int Add(int a,int b)
    {
        return a + b;
    }
};

int main()
{
    Test test;
    //test.Add(add, 1, 2);
    return 0;
}

假如add方法在TestAdd类内部,那你的Test类没辙了,因为Test里的Test函数只接受函数指针。

//继续修改Test.h
class Test
{
public:
    void Add(std::function<int(int, int)> fun, int a, int b)
    {
        int sum = fun(a, b);
        std::cout << "sum:" << sum << std::endl;
    }
};
// Test类中std::function<int(int,int)>表示std::function封装的可执行对象返回值和两个参数均为int类型。


main.cpp
int add(int a,int b)
{
    std::cout << "add" << std::endl;
    return a + b;
}

class TestAdd
{
public:
    int Add(int a,int b)
    {
        std::cout << "TestAdd::Add" << std::endl;
        return a + b;
    }
};

int main()
{
    Test test;
    test.Add(add, 1, 2);

    TestAdd testAdd;
    test.Add(std::bind(&TestAdd::Add, testAdd, std::placeholders::_1, std::placeholders::_2), 1, 2);
    return 0;
}

解释:

  • std::bind第一个参数为对象函数指针,表示函数相对于类的首地址的偏移量;

  • testAdd为对象引用;

  • std::placeholders::_1和std::placeholders::_2为参数占位符,表示std::bind封装的可执行对象可以接受两个参数。

  1. 保存普通函数
void printA(int a)
{
     cout << a << endl;          
}

std::function<void(int a)> func;
func = printA;
func(2);   //2
  1. 保存lambda表达式
std::function<void()> func_1 = [](){cout << "hello world" << endl;};    
func_1();  //hello world
  1. 保存成员函数
class Foo{
    Foo(int num) : num_(num){}
    void print_add(int i) const {cout << num_ + i << endl;}
    int num_;  
};

//保存成员函数
std::function<void(const Foo&,int)> f_add_display = &Foo::print_add;
Foo foo(2);
f_add_display(foo,1);

关于bind的用法:

#include <iostream>
#include <functional>
using namespace std;
class A
{
public:
    void fun_3(int k,int m)
    {
        cout<<"print: k="<<k<<",m="<<m<<endl;
    }
};

void fun_1(int x,int y,int z)
{
    cout<<"print: x=" <<x<<",y="<< y << ",z=" <<z<<endl;
}

void fun_2(int &a,int &b)
{
    a++;
    b++;
    cout<<"print: a=" <<a<<",b="<<b<<endl;
}

int main(int argc, char * argv[])
{
    //f1的类型为 function<void(int, int, int)>
    auto f1 = std::bind(fun_1,1,2,3); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3
    f1(); //print: x=1,y=2,z=3

    auto f2 = std::bind(fun_1, placeholders::_1,placeholders::_2,3);
    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f2 的第一,二个参数指定
    f2(1,2);//print: x=1,y=2,z=3
 
    auto f3 = std::bind(fun_1,placeholders::_2,placeholders::_1,3);
    //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f3 的第二,一个参数指定
    //注意: f2  和  f3 的区别。
    f3(1,2);//print: x=2,y=1,z=3

    int m = 2;
    int n = 3;
    auto f4 = std::bind(fun_2, placeholders::_1, n); //表示绑定fun_2的第一个参数为n, fun_2的第二个参数由调用f4的第一个参数(_1)指定。
    f4(m); //print: m=3,n=4
    cout<<"m="<<m<<endl;//m=3  说明:bind对于不事先绑定的参数,通过std::placeholders传递的参数是通过引用传递的,如m
    cout<<"n="<<n<<endl;//n=3  说明:bind对于预先绑定的函数参数是通过值传递的,如n
    
    A a;
    //f5的类型为 function<void(int, int)>
    auto f5 = std::bind(&A::fun_3, a,placeholders::_1,placeholders::_2); //使用auto关键字
    f5(10,20);//调用a.fun_3(10,20),print: k=10,m=20

    std::function<void(int,int)> fc = std::bind(&A::fun_3, a,std::placeholders::_1,std::placeholders::_2);
    fc(10,20);   //调用a.fun_3(10,20) print: k=10,m=20 
    return 0; 
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容

  • 李亮审稿人:徐方友,邱帅 说起C++语言,它现在被公认为是在各种编程语言中最难学的语言之一,它的语法知识点之广泛,...
    senju阅读 14,585评论 0 1
  • 引子-深拷贝和浅拷贝     在cpp11之前,我们定义一个类如果类中有指针成员,并且其指向一块堆内存,那么往往本...
    于天佐阅读 3,802评论 1 8
  • "C++11 标准新特性之 右值" 什么是右值, 用来做什么的, T &&是什么鬼? 第一节 教你分分钟学会什么...
    阳_逍阅读 382评论 0 1
  • 某日,近午。余课罢早回,致电妻,欲何食。妻曰:苞谷渗可也,另加薯干少许。余按其嘱,逐一备之。欲取薯干,遍寻诸室而不...
    黑荞阅读 251评论 3 2
  • 我今天来到了一个非常漂亮的世界,这里的东西都是由不同程度的黄色组成的,树,草,石头等等,还有一颗非常大的牛头骨,非...
    画师昊渊阅读 363评论 2 3