C++11 新特性整理 (1)

C++11对模板细节的改进:
1. C++11改善了编译器的解析规则,尽可能地将多个右尖括号(>)解析为模板参数结束符,例如在C++98中:

Foo<A<int> > xx;   // 两个右尖括号必须有空格,否则会被编译器解析成右移操作符
return 0;

在C++11中则完全取消了这种限制。

2. 模板的别名:
typedef可以重定义类型,例如:

typedef unsigned int uint;

但是无法重定义模板,在C++11中出现了重定义一个模板的语法:

using 的别名语法涵盖了typedef的全部功能:

template<typename Val>
using map_str_int = std::map<std::string, Val>;

map_str_int<int> map1;

而在C++98中,定义模板的别名只能通过一个外敷类来实现:

template<typename Val>
struct map_str_int    // 外敷类
{
	typedef std::map<std::string, Val> type;
};

map_str_int<int>::type map1;

列表初始化:

C++11中引入了列表初始化的概念,C++98中,只有数组和结构体可以进行列表初始化:

// 数组
int array[] = { 1,2,3 };

// 结构体
struct A
{
	int x;
	int y;
} a = {1, 2};

C++11中,列表初始化的范围增大了,现在它可以用于任何类型对象的初始化:

class Foo
{
public:
	Foo(int) {}
};

int main()
{
	Foo a1(23);        // 常规的初始化
	Foo a2 = { 123 };  // 列表初始化,虽然有=,但是也是初始化
	Foo a3 { 123 };    // 与a2等价

	int b1 = { 3 };
	int b2{ 3 };     // 列表初始化

	int c1[3]{ 1,2,3 };

	// POD类型的初始化  
	// plain old data , 可以直接使用memcpy复制的对象
	struct A
	{
		int x;
		struct B
		{
			float y;
			float z;
		} b;
	};

	A a = { 2, {2.3, 4.4} };
	A b { 2,{ 2.3, 4.4 } };

	// 指针初始化
	int* d1 = new int{ 12 };
	int* arr = new int[3] { 1,2,4 };

	
	return 0;
}

// 列表初始化可以直接使用在函数的返回值上面
struct Value
{
	int x;
	int y;
	Value(int x, int y) {}
};

Value func()
{
	return {12, 22};
}

列表初始化中的一些细节问题:
在这里需要区分一下:列表初始化被用于自定义类型的时候:
1. 自定义的类型中没有构造函数

聚合类型初始化,以拷贝的形式,用初始化列表中的值来初始化成员

// 结构体
struct A
{
	int x;
	int y;
} a = {1, 2};    // C++98 聚合类型初始化

2. 自定义的类型中有构造函数

利用构造函数进行初始化

struct B
{
	int x;
	int y;
	B(int x, int y) {}
} b = {1, 2};

在使用列表初始化时,如果类型满足以下的条件,C++会认为它是一个聚合体:
1. 类型是普通的数组

2. 类型是一个类,且满足:

a. 没有自定义的构造函数

b. 没有基类

c. 没有虚函数

d. 没有private或者protected的非静态数据成员(也就是说非静态的数据成员不能是private或者protected)

e. 不能有{}和=直接初始化的非静态数据成员

例如:

// 有private或者protected的数据成员
struct ST
{
	int x;
	int y;
private:
	int z;
};

ST s(1, 2, 3);   // invalid 

// 有private或者protected的静态数据成员
struct SU
{
	int x;
	int y;
protected:
	static int z;
};

SU s(1, 2);   // valid protected的时静态数据成员

// 有== {} 初始化的非静态数据成员
struct SN
{
	int x;
	int y = 3;
};

SN s(1, 4);

对于非聚合类型的对象,如果需要使用列表初始化,就需要自定义构造函数才可以.

聚合类型的定义是非递归的:当一个类的非静态成员是非聚合类型的时候,这个类也有可能是聚合类型。

例如:

struct ST
{
	int x;
	double y;
private:
	int z;
};

struct Foo
{
	ST st;    // 非聚合类型
	int x;
	double y;
};

// 初始化
Foo foo{ {}, 1, 2.4 };     // 第一个{}相当于调用ST的无参构造函数

所以,列表初始话的过程可以总结为:对于聚合类型,使用初始化列表相当于对其中的每个元素分别赋值;对于非聚合类型,则需要先定义一个合适的构造函数,是用初始化列表相当于调用对应的构造函数。

STL中的容器也支持初始化列表的操作,例如:

// 初始化列表
int arr[] {1,3,4};
// container
std::map<std::string, int> mp = { {"a", 2}, {"b", 4}, {"c", 6} };
std::set<int> ss {1,2,3,4};
std::vector<int> v {1,2,3,4};

STL中的容器是通过使用std::initializer_list实现对初始化列表的支持的,std::initializer_lis用来接收传进来的参数,然后将接收到的参数传入到容器中,例如,自定义一个容器:

// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <memory>
#include <map>
#include <vector>

class myVector
{
private:
	std::vector<int> content;

public:
	myVector(std::initializer_list<int> list)    //构造函数
	{
		// 使用初始化列表相当于调用构造函数, 构造函数将传进来数据进行处理
		for (auto iter = list.begin(); iter != list.end(); iter++)
		{
			// 将list中的数据放入容器
			content.push_back(*iter);
		}
	}
};


class myMap
{
private:
	std::map<std::string, int> content;

public:
	using pair_t = std::map<std::string, int>::value_type;   // using重定义  pair的类型
	myMap(std::initializer_list<pair_t> list)
	{
		for (auto iter = list.begin(); iter != list.end(); iter++)
		{
			content.insert(*iter);
		}
	}

};


int main()
{
	myVector v1 {1,2,3,4};
	myMap mp1{ {"a", 1},{"b", 2},{"c", 3} };
	return 0;
}

通过上述的方法,了解了std::initializer_list的工作原理,它用来接受初始化列表,此外,它还可以作为函数的参数进行传递,例如:(传递同类型参数)

template<typename T>
void func(std::initializer_list<T> l)
{
	for (auto iter = l.begin(); iter != l.end(); iter++)
	{
		std::cout << *iter << " ";
	}
	std::cout << std::endl;
}

int main()
{
	func({ "hello","ya","oaix" });
	return 0;
}

std::initializer_list的细节介绍:

1. 首先,它是一个轻量级的容器,内部定义了iterator等容器必须的概念

2.他可以按照任意的长度接受初始化列表,但是列表中的元素必须是同一种类型的

3.3个成员接口,begin, end, size

4. 只能被整体初始化或者赋值

5. 返回的迭代器是只读的,不能用返回的迭代器对元素进行修改

实际上,std::initializer_list的效率是非常高的,因为他保存的不是初始化列表中元素的拷贝,而实元素的引用,所以不能用于函数返回值

防止类型收窄:

除了上面介绍的功能之外,初始化列表还有一个作用,那就是防止类型收窄,在以下的几种情况中,会出现类型收窄:
1. 从浮点隐式的转化为整数

’2.从高精度隐式的转化为低精度

3. 从整型数隐式的转化为浮点数,并且超出了浮点数的范围

4. 从整型数隐式的转化为一个长度较短的整型数,且超出了范围

如果使用初始化列表,就可以防止上述的错误:

int a = 1.1;   // ok
int b {1.2};   // 报错

float fa = 1e40; // ok
float fb {1e40}; // 报错 超出范围


int x = 1024;

char y = x;   //ok
char z {x};   // 报错

只要遇到类型收窄的情况,初始化列表就会报错。

posted @ 2019-08-15 14:33  Alpha205  阅读(122)  评论(0编辑  收藏  举报