一、构造函数
1、概念
构造函数是一种特殊的成员函数。名字与类名相同,创建类类型对象时,由编译器自动调用,在对象的声明周期内只调用一次,以保证每个数据成员都有一个合适的初始值。
2、构造函数的特性
(1)函数名与类名相同;
(2)没有返回值;
(3)有初始化列表;
(4)新对象被创建,由编译器自动调用,且在对象的生命周期内仅调用一次;
(5)构造函数可以重载,实参决定了调用哪个构造函数。
(6)如果没有显示定义时,编译器会提供一个默认的构造函数。
(7)无参构造函数和带有缺省值得构造函数都认为是缺省构造函数,并且构造函数只能有一个。
(8)构造函数不能用const来修饰。
3、初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔得数据成员,每个数据成员后面跟一个放在圆括号中的初始化。
4、初始化列表
1、每个成员在初始化列表中只能出现一次;
2、初始化列表仅用于初始化数据成员,并不指定这些数据成员的初始化顺序。数据成员在类中定义顺序就是在列表中的初始化顺序。
3、尽量避免使用成员初始化成员,成员的初始化顺寻最好和成员的定义顺序保持一致。
4、类中包含以下成员必须放在初始化列表中初始化:
(1) 引用数据成员;
(2)const数据成员
(3) 类类型成员(该类美哟缺省的构造函数)
5、默认构造函数
类如果没有显示定义构造函数时,编译器会合成一个默认的构造函数,该构造函数中什么工作都不做。只要显示定义了,即使该构造函数什么也不做,编译器也不会为该类合成默认的构造函数。编译器生成的默认构造函数使用与变量初始化相同的规则来初始化成员。内置和复合类型的成员如指针、数组,只对定义在全局作用域中的对象初始化,当对象定义在局部作用域时,内置和符合类型的成员不进行初始化。在某些情况下,默认构造函数是由编译器隐式使用的。
构造函数作用:
(1)、构建对象
(2)、初始化对象
(3)、类型转换
默认构造函数一定会生成的条件:假设有A类和B类,若B类里面包含了用A类定义的对象,A类里面有显示定义的构造函数,B类里面没有显示定义的构造函数。编译器则会在B类里面合成一个默认的构造函数用来调用A类里面显示定义的构造函数。
下面的例子可以看出编译器不会合成默认的构造函数
当满足上面给出的条件的时候编译器则一定会默认的构造函数
6、构造函数的重载
在一个类中可以定义多个构造函数,以便为对象提供不同的初始化的方法,供用户选用。这些构造函数具有相同的名字,而参数的个数或参数的类型不相同,这称为构造函数的重载。
通过下面的例子可以了解怎样应用构造函数的重载
例:定义一个时间类,在时间类中定义两个构造函数,其中一个无参,一个有参
编写程序:
7、带有缺省参数的构造函数
构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,即如果用户不指定实参值,编译系统就使形参的值为默认值。
编写程序:
注意:无参构造函数和带有缺省值得构造函数都认为是缺省构造函数,并且构造函数只能有一个。
二、拷贝构造函数
1、概念
只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为拷贝构造函数。拷贝构造函数是特殊的构造函数,创建对象时使用已存在的同类对象来进行初始化,由编译器自动调用。
2、特征
(1)、它是构造函数的重载。
(2)、它的参数必须使用同类型对象的引用传递
(3)、如果没有显示定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化。
3、使用场景
(1) 对象实例化对象
Date d1(1900,1,1);
Date d2(d1);
(2) 传值方式作为函数的参数
Void FunTest(const Date date)
{}
(3) 传值方式作为函数的返回值
Date FunTest()
{
Date date;
return date;
}
三、析构函数
1、概念
与构造函数功能函数相反,在对象别销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作。
2、特征
(1) 析构函数在类名(即构造函数名)前加上字符~。
(2) 析构函数无参数无返回值。
(3) 一个类有且只有一个析构函数。若无显示定义,系统会自动生成缺省的析构函数。
(4) 对象生命周期结束时,c++编译系统自动调用析构函数。
(5) 析构函数本身并不是删除对象,而是做一些资源的清理工作。
3、程序执行析构函数的几种情况
(1)如果在一个函数中定义了一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
(2)静态局部对象在函数调用结束时对象并不释放,因此也不调用析构函数。只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
(3)如果定义了一个全局的对象,则在程序的流程离开其作用域时,调用该全局的对象的析构函数。
(4)如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
4、注意
析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。程序设计者要事先设计好析构函数,已完成所需的功能,只要对象的生命周期结束,程序就自动执行析构函数来完成这些工作。
由于析构函数没有参数,因此它不能被重载。一个类可以有多个构造函数但是只能有一个析构函数。
四、运算符的重载
1、为什么要对运算符重载
例:通过函数来用“+”号实现复数相加(没有用运算符重载)
编写程序:
运行结果:
从上面的程序可以看出,虽然这种方法可以实现两个复数的加法,但是这种调用方式不直观、太繁琐,使人感到很不方便。人们自然会想到,能否也和整数的加法运算一样,直接用加号“+”来实现复数运算,如:c3=c1+c2;编译器就会自动完成c1和c2两个复数相加的运算。如果能做到,就为对象的运算提供很大的方便。这就需要对运算进行重载。
2、对运算符重载的方法
运算符重载的方法时定义一个重载运算符的函数,是指定的运算符不仅能实现原有的功能,而且能实现相应的功能。也就是说,运算符重载是通过定义函数实现的。运算符的重载实质上是函数的重载。
重载运算符的函数一般格式如下:
函数类型 operator运算符名称(参数列表)
{对运算符的重载处理}
例如,想将“+”用于Complex类(复数)的加法运算,函数的原型可以是这样的:
Complex operator +(Complex& c2);
在上面的格式中,operator是关键字,是专门用于定义重载运算符的函数的,运算符名称就是C++已有的运算符。注意:函数名是由operator和运算符组成的。上面的“operator+”就是函数名,意思是“对运算符+重载的函数”。
例:改写上面的例题,对运算符“+”实行重载,使之能用于两个复数相加
编写程序:
3、重载运算符的规则
(1)C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载。
(2)C++允许重载的运算符。
双目算术运算符 |
+、—、*、/、% |
关系运算符 |
==、!=、<、>、<=、>= |
逻辑运算符 |
||、&&、! |
单目运算符 |
+(正)、—(负)、*(指针)、&(取地址) |
自增自减运算符 |
++、—— |
位运算符 |
|、&、~、<<、>> |
赋值运算符 |
=、+=、*=、\=、^=、<<=、>>= |
空间申请与释放 |
new、delete、new[]、delete[] |
其他运算符 |
(),->,->*,,(逗号),[](下标) |
不能重载的运算符只有五个:
. (成员访问运算符)
* (成员指针访问运算符)
::(域运算符)
sizeof(长度运算符)
?:(条件运算符)
前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具备重载的特性。
(3)重载不能改变运算符运算对象(即操作数)的个数。如关系运算符“>”等是双目运算符,重载后仍为双目运算符,需要两个参数。
(4)重载不能改变运算符的优先级别。
(5)重载不能改变运算符的结合性。
(6)重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数,与前面第三点矛盾。
(7)重载运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或者类的引用)。
(8)用于类对象的运算符一般必须重载,但有两个例外,运算符“=”和“&”不用用户重载。
① 赋值运算符“=”可以用于每一个类的对象,可以利用它在同类对象之间相互赋值。
② 地址运算符“&”也不必重载,它能返回类对象在内存中的起始地址。
(9)从理论上说,可以将一个运算符重载为任意的操作。但是应该使重载运算符的功能类似于运算符作用于标准类型数据时所实现的功能。
4、重载流插入运算符和流提取运算符
用户定义的类型的数据,是不能直接用“<<”和“>>”输出和输入的。如果想用他们的输出和输入自己声明的类型的数据,必须对它们进行重载。
对“<<”和“>>”重载的函数形式如下:
Istream & operator>>(istream&,自定义类型&);
ostream & operator<<(ostream&,自定义类型&)
只能将重载“>>”和“<<”的函数作为友元函数,而不能将它们定义为成员函数。
(1)重载流插入运算符“<<”
例:编写程序
class Complex
{
public:
Complex()
{
_real = 0;
_imag = 0;
}
Complex(double real,double imag)
{
_real = real;
_imag = imag;
}
Complex& operator+ (Complex&complex)
{
complex._real += _real;
complex._imag += _imag;
return complex;
}
friend ostream& operator<<(ostream& _cout,Complex& complex);
private:
double _real;
double _imag;
};
ostream& operator<<(ostream&_cout,Complex&complex)
{
_cout <<"(" <<complex._real <<"+" <<complex._imag <<"i)";
return _cout;
}
int main()
{
Complex c1(2, 4), c2(6, 10), c3;
c3 = c2 + c1;
cout << c3;
system("pause");
return 0;
}
运行结果:
(8+4i)
5、有关运算符重载的归纳
(1)运算符的重载使类的的设计更加丰富多彩,扩大了累的功能和适用范围,使程序易于理解,易于对对象进行操作,它体现了为用户着想、方便用户使用的思想。有了运算符重载,在声明了类之后,人们就可把用于标准类型的运算符用于自己声明的类。类的声明往往是一劳永逸的,有了好的类,用户在程序中就不必许多成员函数去完成某些运算和输入输出的功能,使主函数更加简单易读。
(2)使用运算符重载的的具体做法
① 先确定要重载的是哪一个运算符,想把它用于哪一个类,重载运算符只能把一个运算符用于一个指定的类。不要误以为用一个运算符重载函数就可以适用于所有的类。若想对复数类数据适用加、减、乘、除运算,就应该分别对运算符进行重载。
② 设计运算符重载函数和有关的类。函数的功能完全由设计者指定,目的是实现用户对使用运算符的要求。
③ 在实际工作中,一般并不要求最终用户自己编写每一个运算符重载函数,往往是有人事先把本领域或本单位工作中需要重载的运算符统一编写好一批运算符重载函数,把它们集中放在一个头文件,提供给有关人员使用。
(3)利用引用作函数的参数可以在调用函数的过程中不是用传递值的方式进行虚实结合,而是通过传址的方式使形参成为实参的别名,因此不生成临时变量,减少了时间和空间的开销。此外,如果重载函数的返回值是对象的引用时,返回的不是常量,而是引用所代表的对象,它可以出现在赋值号的左侧而成为左值,可以被赋值或者参与其他操作。但是用引用时要特别小心,因为修改了引用就等于修改了他所代表的对象。