std :: initializer_list
考虑C ++中固定的整数数组:
int array[5];
如果我们想用值初始化这个数组,我们可以通过初始化列表语法直接执行:
int main()
{
int array[5] { 5, 4, 3, 2, 1 }; //初始化列表
for (int count=0; count < 5; ++count)
std::cout << array[count] << ' ';
return 0;
}
这打印:
5 4 3 2 1
这也适用于动态分配的数组:
int main()
{
int *array = new int[5] { 5, 4, 3, 2, 1 }; // 初始化列表
for (int count = 0; count < 5; ++count)
std::cout << array[count] << ' ';
delete[] array;
return 0;
}
在上一课中,我们介绍了容器类的概念,并展示了一个包含整数数组的IntArray类的示例:
#include <cassert> // assert()
class IntArray
{
private:
int m_length;
int *m_data;
public:
IntArray():
m_length(0), m_data(nullptr)
{
}
IntArray(int length):
m_length(length)
{
m_data = new int[length];
}
~IntArray()
{
delete[] m_data;
//我们不需要在这里将m_data设置为null或m_length为0,因为无论如何都会在此函数之后立即销毁该对象
}
int& operator[](int index)
{
assert(index >= 0 && index < m_length);
return m_data[index];
}
int getLength() { return m_length; }
};
如果我们尝试使用此容器类的初始化列表会发生什么?
int main()
{
IntArray array { 5, 4, 3, 2, 1 }; // 这行不能编译
for (int count=0; count < 5; ++count)
std::cout << array[count] << ' ';
return 0;
}
此代码将无法编译,因为IntArray类没有构造函数知道如何处理初始化程序列表。结果,我们将单独初始化我们的数组元素:
int main()
{
IntArray array(5);
array[0] = 5;
array[1] = 4;
array[2] = 3;
array[3] = 2;
array[4] = 1;
for (int count=0; count < 5; ++count)
std::cout << array[count] << ' ';
return 0;
}
那不是那么好。
在C ++ 11之前,列表初始化只能用于静态或动态数组。但是,从C ++ 11开始,我们现在可以解决这个问题。
使用std :: initializer_list进行类初始化
当C ++ 11编译器看到初始化列表时,它会自动将其转换为std :: initializer_list类型的对象。因此,如果我们创建一个带有std :: initializer_list参数的构造函数,我们可以使用初始化列表作为输入创建对象。
std :: initializer_list位于<initializer_list>标头中。
关于std :: initializer_list,有几点需要了解。就像std :: array或std :: vector一样,你必须告诉std :: initializer_list使用斜角括号将列表保存的数据类型。因此,您永远不会看到普通的std :: initializer_list。相反,您将看到类似std :: initializer_list 或std :: initializer_list 的内容。
其次,std :: initializer_list有一个(不符合命名习惯的)size()函数,它返回列表中元素的数量。当我们需要知道传入的列表的长度时,这很有用。
让我们看看使用带有std :: initializer_list的构造函数更新我们的IntArray类。
#include <cassert> // assert()
#include <initializer_list> // std::initializer_list
#include <iostream>
class IntArray
{
private:
int m_length;
int *m_data;
public:
IntArray() :
m_length(0), m_data(nullptr)
{
}
IntArray(int length) :
m_length(length)
{
m_data = new int[length];
}
IntArray(const std::initializer_list<int> &list): // 允许通过列表初始化初始化IntArray
IntArray(list.size()) //使用委托构造函数来设置初始数组
{
//现在从列表中初始化我们的数组
int count = 0;
for (auto &element : list)
{
m_data[count] = element;
++count;
}
}
~IntArray()
{
delete[] m_data;
// 我们不需要在这里将m_data设置为null或m_length为0,因为无论如何都会在此函数之后立即销毁该对象
}
int& operator[](int index)
{
assert(index >= 0 && index < m_length);
return m_data[index];
}
int getLength() { return m_length; }
};
int main()
{
IntArray array { 5, 4, 3, 2, 1 }; //初始化列表
for (int count = 0; count < array.getLength(); ++count)
std::cout << array[count] << ' ';
return 0;
}
这会产生预期的结果:
5 4 3 2 1
有用!现在,让我们更详细地探讨这一点。
这是我们的IntArray构造函数,它采用std :: initializer_list 。
IntArray(const std::initializer_list<int> &list): // 允许通过列表初始化初始化IntArray
IntArray(list.size()) // 使用委托构造函数来设置初始数组
{
// 现在从列表中初始化我们的数组
int count = 0;
for (auto &element : list)
{
m_data[count] = element;
++count;
}
}
第1行:如上所述,我们必须使用有角度的括号来表示我们在列表中期望的元素类型。在这种情况下,因为这是一个IntArray,我们希望列表用int填充。请注意,我们也通过const引用传递列表,因此当它传递给我们的构造函数时,我们不会生成std :: initializer_list的不必要的副本。
第2行:我们通过委托构造函数将IntArray的内存委托给其他构造函数(以减少冗余代码)。这个其他构造函数需要知道数组的长度,所以我们传递list.size(),它包含列表中的元素数。
构造函数的主体保留用于将列表中的元素复制到IntArray类中。由于一些莫名其妙的原因,std :: initializer_list不通过下标(operator [])提供对列表元素的访问。已被多次注意到并且从未得到解决。
但是,有一些简单的方法可以解决缺少下标的问题。最简单的方法是在这里使用for-each循环。for-each循环遍历初始化列表的每个元素,我们可以手动将元素复制到内部数组中。
使用std :: initializer_list的类赋值
您还可以使用std :: initializer_list通过重写赋值运算符来为类分配新值,以获取std :: initializer_list参数。这与上述类似。我们将在下面的测验解决方案中展示如何执行此操作的示例。
请注意,如果实现采用std :: initializer_list的构造函数,则应确保至少执行以下操作之一:
提供重载列表赋值运算符
提供适当的深拷贝复制赋值运算符
使构造函数显式,因此不能用于隐式转换
原因如下:考虑上面的类(没有重载列表赋值或副本赋值),以及以下语句:
array = { 1, 3, 5, 7, 9, 11 }; // 用列表中的元素覆盖数组的元素
首先,编译器会注意到一个带有std :: initializer_list的赋值函数不存在。接下来,它将查找它可以使用的其他赋值函数,并发现隐式提供的复制赋值运算符。但是,只有在可以将初始化列表转换为IntArray时才能使用此函数。由于采用std :: initializer_list的构造函数未标记为显式,因此编译器将使用列表构造函数将初始化列表转换为临时IntArray。然后它将调用隐式赋值运算符,它将浅层复制临时IntArray到我们的数组对象中。
此时,临时IntArray的m_data和array-> m_data都指向相同的地址(由于浅拷贝)。你已经可以看到它的发展方向。
在赋值语句的末尾,临时IntArray被销毁。它调用析构函数,删除临时IntArray的m_data。这使我们的数组变量带有一个悬空的m_data指针。当您尝试将array-> m_data用于任何目的时(包括当数组超出范围并且析构函数去删除m_data时),您将得到未定义的结果(可能是崩溃)。
规则:如果您提供列表构造,那么提供列表分配也是一个好主意。
Summary
实现一个带有std :: initializer_list参数的构造函数(通过引用来防止复制)允许我们使用列表初始化和我们的自定义类。我们还可以使用std :: initializer_list来实现需要使用初始化列表的其他函数,例如赋值运算符。
Quiz Time:
1)使用上面的IntArray类,实现一个带有初始化列表的重载赋值运算符。
应运行以下代码:
int main()
{
IntArray array { 5, 4, 3, 2, 1 }; // initializer list
for (int count = 0; count < array.getLength(); ++count)
std::cout << array[count] << ' ';
std::cout << '\n';
array = { 1, 3, 5, 7, 9, 11 };
for (int count = 0; count < array.getLength(); ++count)
std::cout << array[count] << ' ';
return 0;
}
这应该打印:
5 4 3 2 1
1 3 5 7 9 11
解决方案
#include <cassert> // assert()
#include <initializer_list> // std::initializer_list
#include <iostream>
class IntArray
{
private:
int m_length;
int *m_data;
public:
IntArray() :
m_length(0), m_data(nullptr)
{
}
IntArray(int length) :
m_length(length)
{
m_data = new int[length];
}
IntArray(const std::initializer_list<int> &list) : // 允许通过列表初始化初始化IntArray
IntArray(list.size()) //使用委托构造函数来设置初始数组
{
// 从list中初始化array
int count = 0;
for (auto &element : list)
{
m_data[count] = element;
++count;
}
}
~IntArray()
{
delete[] m_data;
// 我们不需要在这里将m_data设置为null或m_length为0,因为无论如何都会在此函数之后立即销毁该对象
}
IntArray& operator=(const std::initializer_list<int> &list)
{
//如果新列表的大小不同,请重新分配
if (list.size() != static_cast<size_t>(m_length))
{
// 删除全部现有元素
delete[] m_data;
// 重新分配array
m_length = list.size();
m_data = new int[m_length];
}
// 从list中初始化array
int count = 0;
for (auto &element : list)
{
m_data[count] = element;
++count;
}
return *this;
}
int& operator[](int index)
{
assert(index >= 0 && index < m_length);
return m_data[index];
}
int getLength() { return m_length; }
};
int main()
{
IntArray array { 5, 4, 3, 2, 1 }; // 初始化list
for (int count = 0; count < array.getLength(); ++count)
std::cout << array[count] << ' ';
std::cout << '\n';
array = { 1, 3, 5, 7, 9, 11 };
for (int count = 0; count < array.getLength(); ++count)
std::cout << array[count] << ' ';
return 0;
}