1、 构造函数:
(1) 定义:是一个特殊的成员函数,名字与类名相同,创建类类型对象时,由编译器自动调用,在对象的生命周期内只且只调用一次,以保证每个数据成员都有一个合适的初始值。
(2) 特性:
1、函数名与类名相同。
2、没有返回值。
3、有初始化列表(可以不用)。
4、新对象被创建,由编译器自动调用,且在对象的生命期内仅调用一次。
5、构造函数可以重载,实参决定了调用那个构造函数。
6、如果没有显式定义时,编译器会提供一个默认的构造函数。
7、无参构造函数和带有缺省值得构造函数都认为是缺省构造函数,并且缺省构造函数只能有一个。
(3)分类:
无参构造函数:如果没有显式的定义一个构造函数,系统将默认合成一个无参构造函数,参数为空,什么都不做。
显式的定义一个无参构造函数,如下例:
Time()
{
cout<<"Time(int intint )"<<endl;
}
普通构造函数(也称重载构造函数):
class Time
{
public:
Time(int h, int m, int s)
{
_hour = h;
_minute = m;
_second = s;
cout<<”Time(int, int, int)”<<endl;
}
private:
int _hour;
int _minute;
int _second;
};
系统默认合成的构造函数:在A类有缺省的构造函数,B类没有显示的定义构造函数,B类中含有A类类型成员,此时系统默认合成构造函数。
class Time
{
public:
Time()
{
cout<<"Time(int intint )"<<endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
int _year;
int _month;
int _day;
Time t;
};
运行结果:
初始化列表:
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在园括号中的初始化式。
classDate
{
public:
Date(int year = 1, int month = 2, int day = 3)
:_year(year),
_month(month),
_day(day)
{
cout<<"Date(int, int, int)"<<endl;
}
private:
int _year;
int _month;
int _day;
};
初始化顺序:
1、初始化列表仅用于初始化数据成员,并不指定这些数据成员的初始化顺序,数据成员在类中定义顺序就是在参数列表中的初始化顺序。
2、每个成员在初始化列表中只能出现一次,尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义顺序保持一致。
类中包含以下成员必须要放在初始化列表中初始化:
class Time
{
public:
//Time()
//{}
Time(int h, int m, int s)
{
cout<<"Time(int intint )"<<endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year, int month, int day)
:_year(year),
_month(month),
_day(day),
t(0, 0, 0)
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
const int a;
int& b;
};
1、引用数据成员
2、const数据成员
3、类类型成员(该类没有缺省的构造函数):Time类中没有缺省的构造函数(无参构造函数,有参数,但有缺省的构造函数),要调用Date类创建对象,则类内的成员都要初始化,Date类中含有Time类类型成员,只能在初始化列表内进行初始化
2、 拷贝构造函数
显式的定义拷贝构造函数:
class Date
{
public:
Date(int year = 1, int month = 2, int day = 3)
:_year(year),
_month(month),
_day(day)
{
cout<<"Date(int, int,int)"<<endl;
}
Date(const Date &d)
:_year(d._year),
_month(d._month),
_day(d._day)
{
cout<<"Date(&d)"<<endl;
}
private:
int _year;
int _month;
int _day;
};
void FunTest()
{
Date d;
Date d1(d);
}
运行结果:
系统默认合成拷贝构造函数:在A类有缺省的构造函数,B类没有显示的定义拷贝构造函数,B类中含有A类类型成员,此时系统默认合成拷贝构造函数。
class Time
{
public:
Time()
{
cout<<"Time(int intint )"<<endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1, int month = 2, int day = 3)
:_year(year),
_month(month),
_day(day)
{
cout<<"Date(int, int,int)"<<endl;
}
private:
int _year;
int _month;
int _day;
Time t;
};
void FunTest()
{
Date d;
Date d1(d);
}
运行结果:
3、 析构函数:与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作
析构函数特性:
a、析构函数在类名(即构造函数名)加上字符~。
b、析构函数无参数无返回值。
c、一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
d、对象生命周期结束时,C++编译系统系统自动调用析构函数。
e、注意析构函数体内并不是删除对象,而是做一些清理工作
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
cout<<"Date(int, int,int):"<<this<<endl;
}
Date(const Date& d)
: _year(d._year)
, _month(d._month)
, _day(d._day)
{
cout<<"Date(constDate& d):"<<this<<endl;
}
Date& operator=(const Date& d)
{
cout<<"Date&operator=(const Date& d):"<<this<<endl;
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
~Date()
{
cout<<"~Date():"<<this<<endl;
}
private:
int _year;
int _month;
int _day;
};
void FunTest()
{
Date d(2016, 10, 17);
Date d1(d);
}
运行结果:
析构函数销毁对象原则:先构造后析构,后构造先析构。
赋值运算符重载:
在面向对象程序设计中,对象间的相互拷贝和赋值是经常进行的操作,如果对象在申明的同时马上进行的初始化操作,则称之为拷贝运算。例如:
class A(“Date”) ; class B = A;
此时其实际调用的是B(A)这样的浅拷贝操作。
如果对象在申明之后,在进行的赋值运算,我们称之为赋值运算。例如:
class A("Date"); class B;
B=A;
此时实际调用的类的缺省赋值函数B.operator=(A);
class Date
{
public:
Date(int year = 2016, int month = 10 , int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
cout<<"Date(int, int,int):"<<this<<endl;
}
Date(const Date& d)
: _year(d._year)
, _month(d._month)
, _day(d._day)
{
cout<<"Date(constDate& d):"<<this<<endl;
}
Date& operator=(const Date& d)
{
cout<<"Date&operator=(const Date& d):"<<this<<endl;
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
~Date()
{
cout<<"~Date():"<<this<<endl;
}
private:
int _year;
int _month;
int _day;
};
void FunTest()
{
Date d(2016, 10, 17);
Date d1(d); // 调用拷贝构造函数进行拷贝
Date d2;
D2 = d; //d1.operator(&d)赋值运算符重载
}
程序编译之后,d1 和 d2 在栈上都分配了内存,,对象d1 的域被初始化,d2为随机值。
如果我们简单的执行Date d1(d); 即 Date d1 = d; 则其执行的是缺省定义的缺省的赋值运算。所谓缺省的赋值运算,是指对象中的所有位于stack中的域,进行相应的复制。但是,如果对象有位于heap上的域的话,其不会为拷贝对象分配heap上的空间,而只是指向相同的heap上的同一个地址。
因此,对于缺省的赋值运算,如果对象域内没有heap上的空间,其不会产生任何问题。但是,如果对象域内需要申请heap上的空间,那么在析构对象的时候,就会连续两次释放heap上的同一块内存区域,从而导致异常。
故需要用赋值运算符重载
Date d2;
D2 = d; //d1.operator(&d)赋值运算符重载
Date& operator=(const Date& d)
{
cout<<"Date&operator=(const Date& d):"<<this<<endl;
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}//一定要返回引用,否则返回其值后立即消失,不能连续赋值