第十一章 - 使用类

一,运算符重载

1.1,什么是运算符重载?

运算符重载是一种静态的C++多态,C++允许将运算符扩展到用户定义的类型,例如:可以把两个对象相加。编译器将根据操作数的数目以及类型来决定使用哪种加法定义,重载使代码看起来更加的自然。

1.2,运算符重载实例

定义一个与时间相关的类Time

class Time{
private:
    int hours;
    int minutes;
public:
    Time():hours(0), minutes(0){};
    Time(int h, int m):hours(h), minutes(m){}
    Time operator+(const Time &t) const;
    friend ostream & operator<<(ostream &os, const Time &t);
};
Time Time::operator+(const Time &t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes = sum.minutes % 60;
    return sum;
}
ostream & operator<<(ostream &os, const Time &t){
    os<<t.hours<<" "<<t.minutes<<endl;
    return os;
}
int main(){
    Time t1(1, 20);
    Time t2(1, 20);
    Time sum = t1 + t2;
    cout<<sum;
    return 0;
}

输出结果

2 40

Process returned 0 (0x0)   execution time : 0.010 s
Press any key to continue.

1.3,运算符重载的限制

1,重载后的运算符必须至少有一个是用户自定义类型,这将防止用户为标准类型重载运算符。

2,使用运算符时不能违反运算符原来的语法规则(操作数个数、优先级、结合性)。

3,不能重载下面的运算符

  • sizeof 运算符
  • . 成员运算符
  • :: 作用域解析运算符
  • ?: 条件运算符
  • typeid 一个RTTI运算符
  • 四种类型转换运算符

4,大多数的运算符可以通过成员或非成员函数进行重载,但是下面的运算符只能通过成员函数进行重载。

  • = 赋值运算符
  • () 函数调用运算符
  • [] 下标运算符
  • -> 通过指针访问类成员运算符

1.4,重载运算符作为成员函数的局限性

把运算符重载为类的成员函数,运算符左侧的操作数是调用对象,因此,运算符的第一个操作数必须是类对象。因为,重载运算符作为成员函数,当对象调用这个函数时,一个参数通过this指针隐式传递。例如:

Time operator+(const Time &t) const;

加法运算符需要两个操作数,上面operator+的参数只有一个,其中的一个操作数是被隐式传递的调用对象。

1.5,运算符重载与函数重载的区别

1.5.1,函数重载

C++允许功能相近的函数,在相同的作用域内以相同函数名声明,从而形成函数重载。

1.5.2,运算符重载

运算符重载是对已有的运算符赋予更多的含义。C++预定义的运算符只能用于基本类型数据,不能用于自定义类型数据。编译器对重载运算符的选择,遵循函数重载的原则。运算符重载的实质就是函数重载。

二,友元

2.1,什么是友元函数?

通常,公有类方法提供唯一对类对象进行访问途径,但是有时候这种限制太严格,不适合特定的编程问题。通过让函数称为类的友元函数,可以赋予该函数与类的成员函数相同的访问权限。

2.2,如何声明友元函数?

第一步:将函数的原型声明放到类声明中,并在原型声明前面加上friend关键字。
第二步:编写友元函数。

在类中声明

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

定义友元函数

Time operator*(double m, const Time &t){
    Time result;
    result.minutes = t.minutes * m;
    result.hours = t.hours * m + result.minutes / 60;
    result.minutes = result.minutes % 60;
    return result;
}

2.3,定义友元函数时应注意的问题

  1. 友元函数不是成员函数,定义友元函数时不要使用::限定符。
  2. 定义友元函数时,不要使用friend关键字。

2.4,友元函数的作用

为类重载二元运算符时,常常需要友元函数。例如:把Time对象乘以一个整数

A = B * 5;
//转换为下面的函数调用
A = B.operator*(5);

但是下面的语句没法处理

A = 5 * B;

把运算符重载为类的成员函数,则运算符的第一个操作数必须是类对象。如果想把非类对象作为第一个操作数,则必须使用友元函数来反转操作数的顺序。对于非成员重载运算符函数来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数。下面使用友元函数解决上面的问题

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

2.5,常用的友元

可以对<<进行重载,使之能与cout一起来显示对象的内容。ostream类对<<进行了重载,将其转换为一个输出工具,下面使用友元函数来重载<<:

ostream & operator<<(ostream &os, const Time &t){
    os<<t.hours<<" "<<t.minutes<<endl;
    return os;
}

输出Time对象

Time t1(1, 20);
cout<<t1;

2.6,友元类

友元类也是对类接口的扩展,友元类的所有的方法都可以访问原始类的私有成员和保护成员。继承并不能描述现实生活中所有类之间的关系,我们有一个TV类来表示电视机,一个Remote类表示遥控器,很显然这两个类之间并没有继承关系。遥控器可以访问电视机,但是电视机不能访问遥控器,可以把遥控器声明为电视机的友元类。例如:

#include <iostream>
using namespace std;

//Remote类的前向声明
class Remote;
class Tv{
private:
    int state;
public:
    friend class Remote;
    Tv(int s):state(s){}
};

class Remote{
private:
    int model;
public:
    void getState(Tv &t){
        cout<<t.state<<endl;
    }
};

int main() {
    Tv tv(5);
    Remote r;
    r.getState(tv);
    return 0;
}

注意:

友元声明可以位于私有、公有或保护部分,其所在的位置无关紧要。

三,类的自动转换与强制类型转换

3.1,从内置数据类型到用户自定义类型的转换

下面是自定义的Time类

class Time{
private:
    int intVal;
    double doubleVal;
public:
    Time(int val):intVal(val){}
    Time(double val):doubleVal(val){}
};

int main(){
    int val = 10;
    Time obj(5);
    obj = val;
    return 0;
}

程序分析

在C++中,接收一个参数的构造函数为将该参数类型的值转换为自定义的类型提供了蓝图。因此,在上面的构造函数中,可以将int、double类型转换为Time类型。可以将构造函数用作自动类型转换,但是这样做也会导致意外的类型转换。因此,C++提供了explicit关键字用于关闭这种特性。使用explicit关键字关闭了上面的自动类型转换,但仍然允许强制类型转换,如下:

class Time{
private:
    int intVal;
    double doubleVal;
public:
    explicit Time(int val):intVal(val){}
    explicit Time(double val):doubleVal(val){}
};

int main(){
    int val = 10;
    Time obj(5);
    obj = val;        //不合法
    obj = (Time)val;  //合法,进行强制类型转换
    return 0;
}

3.2,从用户自定义类型到内置类型之间的转换

上面介绍的是从int、double到Time类型的转换,这节要讲的是从Time类型到int、double等内置数据类型的转换。要进行相反的转换,必须使用特殊的C++运算符函数–转换函数,转换函数是用户定义的强制类型转换。

转换函数的原型

operator typeName();

使用转换函数应注意下面几点

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

使用类型转换函数

class Time{
private:
    int intVal;
    double doubleVal;
public:
    Time(int val):intVal(val){}
    Time(double val):doubleVal(val){}
    operator int() const{
        return intVal;
    }
    operator double() const{
        return doubleVal;
    }
};

int main(){
    int val = 10;
    Time obj(5);
    val = obj;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/cloud323/article/details/81040225