默认的六个成员函数包括
- 构造函数
- 拷贝构造函数
- 析构函数
- 赋值运算符重载
- 取地址运算符(&)重载
- const修饰的取地址运算符重载
构造函数
- 构造函数的命名必须和类名完全相同;
- 构造函数的功能主要用于在类的对象创建时定义初始化的状态。没有返回值,也不能用void修饰。这就保证了它不仅什么也不用自动返回,而且根本不能有任何选择,而其他方法都有返回值,即使是void返回值。尽管方法体本身不会自动返回什么,但仍然可以让它返回一些东西,而这些东西是不安全的 ;
- 构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用;而一般的方法是在程序执行到它的时候被调用;
- 当定义一个类的时候,通常情况下都会显示该类的构造函数,并在函数中指定初始化的工作也可以省略,不过编辑器会提供一个默认的构造函数,此默认构造函数是不带参数的;
- 当一个类只定义了私有的构造函数,将无法通过new关键字来创建其对象,当一个类没有定义任何构造函数,编译器会为其自动生成一个默认的无参的构造函数;
- 构造函数有回滚的效果,构造函数抛出异常时,够赞的是一个不完整对象,会回滚,将此不完整对象的成员释放;
class Time
{
public:
Time()//带有默认值的无参构造函数,并初始值设置为0
:hour(0)
, mintue(0)
, second(0)
//Time(int _hour,int _month,int _second)//带参数的构造函数
//hour=_hour;
//month=_month;
//second=_second;
{}
private:
int hour;
int mintue;
int second;
};
如果一个类没有定义任何构造函数,那么编译器只有在以下三种情况下,才会提供默认的构造函数:
1. 如果累的虚拟成员函数或虚继承父类(即有虚基类)时;
2. 如果累的基类有构造函数(可以是自定义的构造函数也可以是默认的构造函数);
3. 在类中的所有非静态的对象数据成员,他们对应的类中有构造函数(可以是自定义的构造函数也可以是默认的构造函数)。
拷贝构造函数
调用拷贝构造函数的情景:
1. 一个对象作为函数参数,以值传递的方式传入函数体;
2. 一个对象作为函数返回值,以值传递方式从函数体返回;
3. 一个对象给另外一个对象初始化;
如果前两种情况不使用拷贝构造函数,就会导致指针指向一个已被删除的内存空间。事实上,拷贝构造函数是由普通构造函数和赋值运算符重载共同实现的。
拷贝构造函数必须以一个对象引用的形式传递,因为当一个对象以传递值得方式传入一个函数的时候,拷贝构造函数自动的被调用来生成函数中的临时对象。如果一个对象时被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限递归直至栈溢出。除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回时也同样被调用。
class Time
{
public:
Time()
:hour(0)
, mintue(0)
, second(0)
{}
Time(const Time& t)//拷贝构造函数
{
hour = t.hour;
mintue = t.mintue;
second = t.second;
}
private:
int hour;
int mintue;
int second;
};
如果在类中没有显示的声明一个拷贝构造函数,那么,编译器会自动生成一个来进行对象之间非静态成员的拷贝。这个隐含的拷贝构造函数简单的关联了所有的类成员。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数,除非另外一个构造函数在类初始化或构造列表的时候被调用。
需要注意的是:默认的拷贝构造函数是浅拷贝,比如t1(1900 1 1);t2(t1);此时t2和t1共同指向了同一块空间,如果对同一块空间进行再次析构编译器会挂掉,所以此时就需要用户自定义实现拷贝构造函数和析构函数。在用户自定义实现的拷贝构造函数中为拷贝构造的对象再分配空间(深拷贝)具体代码如下:
Time(const Time &t)
{
hour=new int[strlen(t.hour)];
if(hour != 0)
{
memcpy(hour,t.hour);//重新分配空间
mintue=t.mintue;
second=t.second;
}
}
析构函数
析构函数名与类名相同,只是在函数名前面加一个位取反“~”,以区别构造函数,不带任何参数,没有返回值。一个类只有一个析构函数,不能重载。如果用户没有编写析构函数,编译器会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,如果自定义了析构函数,编译器在执行师会先调用自定义的析构函数再调用合成的析构函数,但是在多继承的类中,父类的自定义析构函数必须定义成虚函数才能调用)
class Time
{
public:
Time()
:hour(0)
, mintue(0)
, second(0)
{}
Time(const Time& t)
{
hour = t.hour;
mintue = t.mintue;
second = t.second;
}
~Time()//析构函数
{}
private:
int hour;
int mintue;
int second;
};
- 析构函数的作用并不是删除对象,而只是在撤销对象时做一些清理工作,比如关闭打开的文件,释放开辟的动态内存等工作;
- 析构函数没有返回值,没有函数类型,没有参数,不能重载;
- 调用构造函数和析构函数的顺序相反,因为在栈帧中,总是现进后出。
运算符重载(赋值运算符,取地址运算符,const修饰的取地址操作)
以赋值运算符重载为例
class Time
{
public:
Time()
:hour(0)
, mintue(0)
, second(0)
{}
Time operator=(Time &t)//赋值运算符重载
{
hour = t.hour;
mintue = t.mintue;
second = t.second;
}
Time operator+(Time &t)
{
hour+=t.hour;
mintue+=t.mintue;
second+=t.second;//这里只是举例说明运算符重载,忽略计算结果的合法性
}
const Time *operator&()const
{
return *this;//取地址操作符重载
}
Time(const Time& t)
{
hour = t.hour;
mintue = t.mintue;
second = t.second;
}
~Time()
{}
private:
int hour;
int mintue;
int second;
};
运算符重载的特点
1. 赋值运算符重载是对一个已经存在的对象进行赋值;
2. 运算符重载不改变运算符的优先级、结合性、操作符个数;
3. 不能重载的运算符:.*(点乘)、::(作用域解析符)、siezof、?:(条件运算符)