《提前总结:一切皆可以使用{}进行初始化》
列表初始化
请好好看代码,理解代码,其实代码中的注释有很多精华的呢😍
C++98中传统的{}
C++98中,一般数组和结构体可以使用{}进行初始化:
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
C++11中的{}
在C++11之前,初始化对象的方式多种多样,这不仅使得代码难以统一,也增加了出错的可能性。C++11引入了列表初始化(也称为统一初始化),旨在简化和统一初始化方式,使得一切对象都可以通过花括号{}
进行初始化。
- 列表初始化的基本概念
列表初始化是C++11中的一项新特性,它允许我们使用花括号{}
来初始化对象。这种方式不仅适用于内置类型,也适用于自定义类型。对于自定义类型,列表初始化本质上是一种类型转换,中间可能会产生临时对象,但编译器优化后,可以变成直接构造。
-
列表初始化的优势
- 统一的初始化方式:C++11的列表初始化提供了一种统一的初始化方式,使得代码更加整洁和一致。
- 省略等号:在列表初始化中,我们可以省略掉等号
=
,直接使用花括号进行初始化。(但这只是省略,代码逻辑不变) - 方便容器操作:在容器中插入或push多参数构造的对象时,列表初始化提供了极大的便利。
- 列表初始化的示例
下面是一个简单的示例,展示了如何在C++11中使用列表初始化:
#include<iostream>
#include<vector>
using namespace std;
// 定义一个简单的Point结构体,包含两个整数成员变量
struct Point {
int _x; // x坐标
int _y; // y坐标
};
// 定义一个Date类,包含年、月、日三个私有成员变量
class Date {
public:
// 这是一个多参数的构造函数,提供了默认参数值
Date(int year = 1, int month = 1, int day = 1)
: _year(year), _month(month), _day(day) {
cout << "Date(int year, int month, int day)" << endl; // 构造时输出信息
}
// 拷贝构造函数,用于复制一个Date对象
Date(const Date& d)
: _year(d._year), _month(d._month), _day(d._day) {
cout << "Date(const Date& d)" << endl; // 拷贝构造时输出信息
}
private:
int _year; // 年份
int _month; // 月份
int _day; // 日期
};
int main() {
// C++98风格的数组初始化
int a1[] = { 1, 2, 3, 4, 5 }; // 初始化一个整型数组
int a2[5] = { 0 }; // 初始化一个整型数组,所有元素为0
Point p = { 1, 2 }; // 初始化一个Point对象,_x为1,_y为2
// C++11支持的列表初始化
// 内置类型支持列表初始化
int x1 = { 2 }; // 列表初始化一个整型变量x1,值为2
// 自定义类型支持列表初始化
// 使用列表初始化构造一个Date对象d1,年为2025,月为1,日为1
Date d1 = { 2025, 1, 1 };
// 使用列表初始化构造一个临时Date对象,并将其引用赋值给d2
// 这里实际上调用了赋值拷贝构造函数,因为列表初始化创建了一个临时对象
const Date& d2 = { 2024, 7, 25 };
// 可以省略掉=的列表初始化
Point p1{ 1, 2 };
int x2{ 2 };
Date d6{ 2024, 7, 25 };
const Date& d7{ 2024, 7, 25 };
vector<Date> v; // 定义一个Date类型的向量
v.push_back(d1);
v.push_back(Date(2025, 1, 1)); // 将一个Date匿名对象添加到v中
//比起有名对象和匿名对象传参,这里{}更有性价比
// 使用列表初始化将一个Date对象添加到v中
// 这里列表初始化创建了一个临时对象,然后调用拷贝构造函数将其添加到向量中
v.push_back({ 2025, 1, 1 });
return 0;
}
- 代码中的优化和构造过程
-
多参数构造函数:
Date
类的多参数构造函数允许使用默认参数值,这使得在不提供所有参数的情况下也能进行对象的构造。 -
拷贝构造函数:
Date
类的拷贝构造函数允许通过复制现有对象来创建新对象。在const Date& d2 = {2024, 7, 25};
这行代码中,由于列表初始化创建了一个临时对象,所以实际上调用了拷贝构造函数。 -
列表初始化与临时对象:在
const Date& d2 = {2024, 7, 25};
和v.push_back({2025, 1, 1});
这两处,列表初始化创建了临时对象,然后通过拷贝构造函数将这些临时对象复制到相应的变量或容器中。(因为临时对象具有常性,所以说Date&需要使用const修饰) -
省略等号的列表初始化:在
Point p1 {1, 2};
、int x2 {2};
和Date d6 {2024, 7, 25};
这几行代码中,我们可以看到可以省略等号的列表初始化,这是C++11提供的一个便利特性。 -
隐式类型转换:在
Date d4 = 2025;
这行代码中,我们可以看到单参数的隐式类型转换。由于Date
类没有定义接受单个int
参数的构造函数,这里会调用多参数构造函数,并使用默认值填充其他参数。
C++中std::initializer_list
std::initializer_list的认识
std::initializer_list
是C++11中引入的一个类模板,它提供了一种便捷的方式来初始化对象,特别是容器类对象。std::initializer_list
内部维护了两个指针,分别指向初始化列表的开始和结束。
std::initializer_list
的特点
- 内部结构:
std::initializer_list
内部包含两个指针,分别指向数组的开始和结束。 - 迭代器支持:
std::initializer_list
支持迭代器遍历,使得可以方便地访问列表中的元素。 - 容器构造函数:STL容器类增加了接受
std::initializer_list
作为参数的构造函数,支持使用花括号{}
进行初始化。
示例代码:
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main() {
// 创建一个int类型的std::initializer_list对象
std::initializer_list<int> mylist = { 10, 20, 30 };
cout << sizeof(mylist) << endl; // 输出sizeof(std::initializer_list<int>),通常为sizeof(void*)的两倍
// 输出begin和end指针的地址,这两个指针指向列表的开始和结束
// 由于列表存储在栈上,所以这两个指针的地址与局部变量i的地址相近
int i = 0;
cout << mylist.begin() << endl;
cout << mylist.end() << endl;
cout << &i << endl;
// 使用std::initializer_list初始化vector对象
vector<int> v1({ 1, 2, 3, 4, 5 }); // 直接构造
vector<int> v2 = { 1, 2, 3, 4, 5 }; // 构造临时对象+临时对象拷贝v2+优化为直接构造
const vector<int>& v3 = { 1, 2, 3, 4, 5 }; // 常量引用绑定到一个临时vector对象
//去掉=:要注意:耶?还可以有一个有(),一个没有
// vector<int>& v4 { 1, 2, 3, 4, 5 };//要注意,这只是省略掉了=,临时对象具有常性,引用对象要const修饰
const vector<int>& v3 { 1, 2, 3, 4, 5 };
vector<int> v5({ 1, 2, 3, 4, 5 }); // 直接构造
// 使用pair对象的{}初始化和map的initializer_list构造结合
map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} }; // 使用花括号初始化map
// 使用initializer_list版本的赋值
v1 = { 10, 20, 30, 40, 50 }; // 将v1赋值为新的initializer_list
return 0;
}
std::initializer_list
的应用
- 容器初始化:
std::initializer_list
使得容器可以直接使用花括号{}
进行初始化,如vector<int> v = {1, 2, 3};
。 - 简化代码:使用
std::initializer_list
可以简化代码,避免编写多个构造函数来支持不同数量的参数。 - 性能优化:编译器可以优化
std::initializer_list
的使用,避免不必要的临时对象拷贝。
🤔 在C++中,`std::initializer_list`是否只能用于容器类的初始化?
在C++中,std::initializer_list
不仅限于容器类的初始化,尽管它在容器类中非常常见。std::initializer_list
可以用于任何需要从初始化列表中创建对象的场景。以下是一些std::initializer_list
的用途:
-
容器类初始化:如
std::vector
、std::list
、std::map
等STL容器,都提供了接受std::initializer_list
作为参数的构造函数和赋值操作符,使得可以直接使用花括号初始化容器。 -
自定义类的初始化:自定义类也可以定义接受
std::initializer_list
的构造函数,从而允许使用花括号列表来初始化对象。 -
函数参数:函数可以接受`std::init`
- 示例 1:自定义类使用
std::initializer_list
这个示例定义了一个 Matrix
类,它使用 std::initializer_list
来初始化一个 2x2 矩阵。
#include <iostream>
#include <initializer_list>
#include <array>
using namespace std;
class Matrix {
public:
// 使用 std::initializer_list 列表初始化矩阵
Matrix(initializer_list<initializer_list<double>> list) {
// 检查是否为 2x2 矩阵
if (list.size() != 2 || list.begin()->size() != 2) {
throw std::invalid_argument("Matrix must be 2x2");
}
// 从初始化列表中复制值到矩阵
auto it = list.begin();
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
matrix[i][j] = *(it->begin() + j);
}
++it;
}
}
// 打印矩阵
void print() const {
for (const auto& row : matrix) {
for (const auto& elem : row) {
cout << elem << " ";
}
cout << endl;
}
}
private:
array<array<double, 2>, 2> matrix; // 2x2 矩阵
};
int main() {
// 使用列表初始化 Matrix 对象
Matrix matrix = {
{1.0, 2.0},
{3.0, 4.0}
};
cout << "Matrix:" << endl;
matrix.print(); // 输出矩阵
return 0;
}
- 示例 2:函数使用
std::initializer_list
作为参数
这个示例定义了一个函数,它接受 std::initializer_list<int>
作为参数,并计算所有元素的和。
#include <iostream>
#include <initializer_list>
// 计算初始化列表中所有元素的和
int sum(std::initializer_list<int> list) {
int total = 0;
for (int value : list) {
total += value;
}
return total;
}
int main() {
// 使用列表初始化并计算和
int result = sum({1, 2, 3, 4, 5});
std::cout << "Sum: " << result << std::endl; // 输出和
return 0;
}
std::initializer_list
是C++11中一个非常有用的工具,它提供了一种简洁且高效的方式来初始化对象,特别是容器类对象。通过使用std::initializer_list
,我们可以简化代码并提高性能。
使用 std::initializer_list
可以简化代码并提高性能,原因如下:
- 简化代码
-
统一的初始化语法:
std::initializer_list
提供了一种统一的方式来初始化对象,无论是基本数据类型、自定义类型还是容器,都可以使用花括号{}
来初始化,这使得代码更加简洁和一致。 -
减少构造函数数量:对于自定义类型,如果需要支持多种初始化方式,通常需要定义多个构造函数。使用
std::initializer_list
可以减少所需的构造函数数量,因为一个接受std::initializer_list
的构造函数可以处理多种不同的初始化情况。 -
避免临时对象:在没有
std::initializer_list
的情况下,初始化容器可能需要创建临时对象,然后再将这些对象复制或移动到容器中。使用std::initializer_list
可以直接在构造函数中处理初始化列表,避免了不必要的临时对象创建和复制。
- 提高性能
-
避免拷贝:
std::initializer_list
本质上是一个包装了两个指针(指向初始化列表的开始和结束)的轻量级对象。它不会复制存储在初始化列表中的元素,因此可以避免不必要的拷贝操作,提高性能。 -
直接访问元素:
std::initializer_list
提供了迭代器,可以直接访问初始化列表中的元素,这允许编译器和标准库算法直接操作这些元素,而不需要额外的间接访问。 -
编译器优化:现代编译器可以对使用
std::initializer_list
的代码进行优化。例如,当使用列表初始化来创建对象时,编译器可以将其优化为直接构造对象,而不是先创建临时对象再进行拷贝或移动。 -
内存分配:对于容器类,使用
std::initializer_list
可以在构造函数中一次性分配足够的内存来存储所有元素,避免了后续可能的多次内存分配和数据复制,这在处理大量数据时尤其重要。 -
STL算法支持:STL算法(如
std::sort
)可以直接接受std::initializer_list
作为参数,这意味着可以直接对初始化列表进行算法操作,无需将数据复制到另一个容器中。
综上所述,std::initializer_list
提供了一种高效且简洁的方式来处理初始化列表,它简化了代码,减少了不必要的对象创建和拷贝,从而提高了程序的性能。
与数组初始化的区别
在C++中,std::initializer_list
和数组初始化是两种不同的概念,它们在用途和行为上有一些关键的区别:
std::initializer_list
-
类型和定义:
std::initializer_list
是一个模板类,定义在头文件<initializer_list>
中。它提供了对初始化列表的访问,包括迭代器、大小等。 -
用途:
std::initializer_list
通常用于函数参数和构造函数参数,以允许函数和构造函数接受可变数量的参数。 -
存储:
std::initializer_list
不存储元素的副本,它只包含指向传递给它的初始化列表的指针。这意味着它不会增加额外的存储开销。 -
生命周期:
std::initializer_list
的对象通常是临时对象,它们的生命周期仅在创建它们的表达式中有效。 -
访问方式:
std::initializer_list
提供了begin()
和end()
方法来获取指向列表开始和结束的迭代器,允许通过迭代器访问元素。 -
类型安全:
std::initializer_list
需要知道元素的类型,因此它是类型安全的。
- 数组初始化
-
类型和定义:数组是C++中的内置数据结构,用于存储固定大小的同类型元素的集合。
-
用途:数组用于存储同类型的多个元素,它们在声明时必须指定大小,或者使用初始化列表来确定大小。
-
存储:数组自身存储了所有的元素,因此它们有固定的内存开销。
-
生命周期:数组的生命周期取决于它们的声明周期,可以是局部的、全局的或者静态的。
-
访问方式:数组通过下标访问其元素,不支持迭代器。
-
大小限制
initializer_list的框架实现
std::initializer_list
是 C++ 标准库中的一个类模板,它提供了一种便捷的方式来访问初始化列表中的元素。下面是一个简化版的 std::initializer_list
的实现,展示了它的基本结构和功能:
#ifndef INITIALIZER_LIST_H
#define INITIALIZER_LIST_H
#include <iterator>
#include <cstddef> // For std::size_t
// 定义一个类模板
template<typename E>
class initializer_list
{
public:
// 使用 T* 作为内部的迭代器类型
typedef E* iterator;
typedef E* const_iterator;
// 构造函数
constexpr initializer_list(const E* array, std::size_t n) noexcept
: m_array(array), m_n(n) {}
// 返回初始化列表中的元素数量
constexpr std::size_t size() const noexcept { return m_n; }
// 返回指向初始化列表第一个元素的指针
constexpr const_iterator begin() const noexcept { return m_array; }
// 返回一个指向初始化列表尾后位置的迭代器
constexpr const_iterator end() const noexcept { return m_array + m_n; }
private:
const E* m_array; // 指向数组的指针
std::size_t m_n; // 数组中元素的数量
};
#endif // INITIALIZER_LIST_H
这个简化版的 std::initializer_list
实现包括以下部分:
-
类型定义:定义了
iterator
和const_iterator
类型,它们都是指向元素类型的指针。 -
构造函数:接受一个指向元素数组的指针和数组的大小,并将它们存储为私有成员。
-
大小方法:返回初始化列表中的元素数量。
-
迭代器方法:提供
begin()
和end()
方法,返回指向初始化列表开始和结束的迭代器。
实现细节
- 内存管理:
std::initializer_list
不拥有其内部数组,它只是持有一个指向数组的指针。因此,它不负责分配或释放内存。 - 生命周期:
std::initializer_list
的生命周期应该与创建它的初始化列表的生命周期一致。如果初始化列表存储在栈上,那么std::initializer_list
的对象也应该存储在栈上。 - 异常安全性:
std::initializer_list
的实现应该是异常安全的,因为它不涉及任何动态内存分配或其他可能抛出异常的操作。 - 性能:由于
std::initializer_list
不复制元素,它提供了一种高效的方式来传递初始化列表。