一、列表初始化
1. { } 初始化
C++98 中的 { } 只能用于数组和C 风格结构体;而 C++11 引入了统一初始化,几乎可以用在任何地方,包括普通变量、类对象和 STL 容器。
在 C++98 中,花括号 { } 被称为,有许多限制,只能用于(不能有构造函数、不能有 private 成员、不能有虚函数),,如下所示:
C++11 引入了统一初始化、自动类型推导关键字(auto、decltype、nullptr)、范围 for 循环、STL 新容器及接口、右值引用与移动语义、类的新功能以及可变参数模板。这些特性提升了代码的安全性、可读性和执行效率,是 C++ 现代化开发的核心基础。

C++98 中的 { } 只能用于数组和C 风格结构体;而 C++11 引入了统一初始化,几乎可以用在任何地方,包括普通变量、类对象和 STL 容器。
在 C++98 中,花括号 { } 被称为,有许多限制,只能用于(不能有构造函数、不能有 private 成员、不能有虚函数),,如下所示:
// 数组 int arr1[5] = { 1, 2, 3, 4, 5 }; // 合法:定义数组并初始化
int arr2[] = { 1, 2, 3 }; // 合法:自动推断大小
int arr3[10] = { 0 }; // 合法:将第一个元素设为 0,其余自动初始化为 0
// int arr[3] {1, 2, 3}; // Error: C++98 不允许省略等号
// C 风格结构体(即:不能有构造函数、不能有 private 成员、不能有虚函数)
struct Point { int x, y; };
Point p = { 1, 2 }; // OK
// 容器
// std::vector<int> vec = {1, 2, 3}; // Error: C++98 没有 initializer_list
std::vector<int> vec;
vec.push_back(1); // 只能这样繁琐地添加
vec.push_back(2);
而在 C++11 中,就将 { } 提升为统一初始化,扩大了用大括号括起的列表 (初始化列表) 的使用范围,使其可用于所有的内置类型和用户自定义的类型。使用初始化列表时,可添加等号 (=),也可不添加。简单来说就是在 C++11 中一切都可以通过 { } 进行初始化。
std::initializer_list 是 C++11 才增加的一个类型,用于表示和访问一组特定类型的常量值的数组,主要用于支持使用花括号 { } 进行的列表初始化。当我们使用 { } 初始化时,编译器会将数据列表转换成一个 initializer_list。
使用场景
std::initializer_list 一般是作为构造函数的参数。在 C++11 中,STL 的容器中增加了许多这样的构造函数,例如 vector 和 map。
对于其他 STL 容器也有一个 initializer_list 参数的构造函数。也正是因为有了这样的构造函数,我们才可以使用 { } 直接初始化各个容器对象,如:
// 初始化 vector
std::vector<int> v = { 1, 2, 3, 4, 5 }; // 调用了 initializer_list 的构造函数
除了构造时有 initializer_list 的使用,operator= 也重载了 initializer_list 参数的使用。
std::vector<int> v;
v = { 1, 2, 3, 4, 5 }; // 调用了 initializer_list 参数的 operator=
在 C++98/03 标准中,auto 用于声明一个具有自动存储期的变量,实际用途几乎为零。在 C++11 中则彻底废弃了 auto 在 C++98 中的旧含义,赋予了它新的功能:自动类型推导。即可以自动推导变量类型,在处理复杂类型的时候,大大方便了我们在 C++ 中的使用。
std::vector<std::string> v = { "sort","insert","find"};
// C++98: 类型冗长,可读性差
std::vector<std::string>::iterator it = v.begin();
// C++11: 简洁明了
auto it2 = v.begin(); // 编译器自动推导为 vector<string>::iterator
关键字 decltype 可以将变量的类型声明为表达式指定的类型。比如,我们有一个 int 类型的变量,现在我们想定义一个与 a 相同类型的变量 b,要求不使用 int 关键字,则就可以通过 decltype 来帮助我们完成定义。
它也适用于表达式,比如,我们要定义一个与一个表达式结果相同的变量,则就可以使用 decltype 来解决。
由于 C++ 中 NULL 被定义成字面量 0,这样就可能会带来一些问题,因为 0 既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11 中新增了 nullptr,用于表示空指针,不会有歧义了。
C++11 中,引入了基于范围的 for 循环,它适用于对一个有范围的集合进行遍历。
int main() {
int arr[] = { 1, 2, 3, 4, 5 };
for (int e : arr) { // 使用原本的类型 int 接收
std::cout << e << " "; // 输出当前元素
}
return 0;
}
这里的意思就是:每次循环,arr 的当前元素会被复制到 e 中(值传递),然后循环会依次访问数组中每一个元素。并且它会自动判断结束。
在 C++11 中的变化有一些几点:
1、增加了几个新容器
std::array 内部会进行严格的检查,安全性能更高。2、增加了新接口
它们其实就是 const 迭代器和 const 反向迭代器,实际意义不大,一般使用 begin、end、rbegin、rend 就够了。
3、所有容器都增加了 emplace 系列
这里的 emplace 需要我们理解了右值引用和可变模板参数才能理解。
4、容器增加了移动构造和移动赋值
移动构造和移动赋值可以大大节省我们使用容器的效率。
这是我们学习 C++11 中的一个难点。
传统的 C++ 语法中就有引用的语法,而 C++11 中新增了的右值引用语法特性,而我们之前使用到的引用叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
区分左值与右值关键在于这个数据可不可以取地址。
&&。以下代码是一个简单的 string 的模拟实现:
namespace MyCreate {
class string {
public:
string(const char*) :_size(strlen(str)) , _capacity(_size) {
std::cout << "string(char* str)" << std::endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
void swap(string& s) {
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s) :_str(nullptr) {
std::cout << "string(const string& s) -- 深拷贝" << std::endl;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s) {
std::cout << "string& operator=(string s) -- 深拷贝" << std::endl;
string tmp(s);
swap(tmp);
return *this;
}
~string() {
delete[] _str;
_str = nullptr;
}
void reserve(size_t n) {
if (n > _capacity) {
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
const char* c_str() const { return _str; }
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
移动构造
移动构造就是用一个**右值(通常是临时对象)**来构造一个新的对象,即:将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了。移动构造的代码如下所示:
// 移动拷贝
string(string&& s) noexcept :_str(nullptr) ,_size(0) ,_capacity(0) {
std::cout << "string(string&& s) -- 移动拷贝构造" << std::endl;
swap(s);
}
移动赋值
移动赋值就是用一个右值来给一个已经存在的对象赋值,也就是窃取别人的资源来赋值自己。
// 移动赋值
string& operator=(string&& s) noexcept {
std::cout << "string& operator=(string&& s) -- 移动赋值重载" << std::endl;
swap(s);
return *this;
}
move 函数的作用为:将一个左值转换为右值引用。从而让编译器认为这个对象可以被'移动'(即可以调用移动构造或移动赋值),而不是被'拷贝'。
MyCreate::string copy2 = move(s1); // 构造
MyCreate::string copy3;
copy3 = move(s2); // s2 的资源会被夺取
对于左值引用的使用场景就是:做函数参数,或则做函数返回值,这样可以减少拷贝,提高效率。
对右值引用,它的价值可以总结为:进一步减少拷贝,弥补左值引用没有解决的场景。
右值引用的常见使用场景:
在模板中的 && 代表了万能引用,它既可以引用左值,也可以引用右值。
template<typename T> void PerfectForward(T&& t) { // ... }
完美转发则可以在传参的过程中保留对象原生类型属性,如果是左值引用那就是左值引用,如果是右值引用,那就是右值引用。它必须在模板中使用,即必须配合万能引用一起使用。
原来 C++ 类中,有 6 个默认成员函数。而 C++11 则新增了两个:移动构造函数和移动赋值运算符重载。
default 关键字可以显示指定对应的默认成员函数生成。
delete 关键字则和 default 相对,delete 可以指示编译器不生成对应函数的默认版本,使用方法和 default 一样,只需要在该函数声明加上 =delete 即可。
在 C++11 以前,我们的模板中类模版和函数模版只能含固定数量的模版参数。而在 C++11 中增加了可变参数模板的概念,它可以让我们的参数可以传递很多个。
下面就是一个基本可变参数的函数模板:
// Args 是一个模板参数包,args 是一个函数形参参数包
template <class ...Args> void ShowList(Args... args) { // ... }
如果我们要获取一个参数包的值,这里有两种方法:递归函数方式展开和逗号表达式展开。
递归函数方式展开
void _ShowList() { std::cout << std::endl; }
template <class T, class ...Args> void _ShowList(T value, Args... args) {
std::cout << value << " ";
_ShowList(args...);
}
template <class ...Args> void PrintList(Args... args) { _ShowList(args...); }
逗号表达式展开
template <class T> void PrintArg(T t) { std::cout << t << " "; }
template <class ...Args> void ShowList(Args... args) {
int arr[] = { (PrintArg(args), 0)... };
std::cout << std::endl;
}
其中 emplace 对应 insert,都是插入的意思,emplace_back 对应 push_back 都是尾插的意思。可以看到,它们都是使用可变参数模板实现的,并且是万能引用。
那么,我们为什么需要 emplace 接口呢?
在 C++11 之前,我们通常使用 push_back 等接口向容器添加对象,但这种方法只能通过传递对象来添加对象。
而 C++11 中一般都是移动构造,所以一般来说 emplace 系列和常规的插入(insert,push_back 等)的效率其实都差不多。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online