【C++】深入解析构造函数初始化

1. 再谈构造函数

1.1 构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值

cpp 复制代码
class Date
{ 
public: 
    Date(int year, int month, int day) 
     {     
        _year = year;     
        _month = month;      
        _day = day; 
     } 
private: 
    int _year; 
    int _month; 
    int _day; 
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

1.2 初始化列表

在构造函数的地方进行初始化

初始化列表:以一个冒号开始 ,接着是一个以逗号分隔的数据成员列表 ,每个**"** 成员变量 " 后面跟一个放在括号中的初始值或表达式。

cpp 复制代码
class Date 
{ 
public: 
    Date(int year, int month, int day)
            : _year(year) 
            , _month(month) 
            , _day(day) 
     {} 
private: 
    int _year; 
    int _month; 
    int _day; 
};

【注意】

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:

  • 引用成员变量

  • const 成员变量

  • 自定义类型成员(且该类没有默认构造函数时)

cpp 复制代码
class A 
{ 
public:  
    A(int a) 
     :_a(a) 
     {} 
private:  
    int _a; 
}; 

class B 
{ 
public:  
    B(int a, int ref) 
     :_aobj(a) 
     ,_ref(ref) 
     ,_n(10) 
     {} 
private:  
    A _aobj;  // 没有默认构造函数  
    int& _ref;  // 引用  
    const int _n; // const  
};

3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化

cpp 复制代码
class Time 
{ 
public:  
    Time(int hour = 0) 
     :_hour(hour) 
     {  
        cout << "Time()" << endl; 
     } 
private:
    int _hour; 
}; 
class Date 
{ 
public:  
    Date(int day) 
     {} 
private: 
    int _day;  
    Time _t; 
}; 

int main() 
{     
    Date d(1); 
}
  1. 成员变量 在类中声明次序 就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关,即初始化时会先初始化_a2,再初始化_a1
cpp 复制代码
class A 
{ 
public:     
    A(int a) 
           :_a1(a) 
           ,_a2(_a1) 
       {}      
        
    void Print() 
    {         
        cout<<_a1<<" "<<_a2<<endl; 
    } 
    
private:     
    int _a2;     
    int _a1; 
}; 

int main() {     
    A aa(1);     
    aa.Print(); 
} 
A. 输出1  1 
B.程序崩溃 
C.编译不通过 
D.输出1  随机值
答案:D

1.3 explicit 关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。'

cpp 复制代码
class Date 
{ 
public:
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用  
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译  
    explicit Date(int year) 
         :_year(year) 
         {}  
/*  
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具 
有类型转换作用  
// explicit修饰构造函数,禁止类型转换  
explicit Date(int year, int month = 1, int day = 1)  
    : _year(year)  
    , _month(month)  
    , _day(day)  
    {}  
*/ 
     
    Date& operator=(const Date& d) 
     {  
        if (this != &d) 
         {  
            _year = d._year;  
            _month = d._month;  
            _day = d._day; 
         } // 
        return *this; 
     } 
private:  
    int _year;  
    int _month;  
    int _day; 
}; 

void Test() 
{  
    Date d1(2022);  
    // 用一个整形变量给日期类型对象赋值  
    // 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值  
    d1 = 2023;  
    // 将1屏蔽掉,放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用 
}

上述代码可读性不是很好,用 explicit 修饰构造函数,将会禁止构造函数的隐式转换

隐式类型转换:

cpp 复制代码
// 单参数构造函数(未用explicit修饰时)
Date(int year) : _year(year) {}

当执行 d1 = 2023 时,编译器会自动进行以下隐式转换:

用 2023 作为参数,隐式构造一个临时的 Date 对象(等价于 Date(2023))调用赋值运算符 operator=,将这个临时对象赋值给 d1