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}; // 报错
只要遇到类型收窄的情况,初始化列表就会报错。