C++:构造函数与初始化列表详解

目录

[1. 关于构造函数](#1. 关于构造函数)

[2. 初始化列表](#2. 初始化列表)

[3. 初始化顺序](#3. 初始化顺序)


**《Effective C++:55 Specific Ways to Improve Your Programs and Designs》**一书中指出,理解C++默认生成并调用的函数至关重要。若需禁用编译器自动生成的函数,必须显式拒绝。

在类与对象部分,存在6种特殊的成员函数 ,它们不需要我们手动调用,是由编译器在特定的时机自动调用的。

这些函数可以交由编译器默认生成,当生成的函数不满足我们的需求时,我们需要自己显式实现。

本文重点介绍C++98标准中的第一个默认成员函数:构造函数。

1. 关于构造函数

  1. 功能: 类通过一个或多个默认构造函数来初始化成员对象
  2. 特点: 函数名与类名相同,无返回值,可以重载。编译器自动生成的构造函数是无参的,一旦我们显式定义,编译器就不会再生成了。
  3. 默认构造函数: 分为无参构造函数,全缺省构造函数,编译器默认生成的构造函数三种。总结一下:不传实参就可以调用的构造就是默认构造。

注意:这三种默认构造函数有且只能有一个存在,不能同时存在。否则就会产生歧义,编译器不知道该调用哪个函数。默认构造函数可以和有参构造同时 存在**。**

使用示例:

cpp 复制代码
//全缺省构造
String(const char* str = "")
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}

//有参构造
String(char* str)
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];
    strcpy(_str, str);
}

在string的模拟实现中,这两种构造函数是可以同时存在的。

总结:

没有显式实现构造函数时,编译器默认生成的构造函数,对于内置类型 成员变量的初始化是不统一 的,因编译器而异;对于自定义类型 的成员变量,会调用这个成员变量的构造函数进行初始化,没有构造函数则报错。

需要自己实现的情况:

  1. 自定义类型的构造函数

  2. 内置类型开辟了空间(eg:指针成员指向的空间需要深拷贝)

2. 初始化列表

  1. 功能: 初始化操作,使成员在创建出来的同时进行初始化
  2. **特点:**以一个冒号开始,用逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号里的表达式或者初始值。

使用示例:

cpp 复制代码
//初始化列表
String()
    :_str(new char[1]{'\0'})
    ,_size(0)
    ,_capacity(0)
{}

tips:切忌加任何分号!

注意:

必须通过初始化列表初始化的情况:

  1. 引用(&)成员变量
  2. const成员变量
  3. 没有默认构造的类类型变量

前两种情形必须在对象创建时完成初始化,无法采用先声明后赋值的方式。由于构造函数内部本质上是先创建对象再进行赋值的流程,因此这些初始化操作无法在构造函数中完成。

第三点更详细来说,如果一个类没有默认构造,那么当它作为另一个类的成员时,必须在外部类的构造函数初始化列表里显式构造它:

cpp 复制代码
class NoDefault
{
public:
    // 没有默认构造
    NoDefault(int x)
    {}
};

class MyClass
{
private:
    NoDefault _obj; // 作为外部类的成员变量

public:
    // 必须写在初始化列表中
    MyClass()
    : _obj(42)
    {}

};

**建议:**尽量使用初始化列表进行初始化,因为所有变量都会先走初始化列表。

对于没有显式出现在初始化列表的内置类型 成员变量是否初始化由编译器决定,自定义类型则会调用默认构造函数,没有则会编译报错。这一点和构造函数的规则很相似。

初始化顺序: 按照成员变量的声明顺序进行初始化,与列表中的顺序无关。因此建议列表的顺序要和声明顺序保持一致。

总结:

  • 无论是否显式写初始化列表,每个构造函数都有初始化列表。
  • 无论是否在初始化列表显式初始化了成员变量,每个成员变量都会走初始化列表。

3. 初始化顺序

我们可以把成员变量初始化的顺序想象成一个到站下车的过程,也可以看作是优先级问题。第一站是初始化列表,第二站是声明位置的缺省值,第三站是默认构造函数,第四站则是构造函数体。

具体的初始化流程为:

  1. 变量先按照声明顺序执行初始化列表:
  • 如果初始化列表显式写了变量,则用指定的值
  • 未出现在初始化列表的变量,会走声明,有缺省值则使用
  • 以上两种都不符,则编译器自动调用默认构造:内置类型随机值,自定义类型调用默认构造
  1. 最后所有变量进入构造函数体,出现在构造函数体中的变量会被赋值。没出现的变量保持初始化列表结束时的值

举个例子:

cpp 复制代码
class A
{
public:
    A(int x, int y) 
        : _b(y)           // ①
        , _c(_a)          // ②
    {
        _a = x;           // ③
        _b = x + y;       // ④
    }

    void Print() const
    {
        cout << "_a = " << _a << ", _b = " << _b << ", _c = " << _c << endl;
    }

private:
    int _a = 10;
    int _b = 20;
    int _c = 30;
};

int main()
{
    A obj(5, 7);

    obj.Print();
    return 0;
}

Q: 这串代码的输出是什么?

A:

首先看主函数,对象obj是A类的实例化,传入了参数5和7;

随后看初始化列表,此时形参x为5,y为7,按照声明顺序进行初始化:

_a未在初始化列表显式写,因此_a会使用声明的缺省值10进行初始化;_b被初始化为7,_c被初始化为_a的值,即为10;

最后,所有变量进入构造函数内部:_a被赋值为5(x),_b被赋值为12(x+y);

输出结果为:

// 感谢阅读~顺手点个免费的赞吧(●'◡'●)

//封面是特蕾西娅殿下TvT

相关推荐
落羽的落羽2 小时前
【Linux系统】总结线程:死锁问题、实现带有日志模块的线程池类
linux·运维·服务器·c++·人工智能·机器学习
琪露诺大湿2 小时前
VeloQueue-测试报告
java·开发语言·消息队列·单元测试·项目·测试报告
minji...2 小时前
Linux 网络套接字编程(四)支持多客户端同时在线、消息能转发给所有人的 UDP 聊天室服务器
linux·运维·开发语言·网络·c++·算法·udp
XS0301062 小时前
Java 基础(十一)反射
java·开发语言
t***5442 小时前
Dev-C++中使用Clang调试有哪些常见错误
java·开发语言·c++
郝学胜-神的一滴2 小时前
[简化版 GAMES 101] 计算机图形学 06:相机视图矩阵的由来
c++·线性代数·unity·矩阵·godot·图形渲染·unreal engine
ydmy2 小时前
强化学习/对齐(个人理解)
开发语言·python
一叶之秋14122 小时前
哈希密钥:解锁unordered容器的极速潜能
开发语言·c++·哈希算法