C++11 之列表初始化

在 c++ 98/03 中的对象初始化方法有很多种,请看下面的代码:

//初始化列表
int i_arr[3] = {1, 2, 3};  //普通数组

struct A {
    int x;
    struct B {
        int i;
        int j;
    } b;
} a = {1, {2, 3}};  // POD 类型即 plain old data 类型,简单来说,是可以直接使用 memcpy 复制的对象。

//拷贝初始化(copy-initialization)
int i = 0;
class Foo {
public:
    Foo(int) {}
} foo = 123;  //需要拷贝构造函数

//直接初始化(direct-initialization)
int j(0);
Foo bar(123);

这些不同的初始化方法,都有各自的适用范围和作用。最关键的是,这些种类繁多的初始化方法,没有一种可以通用所有情况。

为了统一初始化方式,并且让初始化行为具有确定的效果,c++11 中提出了列表初始化(List-initialization)的概念,在 c++11 中可以直接在变量名后面加上初始化列表来进行对象的初始化

struct A {
public:
    A(int) {}

private:
    A(const A&) {}
};

int main() {
    A a(123);
    A b = 123;  // error: 'A::A(const A &)' is private
    A c = {123};
    A d{123};  // c++11

    int e = {123};
    int f{123};  // c++11
    return 0;
}

int i_arr[3] { 1, 2, 3 };  //普通数组
struct A {
    int x;
    struct B {
        int i;
        int j;
    } b;
} a { 1, { 2, 3 } };  //POD类型

在上例中,c、d 使用了新的初始化方式来初始化对象,效果如同 a 的直接初始化;e、f 则是基本数据类型的列表初始化方式。可以看到,它们的形式都是统一的。
【注意1】c 虽然使用了等于号,但它仍然是列表初始化,因此,私有的拷贝构造并不会影响到它。d 和 f 的写法,是 c++98/03 所不具备的。
【注意2】在初始化时,{} 前面的等于号是否书写对初始化行为没有影响。

另外,new 操作符等可以用圆括号进行初始化的地方,也可以使用初始化列表:

int* a = new int { 123 };  
double b = double { 12.12 };  
int* arr = new int[3] { 1, 2, 3 };

指针 a 指向了一个 new 操作符返回的内存,通过初始化列表方式在内存初始化时指定了值为 123;b 则是对匿名对象使用列表初始化后,再进行拷贝初始化;让人眼前一亮的是 arr 的初始化方式,堆上动态分配的数组终于也可以使用初始化列表进行初始化了。

列表初始化也可以用在函数的返回值上:

std::vector<int> func() { 
	return {};  // return 语句就如同返回了一个 func()
}

列表初始化的一些规则:聚合类型可以进行直接列表初始化。那什么是聚合类型:

  • 类型是一个普通数组,如 int[5],char[],double[] 等
  • 类型是一个类,且满足以下条件:
    • 没有用户声明的构造函数
    • 没有用户提供的构造函数(允许显示预置或弃置的构造函数)
    • 没有私有或保护的非静态数据成员
    • 没有基类
    • 没有虚函数
    • 没有 {} 和 = 直接初始化的非静态数据成员
    • 没有默认成员初始化器
struct A {
    int a;
    int b;
    int c;
    A(int, int) {}
};

int main() {
    A a{1, 2, 3};  // error,A有自定义的构造函数,不能列表初始化
}

上述代码类 A 不是聚合类型,无法进行列表初始化,必须以自定义的构造函数来构造对象。

struct A {
    int a;
    int b;
    virtual void func() {}  // 含有虚函数,不是聚合类
};

struct Base {};
struct B : public Base {  // 有基类,不是聚合类
    int a;
    int b;
};

struct C {
    int a;
    int b = 10;  // 有等号初始化,不是聚合类
};

struct D {
    int a;
    int b;
private:
    int c;  // 含有私有的非静态数据成员,不是聚合类
};

struct E {
    int a;
    int b;
    E() : a(0), b(0) {}  // 含有默认成员初始化器,不是聚合类
};

上面列举了一些不是聚合类的例子,对于一个聚合类型,使用列表初始化相当于对其中的每个元素分别赋值;对于非聚合类型,需要先自定义一个对应的构造函数,此时列表初始化将调用相应的构造函数

std::initializer_list

我们平时开发使用 STL 过程中可能发现它的初始化列表可以是任意长度,大家有没有想过它是怎么实现的呢,答案是 std::initializer_list,看下面这段示例代码:

struct CustomVec {
    std::vector<int> data;
    CustomVec(std::initializer_list<int> list) {
        for (auto iter = list.begin(); iter != list.end(); ++iter) {
            data.push_back(*iter);
        }
    }
};

我想通过上面这段代码大家可能已经知道STL是如何实现的任意长度初始化了吧,这个 std::initializer_list 其实是作为函数参数。
【注意】std::initializer_list<T>,它可以接收任意长度的初始化列表,但是里面必须是相同类型T,或者都可以转换为T。

列表初始化的好处

  • 方便,且基本上可以替代括号初始化,统一了初始化方式
  • 可以使用初始化列表接受任意长度
  • 可以防止[[01_C 语法#^88ad66|类型窄化]],避免精度丢失的隐式类型转换
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值