C++11/C++14新特性

原文链接 https://www.bogotobogo.com/cplusplus/C11/C11_initializer_list.php

Table of Contents

一、初始化列表:Initializer lists

二、统一初始化:Uniform initialization

三、类型推理:auto和基于范围的循环:Range-based for loop

3.1 auto

3.2 基于范围的循环:Range-based for loop

四、空指针常量:nullptr和强类型枚举:Strong typed enumerations

4.1 nullptr

4.2 强类型枚举:Strong typed enumerations

五、静态断言:Static assertion和构造函数委托:Constructor delegation 

5.1 static_assert

5.2 构造函数委托:Constructor delegation 

5.3 拷贝构造函数使用委托:Copy constructor using constructor delegation

六、重写:override和final

6.1 override

6.2 final 

七、default和delete

7.1 default

7.2 delete

八、constexpr和string literals

8.1 constexpr

8.2 string literals

九、lambda 函数与表达式:Lambda functions and expressions 

9.1 lambda functions

十、std容器: std::array container

10.1 array

10.2 简单介绍array类:simple version of std::array class


C++11/C++14新特性

一、初始化列表:Initializer lists

之前我们初始化一个vector容器,需要:

// C++03
std::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

现在,使用c++11,我们可以:

// C++11
std::vector v = { 1, 2, 3, 4 };

c++11结合了模板的概念,使用初始化列表,允许构造函数和其他函数使用初始化列表作为参数,例:

#include <iostream>
#include <vector>

class MyNumber
{
public:
    MyNumber(const std::initializer_list<int> &v;) {
        for (auto itm : v) {
            mVec.push_back(itm);
        }
    }

    void print() {
	for (auto itm : mVec) {
	    std::cout << itm << " ";
	}
    }
private:
    std::vector<int> mVec;
};

int main()
{
    MyNumber m = { 1, 2, 3, 4 };
    m.print();  // 1 2 3 4

    return 0;
}

二、统一初始化:Uniform initialization

使用c++11,可以使用同样方法(leo注: 使用大括号{ })进行所有的初始化,包括:

  1. 动态分配数组的初始化:
    int *pi = new int[5]{1, 2, 3, 4, 5};
    
  2. 数组成员变量的初始化:
    class A {
      int arr[3];
    public:
      A(int x, int y, int z) : arr{x, y, z} { };
    };
    
  3. STL容器的初始化:
     std::vector v1{1, 2};
    
  4. 隐式初始化返回对象:
    return {foo, bar};
    
  5. 隐式初始化函数参数:
    f({foo, bar});

另外、优先级顺序如下:

  1. 初始化列表
  2. 常规构造函数
  3. 聚合初始化
#include <iostream>
#include <vector>

class A
{
public:
	int mx;
	double my;
};

class B
{
public:
	B(int x, double y) : mx{ x }, my{ y } {}

	int mx;
	double my;
};

class C
{
public:
	C(int x, double y) : mx{ x }, my{ y } {}
	C(const std::initializer_list<int>& v) {
		mx = *(v.begin());
		my = *(v.begin() + 1);
	}

	int mx;
	double my;
};

int main()
{
	// Aggregate initialization
	A a{ 1, 3.7 };

	// Regular constructor
	B b{ 2, 9.8 };

	// Initializer_list
	C c{ 3, 7 };

	std::cout << a.mx << " " << a.my << std::endl;
	std::cout << b.mx << " " << b.my << std::endl;
	std::cout << c.mx << " " << c.my << std::endl;

	return 0;
}

a的初始化行为类似于聚合初始化。也就是说,对象的每个数据成员,将使用初始化列表的相应值重复初始化。

必要时将使用隐式转换。如果不再存在转换或者仅有缩窄转换(leo注:比如从float转int丢失了精度),则程序是不健全的。 

b的初始化调用构造函数。

如果一个类有一个初始化列表的构造函数,则它的优先级高于其他形式的构造函数。

三、类型推理:auto和基于范围的循环:Range-based for loop

3.1 auto

在C++03,我们必须在申明时确定对象类型 。 现在,C++ 11允许我们在不指定它们的类型的情况下声明对象。

auto a = 2;   // a is an interger
auto b = 8.7; // b is a double
auto c = a;   // c is an integer

而且,关键字auto对于减少代码的冗长性非常有用。例,原来写法:

for (std::vector<int>::const_iterator it = v.begin(); it != v.end(); ++it)

我们可以使用更短的版本:

for (auto it = v.begin(); it != v.end(); ++it)
#include <vector>

int main()
{
	std::vector<int> a, b;
	std::vector<int> v{ 1, 2, 3, 4, 5 };
	for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
		a.push_back(*it);
	for (auto it = v.begin(); it != v.end(); ++it)
		b.push_back(*it);
}

关键字decltype可以用来确定编译时表达式的类型,例如:

#include <vector>
int main() 
{
    const std::vector<int> v(1);
    auto a = v[0];        // a has type int
    decltype(v[1]) b = 1; // b has type const int&, the return type of
                          //   std::vector<int>::operator[](size_type) const
    auto c = 0;           // c has type int
    auto d = c;           // d has type int
    decltype(c) e;        // e has type int, the type of the entity named by c
    decltype((c)) f = c;  // f has type int&, because (c) is an lvalue
    decltype(0) g;        // g has type int, because 0 is an rvalue
}

注意,由decltype表示的类型可以与auto推导的类型不同。

3.2 基于范围的循环:Range-based for loop

C++ 11扩展了for语句的语法,以便于在一系列元素上进行迭代。这种形式的迭代将遍历列表中的每个元素。它将适用于C样式的数组、初始化列表以及具有begin()和end()函数并返回迭代器的类型。(leo注:原文真是绕口啊...)

所有具有begin/end 对的标准库容器将与基于范围的语句一起使用。

#include <iostream>
#include <vector>

int main()
{
	std::vector<int> a, b;
	std::vector<int> v{ 1, 2, 3, 4, 5 };

	// C++03
	for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
		a.push_back(*it);

	// C++11
	for (auto it = v.begin(); it != v.end(); ++it)
        b.push_back(*it);

	for (int item : a)
		std::cout << item << " ";  // read only access

	std::cout << std::endl;

	for (auto &item: b) {//leo注:原文此处有语法错误,多出一分号
		item *= 10;
		std::cout << item << " ";  // read only access
	}
}
输出:
1 2 3 4 5
10 20 30 40 50

四、空指针常量:nullptr和强类型枚举:Strong typed enumerations

4.1 nullptr

常数 0 有双重身份: 常量整型和null指针常量。 在C中,通过使用预处理器宏NULL处理了 0 双重含义中固有的模糊性,该宏NULL通常扩展到((void*)0)或0。C++没有采用相同的行为,只允许 0 作为空指针常量。这在函数重载时交互作用很差:

void foo(char *);
void foo(int);

如果NULL被定义为0,那么函数foo(NULL)将调用哪个函数?

它可能将调用foo(int),这几乎肯定不是程序员想要的。换句话说,这有歧义。

因此,C++ 11通过引入一个新的关键字作为一个区分的空指针常数(nullptr)来纠正这个问题:

char *pc = nullptr;     // OK
int  *pi = nullptr;     // OK
bool   b = nullptr;     // OK. b is false.
int    i = nullptr;     // error

foo(nullptr);           // calls foo(char *), not foo(int);

4.2 强类型枚举:Strong typed enumerations

在C++03中, 枚举不是类型安全的。 它们是有效的整数,即使枚举类型是不同的。这允许对不同枚举类型的两个枚举值进行比较。

   // C++ 03

    enum Day {Sunday, Monday, Tuesday};
    enum Month {January, February, March};

    Day d = Monday;
    Month m = February;

    if (d == m)
	std::cout << "Monday == February\n";  // This will be printed out
    else
	std::cout << "Monday != February\n";

    // C++ 11
    enum class Day { Sunday, Monday, Tuesday };
    enum class Month { January, February, March };

    Day d = Day::Monday;
    Month m = Month::February;

    // No "==" operator is defined for the object type nor overloaded
    // So, in C++ 11, this is an compile time error
    if (d == m)
	std::cout << "Monday == February\n";
    else
	std::cout << "Monday != February\n";

五、静态断言:Static assertion和构造函数委托:Constructor delegation 

5.1 static_assert

static_assert 声明在编译时测试软件断言。这对于模板代码尤其有用。语法如下:

static_assert ( bool_constexpr , string ) 		

其中:

  1. bool_constexpr: 一个布尔型的常量表达式;
  2. string: 如果bool_constexpr为false,自定义字符串将作为编译器错误出现。
// run-time assert
assert(ptr != NULL)

// C++ 11
// compile-time assert
static_assert(sizeof(void *) == 4, "64-bit is not supported.");//leo注:该static_assert用来确保编译仅在32位的平台上进行,不支持64位的平台 

5.2 构造函数委托:Constructor delegation 

在 C++03,类的构造函数不允许调用该类的其他构造函数。每个构造函数必须构造它的所有类成员本身,或者调用一个公共成员函数:

// C++03
class A
{
    void init() { std::cout << "init()"; }
    void doSomethingElse() { std::cout << "doSomethingElse()\n"; }
public:
    A() { init(); }
    A(int a) { init(); doSomethingElse(); }
};

C++ 11允许构造函数调用其他对等构造函数(称为委托)。这允许构造函数利用最少的附加代码使用另一构造函数:

// C++11
class A
{
    void doSomethingElse() { std::cout << "doSomethingElse()\n"; }
public:
    A() { ... }
    A(int a) : A() { doSomethingElse(); }
};

5.3 拷贝构造函数使用委托:Copy constructor using constructor delegation

下面的代码演示了构造函数委托的另一种用法(构造函数调用同一个类的另一个构造函数):拷贝构造函数。正如我们所看到的,拷贝构造函数首先调用默认构造函数,然后用刚刚传入的对象的成员初始化当前对象成员:

A(const A& b) : A()
{
  m_a = b.m_a;
}

六、重写:override和final

6.1 override

在C++03中,当试图重写基类函数时,意外创建新的虚拟函数是可能的。例如:

// C++03
class Base
{
    virtual void f(int);
};

class Derived : public Base
{
    virtual void f(float);
};

目的是重写Base::f()并使用 Derived::f()。但是,因为它有不同的签名,所以它创建了第二个虚函数。

// C++11
class Base
{
    virtual void f(int);
};

class Derived : public Base
{
    virtual void f(float) override;  // Error
};

重写特殊标识符意味着编译器将检查基类,以查看是否存在具有此准确签名的虚拟函数。如果没有,编译器会报错。 

6.2 final 

C++11 还增加了防止继承类或简单地防止派生类中重写方法的能力。这是用特殊标识符final完成的。例如:

// no class can be derived from class A
class A final
{
    virtual void f(int);
};

class B
{
    // no class can override f()
    virtual void f() final; 
};

所以,如果我们像下面这样做了,编译器报错:

class C : public A {};  // Error

class D : public B      // Error
{
    virtual void f();
};

注意 override 和 final 不是语言关键字。 严格来说它们是声明属性的标识符。

七、default和delete

7.1 default

如果用任何构造函数定义一个类,编译器将不会生成默认构造函数。这在很多情况下是有用的,但有时是令人烦恼的。例如:

class A
{
public:
    A(int a){};
};

如果我们:

A a;

编译器抱怨我们没有默认构造函数。这是因为编译器没有为我们做一个,因为我们已经有了一个我们定义的。

我们可以通过使用default说明符来强制编译器为我们创建一个:

class A
{
public:
    A(int a){}
    A() = default;
};

编译器不会再抱怨我们没有默认构造函数:

A a;

7.2 delete

假设我们有一个带有整数的构造函数的类:

class A
{
public:
    A(int a){};
};

以下三个操作将成功:

A a(10);     // OK
A b(3.14);   // OK  3.14 will be converted to 3
a = b;       // OK  We have a compiler generated assignment operator

 然而,如果这不是我们想要的。我们不希望构造函数允许双类型参数,也不允许赋值工作。

C++11允许我们通过使用delete来禁用某些特征: 

class A
{
public:
    A(int a){};
    A(double) = delete;         // conversion disabled
    A& operator=(const A&) = delete;  // assignment operator disabled
};

然后,如果我们编写代码如下:

A a(10);     // OK
A b(3.14);   // Error: conversion from double to int disabled
a = b;       // Error: assignment operator disabled

这样,我们就能实现我们的目标。

八、constexpr和string literals

8.1 constexpr

C++一直有常量表达式的概念。

这些表达式在编译时和运行时都会产生相同的结果,例如3 + 4。 常量表达式是编译器的优化机会,编译器经常在编译时执行它们,并在程序中硬编码结果。(leo注:第一次听说硬编码这个表达,还是孤陋寡闻了。硬编码是将数据直接嵌入到程序或其他可执行对象的源代码中的软件开发实践,与从外部获取数据或在运行时生成数据不同:称作非硬编码或软编码)

// C++03
int a[5];          // OK
int f() { return 7; }  // OK
int a[5 + f()];    // compile error

// C++11
constexpr int g() { return 7; }  // OK
int b[5 + g()];    // create an array of 12 ints (note: vs2013RC doesn't seem to support this)

8.2 string literals

// C++03
char* a = "string";

// C++11
char *    a = u8"string";   // UTF-8 string
char16_t* b = u"string";    // UTF-16
char32_t* c = U"string";    // UTF-32
char*     r = R"string";    // raw string

九、lambda 函数与表达式:Lambda functions and expressions 

9.1 lambda functions

C++ 11提供创建匿名函数的能力, 称作 lambda 函数。它允许在另一个表达式中需要的点定义函数。它是一个函数,我们可以在代码中内联,以便传递给另一个函数。

以前,for_each() 需要一个命名函数,如下面的例子所示:

/* lambda.cpp */
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

// assign a value to each element of a vector
void assign(int& v)
{
  static int n = 1; v = n++;
}

// print out each element
void print(int v)
{
  cout << v << " ";
}

int main()
{
  vector<int> vec(10);
  // output initial value of each element
  for_each(vec.begin(), vec.end(), print);
  cout << endl;

  // assign a value to each element of a vector
  for_each(vec.begin(), vec.end(), assign);

  // output updated value of each element
  for_each(vec.begin(), vec.end(), print);
  return 0;
}
输出:
$ g++ -std=c++11 -o lambda lambda.cpp
$ ./lambda
0 0 0 0 0 0 0 0 0 0 
1 2 3 4 5 6 7 8 9 10 

现在,我们可以使用lambda 函数 [](){} 对于 print() 和 assign() 函数:

/* lambda2.cpp */
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
  vector<int> vec(10);
  // output initial value of each element
  // for_each(vec.begin(), vec.end(), print); ==>
  for_each(vec.begin(), vec.end(), [](int v) {cout << v << " ";});
  cout << endl;

  // assign a value to each element of a vector
  // for_each(vec.begin(), vec.end(), assign); ==>
  for_each(vec.begin(), vec.end(), [](int& v) {static int n = 1; v = n++;});

  // output updated value of each element
  // for_each(vec.begin(), vec.end(), print); ==>
  for_each(vec.begin(), vec.end(), [](int v) {cout << v << " ";});
  return 0;
}

输出和原来一样:

$ g++ -std=c++11 -o lambda2 lambda2.cpp
$ ./lambda2
0 0 0 0 0 0 0 0 0 0 
1 2 3 4 5 6 7 8 9 10

lambda 表达式定义了一个独立的函数,它不需要任何参数,只依赖于全局变量和函数。它甚至不必返回一个值。这样的lambda表达式是括在括号内的一系列语句, 前缀 [],称为 lambda 介绍器或捕获规范,它告诉编译器我们正在创建lambda函数, [](){}。

让我们看看lambda函数:

#include <iostream>
#include <vector>
#include <algorithm>

void foo() { std::cout << "foo()\n"; }
void bar() { std::cout << "bar()\n"; }

int main ()
{
	// 1st lambda function
	auto f  = []() {
		foo();
		bar();
	};
	// the lambda function does something here
	f();

        // 2nd lambda function
	std::vector<int> v(5, 99);
	std::for_each(v.begin(), v.end(), [](int i){std::cout << i << "\n";});

	return 0;
}
输出:foo()
bar()
99
99
99
99
99

第一个lambda表达式是非常不寻常的,因为它在括号内没有参数。如果需要获取参数,我们可以使用参数列表跟随lambda导入器,就像写vectors的所有元素的第二个示例中所示的普通函数一样。

更多示例

这里有更多的样本代码:

int main()
{
    // (1)
    std::cout << [](int a, int b){return a*b; }(4, 5) << std::endl; // 20

    // (2)
    auto f = [](int a, int b) { return a*b; };
    std::cout << f(4, 5) << std::endl;  // 20
}

(1) 和 (2)等价, 且都输出结果20。

9.2 lambda函数返回值

编译器可以从lambda函数推断返回值类型,如下面示例的第1例所示。然而,我们仍然可以明确地指定其返回类型,如在第2例中:

/* lam.cpp */
#include <iostream>
using namespace std;

int main()
{
  /* case #1 - compiler deduces return type */
  cout << [](int n) {return n*n;} (5);
  cout << endl;
  /* case #2 - explicit return type */
  cout << [](int n)->int {return n*n;} (5);

  return 0;
}
输出:
$ g++ -std=c++11 -o lam lam.cpp
$ ./lam
25
25

十、std容器: std::array container

10.1 array

新的std::array是一个用于恒定大小数组的容器。它是在数组中定义的顺序容器类,它在编译时指定一个固定长度的数组。

它可以用聚合初始化来初始化,在大多数n个初始化器中都是可转换的。

T: std::array<int, 5> a = {1, 2, 3, 4, 5};

它结合了C型数组的性能和可访问性和标准容器的优点,如了解其自身的大小、支持分配、随机访问迭代器等。

下面的代码演示了如何处理std::array容器:

/* arr1.cpp */
#include <string>
#include <iterator>
#include <iostream>
#include <algorithm>
#include <array>

int main()
{
    // construction uses aggregate initialization
    std::array<int, 5> i_array1{ {3, 4, 5, 1, 2} };  // double-braces required
    std::array<int, 5> i_array2 = {1, 2, 3, 4, 5};   // except after =
    std::array<std::string, 2> string_array = { {std::string("a"), "b"} };

    std::cout << "Initial i_array1 : ";
    for(auto i: i_array1)
        std::cout << i << ' ';
    // container operations are supported
    std::sort(i_array1.begin(), i_array1.end());

    std::cout << "\nsored i_array1 : ";
    for(auto i: i_array1)
        std::cout << i << ' ';

    std::cout << "\nInitial i_array2 : ";
    for(auto i: i_array2)
        std::cout << i << ' ';

    std::cout << "\nreversed i_array2 : ";
    std::reverse_copy(i_array2.begin(), i_array2.end(),
                      std::ostream_iterator<int>(std::cout, " "));

    // ranged for loop is supported
    std::cout << "\nstring_array : ";
    for(auto& s: string_array)
        std::cout << s << ' ';

    return 0;
}
输出:
$ g++ -std=c++11 -o arr1 arr1.cpp
$ ./arr1
Initial i_array1 : 3 4 5 1 2 
sored i_array1 : 1 2 3 4 5 
Initial i_array2 : 1 2 3 4 5 
reversed i_array2 : 5 4 3 2 1 

10.2 简单介绍array类:simple version of std::array class

下面是一个使用std::array 类的简化版本的例子:(leo注:std::array原理)

/* arr2.cpp */
#include <iostream>
using namespace std;

template <typename T, int n>
class myArray
{
  public:
    myArray() {a = new T[n];}
    ~myArray() {delete[] a;}
    T& operator[](int i) {return *(a+i);}

  private:
    T* a;
};

int main()
{
  myArray<int, 5> arr;

  for(int i = 0; i < 5 ; i++)
        cout << arr[i] << ' ';

  return 0;
}
输出:
$ g++ -std=c++11 -o arr2 arr2.cpp
$ ./arr2
0 0 0 0 0 

请注意,该示例只是演示如何实现std::array。所以,它非常原始,甚至我们不得不重写[]操作符。

 

  • 12
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值