c++11 新特性

《c++Primer》第五版 推荐序3

c++11标准对于语言核心部分做了相当大的改动。主要目的有若干个:

  1. 强化静态类型推导,比如c++11标准变更了auto关键字的语义,引入了decltype关键字等,这些措施利用了既存的变量和函数返回值的类型,一方面增加了程序设计的弹性,一方面避免了书写不必要的类型防止可能的错误,而引入了constexpr关键字则进一步地将常量性的范围从单个变量扩展到了单次运算,这将使得一大批既有的代码通过简单的修改而带来可观的编译期优化;
  2. 支持函数式程序设计的语法,比如引入了lambd表达式、引入了尾式函数申明语法、增加了for语句的冒号语法进行指定范围遍历等,这些使得从函数式语言切换过来的程序员能够很容易地习惯c++这门新语言,也给予了c++作为第一个门或唯一工作语言来学习的程序员以全新的方式书写原本易错的复杂申明和返回赋值的机会
  3. 将构造、析构和赋值过程中的可能错误加以防范,尤其是临时对象生命周期相关的错误,为此c++11标准引入了右值引用(&&饰词)、默认和禁用构造函数等
  4. 增加了对于面向对象范型中的一些一直未涵盖之内容的补充,如允许继承而来的构造函数、引入表达禁止继承的final关键字、引入override关键字来支持派生类函数重写等。

当然,c++11对于标准库、STL和泛型的扩充也绝对不可小觑,但这些变更主要是为了配合语言核心的变更。比如,为支持右值引用带来的对象所有权的流转,引入了move算法--这在数学意义上也是对于代数完备性的一个有力补充,更不用说由此带来的可观存储效率的提升了。

还有新引入的三种智能指针和四种无序关联容器、字符串和数值类型互相转换的工具函数,以及新引入的若干针对标准容器的小改进,如顺序容器的常量起始和终止迭代器以及可以直接插入值而不必再构造临时变量的emplace函数族,等等。

c++11 的新特性

2.1.1 long long 类型

        page:31
        c++ 11重新定义了long long 类型必须大于等于64bit。 这似乎看起来并不是一个什么新的特性,因为c++11之前,各个平台的编译器也自己实现了类型 long long,只是没有明确的标准来统一。所以这个特性对于我们写代码来说,不会有太多区别。


2.2.1 列表初始化:page39


        《c++程序设计语言》 6.3.5
        初始化器就是对象在初始化状态下被赋予的值。初始化器有4中可能的形式:

X a1 {v};
X a2 = {v};
X a3 = v;
X a4(v);

在这些形式中,只有第一种不受任何限制,在所有场景中都能使用。我强烈建议程序员使用这种方式为变量赋初值,它含义清晰,与其他形式相比不太容易出错。不过,第一种初值形式(a1)在c++11新标准中刚刚被踢出,因此在老代码中使用的都是后面三种形式。其中,使用=的两种形式是从c语言继承而来的。使用{}的初始化称为列表初始化(list initialization),它能防止窄化转换。
demo:

#include<iostream>
int main(){
    // 1.0 列表初始化,内置类型提供默认值,
    char *p {}; // 默认值是 nullptr
    long long a = {}; // 默认0
    bool bl ={}; // 默认false

    // 2.0 将会是未知数值。
    long long b;

    // 3.0 窄化转换
    double db {12.123};
    int db2 {db};// 编译器可以检测出错误,警告。发生截断(窄化转换)
    int db3 = db; // 编译器没有提示,发生窄化转换

    
    std::cout<<sizeof (long long)<<std::endl;
    std::cout<<"a "<<a<<std::endl;
    std::cout<<"b "<<b<<std::endl;
    std::cout<<std::boolalpha<<"bl "<<bl<<std::endl;
    std::cout<<" p ==nullptr "<<(p==nullptr)<<std::endl;
    std::cout<<"db2 "<<db2<<std::endl;
    std::cout<<"db3 "<<db3<<std::endl;
}


can@ubuntu:~/work/c++$ g++ -std=c++11 11.cpp -Wall
11.cpp: In function ‘int main()’:
11.cpp:12:14: warning: narrowing conversion of ‘db’ from ‘double’ to ‘int’ [-Wnarrowing]
   12 |     int db2 {db};// 编译器可以检测出错误,警告。发生截断(窄化转换)
      |              ^~
11.cpp:16:22: warning: ‘b’ is used uninitialized in this function [-Wuninitialized]
   16 |     std::cout<<"b "<<b<<std::endl;
      |                      ^
can@ubuntu:~/work/c++$ ./a.out
8
a 0
b 0
bl false
 p ==nullptrtrue
db2 12
db3 12

当我们使用auto 关键字从初始化器推断变量的类型时,没必要采用列表初始化的方式。而且如果初始化器是{}列表,则推断得到的数据类型肯定不是我们想要的结构。当使用auto的时候,应该选择=的初始化形式。

auto z1{99}; // z1的类型时 initizlizer_list<int>

auto z2 = 99; // z2的类型时 int
 

 当我们使用{}符号进行初始化时,不会进行窄化转换(实际操作在g++中是编译器给出警告),在使用auto的语句中,{}列表初始化的类型被推断为std::initializer_list<T>.

2.3.2 nullptr 常量

          NULL  是一个宏定义,预处理为 0 。 这是一个整数,所以在函数调用过程中如果作为参数的化,可能产生二义性,NULL 作为实参就可能被当做实参类型为int,而nullptr则不会,nullptr的类型就是一个指针。
        nullptr 为一个常量,代表空指针。  
        所以尽量使用nullptr来作为空指针。


2.4.4 constexpr变量,常量表达式
6.5.2 constexpr函数
7.5.6 constexpr构造函数     

         constexpr变量
        定义:常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。
        用法:c++11 新标准规定,允许将变量申明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。 (明确指出程序员的意图,让编译器来检测确保它一定是一个常量值)
        建议:一般来说,如果你认定变量是一个常量表达式,那就把它声明成 constexpr类型。
        constexpr函数
        定义:constexpr函数是指能用于常量表达式的函数。
                定义constexptr函数的方法与其他函数类似,不过要遵循几项约定:函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句:
        constexpr int new_sz() { reutrn 42;}
        constexpr int foo = new_sz(); // 正确:foo是一个常量表达式。
执行该任务时,编译器把对constexpr函数的调用替换成结果值。为了能在编译过程中随时展开,constexpr函数被隐式地制定为内敛函数。
        内敛函数和constexpr函数可以再程序中定义多次。毕竟,编译器想要展开函数仅有函数申明是不够的,还需要函数的定义。不过,对于某个给定的内敛函数或者constexptr函数来说,它的定义必须完全一致。基于这个原因,内敛函数和constexprhan函数通常定义在头文件中。
        constexpr函数不一定返回常量表达式:

 

#include<iostream>

constexpr size_t scale(size_t cnt){return 12*cnt;}

int main(){
    int arr[scale(2)];
    int i =2;
    // 需要编译阶段确定scale(i)的值,所以这里是错误的,但是gcc 并没有报错也没有警告?? 什么情况
    int a2[scale(i)];

//运行阶段确定 scale(i)的值。
    std::cout<<scale(i)<<std::endl;

    std::cout<<"arr: "<<sizeof(arr)/sizeof(arr[0])<<std::endl;
    std::cout<<"a2: "<<sizeof(a2)/sizeof(a2)<<std::endl;

    // 结论。 constpexpr 并不是一定在编译阶段确定返回值。
}

can@ubuntu:~/work/c++$ g++ -std=c++11 constexpr.cpp -Wall
can@ubuntu:~/work/c++$ ./a.out
24
arr: 24
a2: 1

can@ubuntu:~/work/c++$ g++ --version
g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

书《c++程序设计语言》2.2.3

  • cosnt :大致意思是“我承诺不改变这个值”。主要用于说明接口,这样在把变量传入函数时就不必担心变量会在函数内被改变了。编译器负责确认并执行const的承诺。
  • constexpr:大致意思是在编译时求值。主要用于说明常量,作用是允许将数据置于只读内存中(不太可能被破坏)以及提升性能。

        constexpr函数可以接受非常量实参,但此时结果将不会是一个常量表达式。当程序的上下文不需要常量表达式时,我们可以使用非常量表达式实参来调用constexpr函数,这样我们就不用把同一个函数定义两次了:其中一个用于常量表达式,一个用于变量。

个人总结: 对于constexpr 变量,编译器会检查确保这个值能在编译期间确定值。  但是如果定义了一个constexpr变量,想让这个变量是通过一个函数计算得到的,那么被使用的这个函数就必须是constexpr函数。  constexpr函数并不会要求它的计算结果是一定在编译阶段确定的,它可以复用,在用于计算constexpr变量时,编译器会检查计算结果确保是编译期间确定,在用于普通的运行时确定值时,它一样遵循普通的函数调用,在运行时确定返回值。所以constexpr函数的“constexpr特性”只是在被constexpr变量使用时才体现。 这也就是 constexpr函数是指能用于常量表达式的函数 这句话的意思。

constexpr 构造函数 
        7.5.6 尽管构造函数不能是const的,但是字面常量类的构造函数可以是constexptr函数。constexpr构造函数可以声明成=default的形式或者是删除的形式。否则,constexpr构造函数就必须既符合构造函数的要求(意味着不能包含返回语句),又符合constexpr函数的要求(意味着它能拥有的唯一可执行语句就是返回语句),综合这两点, (很矛盾,所以应该说constexptr构造函数是个特例),constexpr构造函数体一般来说应该是空的。constexpr构造函数必须初始化所有数据成员,初始值只能用constexpr构造函数或者常量表达式。

2.5.1 类型别名

        传统的方法使用typdef,新标准使用using

        using  MY_DATA = struct _data; // 

2.5.3 decltype 

        decltype(f()) sum = x; // sum的类型就是函数f的返回类型

        int i =0;

        decltype((i)) d = i; // decltype的表达式如果是加上了括号的变量,结果将是引用, d是int&,必须初始化

2.6 类内初始化

        c++ 11新标准规定,可以为数据成员提供一个类内初始值。创建对象时,类内初始值将用于初始化数据成员。
       class Dog{
        private: int age = 1;
        }

3.2.3 范围for语句

//输出 str中的每一个字符,   如果要修改其中的字符,则 应该 在for里面使用 auto &c,引用类型。
        string str("some string");
        for( auto  c : str){
             cout << c << endl;
        }

6.1.4 使用=default 生成默认构造函数

如果我们定义了带参数的构造函数,编译器就不会自动给类定义默认的无参构造函数,如果代码中又确实需要无参的默认构造函数,这个时候可以用 =default 让编译器生成默认构造函数,非常方便。


7.5.2 委托构造函数  

class Sales_data{
    public: 
       // 非委托构造汉纳树使用对应的实参初始化成员
        Sales_data( std::string s, unsigend cnt, double price):
            bookNo(s), units_sold(cnt), revence(cnt*price){
        }
        
        // 其余构造函数全部委托给另一个构造函数
    Sales_data(): Sales_data("", 0, 0){}; // 默认构造函数,被委托给了第一个三参数函数
    Sales_data(std::string s): Sales_data(s,0,0){};// 

    Sales_data(std:::istream &is) Sales_data(){ //该函数委托给了默认构造函数,而默认构造函数再委托给三参数函数
        read(is, *this);
    }
}

9.1 forward_list 单向联表,array 固定大小数组。

10.3.2 lambda 表达式

 可调用对象
        到目前为止,我们使用过的仅有的两种可调用对象时函数和函数指针。还有其他两种可调用对象:重载了函数调用运算符的类,以及lambda表达式。
lambda 表达式表示一个可调用的代码单元。lamba具有一个返回类型,一个参数列表和一个函数体,与函数不同的是,lambda可能定义再函数内部。
        auto compare = [=](const string &s){ return s.size() >= sz; };
find_if(words.begin(),words.end(),compare);

10.3.4 标准库bind函数

         函数适配器,特点就是可以再调用的时候,一次绑定参数关系,可以事先绑定固定的参数,也可以重排原来函数的参数顺序。
        auto newCallable = bind(callable, arg_list);
其中,newCallable本身是一个可调用对象,arg_list 是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。

12.1 智能指针

        shared_ptr<vector <int>> v = make_shared<vector<int>>();
        unique_ptr<vector <int>> v2 = make_unique<vector<int>>();

15.2.2 虚函数override指示符

class Animal{
public : 
    Animal() = default;
    virtual ~Animal() = default;
    virtual void eat() = 0;
};

class Dog : public Animal{
public:
    void eat() override{
        std::cout<<"dog eat"<<std::endl;
    }
};

.........其他。。。。。。。。。。。。。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值