列表初始化

45 阅读4分钟

Hello 大家好,今天我们来学习C++中的列表初始化

1.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;
}

2.C++11中的{}

• C++11以后想统一初始化方式,试图实现一切对象皆可用{}初始化,{}初始化也叫做列表初始化。

• 内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造。

• {}初始化的过程中,可以省略掉=

• C++11列表初始化的本意是想实现一个大统一的初始化方式,其次他在有些场景下带来的不少便利,如容器push/inset多参数构造的对象时,{}初始化会很方便

#include<vector>
using namespace std;
struct Point
{
    int _x;
    int _y;
};
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(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 };
    Point p = { 1, 2 };
    
    // C++11支持的
    // 内置类型支持
    int x1 = { 2 };
    
    // 自定义类型支持
    // 这里本质是用{ 2025, 1, 1}构造一个Date临时对象
    // 临时对象再去拷贝构造d1,编译器优化后合二为一变成{ 2025, 1, 1}直接构造初始化d1
    // 运行一下,我们可以验证上面的理论,发现是没调用拷贝构造的
    Date d1 = { 2025, 1, 1};
    
    // 这里d2引用的是{ 2024, 7, 25 }构造的临时对象
    const Date& d2 = { 2024, 7, 25 };
    
    // 需要注意的是C++98支持单参数时类型转换,也可以不用{}
    Date d3 = { 2025};
    Date d4 = 2025;
    // 可以省略掉=
    Point p1 { 1, 2 };
    int x2 { 2 };
    Date d6 { 2024, 7, 25 };
    const Date& d7 { 2024, 7, 25 };
    
    // 不支持,只有{}初始化,才能省略=
    // Date d8 2025;
    
    vector<Date> v;
    v.push_back(d1);
    v.push_back(Date(2025, 1, 1));
    // 比起有名对象和匿名对象传参,这里{}更有性价比,且书写更简洁
    v.push_back({ 2025, 1, 1 });
    return 0;
}

3 C++11中的std::initializer_list

• 上面的初始化已经很方便,但是对象容器初始化还是不太方便,比如一个vector对象,我想用N个值去构造初始化,那么我们得实现很多个构造函数才能支持, vector v1 ={1,2,3}; vector v2 = {1,2,3,4,5};

• C++11库中提出了一个std::initializer_list的类, auto il = { 10, 20, 30 }; // thetype of il is an initializer_list ,这个类的本质是底层开一个数组,将数据拷贝过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。

• std::initializer_list支持迭代器遍历。

• 容器支持一个std::initializer_list的构造函数,也就支持任意多个值构成的{x1,x2,x3...} 进行初始化。STL中的容器支持任意多个值构成的{x1,x2,x3...} 进行初始化,就是通过std::initializer_list的构造函数支持的。

// STL中的容器都增加了一个initializer_list的构造
vector (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type());
list (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type());
map (initializer_list<value_type> il,const key_compare& comp =
key_compare(),const allocator_type& alloc = allocator_type());
// ...
template<class T>
class vector {
public:
        typedef T* iterator;
        vector(initializer_list<T> l)
        {
        for (auto e : l)
            push_back(e);
        }
private:
        iterator _start = nullptr;
        iterator _finish = nullptr;
        iterator _endofstorage = nullptr;
};
// 另外,容器的赋值也支持initializer_list的版本
vector& operator= (initializer_list<value_type> il);
map& operator= (initializer_list<value_type> il);
#include<iostream>
#include<vector>
#include<string>
#include<map>
using namespace std;
int main()
{
    std::initializer_list<int> mylist;
    mylist = { 10, 20, 30 };
    
    // 这里begin和end返回的值initializer_list对象中存的两个指针
    // 这两个指针的值跟i的地址跟接近,说明数组存在栈上
    int i = 0;
    cout << mylist.begin() << endl;
    cout << mylist.end() << endl;
    cout << &i << endl;
//000000B1E33DFDC8
//000000B1E33DFDD4
//000000B1E33DF644

    // {}列表中可以有任意多个值
    // 这两个写法语义上还是有差别的,第一个v1是直接构造,
    // 第二个v2是构造临时对象+临时对象拷贝v2->优化为直接构造
    vector<int> v1({ 1,2,3,4,5 });
    vector<int> v2 = { 1,2,3,4,5 };
    const vector<int>& v3 = { 1,2,3,4,5 };
    
    // 这里是pair对象的{}初始化和map的initializer_list构造结合到一起用了
    map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"}};
    // initializer_list版本的赋值支持
    v1 = { 10,20,30,40,50 };
    return 0;
}