目录
前言
在我们已经基本了解了类的由来,和其六大基本默认函数后,此时我们再回过头来看,其中的构造函数。在前文已经说过构造函数的作用为初始化成员变量,但是真的就这么简单了吗?如果成员变量为常变量(const)那还能构造成功吗?在构造函数体内部,我们直接可以用类的成员变量来赋值,但是在调用构造函数前是没有这个类对象的,既然都没有这个类对象,哪前面我们直接用来赋值的类中的成员变量是哪里来的?
下面将带来以上的解答与本人理解(若有错误欢迎指出)。
1. 再谈构造函数
1.1 构造函数体赋值
我们先来复习一下构造函数,如下:
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
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 .1什么是初始化列表
在托出初始化列表前为了我们更好的理解,它与构造函数的区别和关系,我们来一个列子作为对比
见上左图:A的第一次为定义赋值,而第二次为的赋值就为二次赋值了。
见下图:我们发现我们在创建A2这个实例化对象时,调用了构造函数但在进入构造函数体前,这个A2居然已经创建(初始化)好了,虽然这是个随机数,那它是在哪里创建这个对象的呢?
此时初始化列表就说:没错就是我干的,是我来真正初始化类成员的,构造函数体内只是二次赋值罢了。
但在上面的图片中,我们明明没看见这个所谓的初始化列表那他在哪里呢?
1.2.2初始化列表的格式
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。
我们以日期类来做列子:
lass Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
1.2.3 关于初始化列表存在意义:
初始化列表不就是定义赋值罢了,我们在构造函数体内二次赋值,结果不是一样的吗?
!注意:
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
①引用成员变量
引用变量为随机值,是一种很危险的行为(引用是由C语言的指针修改而来,本质上还是指向内存的地址)
②const成员变量(整个生命周期只有在定义时可以赋值一次)
const修饰的变量,为常变量,具有常性(不可修改/二次赋值),只能在定义时赋值
③自定义类型成员(且该类没有默认构造函数时,如果有,可以不用,他会自动调用其自己的构造函数)
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
};
1.2.4初始化列表和成员声明缺省值
在前文中,我们还提到了,由于编译器对于内置类型的初始化不做处理(结果内置类型就变为了随机值),在C++11 中针对内置类型成员不初始化的缺陷,打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。
如下图
如果在成员变量处添加了默认值(缺省值),在初始化列表处又该如何运行的呢?
我们发现这2个地方,若都有赋值的话,则会优先初始化列表的赋值,由此我们可以推出以下结论,成员变量处的默认值,是作用于初始化列表的,而不是用于构造函数体内的,且该值为初始化列表的缺省值(我们可以取一个形象一点的名字:初始化列表的缺省值——定义缺省值)
1.2.5初始化列表的顺序
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关。
(很离谱是吧,我也觉得,为了防止因顺序出现的BUG,成员变量的声明顺序最好和初始化列表的先后顺序一致)
来一波热炒热卖吧!请看下题:
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
让我康康有没有小可爱选了A的,有的话罚你在看一遍。
小结
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化,即在创建新的类对象时,都会调用构造函数,也都会经过初始化列表(就算你不写,编译器也会走)。
尽量使用初始化列表初始化还有个好处,就是将初始化的步骤可以尽可能的放在一起,而构造函数体内就可以写其它的需求实现,比如访问或修改除了新类对象的其它变量等等,就可增加代码的可读性。
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);
}