从小白开始学C++ 类与对象三 (操作符重载、友元函数,类的自动转换和强制类型转换)

C++对象与类

  • 一、操作符重载
    • 1. 实现操作符重载
    • 2. 重载限制
  • 二、 友元函数
    • 1. 为什么需要友元函数
    • 2. 友元函数的创建
  • 三、 类的自动转换和强制类型转换
    • 1. 定义类中特定的常量
    • 2. 类的转换
      • 1)其他类型转换为对象类型
      • 2)对象类型转换为其他类型

一、操作符重载

1. 实现操作符重载

我们在前面学习了函数重载或函数多态,旨在让我们能够使用同名的函数来完成相同的基本操作,即使这种操作被用于不同的数据类型。

而操作符重载将重载的概念扩展到操作符上,允许赋予C++操作符多种含义

要实现操作符重载,我们需要使用被称为操作符函数的特殊函数形式

operator op(argument-list) //op是将要重载的操作符

当我们在一个类中定义了一个operator +()成员函数,以重载+操作符,以便实现两个对象的相加,那么定义完之后我们就可以这样来写:

sale3 = sale1 + sale2; //三个都是Sale类的实例
//编译器会自动转译为
sale3 = sale1.operator+(sale2);
//隐式的使用sale1,显式的调用sale2

这里举一个具体的例子,我们设计一个类,用来表示时间,时间分为分钟和小时,我们使用操作符重载来实现分钟和小时的正确相加

这是程序的第一部分

class Time//类声明
{
private:
    int hours;
    int minutes;
public:
    Time()//默认构造函数
    Time(int h, int m);//构造函数
    ~Time()//析构函数
    void addmin(int m);//添加分钟
    void addh(int h);//添加小时
    void reset(int h=0,int m=0);//重置
    Time operator+(const Time&t) const;//操作符重载
    void show()const;//显示时间
};

然后是各种函数的具体代码

Time :: Time()
{
    hours=minutes=0;
}

Time :: Time(int h,int m)
{
    hours = h;minutes=m;
}

Time :: ~Time()
{
}

void Time::addmin(int m)
{
    minutes += m;
    hours += minutes/60;
    minutes %= 60;
}

void Time::addh(int h)
{
    hours += h;
}

void Time::reset(int h, int m)
{
    hours = h;minutes=m;
}

Time Time::operator+(const Time &t)const //操作符重载
{
    Time sum;//创建一个新类对象
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes/60;
    sum.minutes %= 60;
    return sum;
}

void Time::show()const
{
    cout<<hours<<" hours,"<<minutes<<" minutes\n";
}

最后我们调用main函数来创建实例测试我们设计的操作符重载

int main()
{
    Time shopping(2, 50);
    Time coding(2, 30);
    cout << "shopping time : ";
    shopping.show();
    cout << "\ncoding time : ";
    coding.show();
    cout << "\nshopping time + coding time = ";
    Time fix;
    fix = shopping + coding;//操作符重载
    fix.show();
    return 0;
}

输出结果:

shopping time : 2 hours,50 minutes

coding time : 2 hours,30 minutes

shopping time + coding time = 5 hours,20 minutes

操作符重载太酷了有没有
实际上我们的cout输出的<<和cin输入的>>也是操作符重载的范例
使用操作符重载能够让代码显得更加整洁简单

2. 重载限制

重载限制 这个很重要!!!

  1. 重载后的操作符必须至少有一个操作数是用户定义的类型,这防止用户为标准类型重载操作符
  2. 使用操作符时不能违反操作符原来的句法规则
  3. 不能修改操作符的优先级
  4. 不能定义新的操作符 如@ **
  5. 不能重载一下操作符:
sizeof ——sizeof操作符
. ——成员操作符
.*  ——成员指针操作符
?: ——条件操作符
:: ——作用域操作符
const_cast ——强制类型转换操作符
dynamic_cast ——强制类型转换操作符
reinterpret_cast ——强制类型转换操作符
static_cast ——强制类型转换操作符
typeid ——一个RTTI操作符

二、 友元函数

1. 为什么需要友元函数

我们知道C++控制对类对象私有部分的访问。通常,共有类方法提供唯一的访问途径,但是有时候这种限制太严格,以致于不适合特定的编程问题。所以C++提供了另外一种形式的访问权限:友元
友元有三种:

  • 友元函数
  • 友元类
  • 友元成员函数

我们首先介绍为何需要友元。在为类重载二元操作符常常需要友元,比如下面这个例子:

假如我们想要重载乘号,使得它能够完成实数*对象的操作
A = B * 2.5; 这个式子会被编译器转译为:
A = B.operator*(2.5); 没有问题
但是如果这样写:
A = 2.5 * B;
编译器将会报错,因为第一个数2.5不是类对象,所以编译器无法识别

本质上两种调用应该是等效的,但是由于类的特殊性,我们无法交换两个操作数的位置

解决这个问题的方法之一就是告诉所有人,只能按照第一种方式编写,不能写成第二种表达

这很不友好——所以我们有了另外一种解决方法——非成员函数,因为非成员函数不是由对象调用的,它使用的所有值都是显示参数,这样编译器就能识别A = 2.75*B

A = 2.75*B;
与下面的非成员函数等价:
A = operator*(2.75,B)
对应的函数原型:
classname operator*(double m, classname &t);

这还是有问题——非成员函数不能直接访问类的私有数据,所以我们有了友元函数

2. 友元函数的创建

第一步将其原型放在类声明中(这里我们以上面的Time类作为例子)

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

该原型说明了两点:

  1. 该函数虽然在类声明中声明,但它不是成员函数,不能使用成员操作符来调用它
  2. 虽然它不是成员函数,但是它可以访问类的私有数据

下面我们可以编写具体的代码:
注意:他不是成员函数,所以不需要使用::限定符,
还有不需要加上关键字friend

Time operator*(double m, const Time&t) //友元函数操作符重载
{
	Time result;
	long totalminutes = t.hours*m*60 + t.minutes*m;
	result.hours = totalminutes/60;
	result.minutes = totalminutes%60;
	return result;
}

有了上述声明,我们就可以使用第二种表达了

总之类的友元函数是非成员函数,但是其访问权限与成员函数相同


三、 类的自动转换和强制类型转换

1. 定义类中特定的常量

我们不能使用const在类中定义常量!
我们不能使用const在类中定义常量!
我们不能使用const在类中定义常量!
我们使用一下两种方法在类中定义常量:

enum {name = value};
static const typename name = value;

2. 类的转换

我们可以将类定义成与基本类型或另一个类相关,使得从一种类型转换为另一种类型是有意义的。

这里我们将磅转换为英石的程序改写成类的形式。我们可以将重量的两种表示放在同一个类中,然后提供以这两种方式表达重量的类方法

我们还是先将类声明给出:

#include 
using namespace std;
class Stonewt
{
private:
    enum {lbs_per_stn = 14};//转换进制
    int stone;
    double pds_left;
    double pounds;
public:
    Stonewt(double lbs);
    Stonewt(int stn, double lbs);
    Stonewt();
    ~Stonewt();
    void show_lbs()const;
    void show_stn()const;
};

下面我们为声明编写函数:

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

Stonewt ::Stonewt(int stn, double lbs)
{
    stone = stn;
    pds_left = lbs;
    pounds = stn*lbs_per_stn + lbs;
}

Stonewt ::Stonewt()
{
    stone = pounds = pds_left = 0;
}

Stonewt::~Stonewt()
{

}

void Stonewt::show_stn()const
{
    cout << stone << " stone, "<<pds_left<<" pounds\n";
}

void Stonewt::show_lbs()const
{
    cout << pounds<<" pounds\n";
}

我们定义的类对象Stonewt表示一个重量,所以可以提供一些将整数或者浮点数转换为Stonewt对象的方法。

在C++中,接受一个参数的构造函数为将类型与该参数相同的值转换为类提供了可能

1)其他类型转换为对象类型

//因此这个构造函数将可以将double类型的值转换为Stonewt类型
Stonewt(double lbs);
//所以我们可以这么写:
Stonewt objection;
objection = 20.20;

我们来看看程序如何将double类型的值转换为Stonewt类型
程序将使用构造函数来创建一个临时的Stonewt对象,并将19.6作为初始化值,之后采用每个成员一次赋值的方式将该临时对象的内容复制到objection对象。这一个过程称为隐式转换,它不需要我们显式强制转换

注意,只有接受一个参数的构造函数才能作为转换函数

我们还可以关键字explicit来关闭这种自动特性,所以当我们这样声明构造函数时,我们不能自动进行隐式转换,但是我们仍然可以显式强制类型转换

explicit Stonewt(double lbs);//此时不会自动隐式转换

使用强制类型转换:

Stonewt obj;
obj = 20.20 //编译错误
obj = Stonewt(19.6);//强制类型转换
obj = (Stonewt)19.6;//另一种写法

然后我们给出main函数来测试一下

int main()
{
    Stonewt p = 260;
    Stonewt w(20.20);
    Stonewt t(21, 8);
    p.show_stn();
    w.show_stn();
    t.show_lbs();
    p=265.8;
    t=325;
    p.show_stn();
    t.show_lbs();
    display(t, 2);
    display(422, 2);
    return 0;
}

这里我们给出了一个有趣的函数display

void display(const Stonewt &st, int n)
{
    for(int i=0;i<n;i++)
    {
        cout << "wow! ";
        st.show_stn();
    }
}

读者可以先自行分析为何这样的赋值是正确的

贴上解析:

    Stonewt p = 260;
    //当构造函数只接受一个参数时,我们可以直接这样初始化
    Stonewt w(20.20);
    Stonewt t(21, 8);
    p.show_stn();
    w.show_stn();
    t.show_lbs();
    p=265.8;
    //使用接受double参数的构造函数,将265.8转换为一个Stonewt值
    //再使用构造函数设置成员数据
    t=325;
    //这个赋值,先将int值转换为double类型
    //再使用构造函数Stonewt(double)来设置全部3个成员
    p.show_stn();
    t.show_lbs();
    display(t, 2);
    //最后我们看看这个函数,第一个参数是Stonewt对象,遇到int参数时(422)
    //编译器查找Stonewt(int)构造函数,来将int转换为Stonewt类型。
    //但是没有这样的构造函数,因此编译器寻找接受其他内置类型的构造函数
    //所以先把int转换成double,再使用构造函数转换为Stonewt对象
    display(422, 2);

输出结果:

18 stone, 8 pounds
1 stone, 6.2 pounds
302 pounds
18 stone, 13.8 pounds
325 pounds
wow! 23 stone, 3 pounds
wow! 23 stone, 3 pounds
wow! 30 stone, 2 pounds
wow! 30 stone, 2 pounds

2)对象类型转换为其他类型

我们可以进行相反的转换吗?
是可以的,但是我们并不是使用构造函数,我们需要使用C++特殊的操作符函数:转换函数

转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用他们。比如我们定义了Stonewt到double的转换函数,就可以这样转换:

Stonewt wolfe(20.20);
double h = double(wolfe);
double t = (double)wolfe;

转换函数的形式:operator typename()
所以我们可以在public中加上两行转换函数的声明:

publicoperator int()const;
    operator double()const;

然后是具体函数:

Stonewt::operator int() const
{
    return int(pounds+0.5);//进位
}

Stonewt:: operator double() const
{
    return pounds;
}

注意:这两个函数虽然没有声明返回的类型,但这两个函数必须得返回所需的值。另外,int转换将待转换的值四舍五入为最接近的整数,而不是去掉小数部分

最后我们使用main函数进行测试:

int main()
{
	Stonewt test(9, 2.8);
    double p_wt = test; //隐式转换
    cout << p_wt << " pounds\n";
    cout << int(test) << " pounds\n";//显式强制类型转换
}

输出结果:

128.8 pounds
129 pounds

点个赞呗

你可能感兴趣的:(C/C++,c++,编程语言,类)