C++11标准特性的一些理解

(1)auto 和 decltype 关键字

在C++11之前,auto关键字用来指定存储期(C++98中指的是自动生命周期)。在新标准中,它的功能变为类型推断。C++11引入auto关键词与之前C语言的auto意义已经不一样了。这里的auto是修饰未知变量的类型,编译器会通过此变量的初始化自动推导变量的类型。

例如:auto x = 1;编译器会通过“1”值,推导出变量x是整型。但在C++11之前,采用auto关键字定义变量 i = x,会抛出“declaration with no type”错误,如下:

C++11标准特性的一些理解

更新为C++11标准之后,auto关键字能够声明并定义变量类型(自动推导)

#include<iostream>
#include<typeinfo>
using namespace std; int main()
{
auto i = 1;
auto f = 1.5;
auto b = true;
auto ch = 'c';
auto str = "abcd";
cout << typeid(i).name() << endl;
cout << typeid(f).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(ch).name() << endl;
cout << typeid(str).name() << endl; return 0;
}

C++11标准特性的一些理解

可以看出,当使用auto定义变量时,变量的类型会随着赋值的类型改变而改变。

需要注意的是,auto不能用来声明函数的返回值;但可以用来根据函数返回值的类型定义新的变量来接收函数的返回值。auto并不是一个真正的类型,它仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid。

decltype与auto关键字一样,用于进行编译时类型推导。 用法:decltype(变量/表达式) 变量名。decltype实际上有点像auto的反函数,auto可以让你声明一个变量,而decltype则可以从一个变量或表达式中得到类型,例如:

#include<iostream>
#include<typeinfo>
using namespace std; int add(int x, int y)
{
return x + y;
} bool Judge(int x, int y)
{
if (x > y) return true;
else return false;
} int main()
{
decltype(add(1, 2)) i;
decltype(Judge(1, 2)) b;
cout << typeid(i).name() << endl;
cout << typeid(b).name() << endl;
return 0;
}

C++11标准特性的一些理解

(2)defaulted 和 deleted 函数

C++11对函数的定义引入了在函数体外声明函数“=default”和“=delete”,则该函数被定义为默认(defaulted)函数和已删除(deleted)函数。例如:

struct sometype
{
void* operator new(std::size_t) = delete;
void* operator new[](std::size_t) = delete;
};
sometype* p = new sometype;

C++11标准特性的一些理解

注意函数的删除定义必须是该函数的首次声明:先前已声明的函数不能重新声明为已删除。

(3)final 和 override 关键字

final 是 C++11 新标准的一个关键字,它的作用是用来修饰类,使其不能被继承;用来修饰虚函数,使其不能出现在子类中。例如:

#include <iostream>

class Test {
virtual void final_function() final;
}; class Son final : public Test {
virtual void final_function();
};

C++11标准特性的一些理解

注意C++11的final关键字是放在被修饰的函数名后,Java则是放在函数名之前。

override关键字指的是重写基类函数,表示函数需要重写基类中的虚函数。例如:

class A
{
public:
virtual void f(int) const {cout << "A::f " << std::endl;}
}; class B: public A
{
public:
virtual void f(int) override {cout << "B::f" << std::endl;}
};

注意final只能用来修饰函数声明,不能用来修饰函数实现。如果类中没有声明,只有实现的情况下,可修饰。

(4)trailing 返回类型

trailing返回类型在返回类型取决于参数名的或者返回值很复杂情况下很有用。例如:

template <class T, class U> auto add(T t, U u) -> decltype(t + u);
auto fpif(int)->int(*)(int)

(5)rvalue references(右值引用)

C++中左值(lvalue)和右值(rvalue)的区别在于是否能对其取址,如果一个表达式能够取址,就是左值;否则,就是右值(C++03标准中所有的表达式非右即左)。所谓右值引用就是必须绑定到右值的引用。右值引用有一个重要的性质----只能绑定到一个将要销毁的对象。因此,可以*地将一个右值引用的资源”移动”到另一个对象中。它的主要目的有两个方面:

  1)消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率;

  2)能够更简洁明确地定义泛型函数。

注意左值的声明符号为”&”, 为了和左值区分,右值的声明符号为”&&”。

(6)move constructors and move assignment operators

move constructors(移动构造函数):类 T 的移动构造函数是非模板构造函数,其第一个参数是 T&&、const T&&、volatile T&& 或 const volatile T&&,并且要么没有其他参数,要么其余参数都有默认值。例如:

1)class_name ( class_name && )
2)class_name ( class_name && ) = default;
3)class_name ( class_name && ) = delete;

其中 class_name 必须命名当前类(或类模板的当前实例化),或者在命名空间范围或友元声明中声明时,它必须是限定类名。当从右值(xvalue 或 prvalue)初始化(通过直接初始化或复制初始化)对象时,通常会调用移动构造函数。

移动构造函数通常“窃取”参数持有的资源(例如,指向动态分配对象的指针、文件描述符、TCP 套接字、I/O 流、运行线程等)而不是复制它们,并将参数留在 一些有效但其他不确定的状态。 例如,从 std::string 或从 std::vector 移动可能导致参数为空。 但是,不应依赖此行为。 对于某些类型,例如 std::unique_ptr,已完全指定从...移动状态。

move assignment operator(移动赋值运算符):类 T 的移动赋值运算符是名称为 operator= 的非模板非静态成员函数,它只接受一个 &&、const T&、volatile && 或 const volatile T&& 类型的参数。例如:

1)class_name & class_name :: operator= ( class_name && )
2)class_name & class_name :: operator= ( class_name && ) = default;
3)class_name & class_name :: operator= ( class_name && ) = delete;

每当通过重载决议选择移动赋值运算符时都会调用它,例如 当对象出现在赋值表达式的左侧时,其中右侧是相同或隐式可转换类型的右值。

移动赋值运算符通常“窃取”参数持有的资源(例如,指向动态分配的对象、文件描述符、TCP 套接字、I/O 流、正在运行的线程等的指针),而不是制作它们的副本,并使参数处于某种有效或者是其他不确定的状态。 例如,从 std::string 或从 std::vector 移动赋值可能导致参数留空。 然而,这并不是确定的。 移动赋值比普通赋值更少,但没有更严格的定义; 普通赋值在完成时必须留下两份数据,而移动赋值只需要留下一份。

(7)scoped enums

scoped enums(有作用域枚举类型):

1)enum struct|class name { enumerator = constexpr , enumerator = constexpr , ... }
2)enum struct|class name : type { enumerator = constexpr , enumerator = constexpr , ... }
3)enum struct|class name ;
4)enum struct|class name : type ;

  1) 声明一个作用域枚举类型,其基础类型为 int(关键字 class 和 struct 完全等效)
  2) 声明一个作用域枚举类型,其基础类型为 type
  3) 基础类型为 int 的作用域枚举的不透明枚举声明
  4) 基础类型为 type 的作用域枚举的不透明枚举声明

每个枚举器都成为枚举类型(即名称)的命名常量,它包含在枚举的范围内,并且可以使用范围解析运算符进行访问。 尽管可以使用 static_cast 来获取枚举器的数值,但没有从作用域枚举器的值到整数类型的隐式转换。

unscoped enums(无作用域枚举类型):

1)enum name(optional) { enumerator = constexpr , enumerator = constexpr , ... }
2)enum name(optional) : type { enumerator = constexpr , enumerator = constexpr , ... }
3)enum name : type ;

  1) 声明一个无作用域枚举类型,其底层类型不固定(在这种情况下,底层类型是实现定义的整数类型,可以表示所有枚举器值;除非枚举器的值不能容纳该类型,否则该类型不大于 int 在 int 或 unsigned int 中。如果枚举器列表为空,则基础类型就像枚举具有单个值为 0 的枚举器一样。
  2) 声明一个无作用域的枚举类型,其基础类型是固定的。
  3) 无作用域枚举的不透明枚举声明必须指定名称和基础类型。

每个枚举器成为枚举类型(即名称)的命名常量,在封闭范围内可见,并且可以在需要常量时使用。

(9)list initialization

在C++11中list initialization(列表初始化)被适用性被放大,可以作用于任何类型对象的初始化。在C++98/03中我们只能对普通数组和POD(plain old data,简单来说就是可以用memcpy复制的对象)类型可以使用列表初始化,如下:

数组的初始化列表:

int arr[3] = {1,2,3}

而在C++11中,对于任意的STL容器都与未指定长度的数组有一样的初始化能力,如:

int arr[] = { 1, 2, 3, 4, 5 };
map < int, int > mymap { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
list<string> list_str{ "hello", "world", "there" };
vector<double> vec_d { 0.0,0.1,0.2,0.3,0.4,0.5};
class FooVec
{
public:
vector<int> vec;
FooVec(initializer_list<int> list)
{
for (auto it = list.begin(); it != list.end(); it++)
vec.push_back(*it);
}
}; int _tmain(int argc, _TCHAR* argv[])
{
FooVec foo1 { 1, 2, 3, 4, 5, 6 };
FooVec foo2 { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
return 0;
}

列表初始化有以下特点:

  1)它是一个轻量级的容器类型,内部定义了迭代器iterator等容器必须的一些概念。
  2)对于initialzer-list<T>来说,它可以接受任意长度的初始化列表,但是元素必须是要相同的或者可以转换为T类型的。
  3)它只有三个成员接口,begin(),end(),size(),其中size()返回initialzer-list的长度。
  4)它只能被整体的初始化和赋值,遍历只能通过begin和end迭代器来,遍历取得的数据是可读的,是不能对单个进行修改的。

(8)constexpr 关键字 和 literal types(字面类型)

在C++11中,通过声明constexpr让编译器确定一个变量是不是常量表达式,声明为constexpr的变量是const类型的变量,它必须由常量表达式来初始化。例如:

constexpr int i = 1;
constexpr int j = i + 1;

同时,在C++11中,我们可以声明某些函数是常量表达式,这样的函数必须在编译期间计算出它们的值,这样的函数必须满足以下条件:

  1)返回值和参数必须是Literal(字面)类型;
  2)函数体必须只包含一个return语句;
  3)函数提可以包含其他的语句,但是这些语句不能在运行期起作用;
  4)函数可以不返回常量,但是在调用的时候实参必须传入常量表达式。

constexpr int get_year()
{
return 2021;
}

一个类型是literal type(字面类型),则他满足:

  1)常量类型;
  2)一个类类型;
  3)一个简单的复制构造函数,
  4)一个简单的析构函数,
  5)一个普通的默认构造函数或至少一个除复制构造函数之外的 constexpr 构造函数,
  6)所有非静态数据成员和文字类型的基类;
  7)文字类型的数组。

(10)delegating and inherited constructors

在C++98中,如果你想让两个构造函数完成相似的事情,可以写两个大段代码相同的构造函数,或者是另外定义一个init()函数,让两个构造函数都调用这个init()函数。例如:

class X {
int a;
// 实现一个初始化函数
validate(int x) {
if (0<x && x<=max) a=x; else throw bad_X(x);
}
public:
// 三个构造函数都调用validate(),完成初始化工作
X(int x) { validate(x); }
X() { validate(42); }
X(string s) {
int x = lexical_cast<int>(s); validate(x);
}
};

Delegating constructors(委托构造函数):在一个构造函数中调用另外一个构造函数,这就是委托的意味,不同的构造函数自己负责处理自己的不同情况,把最基本的构造工作委托给某个基础构造函数完成,实现分工协作。

class X {
int a;
public:
X(int x) { if (0<x && x<=max) a=x; else throw bad_X(x); }
// 构造函数X()调用构造函数X(int x)
X() :X{42} { }
// 构造函数X(string s)调用构造函数X(int x)
X(string s) :X{lexical_cast<int>(s)} { }
};

inherited constructor(继承构造函数):

  1)如果 using 声明引用所定义类的直接基类的构造函数(例如 using Base::Base;),则该基类的所有构造函数(忽略成员访问)在初始化派生类时对重载可见。

  2)如果重载选择了一个继承的构造函数,那么如果它在用于构造相应基类的对象时是可访问的,则它是可访问的:引入它的 using 声明的可访问性将被忽略。

  3)如果重载在初始化此类派生类的对象时选择了继承的构造函数之一,则继承构造函数的 Base 子对象将使用继承的构造函数进行初始化,而 Derived 的所有其他基类和成员则如同默认的一样初始化默认构造函数(如果提供,则使用默认成员初始值设定项,否则进行默认初始化)。整个初始化被视为单个函数调用:继承构造函数的参数的初始化顺序在派生对象的任何基类或成员的初始化之前。

(11)brace-or-equal initializers

brace(括号)或“=”初始化:变量的初始化在构造时提供其初始值。初始值可以在声明符或新表达式的初始化部分中提供。 它也发生在函数调用期间:函数参数和函数返回值也被初始化。对于每个声明符,初始化器可以是以下之一:

( 表达式列表 ) (1)
= 表达式 (2)
{ 初始化列表} (3)

  1) 逗号分隔的任意表达式列表和括号中的花括号初始化器列表
  2) 等号后跟表达式
  3) 花括号初始化器列表:可能是空的、逗号分隔的表达式列表和其他花括号初始化器列表
根据上下文,初始化程序可能会调用:

  值初始化,例如 std::string s{};
  直接初始化,例如 std::string s("你好");
  复制初始化,例如 std::string s = "你好";
  列表初始化,例如 std::string s{'a', 'b', 'c'};
  聚合初始化,例如 字符 a[3] = {'a', 'b'};
  引用初始化,例如 字符& c = a[0];
如果未提供初始化程序,则应用默认初始化规则。

(12)nullptr

在C++的编程中,我们可以看到NULL和nullptr两种关键字,其实nullptr是C++11版本中新加入的,它的出现是为了解决NULL表示空指针在C++中具有二义性的问题。

而在C语言中,NULL通常被定义为:

#define NULL ((void *)0)
int *pi = NULL; //void * 转为int *
char *pc = NULL; //void * 转为char *

所以说NULL实际上是一个空指针,如果在C语言中写入以下代码,编译是没有问题的,因为在C语言中把空指针赋给int和char指针的时候,发生了隐式类型转换,把void指针转换成了相应类型的指针。

而在C++中,以上代码编译是会出错的,因为C++是强类型语言,void*是不能隐式转换成其他类型的指针的,所以实际上编译器提供的头文件做了相应的处理:

#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif

可见,在C++中,NULL实际上是0。因为C++中不能把void*类型的指针隐式转换成其他类型的指针,所以为了结果空指针的表示问题,C++引入了0来表示空指针,这样就有了上述代码中的NULL宏定义。

因此在函数重载时,会出现:

#include <iostream>
using namespace std; void func(void* i)
{
cout << "func1" << endl;
} void func(int i)
{
cout << "func2" << endl;
} void main(int argc,char* argv[])
{
func(NULL);
func(nullptr);
getchar();
}

C++11标准特性的一些理解

由此可见,NULL在C++中就是0,这是因为在C++中void* 类型是不允许隐式转换成其他类型的,所以之前C++中用0来代表空指针,但是在重载整形的情况下,会出现上述的问题。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,

(13)long long

相比于C++98标准,C++11整型的最大改变就是多了 long long。分为两种:long long 和unsigned long long。在C++11中,标准要求long long 整型可以在不同平台上有不同的长度,但至少有64位。在写常数字面量时,可以使用LL后缀(或是ll)标识一个long long 类型的字面量,而ULL (或ull、Ull、uLL) 表示一个unsigned long long 类型的字面量。比如:

long long int lli=-900000000000000LL; // 有符号的long long 变量lli
unsigned long long int ulli=-900000000000ULL; // 无符号的 unsigned long long 变量ulli。

对于有符号的,下面的类型是等价的:long long、signed long long、long long int、signed long long int; 而unsigned long long 和 unsigned long long int 也是等价的。

与 long long 整型相关常量的一共有3个:LONG_MIN、LONG_MAX 和ULONG_MAX, 它们分别代表了平台上最小的long long 值、最大的long long 值,以及最大的unsigned long long 值。

14)char16_t and char32_t

char16_t,char32_t 是C++ 11 新增的字符类型,char16_t 占两个字节,char32_t 占四个字节。而char 有一个字节表示,wchar_t 宽体字符,由两个字符表示。

#include <iostream>
#include <string> using namespace std; int main()
{
char nameChar[] = "This is a char array";
wchar_t nameWchar[] = L"This is a wchar array";
char16_t nameChar16[] = u"This is a char16 array";
char32_t nameChar32[] = U"This is a char32 array";
}

在C++标准库中,每个 basic_string 类型都专用于窄字符串和宽字符串。
  1)当字符的类型为char时,使用 std::string;

  2)std::wstring 字符类型wchar_t;

  3)std::u16string 字符类型char16_t;

  4)std::u32string 字符类型char32_t;

(15)type aliases(类型别名)

类型别名(type aliases)在C++11之后允许采用typedef给简单或复杂类型起别名,方便声明变量。typedef 声明与变量或函数声明具有相同的语法,但它包含 typedef。 typedef 的存在导致声明声明类型而不是变量或函数。例如:

int T;         // T has type int
typedef int T; // T is an alias for int
int A[100]; // A has type "array of 100 ints"
typedef int A[100]; // A is an alias for the type "array of 100 ints"

一旦定义了类型别名,它就可以与类型的原始名称互换使用。

typedef int A[100];
// S is a struct containing an array of 100 ints
struct S {
A data;
};

注意typedef 从不创建新的类型。 它只提供了另一种引用现有类型的方式。

struct S {
int f(int);
};
typedef int I;
// ok: defines int S::f(int)
I S::f(I x) { return x; }

(16)variadic templates(可变参数列表)

在c++11中新引入了variadic templates(可变参数列表),和initialize_list不同的是,variadic templates可以支持不同类型的参数,而initialize_list只支持同一种类型的参数,并且在vector、max等容器或者函数中内部已经实现了initialize_list,那么variadic templates的用法更加广泛,其中体现最好的就是Tuple容器。

使用variadic templates中重点为... ,他就是所谓的一个包,表示0~任意个数、任意类型的参数,variadic templates用法举例:

int maximum(int n)
{
return n;
} template<typename...Args>
int maximum(int n,Args...args)
{
return max(n,maximum(args...));
}
int main()
{
cout << maximum(1,2,2,4,5,1);
return 0;
}

C++11标准特性的一些理解

(17)generalized (non-trivial) unions

(18)generalized PODs (trivial types and standard-layout types)

(19)Unicode string literals

(20)user-defined literals

(21)attributes

(22)lambda expressions

(23)noexcept specifier and noexcept operator

(24)alignof and alignas

(25)multithreaded memory model

(26)thread-local storage

(27)GC interface

(28)range-for (based on a Boost library)

(29)static_assert (based on a Boost library)

上一篇:C++ Primer中文版(第5版)(*畅销书重磅升级全面采用最新 C++ 11标准)


下一篇:IEEE 802.11 标准列表