类默认生成的六个成员函数
一、构造函数
我们知道,类的数据成员是不能在声明类的时候初始化的,因为类并不是一个实体,而是一种抽象的数据类型,并不占据存储空间。为了解决这个问题,C++提供了构造函数来处理对象的初始化。
1、构造函数的作用
构造函数是一种特殊的成员函数,与其他成员函数不同,构造函数是在对象被实例化的时候自动被调用的,而且只执行这一次,它不能被用户调用。构造函数没有this指针。
构造函数的名字是固定的,与类名相同,不能由用户任意命名,它没有类型,没有返回值。
构造函数的功能是由用户自己定义的,用户根据初始化的要求设计函数体和函数参数。
如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,称作默认构造函数或者缺省构造函数。只是这个构造函数是空的,不执行任何操作,但还是会被调用。
例:设计一个日期类,并定义构造函数
- classdate
- {
- public:
- date() //构造函数
- {
- _year = 0; //初始化为0
- _month = 0;
- _day = 0;
- }
- private:
- int _year; //一般数据成员以_开头或者以m_开头
- int _month;
- int _day;
- };
classdate
{
public:
date() //构造函数
{
_year = 0; //初始化为0
_month = 0;
_day = 0;
}
private:
int _year; //一般数据成员以_开头或者以m_开头
int _month;
int _day;
};
2、带参的构造函数
有时候用户希望对不同的对象赋予不同的初始值,这时就必须用到带参的构造函数,实现不同的初始化。其对应的实参是在定义对象的时候给定的。
构造函数首部的一般形式:
构造函数名(类型1 形参1,类型2 形参2,...)
因为用户不能调用构造函数,所以实参是在定义对象的时候给出的:
类名 对象名(实参1,实参2,...);
例:
- classdate
- {
- public:
- date(int year ,intmonth,intday) //构造函数
- {
- _year =year;
- _month =month;
- _day =day;
- }
- private:
- int _year; //一般数据成员以_开头或者以m_开头
- int _month;
- int _day;
- };
- intmain()
- {
- date d1(2016, 7, 19); //定义一个对象,它有初始值
- system("pause");
- return 0;
- }
classdate
{
public:
date(int year ,intmonth,intday) //构造函数
{
_year =year;
_month =month;
_day =day;
}
private:
int _year; //一般数据成员以_开头或者以m_开头
int _month;
int _day;
};
intmain()
{
date d1(2016, 7, 19); //定义一个对象,它有初始值
system("pause");
return 0;
}
3、用参数列表对数据成员初始化
C++还提供了一种初始化数据成员的方法——参数初始化表。这种方法不再函数体内对数据成员初始化,而是在函数首部实现。
例:
- classdate
- {
- public:
- date(int year , int month,int day ) :_year(year)
- , _month(month)
- , _day(day)
- {
- }
- private:
- int _year; //一般数据成员以_开头或者以m_开头
- int _month;
- int _day;
- };
classdate
{
public:
date(int year , int month,int day ) :_year(year)
, _month(month)
, _day(day)
{
}
private:
int _year; //一般数据成员以_开头或者以m_开头
int _month;
int _day;
};
即在原来函数首部的末尾加上一个冒号,然后列出参数的初始化表,并且参数之间以逗号隔开。
为什么会有这种方法呢???
这是因为有些数据成员比如,引用,const修饰的变量必须在创建的时候初始化,所以只能通过初始化列表来初始化。而且这种方法比较高效。
4、构造函数可重载
尽管构造函数可以有多个,但是对于每一个对象来说,建立对象时只执行其中一个构造函数。还有一点需要强调,如果声明一个无参的对象应该写成
date d1;
而不应该写成
date d1(); //这是一个函数声明,类型是date,函数名是d1,参数为空
5、使用默认参数的构造函数
构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,如果用户不指定实参值,编译系统就使形参取默认值。最好在声明构造函数时指定默认值,而不能只在定义构造函数时指定默认值。
在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数。
二、拷贝构造函数
1、拷贝构造函数就是用一个已有的对象复制出多个完全相同的对象。
例:date d2(d1); 其作用就是用已有的对象d1去创建出一个新对象d2,并且d1与d2完全相同。
拷贝构造函数也是构造函数,但他只有一个参数(这个参数只能是本类的一个对象),而且采用对象的常引用形式。拷贝构造函数的作用就是将实参对象的各成员值一一赋给新的对象中对应的成员。
例:
- classdate
- {
- public:
- date(int year , int month,int day ) :_year(year)
- , _month(month)
- , _day(day)
- {
- }
- date(const date &d) //拷贝构造函数
- {
- _year =d._year;
- _month =d._month;
- _day =d._day;
- }
- private:
- int _year; //一般数据成员以_开头或者以m_开头
- int _month;
- int _day;
- };
classdate
{
public:
date(int year , int month,int day ) :_year(year)
, _month(month)
, _day(day)
{
}
date(const date &d) //拷贝构造函数
{
_year =d._year;
_month =d._month;
_day =d._day;
}
private:
int _year; //一般数据成员以_开头或者以m_开头
int _month;
int _day;
};
注意:拷贝构造函数是从无到有的创建一个新对象。
拷贝构造函数的参数只能是本类对象的引用,如果不是引用的话会引发无穷的递归。
拷贝构造函数是类默认生成的,但是当牵扯到动态内存的时候默认生成的拷贝构造函数就不能满足我们的要求,这时需 要我们自己根据需要定义一个拷贝构造函数。
2、什么时候调用拷贝构造函数?
程序中需要建立一个新的对象,并且用另一个同类的对象初始化它。
当函数的参数为类的对象时。在调用函数时需要将实参对象完整的传递给形参,也就是需要建立一个实参的拷贝。
函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时,此时需要将函数中的对象复制一个临时对象返回
三、析构函数
析构函数也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名前面加取反“~”符号。它是在对象释放前系统自动调用的。
1、析构函数的作用并不是删除对象,而是在撤销对象时做一些清理工作。比如关闭打开的文件,释放开辟的动态内存等工作。
2、析构函数不返回任何值,没有函数类型,没有参数,因此也不能重载。
3、调用构造函数和析构函数的顺序
因为函数压栈的关系,所以先构造的后析构,后构造的先析构。如果有全局对象或者静态局部对象,则它们在main函数结束或者调用exit函数时2被析构。
四、赋值运算符重载
1、如果已经定义了两个或多个对象,则这些同类的对象之间可以相互赋值,即一个对象的值可以赋给另一个同类的对象。这里所指的对象的值是对象中所有数据成员的值。
对象之间的赋值是通过赋值运算符“=”重载实现的,即将一个对象的值一一复制给另一对象的对应成员。
如:
- classdate
- {
- public:
- date(int year , int month,int day ) :_year(year)
- , _month(month)
- , _day(day)
- {
- }
- date operator=(date&d) //复制运算符重载
- {
- _year =d._year;
- _month =d._month;
- _day =d._day;
- }
- private:
- int _year; //一般数据成员以_开头或者以m_开头
- int _month;
- int _day;
- };
classdate
{
public:
date(int year , int month,int day ) :_year(year)
, _month(month)
, _day(day)
{
}
date operator=(date&d) //复制运算符重载
{
_year =d._year;
_month =d._month;
_day =d._day;
}
private:
int _year; //一般数据成员以_开头或者以m_开头
int _month;
int _day;
};
对象赋值的一般形式:
对象名1=对象名2;
2、赋值运算符赋值时,类的数据成员中不能包括动态分配的数据,否则在析构的时候会将同一块内存释放多次,产生错误。
3、要区分赋值运算符重载和拷贝构造函数的功能。赋值运算符是对一个已经创建的对象赋值。
五、取地址运算符重载和const修饰的取地址运算符重载
这两个默认的成员函数一般不用重新定义,
例:
- classdate
- {
- public:
- const date * operator&()const
- {
- return this ;
- }
- private:
- int _year;
- int _month;
- }
classdate
{
public:
const date * operator&()const
{
return this ;
}
private:
int _year;
int _month;
}