【C++ Primer】构造函数
一、构造函数定义
每个类都分别定义了它的对象被初始化的方式,类通过一个或者几个特殊的成员函数来控制其对象的初始化过程,这些函数叫构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
构造函数的名字和类名相同。和其他函数不一样的是构造函数没有返回类型。
不同于其他成员函数,构造函数不能被申明为const。当我们在创建类的const对象时,直到构造函数完成初始化过程,对象才真正取得”常量“属性。因此,构造函数在const对象的构造过程中可以向其写值。
二、合成的默认构造函数
如果我们的类没有显示的定义构造函数,那么编译器编译器会为我们隐式的定义一个默认构造函数。编译器创建的这个函数叫合成默认构造函数。对于大多数类来说,这个合成的默认构造函数将按照如下规则初始化类的数据成员:
- 如果存在类内初始值,用它来初始化成员
- 否则,默认初始化该成员
三、某些类不能依赖合成默认构造函数
合成默认构造函数只适合非常简单的类。对于某些特殊的类由于以下三种原因不能使用合成默认构造函数:
- 第一个原因是编译器只有在发现类不包含任何构造函数的情况下才会替我们生成一个默认构造函数。一旦我们定义了其他的构造函数,那么除非我们再定义一个默认构造函数,否则类将没有默认构造函数。
- 第二个原因是合成的默认构造函数可能执行错误的操作,如果类的成员变量被默认初始化,则他们的值是将是未定义的。因此,含有内置类型,或复合类型成员的类应该在类的内部初始化这些成员,或者定义一个构造函数。否则,用户在创建类的对象时可能得到未定义的值。
- 第三个原因是有时候编译器不能为某些类合成默认构造函数。例如,类中包含一个其他类型的成员,且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。对于这样的类来说,我们必须自定义默认构造函数,否则该类将没有默认的构造函数。
四、=default 的含义
在c++11标准中,如果我们需要默认的行为,那么可以通过在参数列表后面加上 =default 来要求编译器生成默认构造函数。其中,=default 既可以和声明一起在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果 =default 在类的内部,则默认构造函数是内联的;如果它在类的外部,则该成员默认不是内联的。
class Sale_data{
public:
Sale_data() = default; // 类的内部定义default
};
// Sale_data::Sale_data() = default; // 内的外部定义default
五、默认构造构造函数的调用时机
当对象被默认初始化或值初始化时自动调用默认构造函数,默认初始化在以下情况发生:
- 当我们在块作用域内不使用任何初始值定义一个非静态变量或者数组时
- 当一个类本身含有类类型成员且使用合成的默认构造函数时
- 当类类型的成员没有在构造函数初始值列表中显示地初始化时
值初始化在以下情况会发生:
- 在数组初始化的过程中如果我们提供的初始值数量小于数组大小时
- 当我们不使用初始值定义一个局部静态变量时
- 当我么显示的使用形如 T() 的表达式显示的请求初始化时,其中T是类型名
类必须包含一个默认构造函数以便上述情况下使用。
#include <iostream>
using namespace std;
class A{
public:
A(){ cout << "A() default constructor called" << endl; };
A(int a) { cout << "A(int a) constructor called" << endl; };
};
class Sale_data{
public:
Sale_data():b(1){ cout << "Sale_data default constructor called" << endl; }
private:
A a;
A b;
};
int main()
{
Sale_data s; // A() default constructor called
// A(int a) constructor called
// Sale_data default constructor called
A a; // A() default constructor called
A arr[10]; // A() default constructor called 调用10次
A a1 = A(); // A() default constructor called
static A a2; // A() default constructor called
}
六、构造函数初始值列表
在冒号和花括号之间的部分是构造函数初始值列表,它负责为新创建的对象一个或几个数据成员赋初始值。不同成员的初始值用逗号分隔开来。
class Sale_data{
public:
Sale_data(const string &s, int n): // 构造函数
bookNo(s),units_sold(n) // 构造函数初始值列表初始化
{
revenue = 0; // 构造函数初始化
};
private:
string bookNo;
int units_sold = 0; // 类内部初始化
int revenue = 0; // 类内部初始化
};
从上面的代码可以看出,类成员变量可以在以下三个位置初始化:
- 类内部
- 构造函数初始化值列表
- 构造函数
成员变量初始化的执行顺序依次是:
类内部 -> 构造函数初始化值列表 -> 构造函数
七、在类外部定义的构造函数
当我们在外部定义构造函数时,必须指明必须指明该构造函数是哪个类的成员。因此,如下代码中 Sale_data::Sale_data 的含义是我们定义Sale_data类的成员,它的名字是Sale_data。又因为该成员函数的名字与类名相同,所以它是一个构造函数。
class Sale_data{
public:
Sale_data();
Sale_data(const string &s, int n)://内部定义构造函数
{
revenue = 0; // 构造函数初始化成员变量
};
private:
string bookNo;
int revenue = 0;
};
Sale_data::Sale_data() // 外部定义构造函数
{
bookNo = "book1"; // 构造函数初始化成员变量
}
八、委托构造函数
c++11扩展了构造函数初始值的功能,使得我们可以使用所谓的委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把自己的一些职能委托给了其他构造函数。
#include <string>
using namespace std;
class Sale_data{
public:
// 非委托构造函数使用对应的实参初始化成员
Sale_data(string &s)bookNum(s){
cout << "Sale_data(string &s) called" << endl;
};
// 委托构造函数,调用对应的其他构造函数
Sale_data():Sale_data(""){
cout << "Sale_data() called" << endl;
units_sold = 0;
};
private:
string bookNum;
int units_sold;
}
在上面的委托构造函数中,调用构造函数Sale_data() 时,先调用Sale_data(string &s),然后在执行自身的函数体。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了