文章目录
1. 再谈构造函数
1.1 构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能称其为对对象中成员变量的初始化,构造函数体中的语句只能称为对成员变量赋初始值,而不能称其为初始化。因为初始化只能初始化一次,而构造函数体内可以有多次赋值;
下面的内容便是对成员函数进行初始化的讲解;
1.2 初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量 后面跟一个放在括号中的初始值或表达式;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{
}
private:
int _year;
int _month;
int _day;
};
注意:
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次);
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:引用成员变量、const成员变量、 自定义类型成员(且该类没有默认构造函数时);
前两种(引用成员变量,const成员变量)为什么必须在初始化列表进行初始化其实很好理解——因为引用和const修饰的变量必须进行初始化,且只有一次机会,如果不进行初始化编译器便会直接报错;
int main()
{
const int a = 10;
//const int a;//error
int b = 10;
int& rb = b;
//int& rc;//error
return 0;
}
再来看看第三种,为什么对于没有默认构造函数的自定义类型就要在初始化列表初始化呢?
编译器对这里的机制是这样的:每个成员都要走初始化列表,就算不显式在初始化列表写,也会走;如果没有在初始化列表显式初始化:对于内置类型,有缺省值就用缺省值,没有就用随机值;对于自定义类型:调用它的默认构造函数,如果没有默认构造函数就会报错;
class A
{
public:
//不是默认构造
A(int a)
:_a(a)
{
}
private:
int _a;
};
class B
{
B(int a, int ref)
:_aobj(a)//调用它自己的构造函数
,_ref(ref)
,_n(10)
{
}
private:
A _aobj;//没有默认构造
int& _ref;//引用
const int _n;//const
};
3.尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化;
在声明第4点之前,先来看看下面这段代码打印的是什么:
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();
return 0;
}
相信不少人会说打印的就是1 1啊,这有啥吗?但其实不是!打印的是随机值 1,答案就在下方!
4. 成员变量 在类中声明次序 就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关;所以上述代码是先初始化_a2,再初始化_a1的!
注意:在初始化列表阶段,也能在堆区开辟空间,如下:
class A
{
public:
A()
:_a((int*)malloc(sizeof(int) * 4))
{
}
private:
int* _a;
};
1.3 类成员变量的缺省值与初始化列表的联系
通过之前的学习,我们已经了解到,在默认构造函数中,如果对内置类型不进行处理,它将会是一个随机值,但我们可以通过给缺省值的形式进行处理;如下:
class A
{
public:
A()
//:_a(1)
{
}
private:
int _a = 1;
};
类似上述给缺省值的操作其作用时间便是在初始化列表时期!