一、再谈构造函数
构造函数的作用是在我们创建新的变量时,对变量中的成员进行初始值的赋值。
class date
{
public:
date(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
}
在这个构造函数中,成员变量都有了一个初始值,但是构造函数并没有对这些成员进行初始化,函数的作用就是在初始化之后,对其进行赋值。因为初始化只能进行一次,但是在构造函数体内可以多次反复进行值的更改,所以这不是初始化。
二、初始化列表
1.概念
初始化列表的表达形式是以一个冒号开头,然后中间的各个成员用逗号分割开,在每一个成员变量之后有一个括号,括号中是对应的初始化值或者表达式。
注意:在每行的结束不加分号
date(int year,int month,int day)
:year(_year)
,month(_month)
,day(_day)
{}
这就是构造函数中的初始化列表。
2.特点
目前看来初始化列表好像并没有和构造函数体内赋值有很大的区别,所以接下来我们就一起看看有哪些情况是必须使用初始化列表进行初始化的。
**1. 引用成员变量
-
const类成员变量
-
自定义成员类型并且是没有默认构造函数**
以上三种类型的初始化必须是在初始化列表中进行的。在我们之前的学习中就知道,引用和const类成员变量必须在定义时进行初始化,所以就来不及在构造函数内部进行赋初值了,必须在初始化列表里进行初始化。
然后就是自定义成员类型,自定义成员类型的初始化是去找自身的构造函数,如果没有显式定义的就去找默认构造函数,但如果当默认构造函数也没有的话,我们就只能去在初始化列表里进行定义了。
这里的默认构造函数包含三类: -
编译器自动进行编写的
-
无参数传递的构造函数
-
参数全缺省的构造函数
第一种是编译器自动寻找的默认构造函数,也是面临两种情况。当自定义类型的成员变量也是自定义类型时,系统就会去看看这个成员变量有没有显式定义的构造函数,然后去调用这个成员变量的构造函数。 如果大家都比较贫穷,连这个成员变量也没有显式定义的构造函数的话,那么系统还会自动生成一个默认构造函数,但是这个时候的构造函数的赋值就是随机值了。
后面两种其实没有本质上的区别,都是不用传参,但是不同于内置类型的默认构造,并不是没有显式定义才可以判定为要用默认构造,自定义类型的一部分显式定义也被判定为调用了默认构造,就是无参数的以及参数全缺省的这两种,如果这两种默认构造函数也对不上的话,其编译器会报错为没有对应的构造函数,就说明你有构造函数,所以系统就不会再去默认构造函数里找了,但是参数类型可能不一样,所以对应不上,这时候就需要去初始化列表中去进行初始化了。
3.注意事项
1.初始化列表中的所有成员只能出现一次
因为这是进行初始化,所有成员的初始化只能进行一次。
2. 尽量使用初始化列表,因为在自定义成员的初始化中,无论你是否会显式的表达初始化列表,编译器都会去执行初始化列表。
3.初始化列表是存在顺序问题的,声明的顺序决定初始化的顺序
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 随机值
让我们来分析一下这道例题,在整个初始化的过程中,我们可以看到有两次是存在顺序问题的。第一次是在声明时,private里先是声明了_a2,然后有声明了_a1,第二次是在初始化列表中,初始化列表先是给_a1进行赋值,再对_a2进行赋_a1的值。我们事项里说过,是声明的顺序决定了初始化的顺序。所以在初始化列表进行初始化时,会优先去初始化_a2,但是_a1还是随机值,所以这个拷贝构造函数就直接将_a2初始化成了一个随机数。然后再去初始化_a1,将1赋值给_a1,于是在最后输入的就是1和随机值,答案选D。
创作不易,感谢阅读。