C++ 11(一)

    科技2022-07-17  170

    注意:C++ 2.0新特性中关键字的源码都还没看

    C++2.0新特性

    语言+标准库(以头文件的形式呈现)

    了解编译器对 C++ 2.0的支持程度 还有几个重要网站,见STL()的前面

    全文检索工具——grep

    Dev C++ | 如何设置C++11标准

    在DEV C++中选择tool 选Compiler Options

    上图选settings

    Variadic Templates(可变模板参数)

    在C++11之前,类模板和函数模板只能含有【固定数量的模板参数】。 C++11增强了模板功能,允许模板定义中包含【0到任意个模板参数】,这就是【可变参数模板】。 可变参数模板和普通模板的语义是一样的,只是写法上稍有区别, 【声明可变参数模板】时需要在typenameclass后面带上省略号“…”: template<class ... T> void func(T ... args)//T叫模板参数包,args叫函数参数包 {//可变参数模板函数 } func(); // OK:args不含有任何实参 func(1); // OK:args含有一个实参:int func(2, 1.0); // OK:args含有两个实参int和double T叫模板参数包,args叫函数参数包。 省略号“…”的作用有两个: 1、声明一个参数包,这个参数包中可以包含0到任意个模板参数 2、在模板定义的右边,可以将参数包展开成一个一个独立的参数

    可变参数模板函数:

    可变模板参数定义如下:

    参数包的展开:

    1、递归方式展开 通过递归函数展开参数包,需要提供【一个参数包展开的函数】和【一个递归终止函数】。 #include <iostream> using namespace std; //递归终止函数 void debug() { cout << "empty\n"; } //展开函数 template <class T, class ... Args> void debug(T first, Args ... last) { cout << "parameter " << first << endl; debug(last...); } int main() { debug(1, 2, 3, 4); return 0; }

    递归调用过程如下:

    debug(1, 2, 3, 4); debug(2, 3, 4); debug(3, 4); debug(4); debug();

    通过可变参数模板实现打印函数:(奇怪????)

    #include <iostream> #include <stdexcept> using namespace std; void Debug(const char* s) { while (*s) { if (*s == '%' && *++s != '%') { throw runtime_error("invalid format string: missing arguments"); } cout << *s++; } } template<typename T, typename... Args> void Debug(const char* s, T value, Args... args) { while (*s) { if (*s == '%' && *++s != '%') { cout << value; return Debug(++s, args...); } cout << *s++; } throw runtime_error("extra arguments provided to Debug"); } int main() { Debug("a = %d, b = %c, c = %s\n", 250, 'm', "mike"); return 0; } 2、非递归方式展开 #include <iostream> using namespace std; template <class T> void print(T arg) { cout << arg << endl; } template <class ... Args> void expand(Args ... args) { int a[] = { (print(args), 0)... }; } int main() { expand(1, 2, 3, 4); return 0; }

    expand函数的逗号表达式:(print(args), 0), 也是按照这个执行顺序,先执行print(args),再得到逗号表达式的结果0。 同时,通过初始化列表来初始化一个变长数组,{ (print(args), 0)}将会展开 成( (print(args1), 0), (print(args2), 0), (print(args3), 0), etc…), 最终会创建一个元素值都为0的数组int a[sizeof(args)]

    可变参数模板:

    继承方式展开参数包:(后面这里牵扯到模板的偏特化????)

    可变参数模板类的展开一般需要定义2 ~ 3个类,包含类声明和特化的模板类: #include <iostream> #include <typeinfo> using namespace std; template<typename... A> class BMW{}; // 变长模板的声明 template<typename Head, typename... Tail> // 递归的偏特化定义 class BMW<Head, Tail...> : public BMW<Tail...> {//当实例化对象时,则会引起基类的递归构造 public: BMW() { printf("type: %s\n", typeid(Head).name()); } Head head; }; template<> class BMW<>{}; // 边界条件 int main() { BMW<int, char, float> car; return 0; }

    模板递归和特化方式展开参数包:

    #include <iostream> using namespace std; template <long... nums> struct Multiply;// 变长模板的声明 template <long first, long... last> struct Multiply<first, last...> // 变长模板类 { static const long val = first * Multiply<last...>::val; }; template<> struct Multiply<> // 边界条件 { static const long val = 1; }; int main() { cout << Multiply<2, 3, 4, 5>::val << endl; // 120 return 0; }

    这个print()接受一个参数和一包参数(……表示的就是不知道多少个参数)【这里不仅个数随意,参数类型也随意】 template<typename T,typename… Types>【表示的1+其余】 当没有参数的时候会调用①的print()函数 可以帮助我们做递归,这递归的目的就是为了将参数个数一一分解 怎样分解,就是把n变成1+其他 sizeof(arg)可以获取参数的个数

    下面这两块没看懂????

    Space in Template Expressions

    nullptr√

    nullptr是为了【解决原来C++NULL的二义性问题】而引进的一种新的类型,因为NULL实际上代表的是0void func(int a) { cout << __LINE__ << " a = " << a <<endl; } void func(int *p) { cout << __LINE__ << " p = " << p <<endl; } int main() { int *p1 = nullptr; int *p2 = NULL; if(p1 == p2) { cout << "equal\n"; } //int a = nullptr; //err, 编译失败,nullptr不能转型为int func(0); //调用func(int), 就算写NULL,也是调用这个 func(nullptr); return 0; }

    原生字符串字面值√

    原生字符串字面值(raw string literal)使用户书写的字符串“所见即所得”。 C++11中原生字符串的声明相当简单,【只需在字符串前加入前缀,即字母R,并在引号中使用括号左右标识,】 就可以声明该字符串字面量为原生字符串了。 #include <iostream> #include <string> using namespace std; int main(void) { cout << R"(hello, \n world)" << endl; string str = R"(helo \4 \r abc, mike hello\n)"; cout << endl; cout << str << endl; return 0; }

    auto√

    auto的自动类型推导,用于从初始化表达式中推断出变量的数据类型。 从这个意义上讲,auto并非一种“类型”声明,而是一个类型声明时的“占位符”, 编译器在【编译时期】会将auto替换为变量实际的类型。 不要依赖auto,要会类型推到 auto的使用是在类型很长或者很复杂的时候

    重点看下面的注释部分

    #include <iostream> #include <vector> #include <string> using namespace std; double foo() {} void func(vector<string> & tmp) { for (auto i = tmp.begin(); i < tmp.end(); i++) { // 一些代码 } } int main() { auto x = 1; // x的类型为int auto y = foo(); // y的类型为double struct m { int i; }str; auto str1 = str; // str1的类型是struct m auto z; // err, 无法推导,无法通过编译 z = x; return 0; }

    注意点(这几种无法编译的情况为啥呢????)

    1: auto函数参数,有些编译器无法通过编译 2: auto非静态成员变量,无法通过编译 3: auto数组,无法通过编译 4: auto模板参数(实例化时),无法通过编译 void fun(auto x =1) {} // 1: auto函数参数,有些编译器无法通过编译 struct str { auto var = 10; // 2: auto非静态成员变量,无法通过编译 }; int main() { char x[3]; auto y = x; auto z[3] = x; // 3: auto数组,无法通过编译 // 4: auto模板参数(实例化时),无法通过编译 vector<auto> x = {1}; return 0; }

    标准库本身也有使用新的关键字

    Uniform Initialization(统一初始化)√

    初始化 类内成员初始化

    class Mem { public: Mem(int i): m(i){} //初始化列表给m初始化 int m; }; class Group { public: Group(){} private: int data = 1; // 使用"="初始化非静态普通成员,也可以 int data{1}; Mem mem{2}; // 对象成员,创建对象时,可以使用{}来调用构造函数 string name{"mike"}; };

    初始化列表

    初始化方式如下 int a[]{1, 3, 5}; int i = {1}; int j{3}; 下面是用于结构体类型的情况 struct Person { std::string name; int age; }; int main() { Person p = {"Frank", 25}; std::cout << p.name << " : " << p.age << std::endl; } 其他一些不方便初始化的地方使用, 比如std的初始化,如果不使用这种方式,只能用构造函数来初始化,难以达到效果: std::vector<int> ivec1(3, 5); std::vector<int> ivec2 = {5, 5, 5}; std::vector<int> ivec3 = {1,2,3,4,5}; //不使用列表初始化用构造函数难以实现

    防止类型收窄

    【类型收窄】指的是导致**数据内容发生变化**或者**精度丢失**的隐式类型转换。 使用列表初始化可以防止类型收窄。

    关于类型收窄

    为什么要防止类型收窄

    struct Foo { Foo(int i) { std::cout << i << std::endl; } }; Foo foo(1.2); 【以上代码在C++中能够正常通过编译】,但是传递之后的i却不能完整地保存一个浮点型的数据。 上面的示例让我们对类型收窄有了一个大概的了解。具体来说,类型收窄包括以下几种情况: 1)从一个浮点数隐式转换为一个整型数,如int i=2.22)从高精度浮点数隐式转换为低精度浮点数,如从long double隐式转换为doublefloat3)从一个整型数隐式转换为一个浮点数,并且超出了浮点数的表示范围,如float x=(unsigned long long)-14)从一个整型数隐式转换为一个长度较短的整型数,并且超出了长度较短的整型数的表示范围,如char x=65536。 在C++98/03中,像上面所示类型收窄的情况,编译器并不会报错(或报一个警告,如Microsoft Visual C++)。 这往往会导致一些隐藏的错误。【在C++11中,可以通过列表初始化来检查及防止类型收窄】 列表初始化防止类型收窄示例

    请看上图的报错

    int a = 1.1; // OK int b = { 1.1 }; // error float fa = 1e40; // OK float fb = { 1e40 }; // error float fc = (unsigned long long)-1; // OK float fd = { (unsigned long long)-1 }; // error float fe = (unsigned long long)1; // OK float ff = { (unsigned long long)1 }; // OK const int x = 1024, y = 1; char c = x; // OK char d = { x }; // error char e = y; // OK char f = { y }; // OK 上面的各种隐式类型转换中,只要遇到了类型收窄的情况,初始化列表就不会允许这种转换发生。 其中需要注意的是x、y被定义成了const int。如果去掉const限定符,那么***一个变量f也会因为类型收窄而报错

    Initializer Lists(不是很懂)

    不接受窄的转化 Initializer Lists可以实现参数个数不定(但是类型有说法)

    由源码可知,Initializer Lists的背后就是array

    clion编译器中的源码和老师的一样

    Initializer Lists的使用就可以使得max和min接受任意多的参数【这就是Initializer Lists的妙用】

    explicit

    explicit这个关键字主要是用在构造函数身上 下面是C++ 2.0之前的用法(单一实参)

    下面是C++ 2.0【之后】的用法

    for循环的一种规则√

    基于范围的for循环

    int a[] = { 1, 2, 3, 4, 5 }; int n = sizeof(a) / sizeof(*a); //元素个数 for (int i = 0; i < n; ++i) { int tmp = a[i]; cout << tmp << ", "; } cout << endl; for (int tmp : a) { cout << tmp << ", "; } cout << endl; for (int i = 0; i < n; ++i) { int &tmp = a[i]; tmp = 2 * tmp; cout << tmp << ", "; } cout << endl; for (int &tmp : a) { tmp = 2 * tmp; cout << tmp << ", "; } cout << endl;

    使用基于范围的for循环,其【for循环迭代的范围必须是可确定的】:

    int func(int a[])//形参中数组是指针变量,无法确定元素个数 { for(auto e: a) // err, 编译失败 { cout << e; } } int main() { int a[] = {1, 2, 3, 4, 5}; func(a); return 0; }

    静态断言

    C/C++提供了调试工具assert,这是一个宏,用于在【运行阶段】对断言进行检查, 如果条件为真,执行程序,否则调用abort()int main() { bool flag = false; //如果条件为真,程序正常执行,如果为假,终止程序,提示错误 assert(flag == true); //#include <cassert>或#include <assert.h> cout << "Hello World!" << endl; return 0; }

    【C++ 11新增了关键字static_assert】,可用于在【编译阶段】对断言进行测试。

    语法: static_assert(常量表达式,提示字符串) 【注意】:只能是【常量表达式】,不能是变量 示例: int main() { //该static_assert用来确保编译仅在32位的平台上进行,不支持64位的平台 static_assert( sizeof(void *)== 4, "64-bit code generation is not supported."); cout << "Hello World!" << endl; return 0; } 静态断言的好处: 更早的报告错误,我们知道构建是早于运行的,更早的错误报告意味着【开发成本的降低】 减少运行时开销,静态断言是【编译期】检测的,【减少了运行时开销】

    =default,=delete√

    优秀博客

    如果程序员【没有显式地为一个类定义】某个特殊成员函数,【而又需要用到】该特殊成员函数时, 则编译器会隐式的为这个类生成一个默认的特殊成员函数。 default针对的是【默认构造函数】、【析构函数】、【拷贝构造函数】和【拷贝赋值函数】 defaulted 函数特性仅适用于【类的特殊成员函数】(上面四种),且该【特殊成员函数没有默认参数】。

    好处: 1. 减轻程序员的编程工作量; 2. 获得编译器自动生成的默认特殊成员函数的高的代码执行效率。

    default详解

    delete 为了能够让程序员【显式的禁用某个函数】, C++11 标准引入了一个新特性:deleted 函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。 用法: 1、用于禁用类的某些转换构造函数,从而避免不期望的类型转换。 2、用来禁用某些用户自定义的类的 new 操作符,从而避免在自由存储区创建类的对象 3、必须在函数第一次声明的时候将其声明为 deleted 函数,否则编译器会报错。 (即对于类的成员函数而言,deleted 函数必须在类体里(inline)定义,而不能在类体外(out-of-line)定义。) 4、虽然 defaulted 函数特性规定了只有类的特殊成员函数才能被声明为 defaulted 函数, 【但是】 deleted 函数特性并没有此限制。非类的成员函数,即普通函数也可以被声明为 deleted 函数。 5、虽然delete让函数不能被调用。但是【函数标示符】仍是有效的,在名字查找和函数重载解析时仍会查找到该函数标示符。 如果编译器在解析重载函数时,解析结果为 deleted 函数,则会出现编译错误。 构造函数可以重载,但是拷贝构造函数不能

    ????

    Alias Template

    模板模板参数

    Type Alias

    using[用于模板的别名]√

    见下面的示例:

    #include <iostream> #include <type_traits> //std::is_same using namespace std; using uint = unsigned int; typedef unsigned int UINT; using sint = int; int main() { //std::is_same 判断类型是否一致 //这个结构体作用很简单,就是两个一样的类型会返回true cout << is_same<int, sint>::value << endl; // 1 cout << is_same<uint, UINT>::value << endl; // 1 return 0; }

    noexcept√

    void func3() throw(int, char) //只能够抛出 int 和char类型的异常 {//C++11已经弃用这个声明 throw 0; } void BlockThrow() throw() //代表此函数不能抛出异常,如果抛出,就会异常 { throw 1; } //C++11 使用noexcept替代throw() void BlockThrowPro() noexcept //代表此函数不能抛出异常,如果抛出,就会异常 { throw 2; }

    参考这篇博客

    下面是std::initializer_list的默认构造函数,其中使用了noexceptconstexpr initializer_list() noexcept : _M_array(0), _M_len(0) { } 该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。 如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常), 程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。

    C++的异常处理

    C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。 在实践中,一般两种异常抛出方式是常用的: 一个操作或者函数可能会抛出一个异常; 一个操作或者函数不可能抛出任何异常。 后面这一种方式中在以往的C++版本中常用throw()表示,在C++ 11中已经被noexcept代替。 void swap(Type& x, Type& y) throw() //C++11之前 { x.swap(y); } //下面单独使用noexcept,表示其所限定的swap函数绝对不发生异常 void swap(Type& x, Type& y) noexcept //C++11 { x.swap(y); }

    有条件的noexcept

    void swap(Type& x, Type& y) noexcept(noexcept(x.swap(y))) //C++11 { x.swap(y); } 它表示,如果操作x.swap(y)不发生异常,那么函数swap(Type& x, Type& y)一定不发生异常。

    什么时候该使用noexcept

    使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。 然而,【并不是加上noexcept就能提高效率】。 以下情形鼓励使用noexcept1、移动构造函数(move constructor) 2、移动分配函数(move assignment) 3、析构函数(destructor)。 在新版本的编译器中,析构函数是默认加上关键字noexcept的。

    强类型枚举√

    C++ 枚举类型详解

    C++ 11引入了一种新的枚举类型,即“枚举类”,又称“强类型枚举”。 声明请类型枚举非常简单,只需要【在enum后加上使用classstruct】(声明实现)。如: enum Old{Yes, No}; // old style enum class New{Yes, No}; // new style enum struct New2{Yes, No}; // new style “传统”的C++枚举类型有一些【缺点】: 它会在一个代码区间中抛出枚举类型成员(如果在相同的代码域中的两个枚举类型具有相同名字的枚举成员,这会导致【命名冲突】), 【它们会被隐式转换为整型,并且不可以指定枚举的底层数据类型】。 int main() { enum Status{Ok, Error}; //enum Status2{Ok, Error};//err, 导致命名冲突, Status已经有成员叫Ok, Error return 0; }

    上诉的缺点在C++ 11中都获得了解决√

    int main() { enum class Status {Ok, Error}; enum struct Status2{Ok, Error}; //Status flag1 = 10; // err,无法隐式转换为int类型 //Status flag2 = Ok; // err,必须使用强类型名称 Status flag3 = Status::Ok; enum class C : char { C1 = 1, C2 = 2};//指定枚举的底层数据类型 enum class D : unsigned int { D1 = 1, D2 = 2, Dbig = 0xFFFFFFF0U }; cout << sizeof(C::C1) << endl; // 1 cout << (unsigned int)D::Dbig << endl; // 编译器输出一致,4294967280 cout << sizeof(D::D1) << endl; // 4 cout << sizeof(D::Dbig) << endl; // 4 return 0; }

    常量表达式√

    参考博客

    类的改造√

    1、继承构造

    C++ 11【允许派生类继承基类的构造函数】,【默认构造函数、复制构造函数、移动构造函数除外】。 #include <iostream> using namespace std; //基类 class A { public: A(int x, int y) { a = x; b = y; } protected: int a; int b; }; //派生类 class B:public A { public: //继承构造 using A::A; void display() { cout << "a = " << a << ", b = " << b << endl; } //没有增加新的成员变量 int tmp; }; int main() { //派生类对象 B obj(10, 20); obj.display(); return 0; }

    上诉代码若是去掉 using A::A; 这一行,看报错信息

    注意: 1、继承的构造函数【只能初始化基类中的成员变量】,不能初始化派生类的成员变量 2、如果1、基类的构造函数被声明为私有,或者2、派生类是从基类中虚继承,那么不能继承构造函数 3、一旦使用继承构造函数,编译器不会再为派生类生成默认构造函数

    2、委托构造

    和继承构造函数类似,委托构造函数也是C++11中对C++的构造函数的一项改进,其【目的也是为了减少程序员书写构造函数的时间】。 如果一个类包含多个构造函数,C++ 11允许在一个构造函数中的定义中使用另一个构造函数,但这必须通过初始化列表进行操作,如下: #include <iostream> using namespace std; class Test { public: //委托构造,一定要通过初始化列表方式 Test():Test(1, 'a') { } Test(int x): Test(x, 'b') { } Test(char x): Test(11, x) { } int a; char b; private: Test(int x, char y): a(x), b(y) { } }; int main() { //Test obj; // Test():Test(1, 'a') Test obj1(); cout << obj1 << endl; Test obj2('z'); cout << obj2.a << endl; cout << obj2.b << endl; Test obj3(10); cout << obj3.a << endl; cout << obj3.b << endl; return 0; }

    3、继承控制:final和override

    C++11之前,一直没有继承控制关键字,禁用一个类的进一步衍生比较麻烦。 C++ 11添加了两个继承控制关键字:final和override。

    用户定义字面量√

    参考

    模板的改进√

    1、右尖括号>改进

    在C++98/03的泛型编程中,模板实例化有一个很繁琐的地方,就是连续两个右尖括号(>>)会被编译解释成右移操作符, 【而不是模板参数表的形式】,需要一个空格进行分割,以避免发生编译时的错误。 template <int i> class X{}; template <class T> class Y{}; int main() { Y<X<1> > x1; // ok, 编译成功 Y<X<2>> x2; // err, 编译失败 return 0; }; 在实例化模板时会出现连续两个右尖括号,同样static_castdynamic_castreinterpret_castconst_cast表达式转换时 也会遇到相同的情况。C++98标准是让程序员【在>>之间填上一个空格】, 在C++11标准中,要求编译器对模板的右尖括号做单独处理,使编译器能够正确判断出”>>”是一个右移操作符还是模板参数表的结束标记。

    2、函数模板的默认模板参数

    参考博客

    C++11之前,类模板是支持默认的模板参数,却不支持函数模板的默认模板参数:

    //1、普通函数带默认参数,c++98 编译通过,c++11 编译通过 void DefParm(int m = 3) {} //2、类模板是支持默认的模板参数,c++98 编译通过,c++11 编译通过 template <typename T = int> class DefClass {}; //3、函数模板的默认模板参数, c++98 - 编译失败,c++11 - 编译通过 template <typename T = int> void DefTempParm() {}

    【类模板的默认模板参数必须【从右往左】定义】,函数模板的默认模板参数则没这个限定:

    template<class T1, class T2 = int> class DefClass1; template<class T1 = int, class T2> class DefClass2; // 无法通过编译 template<class T, int i = 0> class DefClass3; template<int i = 0, class T> class DefClass4; // 无法通过编译 template<class T1 = int, class T2> void DefFunc1(T1 a, T2 b); template<int i = 0, class T> void DefFunc2(T a);

    override√

    override确保在派生类中声明的函数跟基类的虚函数有相同的签名:

    class A1 { public: //这是第一个虚函数,没有重写,不能用override修饰 virtual int func(int a) { } }; class A2:public A1 { public: //在重写虚函数地方,加上override, 要求重写的虚函数和基类一模一样 virtual int func(int b) override { } }; 上诉代码如果将派生类中的函数改为 virtual int func(float b) override

    final√

    final阻止类的进一步派生和虚函数的进一步重写:

    //final【阻止类】的进一步【派生】,【虚函数】的进一步【重写】 #if 0 class A1 final //加上final,指定A1不能派生 { int a; }; class A2: public A1 //err, 基类不能再派生了 { }; #endif //基类 class B1 { public: virtual void func() final {} //这是最终版本的虚函数,不能再重写 }; //派生类重写基类的虚函数 class B2: public B1 { public: //virtual void func() {} //err, 基类中的虚函数是最终版本,不能再重写 };

    decltype√

    decltype实际上有点像auto的反函数, auto可以让你声明一个变量, 而decltype则可以从一个变量或表达式中【得到其类型】,如下: #include <typeinfo> #include <iostream> #include <vector> using namespace std; int main() { int i; decltype(i) j = 0; cout << typeid(j).name() << endl; // 打印出"i", g++表示integer float a; double b; decltype(a + b) c; cout << typeid(c).name() << endl; // 打印出"d", g++表示double vector<int> vec; typedef decltype(vec.begin()) vectype; // decltype(vec.begin()) 改名为 vectype vectype k; // 这是auto无法做到的 //decltype(vec.begin()) k; // 这是auto无法做到的 for (k = vec.begin(); k < vec.end(); k++) { // 做一些事情 } enum {Ok, Error, Warning}flag; // 匿名的枚举变量 decltype(flag) tmp = Ok; return 0; }

    追踪返回类型

    返回类型后置:在函数名和参数列表后面指定返回类型。

    基本用法

    int getSize();int main(void) { int tempA = 2; /*1.dclTempA为int.*/ decltype(tempA) dclTempA; /*2.dclTempB为int,对于getSize根本没有定义,但是程序依旧正常,因为decltype只做分析,并不调用getSize().*/ decltype(getSize()) dclTempB;return 0; }

    与const结合

    double tempA = 3.0; const double ctempA = 5.0; const double ctempB = 6.0const double *const cptrTempA = &ctempA; /*1.dclTempA推断为const double(保留顶层const,此处与auto不同)*/ decltype(ctempA) dclTempA = 4.1; /*2.dclTempA为const double,不能对其赋值,编译不过*/ dclTempA = 5; /*3.dclTempB推断为const double * const*/ decltype(cptrTempA) dclTempB = &ctempA; /*4.输出为4(32位计算机)和5*/ cout<<sizeof(dclTempB)<<" "<<*dclTempB<<endl; /*5.保留顶层const,不能修改指针指向的对象,编译不过*/ dclTempB = &ctempB; /*6.保留底层const,不能修改指针指向的对象的值,编译不过*/ *dclTempB = 7.0;

    与引用结合

    int tempA = 0, &refTempA = tempA;/*1.dclTempA为引用,绑定到tempA*/ decltype(refTempA) dclTempA = tempA; /*2.dclTempB为引用,必须绑定到变量,编译不过*/ decltype(refTempA) dclTempB = 0; /*3.dclTempC为引用,必须初始化,编译不过*/ decltype(refTempA) dclTempC; /*4.双层括号表示引用,dclTempD为引用,绑定到tempA*/ decltype((tempA)) dclTempD = tempA; const int ctempA = 1, &crefTempA = ctempA; /*5.dclTempE为常量引用,可以绑定到普通变量tempA*/ decltype(crefTempA) dclTempE = tempA; /*6.dclTempF为常量引用,可以绑定到常量ctempA*/ decltype(crefTempA) dclTempF = ctempA; /*7.dclTempG为常量引用,绑定到一个临时变量*/ decltype(crefTempA) dclTempG = 0; /*8.dclTempH为常量引用,必须初始化,编译不过*/ decltype(crefTempA) dclTempH; /*9.双层括号表示引用,dclTempI为常量引用,可以绑定到普通变量tempA*/ decltype((ctempA)) dclTempI = ctempA;

    与指针结合

    int tempA = 2; int *ptrTempA = &tempA; /*1.常规使用dclTempA为一个int *的指针*/ decltype(ptrTempA) dclTempA; /*2.需要特别注意,表达式内容为解引用操作,dclTempB为一个引用,引用必须初始化,故编译不过*/ decltype(*ptrTempA) dclTempB;

    decltype和auto的区别√

    decltypeauto都可以用来推断类型,但是二者有几处明显的差异: 1auto忽略顶层constdecltype保留顶层const2、对引用操作,auto推断出原有类型,decltype推断出引用; 3、对解引用操作,auto推断出原有类型,decltype推断出引用; 4auto推断时会实际执行,decltype不会执行,只做分析。总之在使用中过程中和const、引用和指针结合时需要特别小心。

    Lambdas

    参考

    C++ 11新特性

    Processed: 0.010, SQL: 8