前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【C++11】统一的 {} 列表初始化

【C++11】统一的 {} 列表初始化

作者头像
利刃大大
发布2025-03-01 22:03:30
发布2025-03-01 22:03:30
8000
代码可运行
举报
文章被收录于专栏:csdn文章搬运
运行总次数:0
代码可运行

Ⅰ. {} 列表初始化

​ 在 C++98 中,标准允许使用花括号 {} 对数组或者结构体元素进行统一的列表初始值设定。比如:

代码语言:javascript
代码运行次数:0
复制
struct Point
{
	int _x;
	int _y;
};

int main()
{
	int a1[] = { 1,2,3,4,5 };
	int a2[5] = { 0 };
	Point p1 = { 1, 2 };
	Point p2{ 1, 2 }; // C++98不支持这种写法
	return 0;
}

​ 对于一些自定义的类型,却无法使用这样的初始化,比如:

代码语言:javascript
代码运行次数:0
复制
vector<int> v{1,2,3,4,5};

​ 在 c++98 中无法通过编译,导致每次定义 vector 时,都需要先把 vector 定义出来,然后使用循环对其赋初始值,非常不方便。

​ 所以 C++11 扩大了用花括号括起的列表的使用范围,使其 可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号 = ,也可不添加。

​ 初始化列表的优点在于可 以简化代码并提高可读性。在某些情况下,它还可以提高性能,因为使用初始化列表可以避免不必要的对象拷贝和转换。

列表初始化在初始化时,如果出现类型截断,编译器是会报警告或者错误的,具体的行为取决于编译器的实现,(例如将一个较大的数值赋值给一个较小的整数类型)比如下述代码:

代码语言:javascript
代码运行次数:0
复制
int main()
{
    int a = 10;
    double b{ a }; // ❌因为从int到double需要收缩转换

    double c = 10.0;
    int d{ c };    // ❌因为从double到int也需要收缩转换
}

int main()
{
	int m = 100;
	char n = { m };  // ❌收缩,无法通过编译

	char m1 = 100;
	int n1 = { m1 }; // 可以通过编译

	const int x = 1024;
	const int y = 10;
	char a = x;                    // 收缩,但可以通过编译
	char* b = new char(1024);      // 收缩,但可以通过编译
	char c = { x };                // ❌收缩,无法通过编译,因为x超出char的范围
	char d = { y };                // 可以通过编译,因为y的值没有超过char的范围,这种转化称为整数类型收缩
	char e = static_cast<char>(x); // 显式进行类型转换,并进行范围检查,比上面的做法安全的多

	unsigned char e{ -1 };         // ❌收缩,无法通过编译,从int转换到unsigned char需要收缩转换
	float f{ 7 };                  // 可以通过编译,由小到大转化不会影响
	int g{ 2.0f };                 // ❌收缩,无法通过编译
	float* h = new float{ 1e48 };  // ❌收缩,无法通过编译,该浮点常量算术溢出
	float i = 1.2l;                // 可以通过编译,从long double到float截断
	return 0;
}

​ 在 C++ 中,将一个 const int 类型的值赋给 char 类型的变量时,编译器会发生一种叫做 整数类型收缩 的隐式类型转换,而不是发生强制类型转换。

​ 整数类型收缩发生在以下场景中:将一个整数类型的值赋给另一个较小的整数类型的变量时,编译器会将原来类型的值的高位截断,然后赋给目标类型的变量。这个过程中,可能会导致信息的丢失或不可预期的行为。但是这种类型转换不会导致编译器报错,因为 C++ 标准规定了这种类型转换是可以进行的。

​ 为了避免类型收缩转换带来的问题,建议在转换时进行类型检查,并使用合适的转换函数(例如 static_castdynamic_cast)来确保转换的正确性。如果必须进行类型收缩转换,建议进行额外的检查和处理,以避免不可预期的行为。

内置类型的列表初始化:

代码语言:javascript
代码运行次数:0
复制
int main()
{ 
    // 内置类型变量
    int x1 = {10};
    int x2{10};
    int x3 = 1+2;
    int x4 = {1+2};
    int x5{1+2};
    
    // 数组
    int arr1[5] = {1,2,3,4,5};
    int arr2[]{1,2,3,4,5};

    // 动态数组,在C++98中不支持
    int* arr3 = new int[5]{1,2,3,4,5};

    // 标准容器
    vector<int> v1{1,2,3,4,5};
    vector<int> v2 = {1,2,3,4,5};
    
    map<int, int> m1{{1,1}, {2,2},{3,3},{4,4}};
    map<int, int> m2 = {{1,1}, {2,2},{3,3},{4,4}};
    
    return 0;
}

自定义类型的列表初始化:

代码语言:javascript
代码运行次数:0
复制
class Point
{
public:
    Point(int x = 0, int y = 0): _x(x), _y(y)
    {}
private:
    int _x;
    int _y;
};

int main()
{
    // 本质是{1, 2}调用了initializer_list构造出一个对象然后再初始化,这个下面会讲
    Point p{ 1, 2 }; 
    
    // 使用new和上面的p就不太一样了,会去调用构造函数初始化
    // 若没有自己写的构造函数,则下面的初始化对内置类型_X,_y是没有影响的,也就是没有初始化值
	Point* ptr = new Point[2]{{1, 1}, {2, 2}};
    
    return 0; 
}

Ⅱ. 引入 std::initializer_list

官方文档

​ 我们来看看上面出现的一个问题:

代码语言:javascript
代码运行次数:0
复制
vector<int> v1{1,2,3,4,5};

​ 这么仔细一想,这里的 {1,2,3,4,5} 是怎么构造给 v1 的呢?是用迭代器区间构造的吗?

​ 答案:不是!这里其实是 C++11 引入的新特性:initializer_list

​ 这是一个专门用来初始化列表的类!

​ 它可以将你放入 {} 中的元素按照你要的类型 T 生成一个 initializer_list 对象,接着还有重要的一步,就是如 vectorlist 等容器中,C++11 已经添加了新的构造函数参数:以 initializer_list 为参数的构造函数,这样子的话我们就能运用 {} 来初始化容器了!

​ 注意:一般 initializer_list 的头文件被容器的头文件包含了,所以如果引入了容器的头文件了,那么就不需要再包 initializer_list 的头文件了!

代码语言:javascript
代码运行次数:0
复制
int main()
{
	// the type of il is an initializer_list 
	auto il = { 10, 20, 30 };
	cout << typeid(il).name() << endl;
	return 0;
}

// 运行结果
class std::initializer_list<int>

​ 那么我们如何在模拟实现 vectorlist 等容器的时候增加 initializer_list 初始化呢?

💡 只需要多写一个构造函数,然后利用 initializer_list 的迭代器进行插入元素即可:

代码语言:javascript
代码运行次数:0
复制
namespace liren
{
    template<class T>
    class vector {
    public:
         typedef T* iterator;
        
         vector(initializer_list<T> l)
         {
             _start = new T[l.size()];
             _finish = _start + l.size();
             _endofstorage = _start + l.size();
             
             iterator vit = _start; // 定义一个当前vector的迭代器
             
             // 主要是下面这些步骤:
             // 这里typename是去取initializer_list中的内嵌类型iterator
             typename initializer_list<T>::iterator lit = l.begin();
             while (lit != l.end())
             {
                 *vit++ = *lit++;
             }
             
             //for (auto e : l) 也可以直接使用范围for来简化代码
             //   *vit++ = e;
         }
        
         vector<T>& operator=(initializer_list<T> l) 
         {
             vector<T> tmp(l);
             std::swap(_start, tmp._start);
             std::swap(_finish, tmp._finish);
             std::swap(_endofstorage, tmp._endofstorage);
             return *this;
         }
    private:
         iterator _start;
         iterator _finish;
         iterator _endofstorage;
     };
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Ⅰ. {} 列表初始化
  • Ⅱ. 引入 std::initializer_list
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档