C++基础教程面向对象(学习笔记(41))

本文探讨了C++中使用std::initializer_list进行类初始化的方法,展示了如何使用初始化列表语法直接初始化数组和自定义类。通过更新IntArray类以接受初始化列表,实现了列表初始化的灵活性,并提供了重载赋值运算符的示例。

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值