当前位置:   article > 正文

C++11新特性一览 - 引自《C++ Primer》

c++11新特性


前言

本文主要对C++11 涉及的新特性知识点进行梳理。
搬运来源:绝大部分来源于《C++ Primer》,不管是一些示例,还是目录分类的逻辑也是按照原书中来的。其次还有一些大大小小的公众号或是优质文章等等,提及的或未提及的,都写明了原文的出处,特此说明。感谢大佬们分享。


一、变量和基本类型:

C++11 长整数 long long

long long 长整型,占8个字节;
范围为-2^ 63 ~ 2^63-1

C++11 列表初始化(统一的初始化语法)

《C++11新特性之列表初始化》

初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,以一个新值替代。(C++ Primer)

作为C++11新标准的一部分,用花括号来初始化变量得到了全面应用,这种初始化的形式被成为列表初始化

int* a = new int { 3 };
double b = double{ 12.12 };
int * arr = new int[] {1, 2, 3};
  • 1
  • 2
  • 3

无论是类的变量,数组,stl的容器,类的构造,都统一使用{},以后只要是初始化就首先考虑{}的初始化就OK了。

C++11 nullptr常量

nullptr出现的目的是为了替代NULL。

在某种意义上来说,传统C++会把NULL、0视为同一种东西,这取决于编译器如何定义NULL,有些编译器会把NULL定义为((void*)0),有些则会直接将其定义为0。C++不允许直接将void隐式转换到其他类型,但如果NULL被定义为((void)0),那么当编译char*ch=NULL时,NULL只好被定义为0。而这依然会产生问题,将导致了C++中重载特性会发生混乱,考虑:

void func(int);
void func(char *)
  • 1
  • 2

对于这两个函数来说,如果NULL又被定义为了0,那么func(NULL)这个语句将会去调用func(int),从个人导致代码违反直观。
为了解决这个问题,C++11引入了nullptr关键字,专门用来区分空指针、0。nullptr的类型为nullptr_t,能够隐式地转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。(来源:公众号herongwei)

当需要使用NULL的时候,养成直接使用nullptr的习惯。

C++11 泛化常量表达式

常量表达式(const experssion): 是指
(1)值不会改变
(2)在编译过程就能得到计算结果的表达式。(编译时计算)

constexpr是c++11新添加的特征,可以修饰函数、结构体,目的是将运算尽量放在编译阶段,而不是运行阶段。这个从字面上也好理解,const是常量的意思,也就是后面不会发生改变,因此当然可以将计算的过程放在编译过程。
将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式,声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

int N = 5;
int arr[N];
//编译器会报错,不过编译器不知道这一点,C++11的泛化常数给出了解决方案:
  • 1
  • 2
  • 3

constexpr变量和函数

constexpr int N = 5; // N变成了一个只读的值
int arr[N]; // OK
  • 1
  • 2

constexpr告诉编译器这是一个编译期常量,甚至可以把一个函数声明为编译期常量表达式。

constexpr int getFive(){ return 5; }
int arr[getFive() + 1];
constexpr int fun(int a){ return a*10; }
若a是常量表达式,fun(a)就是常量表达式。若a不是常量表达式,fun(a)就是变成普通函数。
  • 1
  • 2
  • 3
  • 4

constexpr和指针

const int *p1=nullptr;		//常量指针
constexpr int *p2=nullptr;	//constexpr只能在开头,等效于指针常量(和const相反)
(等效于 int *const p2=nullptr;)
  • 1
  • 2
  • 3

常量表达式

C++11 别名声明

类型别名有两种方法:

  1. 传统方法是用typedef
typedef double wages;
  • 1
  1. 别名声明 using(C++11)
using SI = Sales_item
  • 1

C++11 auto关键字&decltype类型指示符

auto:
 auto进行类型推断时,一般会忽略顶层const(即该变量自身是常量),而保留底层const(即该变量指向常量)

const int ci = i,&cr = ci;
auto b = ci; b只是一个整数(顶层const被忽略)
auto c = cr; b是一个整数(cr是别名,同理)
auto d = &i; d是一个整型指针(整数的地址就是整型指针)
auto e = &ci; e是一个常量指针(底层const保留)
  • 1
  • 2
  • 3
  • 4
  • 5

 如果希望推断出的auto类型是一个顶层const,需要明确指出

const auto f = ci;
  • 1

decltype:
 当:希望从表达式类型推断出要定义的变量的类型,但不想用该表达式的值初始化变量(总结:只得到类型,初始化的值另外决定

autodecltype都是类型推断的两种方式,但之间又有区别。

《auto 和 decltype的区别》

  1. auto通过变量来推断类型并赋上变量的值,decltype也通过变量来推断类型但不一定赋变量的值,可自定
  2. auto会忽略顶层const性质,但是decltype是保留的
  3. decltype的结果类型与表达式形式有密切关系。decltype(( ))返回的是引用

二、字符串、向量和数组

C++11 范围for语句

不多赘述,但介绍一下《auto的三种用法》

   想要读取元素: for(auto x:range)			拷贝了一份,可修改但不影响原数组

   想要修改元素: for(auto &&x:range)			可以修改range的元素

   想要只读元素: for(const auto& x:range)    只读,不可修改

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

C++11 定义vector对象的vector(向量的向量)

在早期版本的C++标准中如果vector的元素是vector,则其定义的形式与现在C++11新标准略有不同。过去,必须在外层vector对象的右尖括号和其元素之间添加一个空格,如vector<vector<int.>space`> (C++Primer)。

C++11 容器的cbegin和cend函数

cbegin()和cend()是C++11新增的,它们返回一个const的迭代器,不能用于修改元素。
iterator,const_iterator作用:遍历容器内的元素,并访问这些元素的值。iterator可以改元素值,但const_iterator不可改。跟C的指针有点像。
C++基础之容器的cbegin和cend函数

C++11 begin和end函数

迭代器和指针本极其相似,如果说begin()end()函数返回了两个迭代器,那么:
beginend标准库函数可以返回普通数组首元素或尾后元素的指针

int *beg = begin(a);	指向a首元素的指针
int *end = end(a);		指向a尾后元素的指针
  • 1
  • 2

不过数组毕竟不是类,因此这两个函数不是成员函数。使用方式是将数组作为它们的参数

C++11 除法取整规则

早期C++两数相除如果商为负值,可向上或向下取整
C++11商一律向0取整(去除小数部分)

C++11 initializer_list

initializer_list一种模板类型(近似于vector)。
区别: 元素永远是常量值,我们无法改变其对象中元素的值。
适用前提: 全部实参的类型都相同。
具体作用: 例如,编写输出错误信息的函数,使其可以作用于可变数量的实参

C++11 尾置返回类型

任何函数的定义都能使用尾置返回,但当返回类型比较复杂的时候才更适用。

尾置返回类型跟在形参列表后,auto开头
auto func(int i) -> int(*)[10];		//返回的是指针,指向含有10个整数的数组
  • 1
  • 2

C++11 constexpr函数

constexpr函数指能用于常量表达式的函数。
有两个条件:

  1. 返回值、形参列表必须是字面值类型
  2. 函数中有且只有一条return

字面值类型:
算术类型、引用、指针   
自定义类、IO库、string   不是

特性:

  1. constexpr函数被隐式为内联函数
  2. 当输入实参为常量表达式时,才返回常量表达式,否则返回非常量表达式。

C++11 = default

如果我们需要默认的行为,那么可以通过在参数列表后面写上= default来要求编译器生成构造函数。
= default既可以出现在声明处、也可以出现在定义处,如果在类的内部,则它是内联的。

三、表达式

四、语句

五、函数

C++11 return{}

返回用大括弧初始化的该函数对象(返回的类型和函数类型一样)

《return{}的含义》

六、类

C++11 委托构造函数

《C++11委托构造函数》

如果一个类中重载了多个构造函数,并且在每个构造函数中都需要对某些变量进行初始化,这时候就会出现很多重复的代码,在C++11之前存在这个问题。而C++11中新增的委托构造函数,就是为了解决这个问题,它允许在同一个类中一个构造函数可以调用另外一个构造函数,从而可以在初始化时简化变量的初始化。(写法类似于继承)

class ClassA{

public:
    ClassA(){}
    ClassA(int max){
        m_max = max > 0 ? max : 20;
    }
    ClassA(int max,int min):ClassA(max){
        m_min = min > 0 && min < m_max ? min : 5;
    }
    ClassA(int max,int min,int middle):ClassA(max,min){
        m_middle = middle < max && middle > min ? middle : 10;
    }

private:
    int m_max;
    int m_min;
    int m_middle;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
class myBase {
int number; string name;
myBase( int i, string& s ) : number(i), name(s){}
public:
myBase( ) : myBase( 0, "invalid" ){}
myBase( int i ) : myBase( i, "guest" ){}
myBase( string& s ) : myBase( 1, s ){ PostInit(); }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意:

1. 这种链式调用构造函数不能形成一个环,否则将会在运行时抛异常。
2. 委托构造函数不能和成员初始化列表同时使用。

七、C++标准库的更新

C++11 容器的非成员函数swap

早期标准库版本只提供成员函数版本的swap。非成员版本的swap在泛型编程中是非常重要的,统一使用非成员版本的swap是个好习惯。

a.swap(b);//成员版本
swap(a,b)//非成员版本
  • 1
  • 2

C++11 Lambda表达式

Lambda表达式 就是一个匿名函数(闭包),也就是一个没有函数名的函数
简单语法如下:[capture] (parameters) -> return value { body },只有 [capture] 捕获列表和 { body } 函数体是必选的,其他可选。 [capture] 捕获列表是lambda开始的标识。(依次为:捕获、参数和函数体;括号中-大-小)
C++ lambda表达式入门

C++11 无序关联容器

C++的map,multimap,set,multiset使用红黑树实现,插入和查询都是O(logn)的复杂度;
但C++11为四种unordered模板类提供底层哈希实现以达到O(1)的复杂度:

  • unordered_set
  • unordered_multiset
  • unordered_map
  • unordered_multimap

C++11 智能指针

八、类设计者的工具

C++11 default和delete(移动构造函数,移动赋值运算)

《C++11:Default和Delete》

Big Three:拷贝构造、拷贝赋值、析构函数(针对带着指针成员变量的类)

default

default关键字,功能是指示编译器完成类特殊成员函数的默认生成工作,程序员只需在类特殊成员函数后面加上“=default;”,即可将该函数声明为 "=default"函数,编译器将为显式声明 "=default"函数自动生成默认函数体。
default具有的优势:
(1) 可以获取更高的执行效率,(2) 避免了程序员定义函数的繁重工作。

C++98标准虽然没有default关键字,但是一般情况下编译器会默认帮程序员添加类的默认构造,析构函数,拷贝构造函数以及拷贝赋值函数。C++11的default只是把这个默认实现变的更直观而已。

delete

delete关键字主要是控制类的6个特殊函数的使用工作,声明为“=default”的函数在其他地方无法使用,即这个函数不存在,禁止定义。

PS:在C++11标准之前,程序员想控制一个函数不允许被定义,一般通过两个手段达到目标:
(1)声明此函数为私有成员函数;(2)此函数只有函数声明,没有函数定义。

default仅仅可以控制类的特殊成员函数的默认生成,而delete可应用于任何函数

C++11 右值引用

右值引用及其作用

左值:永久对象,可以存在于=左边的值,可以取地址;
典型的左值:有名称的变量、函数形参(栈中的对象)等。
右值:临时对象(即将销毁),不能存在于=左边的值,不可以取地址。
典型的 rvalue:字面常量(如1、2…等)、匿名对象(临时对象)以及函数的返回值等。另外,也可以通过 std::move 显式地将一个左值转换为右值。
函数形参都是左值,因为函数形参都有名称,都可以对形参进行取地址操作。

int a = 9, b = 8;
a = 8, b = 0;       // a,b为左值
// a + 4 = 5;       // 错误 a + 4为右值,a + 4为一个临时对象
// -a = 4;          // 错误 -a为右值,为一个临时对象
(a) = 5;            // 正确 (a)为左值,返回的是a
++a = 3;            // 正确 ++a为左值,可以理解为a先+1,然后返回a本身,也即整个表达是a
// a++ = 3;         // 错误 a++为右值,可以理解为int tmp = a;a = a +1;return tmp;返回的是一个临时变量
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

C++03及之前的标准中,右值是不允许改变的。实践中也通常通过const T&的方式传递右值。然而这是效率低下的做法,例如:

Person get(){
Person p;
return p;
}
Person p = get();
  • 1
  • 2
  • 3
  • 4
  • 5

所谓右值引用就是必须绑定到右值的引用,我们通过&&而不是&来获得右值引用。右值引用有一个重要的性质——只能绑定到一个将要销毁的对象。因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。
对左值的引用即为左值引用,对右值的引用即为右值引用。需要注意得是引用是对值得别名,所以不会产生副本,同时引用本身也是变量,所以也是左值。如下:

int &t = a;         // a为左值,所以可以赋给左值引用
// int &t1 = 3;     // 错误 3为一个临时值,为右值,不能赋给左值引用
// int &&t = a;     // 错误 a为左值,不能赋给右值引用
int &&t = 3;        // 可以
int &&t = -a;       // 可以
// int &t = -a;     // 不可以
// int &&t1 = t;    // 不可以,t本身是左值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

右值引用和左值引用都是引用,都是一个变量(即都是一个左值),左值引用通过在类型名后加 & 来表示,而右值引用则通过在类型名后加 && 表示。只不过左值引用引用的是左值,而右值引用只能引用右值。

C++11 override和final

C++11中final和override的用法

override:
主要作用: 显式地声明对基类虚函数的重写
其他:

  1. 防止因疏忽把本来的想重写的基类的虚函数在派生类中声明为重载
  2. 防止在派生类中重写的虚函数声明中漏掉virtual

final:

  • 函数: 把函数定义成final,则之后任何尝试覆盖该函数的操作都将引发错误。
  • 类: final作用于类时,限制这个类不能被继承。

九、高级主题

C++11 tuple元组

tuple是一个固定大小的不同类型值的集合,是泛化的std::pair
创建和初始化:

std::tuple<T1, T2, TN> t1; //创建一个空的tuple对象(使用默认构造),它对应的元素分别是T1和T2...Tn类型,采用值初始化。
std::tuple<T1, T2, TN> t2(v1, v2, ... TN); //创建一个tuple对象,它的两个元素分别是T1和T2 ...Tn类型; 
//要获取元素的值需要通过tuple的成员get<Ith>(obj)进行获取(Ith是指获取在tuple中的第几个元素,请看后面具体实例)。
std::tuple<T1&> t3(ref&); // tuple的元素类型可以是一个引用
std::make_tuple(v1, v2); // 像pair一样也可以通过make_tuple进行创建一个tuple对象
  • 1
  • 2
  • 3
  • 4
  • 5

相关操作:

  1. 等价结构体
struct person {
    char *m_name;
    char *m_addr;
    int *m_ages;
};
//可以用tuple来表示这样的一个结构类型,作用是一样的。
std::tuple<const char *, const char *, int>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 当有一个tuple对象但不知道有多少元素可以通过如下查询
 	tuple<int, char, double> mytuple (10, 'a', 3.14);
    cout << "mytuple has ";
    cout << std::tuple_size<decltype(mytuple)>::value;
    cout << " elements." << '\n';
    //输出结果:
	mytuple has 3 elements
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 获取tuple对象元素的值可以通过get(obj)方法进行获取
    (Ith - 是想获取的元素在tuple对象中的位置、obj - 是想获取tuple的对象)

tuple<int, char, double> mytuple (10, 'a', 3.14); 
cout << "mytuple has ";
cout << tuple_size<decltype(mytuple)>::value;
cout << " elements." << '\n';
//获取元素
cout << "the elements is: ";
cout << std::get<0>(mytuple) << " ";
cout << std::get<1>(mytuple) << " ";
cout << std::get<2>(mytuple) << " ";
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

and so on…《C++11:tuple元组详解》

C++11 正则表达式



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

闽ICP备14008679号