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

本文介绍了C++中的运算符重载概念,包括加法运算符的重载示例、重载限制及单个对象的操作。同时探讨了友元函数的应用,如完善单个对象的操作及重载输出运算符。最后讲解了类的自动转换和强制类型转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
{