zoukankan      html  css  js  c++  java
  • 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指针。

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

    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, "
    "), 
            [](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, "
    "), 
            [](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,很多东西在不知不觉中了然在胸,很多东西看了很多遍却依旧有待继续深入。写了这篇文章。又收获了很多很多,真是很感谢教会我分享的人们!

  • 相关阅读:
    解决点击链接自动置顶问题
    ie6 下遮罩层 height 不显示100%的解决方法
    【转帖】微软分布式缓存框架Volocity资源推荐
    Tips 2 MVC实现多个按钮提交的几种方法
    .NET Framework 4 中的新增功能
    Memcached 汇总 不断更新
    理解敏捷Agile
    GPIOPS中断成功,问题仍旧存在 ZEDBOARD,ZYNQ7000
    Xilinx驱动API的一个重要BUG,ZEDBOARD,ZYNQ7000
    RelativeLayout相对布局
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/6936506.html
Copyright © 2011-2022 走看看