当前位置:   article > 正文

c++11新特性

c++11新特性

1.c++11新特性

C++11的简要介绍
(1)出于保证稳定性与兼容性增加了不少新特性,如long long整数类型、静态断言、外部模板等等 ;
(2)具有广泛应用性、能与其他已有的或者新增的特性结合起来使用的、具有普适性的一些新特性,如继承构造函数,委派构造函数,列表初始化等等;
(3)对原有一些语言特性的改进,如auto类型推导、追踪返回类型、基于范围的for循环,等等;
(4)在安全方面所做的改进,如枚举类型安全和指针安全等方面的内容;
(5)为了进一步提升和挖掘C++程序性能和让C++能更好地适应各种新硬件,如多核,多线程,并行编程等等;
(6)颠覆C++一贯设计思想的新特性,如lambda表达式等;
(7)C++11为了解决C++编程中各种典型实际问题而做出的有效改进,如对Unicode的深入支持等。
C++11

1.auto类型推导

在早期版本中,关键字auto主要是用于声明具有自动存储期的局部变量。然而,它并没有被经常使用。原因是:除了static类型以外,其他变量(以“数据类型+变量名”的方式定义)都默认为具有自动存储期,所以auto关键字可有可无。

所以,在C++11的版本中,删除了auto原本的功能,并进行了重新定义了。即C++11中的auto具有类型推导的功能。在讲解auto之前,我们先来了解什么是静态类型,什么是动态类型。

(1)静态类型,动态类型,类型推导

通俗的来讲,所谓的静态类型就是在使用变量前需要先定义变量的数据类型,而动态类型无需定义。

严格的来讲,静态类型是在编译时进行类型检查,而动态类型是在运行时进行类型检查。

如python:a = "helloworld"; // 动态类型
而C++:std::string a = "helloworld"; // 静态类型

如今c++11中重新定义了auto的功能,这便使得静态类型也能够实现类似于动态类型的类型推导功能,十分有趣~

下面是auto的基本用法:

  1. double func();
  2. auto a  = 1; // int, 尽管1时const int类型,但是auto会自动去const
  3. auto b = func(); // double
  4. auto c; // wrong, auto需要初始化的值进行类型推导,有点类似于引用

注意: 其实auto就相当于一个类型声明时的占位符,而不是一种“类型”的声明,在编译时期编译器会将auto替代成变量的实际类型。

(2) auto的优势

I. 拥有初始化表达式的复杂类型变量声明时的简化代码。

也就是说,auto能够节省代码量,使代码更加简洁, 增强可读性。

  1. std::vector<std::string> array;
  2. std::vector<std::string>::iterator it = array.begin();
  3. // auto
  4. auto it = array.begin();

auto在STL中应用非常广泛,如果在代码中需要多次使用迭代器,用auto便大大减少了代码量,使得代码更加简洁,增强了可读性。

II.免除程序员在一些类型声明时的麻烦,或者避免一些在类型声明时的错误。

  1. class PI {
  2. public:
  3.     double operator *(float v) {
  4.         return (double)val*v;
  5.     }
  6.     const float val = 3.1415927f;
  7. };
  8. int main(void) {
  9.     float radius = 5.23f;
  10.     PI pi;
  11.     auto circumference = 2*( pi*radius);
  12.     return 0;
  13. }

设计PI类的作者将PI的*运算符进行了重载,使两个float类型的数相乘返回double类型。这样做的原因便是避免数据上溢以及精度降低。假如用户将circumference定义为float类,就白白浪费了PI类作者的一番好意,用auto便不会出现这样的问题。

但是auto并不能解决所有的精度问题,如:

  1. unsigned int a = 4294967295; // unsigned int 能够存储的最大数据
  2. unsigned int b = 1;
  3. auto c = a+b;
  4. cout << c << endl; // 输出c为0

a+b显然超出了unsigned int 能够存储的数据范围,但是auto不能自动将其匹配为能存储这一数据而不造成溢出的类型如unsigned long类型。所以在精度问题上自己还是要多留一点心,分析数据是否会溢出。

III.“自适应”性能在一定程度上支持泛型编程

如上面提到PI类,假如原作者要修改重载*返回的数据类型,即将double换成其他类型如long double,则它可以直接修改而无需修改main函数中的值。再如这种“适应性”还能体现在模板的定义中:

  1. template <typename T1, typename T2>
  2. double func(const T1& a, const T2& b) {
  3.     auto c = a + b;
  4.     return c;
  5. }

其实直接return a+b;也是可以的,这里只是举个例子,同时点出auto不能用于声明函数形参这一易错点

但是有一点要注意:不能将auto用于声明函数形参,所以不能用auto替代T1,T2。

然而,因为func()只能返回double值,所以func()还可以进一步泛化,那就需要decltype的使用了,在后面会详细讲解。

现此处有一段有趣的宏定义:用Max2的宏定义效率更高。

  1. # define Max1(a, b) ((a) > (b)) ? (a) : (b)
  2. # define Max2(a, b) ({ \
  3.             auto _a = (a);
  4.             auto _b = (b);
  5.             (_a > _b) ? _a : _b;})

(3) auto 使用细则

  1. int x;
  2. int* y = &x;
  3. double foo();
  4. int& bar();
  5. auto* a = &x; // a:int*
  6. auto& b = x; // b:int&
  7. auto c = y; // c:int*
  8. auto* d = y; // d:int*
  9. auto* e = &foo(); // wrong, 指针不能指向临时变量
  10. auto &f = foo(); // wrong, 左值引用不能存储右值
  11. auto g = bar(); // int
  12. auto &h = bar(); // int&

其实,对于指针而言, auto* a = &x <=> auto a = &x
但是对于引用而言,上面的情况就不遵循了,如果是引用, 要在auto后加&

  1. int x;
  2. int* y = &x;
  3. double foo();
  4. int& bar();
  5. auto* a = &x; // a:int*
  6. auto& b = x; // b:int&
  7. auto c = y; // c:int*
  8. auto* d = y; // d:int*
  9. auto* e = &foo(); // wrong, 指针不能指向临时变量
  10. auto &f = foo(); // wrong, 左值引用不能存储右值
  11. auto g = bar(); // int
  12. auto &h = bar(); // int&

auto 会自动删除const(常量性),volatile(易失性)。

对于引用和指针,即auto*, auto&仍会保持const与volatile。

  1. auto x = 1, y = 2; // (1) correct
  2. const auto* m = &x, n = 1; // (2)correct
  3. auto i = 1, j = 3.14f; // (3) wrong
  4. auto o = 1, &p = 0, *q = &p; // (4)correct

auto有规定,当定义多个变量时,所有变量的类型都要一致,且为第一个变量的类型,否则编译出错。

对于(1):   x, y 都是int类型,符合auto定义多变量的机制, 编译通过;
对于(2):我们发现,m、n的类型不同,那为什么不报错?变量类型一致是指auto一致。m为const int*, 则auto匹配的是int,而n恰好为int类型,所以编译通过;
对于(3): i 的类型是int, j 的类型是float,类型不相同,编译出错;
对于(4): o的类型是int, p前有&,其实就是auto&, 即p为int&,而q前有,相当于auto,即q为int*,不难发现o, p, q三者auto匹配都为int,所以符合auto定义多变量的机制,编译通过。

(4)局限性

  1. void func(auto x = 1) {} // (1)wrong
  2. struct Node {
  3.     auto value = 10; // (2)wrong
  4. };
  5. int main(void) {
  6.     char x[3];
  7.     auto y = x;
  8.     auto z[3] = x; // (3)wrong
  9.     vector<auto> v = {1}; // (4)wrong
  10. }

I.  auto不能作为函数参数,否则无法通过编译;
II. auto不能推导非静态成员变量的类型,因为auto是在编译时期进行推导;
III.auto 不能用于声明数组,否则无法通过编译;
IV.auto不能作为模板参数(实例化时), 否则无法通过编译。

 

2. 基于范围的for循环

基于范围的for循环,结合auto的关键字,程序员只需要知道“我在迭代地访问每一个元素”即可,而再也不必关心范围,如何迭代访问等细节。

  1. // 通过指针p来遍历数组
  2. # include <iostream>
  3. using namespace std;
  4. int main(void) {
  5.     int arr[5] = {1, 2, 3, 4 , 5};
  6.     int *p;
  7.     for (p = arr; p < arr+sizeof(arr)/sizeof(arr[0]); ++p) {
  8.             *p *= 2;
  9.     }
  10.     for (p = arr; p < arr+sizeof(arr)/sizeof(arr[0]); ++p) {
  11.             cout << *p << "\t";
  12.     }
  13.     return 0;
  14. }

而如今在C++模板库中,有形如for_each的模板函数,其内含指针的自增

  1. # include <iostream>
  2. # include <algorithm>
  3. using namespace std;
  4. int action1(int &e) { e*=2; }
  5. int action2(int &e) { cout << e << "\t"; }
  6. int main(void) {
  7.     int arr[5] = {1, 2, 3, 4, 5};
  8.     for_each(arr, arr+sizeof(arr)/sizeof(a[0]), action1);
  9.     for_each(arr, arr+sizeof(arr)/sizeof(a[0]), action2);
  10.     return 0;
  11. }

以上两种循环都需要告诉循环体其界限范围,即arr到arr+sizeof(arr)/sizeof(a[0]),才能按元素执行操作。

c++11的基于范围的for循环,则无需告诉循环体其界限范围

  1. # include <iostream>
  2. using namespace std;
  3. int main(void) {
  4.     int a[5] = {1, 2, 3, 4, 5};
  5.     for (int& e: arr) e *= 2;
  6.     for (int& e: arr) cout << e << "\t";
  7.     // or(1)
  8.     for (int e: arr) cout << e << "\t";
  9.     // or(2)
  10.     for (auto e:arr) cout << e << "\t";
  11.     return 0;
  12. }


基于范围的for循环后的括号由冒号“:”分为两部分,第一部分是范围内用于迭代的变量第二部分则表示被迭代的范围。

注意:auto不会自动推导出引用类型,如需引用要加上&
auto& :修改
auto:不修改, 拷贝对象
基于范围的循环在标准库容器中时,如果使用auto来声明迭代的对象的话,那么该对象不会是迭代器对象,而是解引用后的对象。

continue与break的作用与原来的for循环是一致的。

使用条件:
(1) for循环迭代的范围是可确定的:对于类,需要有begin()与end()函数;对于数组,需要确定第一个元素到最后一个元素的范围;
(2) 迭代器要重载++
(3) 迭代器要重载*, 即*iterator;
(4) 迭代器要重载== / !=。

对于标准库中的容器,如string, array, vector, deque, list, queue, map, set,等使用基于范围的for循环没有问题,因为标准库总是保持其容器定义了相关操作。

注意:如果数组大小不能确定的话,是不能使用基于范围的for 循环的

  1. // 无法通过编译
  2. # include <iostream>
  3. using namespace std;
  4. int func(int a[]) {
  5.     for (auto e: a) cout << e << "\t";
  6. }
  7. int main(void) {
  8.     int arr[] = {1, 2, 3, 4, 5};
  9.     func(arr);//传入的是数组的首地址,并非整个数组
  10.     return 0;
  11. }

这段代码无法通过编译,原因是func()只是单纯传入一个指针,并不能确定数组的大小,所以不能使用基于范围的for循环。

3. nullptr

在良好的C++编程习惯中,声明一个变量的同时,总是需要记得在合适的代码位置将其初始化。对于指针类型的变量,这一点尤其应当注意。未初始化的悬挂指针通常会是一些难于调试的用户程序的错误根源。

而典型的初始化指针通常有两种:0NULL, 意在表明指针指向一个空的位置。

  1. int *p = 0;
  2. int *q = NULL;

NULL其实是宏定义,在传统C头文件(stddef.h)中的定义如下:

  1. // stddef.h
  2. # undef NULL
  3. # if define(_cplusplus)
  4. # define NULL 0
  5. # else
  6. # define NULL ((void*)0)
  7. # endif

从上面的定义中我们可以看到,NULL既可被替换成整型0,也可以被替换成指针(void*)0。这样就可能会引发一些问题,如二义性:

  1. # include <iostream>
  2. using namespace std;
  3. void f(int* ptr) {}
  4. void f(int num) {}
  5. int main(void) {
  6.     f(0);
  7.     f((int*)0);
  8.     f(NULL);   // 编译不通过
  9.     return 0;
  10. }

NULL既可以被替换成整型,也可以被替换成指针,因此在函数调用时就会出现问题。因此,在早期版本的C++中,为了解决这种问题,只能进行显示类型转换。

所以在C++11中,为了完善这一问题,引入了nullptr的指针空值类型的常量。为什么不重用NULL?原因是重用NULL会使已有很多C++程序不能通过C++11编译器的编译。为保证最大的兼容性且避免冲突,引入新的关键字是最好的选择。

而且,出于兼容性的考虑,C++11中并没有消除NULL的二义性。

那么,nullptr有没有数据类型呢?头文件对其类型的定义如下:

  1. // <cstddef>
  2. typedef decltype(nullptr) nullptr_t;

即nullptr_t为nullptr的类型, 称为指针空值类型。指针空值类型的使用有以下几个规则:
1.所有定义为nullptr_t类型的数据都是等价的,行为也是完全一致的。
也就是说,nullptr_t的对象都是等价,都是表示指针的空值,即满足“==”
2.nullptr_t类型的数据可以隐式转换成任意一个指针类型
3.nullptr_t类型数据不能转换成非指针类型,即使用reinterpret_cast()的方式也不可以实现转化;
4.nullptr_t类型的对象不适用于算术运算的表达式;
5.nullptr_t类型数据可以用于关系运算表达式,但仅能与nullptr_t类型数据或者是指针类型数据进行比较,当且仅当关系运算符为-=, <=, >=, 等时返回true。

  1. # include <iostream>
  2. # include <typeinfo>
  3. using namespace std;
  4. int main(void) {
  5.     // nullptr 隐式转换成char*
  6.     char* cp = nullptr;
  7.     // 不可转换成整型,而任何类型也不可能转化成nullptr_t
  8.     int n1 = nullptr;  // 编译不通过
  9.     int n2 = reinterpret_cast<int>(nullptr);  // 编译不通过
  10.     // nullptr 与 nullptr_t 类型变量可以作比较
  11.     nullptr_t nptr;
  12.     if (nptr == nullptr)
  13.         cout << "nullptr_t nptr == nullptr" << endl;
  14.     else 
  15.         cout << "nullptr_t nptr != nullptr" << endl;
  16.     if (nptr < nullptr)
  17.         cout << "nullptr_t nptr < nullptr" << endl;
  18.     else
  19.             cout << "nullpte_t nptr !< nullptr" << endl;
  20.     // 不能转化成整型或bool类型,以下代码不能通过编译
  21.     if (0 == nullptr);
  22.     if (nullptr);
  23.     // 不可以进行算术运算,以下代码不能通过编译
  24.     // nullptr += 1
  25.     // nullptr * 5
  26.     // 以下操作均可以正常进行
  27.     // sizeof(nullptr) == sizeof(void*)
  28.     sizeof(nullptr);
  29.     typeid(nullptr);
  30.     throw(nullptr);
  31.     return 0;
  32. }

输出:
nullptr_t nptr == nullptr
nullptr_t nptr !< nullptr
terminate called after throwing an instance of "decltype(nullptr)" Aborted


nullptr_t 看起来像个指针类型,用起来更像。但是在把nullptr_t应用于模板的时候,我们会发现模板只能把它作为一个普通的类型进行推导,并不会将其视为T*指针。

  1. # include <iostream>
  2. using namespace std;
  3. template<typename T>
  4. void g(T* t) {}
  5. template<typename T>
  6. void h(T t) {}
  7. int main(void) {
  8.     // nullptr 并不会被编译器“智能”地推导成某种基本类型的指针或者void*指针。
  9.     // 为了让编译器推导出来,应当进行显示类型转换
  10.     g(nullptr); // 编译失败,nullptr的类型是nullptr_t,而不是指针
  11.     g((float*)nullptr); // T* 为 float*类型
  12.     h(0);  // T 为 整型
  13.     h(nullptr);  // T 为 nullptr_t类型
  14.     h((float*)nullptr); // T 为 float*类型
  15.     return 0;
  16. }


null与(void*)0的:
1.nullptr是编译时期的常量,它的名字是一个编译时期的关键字,能够为编译器所识别,而(void*)0只是一个强制类型转化的表达式,其返回值也是一个void*的指针类型。
2.nullptr 能够隐式转化成指针,而(void*)0只能进行显示转化才能变成指针类型(c++11)。虽然在c++标准中(void*)类型的指针可以实现隐式转化。

  1. int *p1 = (void*)0;  // 编译不通过
  2. int *p2 = nullptr;

补充: 除了nullptr, nullptr_t的对象的地址都可以被用户使用 nullptr是右值,取其地址没有意义,编译器也是不允许的。如果一定要取其地址,也不是没有办法。可以定义一个nullptr的右值引用,然后对该引用进行取地址。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/732346
推荐阅读
相关标签
  

闽ICP备14008679号