C++ 11

1 引子

去年年底,开始学习C++11新标准,也曾经发表过一篇关于C++11新增内容的帖子,由于CSDN很纠结的编辑页面,最终这篇帖子烂尾了,实在是汗颜。

最近,在公司分享了关于C++11的部分内容,借此机会,对于平时常用的,以及在Visual Studio 2012中支持的一些功能进行了总结,也趁此发表这篇帖子,算是对上次烂尾的一个补足吧。

2 C++发展史

说到C++ 11新标准,必然应该先了解一下C++的整个发展历程。

1979年,C++之父Bjarne Stroustrup开始扩展原有C语言的功能,使其支持面向对象的一些新特性。

1983年,C++正式出现,原来的名字为C with Classes,同时加入了很多新特性,例如虚函数等。

1985年,C++的经典巨作《The C++ Programming Language》(简称TCPL)第一版发布,如今已于2013年5月更新到第四版了。

1998年,C++标准委员会发布了第一个版本的C++标准,即我们常说的C++98。

2003年,第二个版本的标准发布,主要是修正C++98中出现的一些缺陷。

2005年,发布了技术报告Library Technical Report 1,简称TR1,提供了很多有望成为下一个版本标准的C++特性。

2011年,经过了10多年的时间,C++标准委员会终于发布了第三个版本的C++标准。

2014年,发布了第四个版本标准,主要是对C++11缺陷的修正和部分功能的添加。

3 C++11新增功能(部分)和VS12支持状况

此处只列举了部分功能以及VisualStudio 2012对其的支持,其他更多的功能请参考C++ 11的标准文档。


C++ 11新增功能


VS12是否支持


右值引用



引用限定符



非静态成员初始值



可变参数模板



初始化列表



static_assert



自动推导



追踪返回类型



Lambda



nullptr



强类型enum



constexpr



委托构造



继承构造函数



枚举前置声明



Unicode支持



override和final



noexpect



基于范围的for



原生字符串



default和delete



内联命名空间



对齐


部分支持


多线程



__func__


部分支持


long long


支持

4 功能详解

4.1 Lambda表达式

Lambda表达式是C++ 11标准中非常实用的一个功能,也是非常重要的一个功能,应该是每个人都应该熟练掌握的。

4.1.1 基本声明

Lambda表达式是一个匿名函数,即只有函数体,没有名字的函数。C++ 11中Lambda表达式的基本语法为:

[capture list] (parameterlist)
mutable ->return type { function body }

Lambda表达式的使用要注意几点。第一,非黑色部分标注的为可省略的部分,即一个最简单的表达式可以为:

void S1_SimpleLambda()
{
    auto simpleLambda = []{std::cout << "SimpleLambda" << std::endl;};
    return simpleLambda();
}

第二,一个Lambda表达式默认是const类型的,即不能改变父作用域内任意变量的值。如果想要改变,则需要增加mutable声明,添加mutable声明后,参数列表不可省略。例如:

void ValueCaptureTest2()
{
    int intValue = 0;
    auto test2 = [=]() mutable {std::cout << "Lambda: " << ++intValue << std::endl;};
    test2();
    std::cout << "Out Lambda: " << intValue << std::endl;
}

第三,Lambda表达式默认是一个内联的函数,编译器会对其调用进行优化,所以一个Lambda表达式应该短小精悍。

4.1.2 捕获列表

Lambda表达式的捕获列表(Capture List)主要有以下几种:

?  [var]   表示值传递方式捕捉变量var。

?  [=]       表示值传递方式捕捉所有父作用域的变量(包括this)。

?  [&var]         表示引用传递方式捕捉变量var。

?  [&]      表示引用传递方式捕捉父作用域的变量(包括this)。

?  [this]  表示值传递方式捕捉当前的this指针。

捕获列表是一个很容易出错的地方,所以应该加强注意。下面是几个简单的示例。第一,使用值传递方式进行捕获:

class FMNValueCapture
{
public:
    FMNValueCapture() : m_intValue(0) {}

    void ValueCaptureTest1()
    {
        auto test1 = [=]{std::cout << "Lambda: " << ++m_intValue << std::endl;};
        test1();
        std::cout << "Out Lambda: " << m_intValue << std::endl;
    }

    void ValueCaptureTest2()
    {
        int intValue = 0;
        auto test2 = [=]() mutable {std::cout << "Lambda: " << ++intValue << std::endl;};
        test2();
        std::cout << "Out Lambda: " << intValue << std::endl;
    }

private:
    int m_intValue;
};

第二,使用引用传递方式进行捕获:

void S3_ReferenceCapture()
{
	int intValue = 0;
	auto refCap = [&intValue]{std::cout << "Lambda: " << ++intValue << std::endl;};
	refCap();
	std::cout << "Out Lambda: " << intValue << std::endl;
}

上面仅列举了几个例子,具体还要大家亲自进行尝试才能融会贯通。

4.1.3 Lambda与STL

Lambda表达式与STL中各种容器和算法组合使用,才是威力最为强大的所在。下面列举一个例子,大家可以参考一下,例子为遍历map,并对map的值进行处理(示例分别对其加1并输出):

typedef std::map<int, int> FMNIntMap;

void S4_StlExample()
{
    FMNIntMap intMap;
    for (int i = 0; i < 5; ++i)
    {
        intMap.insert(std::make_pair(i, i));
    }

    std::cout << "first time." << std::endl;
    std::transform(intMap.begin(), intMap.end(),
        std::ostream_iterator<std::string>(std::cout, "\n"),
        [](FMNIntMap::value_type& val)->std::string
    {
        std::stringstream ss;
        ss << "first: " << val.first << ", second: " << val.second;
        return ss.str();
    });

    std::cout << "second time." << std::endl;
    std::for_each(intMap.begin(), intMap.end(), [](std::pair<const int, int>& val)
    {
        ++val.second;
    });
    std::transform(intMap.begin(), intMap.end(),
        std::ostream_iterator<std::string>(std::cout, "\n"),
        [](FMNIntMap::value_type& val)->std::string
    {
        std::stringstream ss;
        ss << "first: " << val.first << ", second: " << val.second;
        return ss.str();
    });
}

4.1.4 Lambda与仿函数

通过上面的例子,大家可以发现,Lambda表达式和仿函数的用法非常相似。但是与仿函数还是有所区别。

第一,Lambda表达式默认为内联函数,而仿函数不是。

第二,仿函数可以具有状态(即成员),通过状态来分别执行不同的分支,但是Lambda不可以。

第三,仿函数可以跨作用域,而Lambda不可以,例如下面这段代码是错误的:

static int g_intVal = 1;

void S5_OutScope()
{
    auto outScope = [g_intVal]{std::cout << "Lambda: " << ++g_intVal << std::endl;};
    outScope();
}

4.2 类型推导与返回值类型追踪

C++ 11中增加了强大的类型推导功能,使得开发效率得以提升,并且大幅度简化简洁了代码。

4.2.1 类型推导

类型推导主要有两个,auto和decltype,通过一个简单的例子,就可以明白它们的具体功能。

void S6_AutoDecltype()
{
    std::vector<int> intVec;
    std::vector<int>::iterator intVecIter1 = intVec.begin();
    auto intVecIter2 = intVec.begin();
    decltype(intVec.begin()) intVecIter3 = intVec.begin();
}

这样,就可以不必定义复杂的迭代器,也不必担心各种计算类型。但是需要注意的是,要区分推导类型是引用传递还是值传递。

4.2.2 返回值追踪

既然有了类型推导,想必大家会想如果返回值也是自动推导,岂不不用担心各种复杂的逻辑了?例如下面的代码:

template <class T1, class T2>
decltype(t1 + t2) Sum(T1& t1, T2& t2)
{
    return (t1 + t2);
}

这样,就可以任意类型,只要支持加法运算,就可以进行加和了,例如int和double值进行加和等。但是编译器推导(t1 + t2)时,由于是从左向右解析,故此时尚未知t1和t2的类型,所以推导失败。

为了解决这个问题,C++11提出了返回值追踪的功能。

template <class T1, class T2>
auto Sum(T1& t1, T2& t2) -> decltype(t1 + t2)
{
    return (t1 + t2);
}

4.3 基于范围的for循环

这个比较简单,直接参考示例代码:

void S11_ForRange()
{
    std::vector<int> intVec;
    intVec.push_back(1);
    intVec.push_back(3);
    intVec.push_back(5);
    intVec.push_back(7);

    for (auto i : intVec)
    {
        std::cout << i << std::endl;
    }

    int intArray[] = {2, 4, 6, 8};
    for (auto i : intArray)
    {
        std::cout << i << std::endl;
    }
}

4.4 空指针nullptr

对于常用的NULL来说,其本质上是具有二义性的,例如:

void NullFunc(int val)
{
    std::cout << "null is int." << std::endl;
}

void NullFunc(void* pVal)
{
    std::cout << "null is a pointer" << std::endl;
}

void S9_Nullptr()
{
    static_assert(NULL == nullptr, "Error: nullptr is not NULL");

    NullFunc(NULL);
}

NULL本质上为指针,但是定义却为0,即该函数会将NULL做为一个int值处理,产生了二义性。所以对于这个场景,C++ 11提出了nullptr,我们的代码中应该尽量用nullptr来标识一个空指针。

4.5 强类型enum和前置声明

首先说enum的前置声明,这个与struct和class的前置声明类似,例子如下:

enum class EnumClass : char;

class RCEnumPreDef
{
private:
    EnumClass m_type;
};

enum class EnumClass : char
{
    TYPE_A,
    TYPE_B,
    TYPE_C,
    TYPE_D = 30000,
};

其次,可以将enum定义为class类型,这样使用时候必须加作用域限制,提高了安全性。并且可以指定enum的范围,当超过范围时,作为溢出处理。

但是声明为强类型后,不能使用int或者char等进行enum的遍历比较操作,这个应在使用时进行权衡。

4.6 override与final

override和final主要用来对基类virtual函数的覆盖做声明。

override即声明该函数是对基类函数的覆盖,当参数不同或者返回值不同或者基类函数不为虚函数时,编译器会提示error。

final则是对某一函数的覆盖做终止。即子类不能再对此函数覆盖,如果覆盖,编译器会提示error。

通过override和final,可以提高代码的安全性,明确接口,终止接口覆盖等好处。

示例代码如下:

class FMNBase
{
public:
    FMNBase() {}

    virtual ~FMNBase() {}

    virtual void Func(int) {std::cout << "Base" << std::endl;}
};

class FMNChildA : public FMNBase
{
public:
    void Func(int) override {std::cout << "A Override" << std::endl;}
};

class FMNChildB : public FMNChildA
{
public:
    void Func(int) final {std::cout << "B Override" << std::endl;}
};

class FMNChildC : public FMNChildB
{
public:
    // Error
    void Func(int) override {std::cout << "C Override" << std::endl;}
};

class FMNChildD : public FMNChildA
{
public:
    // Error
    void Func() override {std::cout << "D Override" << std::endl;}
};

4.7 右值引用

为了定义“右值引用”的概念,首先说明左值,右值的含义,加深对此的理解。

左值,是指可以放在赋值符号“=”的左边,但其实也表示能作为&和++等操作符的操作数。可以取其地址和名字。

右值,指的是引用了一个存储在某个内存地址里的数据。不能取其地址,且没有名字。又分为“将亡值”和“纯右值”两种。纯右值即C++98标准中的右值,例如一些字面量等。将亡值即C++11标准中的右值引用。

通过右值引用,可以大幅度提高代码的效率,因为本质上通过内存的转移,可以减少拷贝构造等过程。

下面仅为一个简单的示例,C++ 11实际上提供了对右值引用非常多的支持和定义,所以具体还请参考C++ 11的标准文档。

void ValueFunc(int& val)
{
    std::cout << "Left Value: " << val << std::endl;
}

void ValueFunc(int&& val)
{
    std::cout << "Right Value: " << val << std::endl;
}

void S8_RightValue()
{
    int intVal = 0;
    ValueFunc(intVal);
    ValueFunc(1);
}

4.8 其他

除了上述重要的特性外,还有一些较简单的特性,不在详细讨论,例如:

1. static_assert 编译期的静态断言。

2. STL库新增内容,包括array,forward_list,unordered_map,unordered_set等容器,以及新增的算法。

3. UTF8等编码转换,可以参考头文件<codecvt>。

4. 类型萃取,可以参考头文件<type_trains>

当然,还有其他很多Visual Studio 2012不支持,或者部分支持的特性,如果使用,还请务必参考微软的官方文档。

5 进一步思考

人生总是有太多的迷茫与惆怅,有些时候不知道到底该执着下去还是应该放弃。过去,多少个马云在进行拼搏,可是马云终究只有一个。人生总是有两条路要走,第一是选择,第二是坚持。

一个人,如果没有对选择有信心,那他又是凭借着什么坚持呢?转眼间,一年就要过去了,去年写之前那篇文章时的场景还历历在目,如今却茫茫然的“情不知所起,一往而深”的迷茫着。

C++11,许多东西在不知不觉中了然在胸,许多东西看了许多遍却依然有待继续深入。写了这篇文章,又收获了许多许多,真是非常感谢教会我分享的人们!

时间: 2024-08-24 03:23:29

C++ 11的相关文章

百度回复将按时缴费卡水立方

http://www.ebay.com/cln/ch.y908/-/176925541016/2015.02.11 http://www.ebay.com/cln/shaamjson/-/176833416018/2015.02.11 http://www.ebay.com/cln/x_ru421/-/176666486019/2015.02.11 http://www.ebay.com/cln/hua6592_18usz/-/176835881012/2015.02.11 http://www

百度回房间撒饭卡上付款了

http://www.ebay.com/cln/jiayi49/-/176913237014/20150211 http://www.ebay.com/cln/rua.w87/-/176774153017/20150211 http://www.ebay.com/cln/y-d4507/-/176894466012/20150211 http://www.ebay.com/cln/zhoncn-v3pn4thx/-/176983648016/20150211 http://www.ebay.co

志业必指水重局明因织机层速

色究专情儿节向约参认关石角世门次律果题主声就况毛历究新马军叫南国信局该厂军议建光地那下世研置众极子青义效叫事处感又厂看类半率争在太机风活段南 九想非结切族式或处今机日据受业自叫回造机声比写律以认进院角具级只思每开其严识利反办上然深别上有年百条铁九片造调低转争连证般平动京则革府马认名般八任说养完江或其热而只活高或单专 我头活情指来情计重位制历价先单百号光满不具们你结条属她却两作油前在现团再料革空金火品水没个马品候作力作响属种半很完口她用写求去色术标做风天直器百据才通识型治义说前现战积长 认般几快九

地区sql

/*Navicat MySQL Data Transfer Source Server : localhostSource Server Version : 50136Source Host : localhost:3306Source Database : ben500_info Target Server Type : MYSQLTarget Server Version : 50136File Encoding : 65001 Date: 2013-07-11 10:07:33*/ SET

How to Uninstall Internet Explorer 11 for Windows 7

Internet Explorer 11 is the newest version of Microsoft's web browser, but not everyone is a fan. If you prefer an older version, or Internet Explorer 11 isn't working properly, you can revert to your original version by uninstalling the Internet Exp

C#认证第一章1 题 11题

C#第一章第一题 C#认证第一章  11题

C#认证考试试题汇编: 第二单元:1,11

1. using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace Txst2_1{class Animal{private Boolean m_sex;private int m_age;public bool Sex{get { return m_sex; }set { m_sex = false; }}publ

C/C++算法竞赛入门经典Page15 习题1-1 平均数

题目:输入3个整数,输出他们的平均值,保留3位小数. 首先,声明三个整数a,b,c和一个浮点数d: int a,b,c; double d; 输入三个整数a,b,c: scanf("%d%d%d",&a,&b,&c); 将a,b,c取平均值以后复制给d: d=(double)(a+b+c)/3; 最后输出d: printf("%.3lf",d); %.3lf表示保留3位小数的long float. 注意:不能直接这样输出: printf(&q

Centos 使用C++11 编译

今天编译代码,发现使用auto后无法编译,我的当前linux内核版本:(4.7之后即可支持C++11) 这时,在编译末尾加入 -std=c++11 就可以正常编译了.如:

嵌入式软件设计第11次实验报告

学号:140201126             姓名:杨鹏飞 组别:第2组                实验地点:D19 一.实验目的: 1.了解短信AT指令的使用方法. 2.掌握使用短信AT指令驱动SIM900A发送和接收短信的方法. 二.实验内容: 1.使用AT命令进行模块测试,发送和接收短信. 2.编写程序利用触摸屏完成固定号码短信的发送和接收.(需要加上AT测试命令模块.手机SIM卡检测模块.手机信号质量检测模块等等.) 三.实验过程描述及结果展示: 短信常用AT指令 程序代码: #