一、运算符重载
运算符重载和函数重载一样,都属于C++多态性的一种体现。其实运算符的重载有很多的例子,C++编译器将会根据运算符以及操作数的类型来决定这个运算符到底会执行什么操作。
这种运算符重载也是为了能够让对象用的更加方便,比如我想两个数组相加,那么正常情况下要利用循环使得两个数组的对应元素都相加起来,但是如果你定义了一个数组类的话,就可以使用运算符重载的特性,想计算普通加法那样实现两个数组对象的相加。
如果我们想 重载运算符,那么我们就需要使用特定的重载运算符的形式。并且重载运算符也必须要重载C++中已经存在的运算符,不能自己创造一个运算符出来。
operator +()
{
}
上面这种形式就是重载了一个加法运算符。
在运算符表示法中,运算符左侧的对象是调用这个运算符的对象,右边的对象是作为参传递进去的对象。
加入现在有一个TIME类
TIME a, b;
a + b ==== a.opetator+(b)
上面两种形式的作用是相等的,都会调用类中的运算符重载函数。
另外如果将运算符重载运用到类的对象中时,不单可以实现两个类对象相加,多个也是同样可以的,因为运算符一般都是从左向右结合的。
二、重载限制
运算符有一个很重要的限制就是,虽然运算符的重载可以不必放在类中做成员函数,但是重载的这个运算符的操作数中必须有一个是用户自定义的类型。换句话说我们不能为基本数据类型进行运算符重载。比如结构体,数组,类对象,这些都算作用户自定义的类型。
另外,使用重载运算符的时候,使用运算符不能够违反运算符本身的句法规则,比如不能将两个操作数的运算符重载为一个操作数的。并且不能修改运算符的优先级。运算符即使经过重载,优先级也是和之前的运算符相同。
并且也不是所有的运算符都可以进行重载
比如:sizeof . :: ?:这些一系列的运算符都不能够进行重载。
运算符的重载一般可以作为成员函数,或者是非成员函数,但是仍然有一些运算符是必须用成员函数来进行重载的。
=
()
[]
->
三、友元
我们先来看一个程序当中实际发生的问题:
比如我现在重载了一个+运算符,这个运算符是用于一个类对象和一个基本数据类型int相加的。
time a;
a = a+1 ===> a.operator+(1)
这样调用是没错的。我们要时刻记住运算符的左侧是调用它的对象,右边是作为参数传递进去的。
如果是这种形式:a = 1+a。按理来讲这两种形式是一样的,但是,如果这样调用的话会出问题,因为运算符左侧是调用的对象,如果这个函数重载为类的一个成员函数的话,因为int型数据不是类对象,所以不能够调用这个运算符重载的函数。
解决办法有两个,第一个就是按照规矩来写,第二个就是使用非成员函数来重载运算符,这样一来,非成员函数使用 的任何数值 都会被显式的作为参数传递进去。
所以像这种调用
a = 1 + a; ===> a = operator+(1,a);
就可以按照这种形式来完成。虽然我们可以用这种非成员函数来暂时解决这个问题,但是一个新问题出现了,既然我们需要类的对象,计算的时候肯定需要这个对象对应的数据,那么在非成员函数中是不允许访问类中的私有成员的,这时候就需要了一类特殊的非成员函数,即友元函数。
四:创建和使用友元函数
创建友元函数很简单,首先把我们要声明的非成员函数写在类的声明中,并在函数的头部加上friend
这样的语句表达了两个信息:
1、这个函数虽然在类中声明,但是不属于类的成员函数
2、这个函数虽然不是类的成员函数但是有着和成员函数一样的权限。
声明的时候需要使用friend关键字,但是在类外定义的时候是不再需要的。
在分辨一个函数是不是 这个类的友元函数的时候,就是看在她的函数代码的实现里面有没有访问到该类的私有数据成员。
五、重载<<运算符
这个运算符其实从C到C++就已经发生了重载,从原来的左移运算符到现在的有输出功能的运算符。
一般来讲,我们只能将这个运算符的输出功能运用于cout这个ostream类的对象,之所以这个运算符在输出上提供了很大的方面,可以不用写格式控制符就输出不同基本类型的数据,就是因为在ostream类中重载了这个函数的很多版本。
前面既然提到了为什么<<这个运算符可以用于不同种类的基本数据类型的输出,那么当我们自己定义一个类的时候,如果也想利用这种cout<<对象名的形式输出的时候,我们自己也要重载这个运算符。
如果你想把这个函数重载为自定义类的成员函数的时候,因为调用成员函数的时候,特别是运算符的重载函数,运算符左边都是调用者,右边是传进这个函数的参数,所以如果一旦重载为成员函数的时候,就必须使用对象名<<cout这种形式来调用,如果我们还想按照正常形式的话,就要重载为友元函数。友元函数的返回值类型可以设置为void.
但是,如果你看过ostream的文件的话,你就会发现重载<<运算符的函数的返回值类型是ostream对象的类型。为什么会这么做,就是因为我们平时有着这样的用法。
int x, y;
cout<<x<<y<<endl;
由于我们说过运算符被重载为成员函数之后,运算符左边都是调用者,右边都是这个函数的一个参数。所以说,我们必须保证每次使用<<来输出基本类型数据的时候,左边都是ostream对象。所以在调用cout<<x的时候,返回值必须是一个ostream对象才能够继续运行cout<<Y。
所以根据这种需求,我们在重载<<运算符的时候,必须要使用ostream对象引用的形式作为函数的返回值。
因为ostream对象的引用可以指向ostream对象和fstream对象,所以可以利用这种方式使得把信息输出到文件更加方便。
六、自定义类对象的类型转换
如果我们定义了一个对象,但是这个对象只有一个成员数据就是一个double的值。我们是不是可以这样给对象赋值呢:
对象名=double值。道理上来讲是不行的,因为一个是基本的数据类型,另外一个是用户自定义的数据类型,这两个类型是不兼容的,所以编译器不会执行自动的类型转换,但是只有一个参数的构造参数将会为这种自动类型转换增加可能。
先创建一个对象,但是并不初始化(因为定义了一个带有一个参数的构造函数),之后利用对象名=double值的语句为对象赋值,此时,程序会自动调用有一个参数的构造函数,创建一个匿名的临时对象,将double的值初始化这个对象中的那个数据成员,之后,采用对象和对象直接的成员依次赋值的方式,完成我们预期的赋值操作。这是一种隐式的类型转换,但是这种转换也仅仅只能够发生在带有一个参数的构造函数上面。但是带有两个参数的构造函数,完全可以把另外一个指定为默认参数,这样在调用的时候也可以实现把构造函数变为类型转换函数的功能。
有的时候这样的特性并不是我们需要的,所以我们在函数的前面可以加上explicit关键字来屏蔽掉这种特性。这样的话就关闭了隐式转换的特性,再想转换的时候,可以通过强制类型转换来实现。
当类中的构造函数仅有一个参数的时候,下面的三种方式都可以用来初始化类对象:
A a = 5;
A a(5);
A a = A(5);
前面讲了,可以通过单个参数的构造函数把一个基本数据类型转化为类对象,那么也可以做相反的转化,只不过不是用构造函数,而是我们要自己定义一个转换函数来执行这个操作。
七、转换函数的创建和使用
转换函数是一种成员运算符函数。
转换函数的创建形式:
operator 类型名();
这就是转换函数的形式,但是转换函数必须是类成员方法,并且不能有返回值和参数,所以如果想把一个类的对象转化为基本的数据类型,可以
operator double()把这个函数作为类的成员函数来定义。
为了防止这种转换函数以及上面提到的构造函数的转换函数 的自动执行,可以使用explict关键字进行声明,也可以破坏转换函数的定义规则,为其加上返回值,应该尽量避免隐式调用。
八、
在重载<<运算符的时候,如果想用它来输出用户自定义类型的数据,与cout一起适用的话,必须重载为一个友元函数,而且,如果想让这个运算符的连续输出特性用于用于自定义的类型的时候,要将返回值设置为ostream &类型。