构造函数体内的赋值:
创建一个类对象时,编译器通过调用构造函数,给对象中的成员变量一个初值
class Date
{
public:
//构造函数体内赋值
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作类对象成员的初始化,构造函数体中操作只能将其称作为赋初值,而不能将其称作为初始化。因为初始化只能初始化一次,而构造函数体内,可以给 成员多次赋值
初始化列表:
以一个冒号开始,接着是一个以逗号分割的数据成员列表每个"成员变量"后面跟一个放在括号中的初始值或表达式
class Date
{
public:
//用初始化列表初始化对象成员变量
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
说明:
- 每个成员在初始化列表中只能出现一次
- 初始化列表仅用于初始化类的数据成员,并不指定这些数据成员的初始化顺序,数据成员在类中定义顺序就是在初始化列表中的初始化顺序
- 尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义顺序保持一致
- 类中包含以下成员,必须放在初始花列表中对其初始化:
- 引用成员标量
- const 修饰的成员变量
- 类类型成员
构造函数作用:
构造函数不仅可以把对象构造成功,还可以给对象中各个成员变量一个初始值,有时候还具有类型转换的作用(这点以后解释)
默认构造函数:
如果一个类未显式定义构造函数时,编译器会合成一个默认的构造函数。如果类显式定义了,编译器将不再合成。
注意:编译器合成的构造函数一定是不带参数的
class Date
{
public:
void show()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void test()
{
Date d;
d.show(); //随机值
}
以上代码中我们没有显示定义构造函数,对象可以创建成功,因为编译器合成了一个默认的不带参数的默认构造函数,但是该默认构造函数实际什么事情都不做,类似以下方式:
Date()
{}
即该构造函数实际没有任何意义。那完全可以不用合成,合成了就要调用,而调用了该构造函数又没有实际意义, 而且还影响程序的效率
通过汇编代码发现,创建对象语句底层没有构造函数对应的汇编语句,即编译器并未合成构造函数(可以显式添加无参构造函数,就会看到调用构造函数的汇编代码)
因此编译器对是否合成构造函数进行了优化,编译器根据自己的需求选择性的合成构造函数,即如果类中没有显式定义构造函数,在编译器需要且有能力合成时,会合成一个无参的构造函数。比如
class Time
{
public:
Time(int hour = 12,int minute = 12,int second = 12)
:_hour(hour)
,_minute(minute)
,_second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void show()
{
cout << _year << "-" << _month << "-" << _day<< endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
void test()
{
Date d;
d.show();
}
Time类显式定义了其缺省的构造函数,Date类未显式定义构造函数,其中包含了一个Time类对象。当创建Date类对象时,编译器需要调用Date类的构造函数完成Date类对象的构造,由于Date未显式定义,此时编译器必须合成:因为Time类的构造函数存在,合成Date构造函数的目的就是为了完成Data类对象中包含的Time类对象的构造
但是,如果将Time类缺省的构造函数改成非缺省构造函数编译器就没有能力合成,此时会报错
这里很好的说明了编译器 有必要且有能力 合成构造函数