构造函数初始化列表
构造函数:
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
//可以修改值
_year = 2024;
_month = 9;
_day = 21;
}
private:
int _year;
int _month;
int _day;
};
这样的构造函数和普通函数一样,都是在函数体里对成员变量的值进行修改。
但是初始化并不是在函数体里实行的。而是在函数体还没执行之前就已经初始化了。当然这个初始化是我们可以控制的
引入初始化列表:
初始化列表就是在函数体之前,以 冒号开头,逗号分割的对成员函数进行初始化。如下代码。
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
attention:
1.初始化列表不管你写于不写,都是会执行的。(因为变量都是初始化后才能赋值)
int i = 1;//正确
i = 1;//错误
2.每个成员变量在初始化列表中只能出现一次。(重复初始化是错的)
int i = 1;//初始化1次
int i = 2;//重复初始化错误
3.成员变量的 初始化和初始化列表的顺序无关,而是和声明的顺序有关。
#include<iostream>
using namespace std;
const int i = 0;
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
: _day(i++)
, _year(i++)
,_month(i++)
{}
private:
int _year; // == 0
int _month; // == 1
int _day; // == 2
//i == 3
};
如上,初始化顺序依然是year month day。 结果year == 0,month == 1,day == 1.
我们是根据声明的顺序去找对应的初始化方案。
初始化列表的作用:
1. 提高运行效率:
要初始化的成员变量无非两种:内置类型和自定义类型
对于内置类型而言:使用初始化列表和不使用相差不大。
// 使用初始化列表
int a = 10;
// 在构造函数体内初始化(不使用初始化列表)
int a;
a = 10;
对于自定义类型而言:使用初始化列表可以提升效率。
class Time
{
public:
Time(int hour = 0)
{
_hour = hour;
}
private:
int _hour;
};
class Test
{
public:
// 在构造函数体内初始化(不使用初始化列表)
Test(int hour)
{ //初始化列表调用一次Time类的构造函数(不使用初始化列表但也会走这个过程)
Time t(hour);// 调用一次Time类的构造函数
_t = t;// 调用一次Time类的赋值运算符重载函数
}
private:
Time _t;
};
上述代码,我们没有用初始化列表初始化_t 对象
导致的问题就是我们在创建对象时,已经默认初始化了一次,现在_t 是存在的,但是值是不清楚的。所以要对其进行更改,就得先创建一个对象,然后通过赋值重载给_t 对象
如果使用初始化列表:初始化列表在对象创建时,通过传入参数的值直接对成员变量初始化。就省去了赋值的操作
Test(int hour) :_t(hour) {}
2. 对于必须要初始化的成员变量,必须放在初始化列表
1.const成员变量:
很好理解,因为const int i ;const int 这类变量是必须初始化的。因为const 关键字是定义了一个常量,常量是初始化后不允许被修改的。如果不初始化,那就违背了const的初衷
2.引用类型的成员变量:
一样的,引用类型的变量必须被初始化。(因为引用就是起外号,没有说先起一个外号留着备用的,而是根据一个人取的。)
const和引用类型都是语法要求的
3.没有默认构造函数的类:
默认构造函数:并不只是单一的指我们没写时,系统自动添上的。事实上有三种默认构造函数
1.没有构造函数时系统自动添上(只要有构造函数,这个就失效了)
2.构造函数的参数是全缺省的(参数都有缺省值)
Date(int year = 0, int month = 1, int day = 1) :_year(year) , _month(month) , _day(day) {}
3. 没有参数的构造函数。
三小只必须用初始化列表初始化 ,因为语法要求。
explicit关键字
explicit(显示)关键字只用于单参数的构造函数。相反的就是implicit(隐式)
首先介绍一下什么是隐式类型转换
int a = 20;
double sum = 0;
sum = a;//隐式类型转换
//double tmp = a,sum = tmp;
sum = a + 20.0;//隐式类型转换
sum = a时,会有一个转换的过程。编译器会临时使用内部的数据结构或寄存器来存储转换后的值,但是这个值不可见。然后再把这个值赋给sum。这就是隐式类型转换。a + 20.0也是同理。
对于单参数的构造函数而言默认情况下是不使用explicit关键字的,也就是说是可以进行隐式类型转换的
未加入explicit关键字:
class Date
{
public:
// 构造函数
Date(int year = 0)
: _year(1)
{}
private:
int _year;
};
int main()
{
Date year1(1);//对
Date year2 = 2;//对
Date year3 = 'a';//对
Date year4 = 4.0;//对
}
对于上述代码我们只有一个参数 下面的year1是正常调用构造函数。
year2的实际做法是:转换过程中有一个Date对象用2初始化,然后把这个对象赋值给了year2.
Date year2 = 2;
//拆分过程(抽象)
Date tmp(2);
year2 = tmp;
year3 和 year4 的做法都是:先将'a'转换为int(ASCII码表),4.0转为int。然后进行year2的做法。两次隐式类型转换
加入了explicit关键字:
1.有效的添加
explicit Date(int year = 0)
: _year(1)
{}
现在隐式类型转换失效,上述的year2 year3 year4全失效。
2.无效的添加
explicit Date(int year,int month)
: _year(year),
_month(month)
{}
当有两个以上的参数时,explicit等于没有,因为无法赋值两个变量
int i = 1,3;
没有这种初始化方法。
3.例外添加
explicit Date(int year,int month=1,int day=1)
: _year(year),
_month(month),
_day(day)
{}
当只有第一个参数没有缺省值而其他参数都有缺省值时,这时候也可以进行隐式类型转换。所以explicit生效。