您的位置:首页 > 编程语言 > C语言/C++

STL学习笔记(二)——C++11新特性

2017-12-29 18:07 344 查看

C++11新特性

1.Template 表达式内的空格

//“在两个 template 闭符之间放一个空格”的要求已经过时了:
std::vector<std::list<int> >;   // OK in each C++ version
std::vector<std::list<int>>;    // OK since C++11

2.nullptr 和 std::nullptr_t

//nullptr 是个新关键字。
//它被自动转换为各种 pointer 类型,但不会被转换为任何整数类型,
//其类型为std::nullptr_t,定义于 <cstddef>.

void f(int);
void f(void *);

f(0);          // 调用 f(int).
f(NULL);       // 如果定义NULL为0,则调用 f(int),否则调用 f(void *).
f(nullptr);    // 调用 f(void *).

3.以 auto 完成类型自动推导

// C++11 允许你声明一个变量或对象而不需要指明其类型.
// auto 声明的变量,其类型会根据其初始值被自动推导出来,
// 因此一定需要一个初始化操作.

auto w;                            // 未指定初始值,错误!
auto i = 100;                      // i 为int类型.
double f()
{
return 3.1415926;
}
auto d = f();                     // d 为double类型.
static auto VAT = 0.19;          // VAT 为static double类型.

4.一致性初始化 与 初值列

// 一致性初始化:面对任何初始化动作,均可使用大括号.
int iNum[] { 1, 2, 3, 4, 5 };
std::vector<int> vecNum { 1, 2, 3, 4, 5 };
std::vector<std::string> vecStr { "aaa", "bbb", "ccc"};
std::complex<double> comNum { 4.0, 3.0}; //复数

// 初值列:强迫初始化为 0 (或nullptr).
int i;                 // i 初始化为未定义值.
int j{};               // j 初始化为 0 .
int * p;               // p 初始化为未定义值.
int * q{};             // q 初始化为 0 .

// 窄化(精度降低或造成数值变动)对大括号而言是不成立的.
int x0(3.4);                                // ok.
int x1 = 3.4;                               // ok.
int x2 { 3.4 };                             // wrong.
int x3 = { 3.4 };                           // wrong.
std::vector<int> v1 { 1, 2, 3 };            // ok.
std::vector<int> v2 { 1.1, 2.2, 3.3 };      // wrong.

// 用户自定义类型之初值列(扩展:可用于定义可变参数函数).
void print(std::initializer_list<int> vals)
{
// 处理一系列值.
for(auto p = vals.begin(); p != vals.end(); ++p)
{
std::cout << *p << std::endl;
}
}
print({11,22,33,44,55,66});

5.关键字 explicit

C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).
// 没有使用explicit关键字的类声明, 即隐式声明.
class CxString
{
public:
CxString(int length)
{
// TODO:
}
};
// 调用.
CxString str1(100);			// ok.
CxString str2 = 100;		// ok.

// 使用explicit关键字的类声明, 即显式声明.
class CxString
{
public:
explicit CxString(int length)
{
// TODO:
}
};
// 调用.
CxString str1(100);			// ok.
CxString str2 = 100;		// wrong.

// 使用explicit关键字且带默认参数的类声明.
class CxString
{
public:
explicit CxString(int length, const char * p = "")
{
// TODO:
}
};
// 调用.
CxString str1(100);			// ok.
CxString str2 = 100;		// wrong.

6.Range-Based for 循环

C++11引入了一种崭新的 for 循环,可以逐一迭代某个给定的区间、数组、集合内的每一个元素(即 C# 中的 foreach 循环)。
// 1.成员函数有 begin() 和 end() 的类.
std::vector<double> vecDbl {0.12, 1.13, 4.567};
for(auto& elem : vecDbl)
{
elem *= 10.0;
}

std::initializer_list<double> initDbl {0.12, 1.13, 4.567};
for(const auto& elem : initDbl)
{
std::cout << elem << std::endl;
}

// 2.同样适用于普通数组.
int array[] = { 0, 1, 2, 3, 4, 5 };
for(auto& elem : array)
{
elem++;
}

// 3.当元素在 for 循环中被初始化为 decl,不得有任何显式类型转换.
class Test
{
public:
explicit Test(const std::string& s);
};
std::vector<std::string> vs;
for(const Test& elem : vs) // Error!
{
std::cout << elem << std::endl;
}

7.Move 语义 和 Rvalue Reference

在 c++11 标准之前,对于非必要拷贝和临时对象只能再copy一份副本,如以下代码所示:
X x;
coll.insert(x);              // insert中建立x的一份拷贝.
coll.insert(x+x);            // insert中建立(x+x)临时变量的一份拷贝.
coll.insert(x);              // insert中建立x的一份拷贝(尽管x变量不再被使用).
然而,对于后两次x的插入操作,实参值(x和x+x)在本次函数调用后不再被使用,因此我们完全可以把这两类实参值的内存数据直接分配(move)给coll集合,并且把原实参值(x和x+x)清空(这样做的目的是考虑到x对象的析构函数可能对move的内存块执行一些操作)。
自 c++11 起,上述行为成为可能,但是需要程序员显式地指明不再被使用的实参,具体代码实现如下所示:
X x;
coll.insert(x);              // insert中建立x的一份拷贝.
coll.insert(x+x);            // 将(x+x)临时变量的内存搬迁到 coll 集合中.
coll.insert(std::move(x));   // 将实参 x 的内存搬迁到 coll 集合中.
【注】当且仅当X为有特定实现的复杂类型时才支持搬迁语义(如C++11标准实现的STL模板类string、complex等),int、double等基本类型不支持。
详细了解 Move 语义和 Rvalue Reference 可访问该网址

8.新式的字符串字面常量 (String Literal)

自 c++11 起,你可以定义 raw string 和 multibyte/wide-character 等字符串字面常量。

a) Raw String Literal

Raw string 允许我们定义字符序列,做法是确切写下其内容使其成为一个 raw character sequence。于是你可以省下很多用来装饰特殊字符的escape符号。
Raw string 以 R"( 开头,以 )" 结尾,可以内含 line break (行中断符)。例如以下两种写法是相同的:
// 均表示字符串:\\n.
const char * pStr1 = "\\\\n";
const char * pStr2 = R"(\\n)";
Raw string 的完整语法是 R"delim(...)delim",其中 delim 是个字符序列,最多 16 个基本字符,不可含反斜线、空格和小括号。举个例子,以下两种形式是等同的:
// 均表示字符串:a\[\n]     b\nc()"[\n]123456.
const char * pStr1 = "a\\\n     b\\nc()\"\n123456";
const char * pStr2 = R"nc(a\
     b\nc()"
123456)nc";   // 注:()两端的nc表示raw string以"nc(和"nc)为起止符,遇到"(和)"不中断.
注:定义正则表达式(regular expression)时 raw string literal 特别有用。

b) 编码的 (Encoded) String Literal

只要使用编码前缀,你就可以为 string literal 定义一个特殊的字符编码。下面这些编码前缀都预先定义好了:
u8 定义一个 UTF-8 编码。UTF-8 string literal 以 UTF-8 编订的某个给定字符起头,字符类型为 const char。

u 定义一个 string literal,带着类型为 char16_t 的字符。

U 定义一个 string literal,带着类型为 char32_t 的字符。

L 定义一个 wide string literal,带着类型为 wchar_t 的字符。

代码示例:
wchar_t * pStr = L"Hello world!"; // 将 Hello world! 定义为 wchar_t string literal类型的字符串.

9.关键字 noexcept

关键字 noexcept 用来指明某个函数无法(或不打算)抛出异常。例如:
void foo() noexcept; // 如果foo()抛出异常,程序会被终止,然后std::terminate()被调用并默认调用std::abort().

10.关键字 constexpr

关键字 constexpr 可用来让表达式核定于编译期。例如:
constexpr int square(int x)
{
return x * x;
}
float a[square(10)]; // 由于square()函数在编译期即被运行,因此此处可成功分配100个单元的float型数组.
a) constexpr函数要求所定义的函数足够简单以使得编译时就可以计算其结果:
constexpr int MAX(int a, int b)
{
return a > b ? a : b;
}
b) constexpr还能修饰类的构造函数,即保证传递给该构造函数的所有参数都是constexpr,那么产生的对象的所有成员都是constexpr,该对象也是constexpr对象了,可用于只使用constexpr的场合。 
class Test
{
public:
constexpr Test(int arg1, int arg2) : v1(arg1), v2(arg2) {}
private:
int v1;
int v2;
}

constexpr Test A(1,2)
enum e = {x = A.v1, y = A.v2};
注:constexpr构造函数的函数体必须为空,所有成员变量的初始化都放到初始化列表中。
c) 使用 constexpr 的好处:是一种很强的约束,更好的保证程序的正确定语义不被破坏;

编译器可以对constexper代码进行非常大的优化,例如:将用到的constexpr表达式直接替换成结果;

相比宏来说没有额外的开销。

11.崭新的 Template 特性

Variadic Template

自 c++11 起, template 可拥有“得以接受个数不定之template实参”的参数。此能力称为 variadic template(即可变参数函数)。例如:
#include <iostream>
#include <string>
using namespace std;

void print(){}

//// 注掉void print(){},保留以下函数编译器也不会报错.
//template <typename T>
//void print(const T& arg)
//{
//	std::cout << 0 << "\t" << arg << std::endl;
//}

template <typename T, typename ... Types>
void print(const T& firstArg, const Types&... args)
{
// 在variadic template内,sizeof...(args)会生成实参个数.
// std::tuple<> 大量使用了这一特性.
std::cout << sizeof...(args) << "\t" << firstArg << std::endl;
// 递归调用print函数.
print(args...);
}

int main()
{
print("hello world", 123, 123.21, 789.6f, 'W', std::string("std::string"));
return 0;
}
输出结果:
5       hello world
4       123
3       123.21
2       789.6
1       W
0       std::string

Alias Template(带别名的模板,或者叫Template Typedef)

自 c++11 起,支持template (partial) type definition。然而由于关键字typename用于此处时总是出于某种原因而失败,所以引入关键字using,并因此引入了一个新术语alias template。例如:
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>; // 标准容器使用自己的allocator.
Vec<int> coll;

// 等价于:

std::vector<int, MyAlloc<int>> coll;

其他的 Template 新特性

自 c++11 起,函数模板可拥有默认的模板实参。此外,自定义类型可被当作模板实参。

12.Lambda

详细了解lambda单击这里这里

13.关键字 decltype

新关键字 decltype 可让编译器找出表达式类型(即升级版的typeof)。例如:
std::map<std::string, float> coll;
decltype(coll)              coll_copy; // coll_copy的类型与coll一致.
decltype(coll)::value_type  map_pair;  // map_pair的类型为coll的子集类型(pair).
decltype的应用之一是声明返回类型,另一用途是在metaprogramming(超编程)或用来传递一个lambda类型。

14.新的函数声明语法

有时候,函数的返回值类型取决于某个表达式对实参的处理,例如:
// 合乎逻辑,但不合乎c++11语法.
template <typename T1, typename T2>
decltype(x + y) Add1(T1 x, T2 y)
{
return x + y;
}
// 合乎逻辑,且合乎c++11语法.
template <typename T1, typename T2>
auto Add2(T1 x, T2 y) -> decltype(x + y)
{
return x + y;
}

15.带领域的(Scoped)Enumeration

枚举类型在C++里用的最多就是声明某种数值类型,之后用switch来分别处理这些类型;

C++11 定义了一种 enumeration(枚举类型),它区别于C的 enumerators(枚举器),这种类型可以定义枚举常量的类型为数值类型,比如char,不能定义非数值类型的枚举常量有点遗憾. 还可以给枚举常量增加使用范围, 也就是scoped enumerations;

范围枚举不能通过=来直接和数值类型相互转化, 这就增加了安全性。

详细了解scoped enumeration单击这里

16.新的基础类型

c++11 定义了以下新式基本数据类型:
char16_t 和 char32_t
long long 和 unsigned long long
std::nullptr_t
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  STL C 11新特性