重学C++笔记之(九)使用类

1. 运算符重载

前面我们介绍了C++函数的多态或者称为函数重载。运算符重载是一种形式的C++多态,它将重载的概念扩展到运算符上,允许赋予C++运算符多种含义。

实际上很多C++运算符已经被重载。例如*运算符用于地址,它也可以用于两个数字相乘。C++允许将运算符重载扩展到用户定义的类型。例如我们可以重载一个数组相加的类等。但是重载的运算符必须是有效的C++运算符,例如,不能对"@"作为运算符重载

1.1 重载加法举例

下面进行类的加法重载:

my_class.h代码如下

#ifndef MY_CLASS_H
#define MY_CLASS_H

class my_class
{
    
    
public:
    my_class();
    my_class(int h, int m = 0);
    my_class operator+(const my_class& t) const;//加法重载
    void show();
private:
    int hours;
    int minutes;
};
#endif // MY_CLASS_H

my_class.cpp代码如下

#include "my_class.h"
#include<iostream>
using namespace std;

my_class::my_class()
{
    
    
    hours = minutes = 0;
}
my_class::my_class(int h, int m)
{
    
    
    hours = h;
    minutes = m;
}
my_class my_class::operator +(const my_class& t) const
{
    
    
    my_class sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes/60;
    sum.minutes %= 60;
    return sum;
}
void my_class::show()
{
    
    
    cout<<"After operator +: \n";
    cout<<hours<<" : "<<minutes<<endl;
}

main函数代码

#include"my_class.h"
int main()
{
    
    
    my_class time1(2, 40);//2小时40分
    my_class time2(3, 30);//3小时30分
    my_class total;

    total = time1 + time2;//重载+,调用
    total.show();
}

输出:

After operator +: 
6 : 10

上面的式子也可以进行三个对象的相加,因为每次相加返回的一个对象。

1.2 重载限制

  • 重载后的运算符至少有一个或一个以上操作数是用户定义的。比如不能将加法运算符重载为double值的和,因为double类型不是用户定义的。
  • 重载运算符时,不能违反运算符原来的句法规则,也不能修改运算符的优先级。也就是要保证原来的运算符规则。
  • 不能创建新的运算符。
  • 不能重载下面的运算符
  1. sizeof运算符
  2. "."成员运算符
  3. 作用域运算符::
  4. 条件运算符?:
  5. 一个RTTI运算符typeid
  6. const_cast、dynamic_cast、reinterpret_cast和static_cast强制类型转换运算符。

在这里插入图片描述
在这里插入图片描述

1.3 操作单个对象

创建操作单个对象的运算符重载,比如让一个对象乘以倍数。它的原型如下:

 my_class operator* (double n) const;

定义:

my_class my_class::operator *(double n) const
{
    
    
    my_class result;
    long totalMinutes = hours * n * 60 + minutes * n;
    result.hours = totalMinutes/60;
    result.minutes = totalMinutes%60;
    return result;
}

调用:

total = total * 1.5;//对象扩大1.5倍
total.show();

2. 友元函数

2.1 完善操作单个对象

除了使用公有方法的方式访问私有成员访问,我们还可以通过友元的方式进行访问。友元有三种:友元函数、友元类和友元成员函数。这里只介绍友元函数,其它两种将会在后续讲解。

我们看到上一个示例代码当中,一个对象乘以扩大因子的方式为:

A = B *2.75;

它实际上等价于:

A = B.operator*(2.75);

但是如果我们遇到A = 2.75 * B;应该怎么处理呢。因为乘法的左侧必须是调用对象,而2.75不是对象,因此编译器不能使用成员函数调用来替换该表达式。

因此就出现了非成员函数的调用方式,为了访问私有成员,就出现了友元函数。

friend my_class operator* (double m, const my_class& t);

定义重载运算符时,不需要使用作用域限定符,而且定义的时候不需要使用friend。

my_class operator *(double m, const my_class&t)
{
    
    
    my_class result;
    long totalMinutes = t.hours * m * 60 + t.minutes * m;//注意需要使用t.hours
    result.hours = totalMinutes/60;
    result.minutes = totalMinutes%60;
    return result;
}

main函数调用:

    total = 2 * total;
    total.show();

我们还可以对上面友元函数进行修改。

my_class operator* (double m, const my_class& t)
{
    
    
	return t * m;
}

2.2 常用的友元:重载<<运算符

(1)输出对象

实际上<<运算符已经被重载了很多次了,最初它是C和C++运算符的位左移。后来,ostream类对它进行了重载,将它作为一个输出工具。我们还可以对它进行重载,比如输出一个对象类型。

我们知道,如果使用my_class来进行重载,那么它的对象就是第一个操作数,则我们的输出形式将会是time << cout;这样将会变得很别扭。于是我们使用友元来解决。

friend void operator << (std::ostream& os, const my_class& t);

原型当中可以看出,cout作为第一个输入对象,而my_class为第二个对象,我们需要访问my_class的数据成员,因此需要用到my_class的友元函数,而不需要ostream的友元,只需要用到cout的别名。它的定义形式如下。

void operator << (std::ostream & os, const my_class& t)
{
    
    
    os << t.hours <<" hours," <<t.minutes<<" minutes";
}

然后我们就可以正常使用<<输出对象了:

cout<<total;

(2)与常规cout结合使用

上面的形式不能用于类似于下面的操作:

cout <<"Trip time:" <<total<<endl;

我们知道cout是按从左至右输出的,所以cout<<x<<y;意味着它等同于

(cout << x)<<y;

所以我们必须要求<<运算符操作之后,返回一个ostream对象的引用。也就是说(cout << x)本身就是一个ostream对象cout,从而可以让它可以位于左侧。于是我们继续对友元函数进行修改。

//原型修改为:
friend std::ostream& operator << (std::ostream& os, const my_class& t);
//定义修改为:
std::ostream& operator << (std::ostream & os, const my_class& t)
{
    
    
    os << t.hours <<" hours," <<t.minutes<<" minutes";
    return os;
}

2.3 重载运算符:作为成员函数还是非成员函数

对于重载运算符,非成员函数形式一般使用友元函数形式,因为可以访问私有成员。对象相加的运算符重载,下列形式效果一样。

my_class operator+ (const my_class& t) const;//成员函数形式
friend my_class operator* (const my_class& t1, const my_class& t2);//友元函数形式

加法运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式传递。对于友元版本来说,两个操作数都作为参数来传递。

3. 类的自动转换和强制类型转换

我们先来复习一下C++是如何处理内置类型转换的:如果两个标准类型兼容,则将C++自动将这个值转换为接收变量的类型。例如

long count = 8;//自动将8转换为long
double time = 11;//自动将11转换为double
int side = 3.33;//自动将3.33转换为int

C++不自动转换不兼容的类型,例如

int *p = 10;//不能进行转换,错误!!!

但是在C++中我们可以使用强制类型转换,将10强制转换为int指针类型,将指针设置为地址10,这种转换有没有意义就是另一回事了。

int * p  = (int *) 10;

3.1 对象自动隐式转换

如果有这样一个构造函数:

Stonewt::Stonewt(double lbs)
{
    
    
    stone = int(lbs) / Lbs_per_stn;
    pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
    pounds = lbs;
}

那么它可以这么调用构造函数:

Stonewt myCat(19.6);

还有一种隐式转换的调用方法:

Stonewt myCat;
myCat = 19.6;//隐式转换

如果有两个以上参数的构造函数,就不能使用隐式转换。如果不需要这种隐式转换,可以在定义构造函数时,添加关键字explicit,用于关闭这种自动特性。

explicit Stonewt(double lbs);//不允许隐式转换!!!

但是,我们还可以进行显式转换,即显式强制类型转换。

Stonewt myCat;
myCat = Stonewt(19.6);
//或者
myCat = (Stonewt) 19.6;

3.2 转换函数

以上可以将double转换为Stonewt类型,那么可以将Stonewt转换为double类型吗?这里就要用到C++特殊的运算符函数——转换函数。

转换函数是用户定义的强制类型转换,可以像使用强制类型类型转换那样使用它们。它的定义方式:

operator typeName();//typeName为要转换的类型

请注意以下几点:

  • 转换函数必须是类方法;
  • 转换函数不能指定返回类型;
  • 转换函数不能有参数;

我们在类中定义int和double两种转换函数:

operator double()const;//转换为double类型
operator int() const;//转换为int类型

它们的实现:

Stonewt::operator double()const
{
    
    
    return pounds;
}
Stonewt::operator int()const
{
    
    
    return int (pounds + 0.5);//pounds为类的数据成员,也就是对象的数据成员
}

main函数中的使用:

    int x = int(myCat);
    //或者
    x = myCat;
    cout<<x<<endl;

如果只定义了一种转换函数,我们还可以配合cout输出对象(自动转换),因为没有其它转换函数使得造成歧义。但是如果有两个以上的转换函数,将会造成歧义,导致编译错误。

cout << myCat<<endl;//如果没有歧义的情况下,可以使用
long gone = myCat;//如果没有歧义的情况下,可以使用

在C++11中,增加了explicit用于转换函数,这样的话,就必须显示的使用转换函数。当然读者也可以自己编写普通的成员函数,而不是使用转换函数!

3.3 转换函数和友元函数

下面我们为Stonewt类重载加法运算符。我们可以使用成员函数或者友元函数实现:

成员函数实现方式:

Stonewt Stonewt::operator+ (const Stonewt& st)const
{
    
    
	double pds = pounds + st.pounds;
	Stonewt sum(pds);//普通构造函数
	return sum;
}

友元函数实现方式:

Stonewt operator+ (const Stonewt& st1, const Stonewt& st2)
{
    
    
	double pds = st1.pounds + st2.pounds;
	Stonewt sum(pds);//普通构造函数
	return sum;
}

以上方式都可以进行加法运算:

Stonewt stone1(9, 12);
Stonewt stone2(12,8);
Stonewt total;
total = stone1 + stone2;

如果提供了Stonewt(double)构造函数,则可以这么做:

Stonewt stone1(9, 12);
double stoneDouble = 12.8;
Stonewt total;
total = stone1 + stoneDouble;//必须是stone1在前面

但是只有友元函数可以像下列方式操作(对于成员函数,第一个参数必须是对象):

Stonewt stone1(9, 12);
double stoneDouble = 12.8;
Stonewt total;
total = stoneDouble + stone1;//stone1在后面,不允许!!!

所以我们对于对象和double的加法有两种选择,第一是将加法重载为友元函数,并且让Stonewt(double)构造函数将double类型的参数转换为Stonewt类型的参数。第二种就是前面介绍过的重载一个double类型的加法。第一种方式代码少,出错的机会少,但是每次都调用转换构造函数,增加了时间和内存的开销。第二种方式代码多,程序员完成的工作更多,但是运算速度快。

第二种的实现方式为:

//成员函数与友元函数的结合方式实现加法重载
Stonewt operator+(double x);
friend Stonewt operator+(double x, Stonewt& s);

总览目录
上一篇:(八)对象和类
下一篇:(十)类和动态内存分配


文章参考:《C++ Primer Plus第六版》

猜你喜欢

转载自blog.csdn.net/QLeelq/article/details/111058664