C++ 一致性初始化、初值列(initializer_list)及成员变量初始化方式
发布日期:2021-05-07 15:11:16 浏览次数:18 分类:精选文章

本文共 3287 字,大约阅读时间需要 10 分钟。

列表初始化/一致性初始化

设计目的

在C++11之前,如何初始化一个变量或对象的概念比较混淆。初始化的场景可以发生在:小括号、大括号或赋值操作符中。C++11引入了“列表初始化/一致性初始化”,意思为:面对任何初始化动作,你可以使用相同的语法,就是使用大括号。

例子

以下是正确的初始化方式:

int units_sold = 0;
int units_sold = { 0 };
int units_sold{ 0 };
int units_sold(0);
std::vector
v{ 2, 3, 5, 7, 11, 13, 17 };
std::string cites{ "Berlin", "New York", "London" };

局部基础数据类型的默认初始化

对于局部变量的定义,如果我们没有给出初始值,那么这个变量定义的值是不明确的未知的。如果我们使用{}对普通数据类型的变量进行定义,即使没有给出初始值,那么会根据数据类型进行默认初始化。

例子

int i;    // 随值
int j{}; // 0
int *p; // p指向未知地址
int *q{}; // q为 nullptr

禁止窄化

“窄化”意为:精度降低或造成数值变动。对于一般的赋值与初始化,允许窄化的发生。但对于大括号的初始化,不允许窄化的发生,如果有,那么编译将不通过。主要的原因是为了防止数据在转换中的丢失。

例子

int x1(5.3);   // 正确,允许double向int转变,但是x1为5
int x2 = 5.3; // 正确,同上
int x3{ 5.3 }; // 错误,不允许,因为造成了数据丢失
char c1{ 7 }; // 正确,7也属于char
char c2{ 99999 }; // 错误,99999不属于char的范围,不允许转换

std::initializer_list

为了支持“用户自定义类型之初值列”概念,C++11提供了std::initializer_list,它是一个标准库模板类型,用于支持以一些列值进行初始化。initializer_list中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。

操作

  • std::initializer_list定义在头文件<initializer_list>中。
  • 提供的操作包括:begin()end()等成员函数。

示例

void print(std::initializer_list
vals) {
for (auto p = vals.begin(); p != vals.end(); ++p) {
std::cout << *p << std::endl;
}
print({ 12, 3, 5, 4, 8 });

在类中的使用

class P {
public:
P(int, int);
P(std::initializer_list
);
};
int main() {
P p(77, 5); // 调用P(int, int)
P p2(77, 5, 20); // 错误
P p3{ 77, 5 }; // 调用P(std::initializer_list
)
P p4 = { 77, 5 }; // 调用P(std::initializer_list
)
P p5{ 77, 5, 10, 15 }; // 调用P(std::initializer_list
)
return 0;
}

explicit关键字

explicit关键字会抑制类的构造函数转换行为。例如:

class P {
public:
P(int, int);
explicit P(int, int, int);
};
int main() {
P p(77, 5); // 正确,调用P(int, int)
P p2(77, 5, 20); // 正确,调用explicit P(int, int, int)
P p4 = { 77, 5 }; // 正确,调用P(int, int)
P p4 = { 77, 5, 10 }; // 错误,explicit关键字抑制initializer_list列表使用P(int, int, int)构造函数
return 0;
}

成员变量初始化方式

成员变量的初始化有三种方式:

  • 在构造函数体内赋值初始化
  • 在自定义的公有函数体中赋值初始化(一般用于成员变量的初始化)
  • 在构造函数的成员初始化列表初始化
  • 构造函数体内初始化

    在构造函数体内的初始化方式,本质上是为成员变量赋值,而不是真正意义上的初始化。与成员初始化列表初始化相比,效率较低。

    自定义的公有函数体中赋值初始化

    与构造函数体内初始化方式一样,此种方式本质上也是赋值,而不是初始化。

    成员初始化列表初始化

    特点:

    • 写在构造函数的后面,随着构造函数的执行而执行
    • 初始化顺序:与书写的在构造函数后的顺序无关,而与成员变量的定义顺序有关
    • 初始化列表初始化优先于构造函数内的代码执行顺序
    • 多个成员之间用逗号隔开,括号内为形参
    • 一般只对无动态内存的成员、const成员、引用初始化(const成员、引用成员必须在初始化列表初始化)
    • 成员初始化列表初始化效率更高
    • 有动态内存的成员必须在构造函数内部进行初始化(因为动态内存不能进行简单的赋值,因此所存在的地址不同,要自己申请动态内存并初始化)。牢记:内部数据内部处理,外部数据外部处理

    成员的初始化顺序

    成员初始化的顺序,与在构造函数后面书写的顺序无关。而与成员变量定义的顺序有关。例如:

    class Cperson {
    private:
    int m_age;
    float m_height;
    public:
    Cperson(int age, float height);
    };
    Cperson::Cperson(int age, float height) : m_height(height), m_age(age) {}

    错误事例(初始化顺序导致的错误)

    一个特殊情况:如果用一个成员变量去初始化另一个成员变量,就要注意初始化顺序了。因此,我们在初始化的时候,尽量避免用某些成员去初始化另一个成员。

    更正

    因为初始化列表初始化比构造函数内初始化早,所以可以将上面的代码改为下面的形式就不会出错了:

    class X {
    private:
    int i;
    int j;
    public:
    X(int value) : j(value) {
    i(j);
    }
    };

    错误事例(针对const成员与引用成员)

    const成员和引用成员必须在成员初始化列表进行初始化。例如:

    class Person {
    private:
    const int id;
    int &m_id;
    public:
    Person(int i);
    };
    Person::Person(int i) {
    id = i; // 错误,`const`成员变量必须在成员初始化列表初始化
    m_id = id; // 错误,引用也必须在成员初始化列表初始化
    }

    初始化方式总结

    根据上面的三种方式,总结出:成员初始化列表初始化成员才是真正意义上的初始化,其他两种方式都是为赋值。初始化和赋值涉及到底层效率的问题:初始化是直接初始化。而赋值是先初始化一个临时变量,再赋值。前者效率高。

    上一篇:C++ this指针以及运算符重载(operator)
    下一篇:C++ 类静态成员(static)、类常量成员(const)、friend友元

    发表评论

    最新留言

    能坚持,总会有不一样的收获!
    [***.219.124.196]2025年03月24日 06时20分37秒