美文网首页程序员C/C++知识点
C++11 中值得关注的几大变化(一)

C++11 中值得关注的几大变化(一)

作者: Python编程导师 | 来源:发表于2018-12-21 20:19 被阅读2次

    首先呢,小编觉得c++17虽然已经出来了,c++20也即将面世,但是并不代表着C++11 的没落,我们现在所学到的C++知识大多还是C++11 的,并不是书本不更新,而是其仍然起着重要的作用!接下来我们就来看看C++11中的一些值得关注的几大变化吧!

    Lambda 表达式

    Lambda表达式来源于函数式编程,说白就了就是在使用的地方定义函数,有的语言叫“闭包”,如果 lambda 函数没有传回值(例如 void ),其回返类型可被完全忽略。 定义在与 lambda 函数相同作用域的变量参考也可以被使用。这种的变量集合一般被称作 closure(闭包)。我在这里就不再讲这个事了。表达式的简单语法如下,

    [capture](parameters)->return_type {body}

    原文的作者给出了下面的例子:

    intmain()

    {

    chars[]="Hello World!";

    intUppercase = 0;//modified by the lambda

    for_each(s, s+sizeof(s), [&Uppercase] (charc) {

    if(isupper(c))

         Uppercase++;

        });

    cout<< Uppercase << " uppercase letters in: " << s <

    }

    在传统的STL中for_each() 这个玩意最后那个参数需要一个“函数对象”,所谓函数对象,其实是一个class,这个class重载了operator(),于是这个对象可以像函数的式样的使用。实现一个函数对象并不容易,需要使用template,比如下面这个例子就是函数对象的简单例子(实际的实现远比这个复杂):

    template<classT>

    classless

    {

    public:

    booloperator()(const T&l, const T&r)const

        {

    returnl < r;

        }

    };

    C++引入Lambda的最主要原因就是:1)可以定义匿名函数,2)编译器会把其转成函数对象

    那么,除了方便外,为什么一定要使用Lambda呢?它比传统的函数或是函数对象有什么好处呢?我个人所理解的是,这种函数之年以叫“闭包”,就是因为其限制了别人的访问,更私有。也可以认为他是一次性的方法。Lambda表达式应该是简洁的,极私有的,为了更易的代码和更方便的编程。

    自动类型推导 auto

    在这一节中,原文主要介绍了两个关键字 auto 和 deltype,示例如下:

    1.

    auto x=0;//x has type int because 0 is int

    auto c='a';//char

    auto d=0.5;//double

    auto national_debt=14400000000000LL;//long long

    2.

    vector<int>::const_iterator ci = vi.begin();

    可以变成:

    auto ci = vi.begin();

    模板这个特性让C++的代码变得很难读,不信你可以看看STL的源码,那是一个乱啊。使用auto必需一个初始化值,编译器可以通过这个初始化值推导出类型。因为auto是来简化模板类引入的代码难读的问题,如上面的示例,iteration这种类型就最适合用auto的,但是,我们不应该把其滥用。

    比如下面的代码的可读性就降低了。因为,我不知道ProcessData返回什么?int? bool? 还是对象?或是别的什么?这让你后面的程序不知道怎么做。

    auto obj = ProcessData(someVariables);

    但是下面的程序就没有问题,因为pObject的型别在后面的new中有了。

    auto pObject =newSomeType::SomeOtherType();

    自动化推导 decltype

    关于 decltype 是一个操作符,其可以评估括号内表达式的类型,其规则如下:

    如果表达式e是一个变量,那么就是这个变量的类型。

    如果表达式e是一个函数,那么就是这个函数返回值的类型。

    如果不符合1和2,如果e是左值,类型为T,那么decltype(e)是T&;如果是右值,则是T。

    原文给出的示例如下,我们可以看到,这个让的确我们的定义变量省了很多事。

    const vector<int> vi;

    typedefdecltype(vi.begin()) CIT;

    CIT another_const_iterator;

    还有一个适合的用法是用来typedef函数指针,也会省很多事。比如:

    decltype(&myfunc) pfunc = 0;

    typedefdecltype(&A::func1) type;

    auto 和 decltype 的差别和关系

    Wikipedia 上是这么说的(关于decltype的规则见上)

    #include <vector>

    intmain()

    {

    const std::vector<int> v(1);

    auto a = v[0];// a 的类型是 int

    decltype(v[0]) b = 1;// b 的类型是 const int&, 因为函数的返回类型是

    // std::vector<int>::operator[](size_type) const

    auto c = 0;// c 的类型是 int

    auto d = c;// d 的类型是 int

    decltype(c) e;// e 的类型是 int, 因为 c 的类型是int

    decltype((c)) f = c;// f 的类型是 int&, 因为 (c) 是左值

    decltype(0) g;// g 的类型是 int, 因为 0 是右值

    }

    如果auto 和 decltype 在一起使用会是什么样子?能看下面的示例,下面这个示例也是引入decltype的一个原因——让C++有能力写一个模板”

    template<typenameLHS,typenameRHS>

    auto AddingFunc(const LHS &lhs, const RHS &rhs) ->decltype(lhs+rhs)

    {returnlhs + rhs;}

    这个函数模板看起来相当费解,其用到了auto 和 decltype 来扩展了已有的模板技术的不足。怎么个不足呢?在上例中,我不知道AddingFunc会接收什么样类型的对象,这两个对象的 + 操作符返回的类型也不知道,老的模板函数无法定义AddingFunc返回值和这两个对象相加后的返回值匹配,所以,你可以使用上述的这种定义。

    统一的初始化语法

    C/C++的初始化的方法比较,C++ 11 用大括号统一了这些初始化的方法。

    比如:POD的类型。

    intarr[4]={0,1,2,3};

    structtm today={0};

    关于POD相说两句,所谓POD就是Plain Old Data,当class/struct是极简的(trivial)、属于标准布局(standard-layout),以及他的所有非静态(non-static)成员都是POD时,会被视为POD。如:

    structA {intm; };// POD

    structB { ~B();intm; };// non-POD, compiler generated default ctor

    structC { C() : m() {}; ~C();intm; };// non-POD, default-initialising m

    POD的初始化有点怪,比如上例,new A; 和new A(); 是不一样的,对于其内部的m,前者没有被初始化,后者被初始化了(不同 的编译器行为不一样,VC++和GCC不一样)。而非POD的初始化,则都会被初始化。&lt;/span&gt;

    从这点可以看出,C/C++的初始化问题很奇怪,所以,在C++ 2011版中就做了统一。原文作者给出了如下的示例:

    C c {0,0};//C++11 only. 相当于: C c(0,0);

    int* a =newint[3] { 1, 2, 0 }; /C++11 only

    classX {

    inta[4];

        public:

    X() : a{1,2,3,4} {}//C++11, member array initializer

    };

    容器的初始化:

    // C++11 container initializer

    vector<string> vs={ "first", "second", "third"};

    map singers =

    { {"Lady Gaga", "+1 (212) 555-7890"},

    {"Beyonce Knowles", "+1 (212) 555-0987"}};

    还支持像Java一样的成员初始化:&lt;/span&gt;

    classC

    {

    inta=7;//C++11 only

    public:

       C();

    };

    Delete 和 Default 函数

    我们知道C++的编译器在你没有定义某些成员函数的时候会给你的类自动生成这些函数,比如,构造函数,拷贝构造,析构函数,赋值函数。有些时候,我们不想要这些函数,比如,构造函数,因为我们想做实现单例模式。传统的做法是将其声明成private类型。

    在新的C++中引入了两个指示符,delete意为告诉编译器不自动产生这个函数,default告诉编译器产生一个默认的。原文给出了下面两个例子:

    structA

    {

    A()=default;//C++11

    virtual ~A()=default;//C++11

    };

    再如delete

    structNoCopy

    {

    NoCopy &operator=( const NoCopy & ) = delete;

        NoCopy ( const NoCopy & ) = delete;

    };

    NoCopy a;

    NoCopy b(a);//compilation error, copy ctor is deleted

    这里,我想说一下,为什么我们需要default?我什么都不写不就是default吗?不全然是,比如构造函数,因为只要你定义了一个构造函数,编译器就不会给你生成一个默认的了。所以,为了要让默认的和自定义的共存,才引入这个参数,如下例所示:

    structSomeType

    {

    SomeType() =default;// 使用编译器生成的默认构造函数

    SomeType(OtherTypevalue);

    };

    关于delete还有两个有用的地方是

    1)让你的对象只能生成在栈内存上:

    structNonNewable {

    void*operatornew(std::size_t) = delete;

    };

    2)阻止函数的其形参的类型调用:(若尝试以 double 的形参调用 f(),将会引发编译期错误, 编译器不会自动将 double 形参转型为 int 再调用f(),如果传入的参数是double,则会出现编译错误)

    voidf(inti);

    voidf(double) = delete;

    nullptr

    C/C++的NULL宏是个被有很多潜在BUG的宏。因为有的库把其定义成整数0,有的定义成 (void*)0。在C的时代还好。但是在C++的时代,这就会引发很多问题。你可以上网看看。这是为什么需要 nullptr 的原因。 nullptr 是强类型的。

    voidf(int);//#1

    voidf(char*);//#2

    //C++03

    f(0);//二义性

    //C++11

    f(nullptr)//无二义性,调用f(char*)

    所以在新版中请以 nullptr 初始化指针。

    注:喜欢小编文章可以收藏!有兴趣学习C/C++的小伙伴可以进群:941636044

    相关文章

      网友评论

        本文标题:C++11 中值得关注的几大变化(一)

        本文链接:https://www.haomeiwen.com/subject/ubwvkqtx.html