列表初始化

文章参考:

爱编程的大丙 (subingwen.cn)
C++中有多种数据类型,如:变量、数组、对象。这些数据类型各自的初始化方式,并没有统一的方法。为了简化初始化过程,C++11提出了一种统一的初始化方法,即列表初始化

1. 统一的初始化

1.1 C++98

在C++98中,有两种情况可以使用列表初始化:

  • 普通的数组。
  • 可以直接使用内存拷贝函数memcpy()的对象。

EG:

// 数组初始化
int arr1[] = {1, 2, 3, 4, 5};
double arr2[] = {1.1, 1.2, 1.3};

// 对象的初始化
struct Person{
    int id;
    double salary;
}zhangSan{1, 3333};

1.2 C++11

在C++11中,列表初始化的运用变得更加的灵活。

EG:

  • 类使用成员初始化列表进行初始化:

    #include <iostream>
    using namespace std;
    
    class Test{
    private: 
        int _num;
    public:
        Test(int num);
        Test(const Test& test);
    };
    // 一般构造函数
    Test::Test(int num): _num(num){
        cout << "common construction function" << endl;
    }
    // 拷贝构造函数
    Test::Test(const Test& test){
        _num = test._num;
        cout << "copy construction function" << endl;
    }
    
    int main(void){
        Test t1(1);         // 调用一般构造函数
        Test t2 = 2;        // 这是一个特殊情况,见下文:
        Test t3 = Test(2);	// 和上一句本质上是一样的,见下文
        Test t4(t1);        // 调用拷贝构造函数
        Test t5 = t2;       // 调用拷贝构造函数
        Test t6 = {3};      // 成员初始化列表,调用一般构造函数
        Test t7{4};         // 成员初始化列表,调用一般构造函数
        Test t8 = {t1};     // 成员初始化列表,调用拷贝构造函数
        Test t9{t2};        // 成员初始化列表,调用拷贝构造函数
        return 0;
    }
    
    • Test t2 = 2:该指令会被编译器改写为Test t2 = Test(2)。注意:之后并没有调用拷贝构造函数或移动构造函数(即通过匿名对象(右值)来给左值变量进行初始化),这时C++语法的一种特例,编译器直接令t2Test(2)等同,完全等价于Test t2(2),因此只会调用一次普通的构造函数。综上:Test t2 = 2等价于Test t2(2)
  • new对象时使用成员初始化列表进行初始化:

    int * p = new int{520};
    double b = double{52.134};
    int * array = new int[3]{1,2,3};
    
    • p:指向了一个new的内存,并使用成员初始化列表初始化为520。
    • b:对匿名对象使用列表初始化后,再进行拷贝初始化。
    • array:在堆内分配了一块内存,使用成员初始化列表将的堆多个元素进行初始化。
  • 作用于函数返回值:

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class Person{
    private: 
        int _id;
        string _name;
    public:
        Person(){}
        Person(int id, string name): _id(id), _name(name){}
        void show_msg();
    };
    void Person::show_msg(){
        cout << "id: " << _id << "\t" << "name: " << _name << endl;
    }
    // 使用成员初始化列表,直接匿名返回对象
    Person get_case(){
        return {1, "zhangSan"};
    }
    
    int main(void){
        Person p = get_case();
        p.show_msg();
        return 0;
    }
    

2. 列表初始化细节

列表初始化有两种情况:

  • 聚合体:可以直接进行初始化。
  • 非聚合体:必须要有对应的构造函数。

2.1 聚合体

聚合体可以直接使用列表初始化,无需对应的构造函数。

要成为聚合体,需要同时满足以下五个条件,否则就是非聚合体:

  • 没有自定义的构造函数。

  • 没有私有/保护属性的非静态成员。如果有静态成员,那么该静态成员不能通过列表初始化进行初始化,因为静态成员的初始化是先于类的初始化,在编译阶段完成的,而列表初始化是在运行阶段进行的,因此要对静态成员进行独立的初始化。

    struct Test1{
        int a;
        int b;
    private:
        int c;
    }t1{1, 2, 3};		// error,因为存在私有/保护属性的非静态成员,无法使用初始化列表进行初始化。
    
    struct Test2{
        int a;
        int b;
    private:
        static int c;
    }t1{1, 2, 3};		// error,因为	静态成员无法使用初始化列表进行初始化
    
    struct Test2{
        int a;
        int b;
    private:
        static int c;
    }t1{1, 2};		
    int Test2::c = 3;		// 需要对静态成员进行单独初始化。
    
  • 没有基类。

  • 没有虚函数。

  • 类中不能有使用{}=直接初始化的非静态成员(注意:从C++14开始,即使有,也可以使用列表初始化。

2.2 非聚合体

非聚合体也可以使用列表初始化,但前提是要有对应的构造函数,列表初始化会自动调用对应的构造函数进行初始化。

Eg:

#include <iostream>
using namespace std;

struct T1{
private:
    int _a;
public:
    int _b;
    T1(int a, int b): _a(a), _b(b){}
};

int main(void){
    T1 t = {1, 2};		// 列表初始化调用了自定义的构造函数
    return 0;
}

注意:

即使当前类的某个成员是非聚合体,当前类也可以是聚合体。也就是说:聚合体/非聚合体不是嵌套的概念。

#include <iostream>
using namespace std;

struct T1{
private:
    int _a;
public:
    int _b;
};

struct T2{
public:
    T1 t;
    int _a;
    int _b;
};

int main(void){
    T2 t = {{}, 1, 2};
    return 0;
}

这里尽管T2的成员变量t是非聚合体,但T2依旧是聚合体,在使用列表初始化时,通过{}来初始化t变量,其实就是调用了默认的无参构造函数。

3. std::initializer_list

3.1 概述

引入:有时我们在传递参数时,需要传递多个相同类型、数量不定的参数,这时我们可以使用数组、vectorlist等。在C++11中,为我们提供了一个更加轻量级的模板类std::initializer_list来实现这一功能。

特点:

  • 内部定义了iterator等容器必须的概念,且遍历时得到的迭代器是只读的。
  • 可以接收长度任意的初始化列表,但元素类型必须一致。
  • 有三个成员接口:
    • begin()
    • end()
    • size()
  • 只能被整体初始化或赋值。

3.2 作为普通函数参数

#include <iostream>
using namespace std;

void traversal(initializer_list<int> var){
    auto it = var.begin();
    for (; it != var.end(); ++temp){
        cout << *it << " ";
    }
    cout << endl;
}

int main(void){
    traversal({1, 2, 3, 4, 5});
    return 0;
}

std::initializer_list在遍历时得到一个只读的迭代器,因此如果想要修改值,只能通过值覆盖的方式。

std::initializer_list的效率较高,因为它并不是保存了初始化列表中元素的拷贝,而是仅仅存储了初始化列表中元素的引用。

3.3 作为构造函数

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Test
{
private:
    vector<string> m_names;
public:
    Test(initializer_list<string> list)
    {
        for (auto it = list.begin(); it != list.end(); ++it)
        {
            cout << *it << " ";
            m_names.push_back(*it);
        }
        cout << endl;
    }
};

int main(void)
{
    Test t({ "jack", "lucy", "tom" });
    return 0;
}
posted @   BinaryPrinter  阅读(120)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示