《C++ Primer Plus》第11章:使用(1)
创始人
2024-03-29 09:45:31
0

文章目录

  • 运算符重载
    • 重载运算符的格式
    • 可重载的运算符
    • 有关运算符重载的限制
  • 友元
    • 为何需要友元
    • 创建友元
      • 第一步
      • 第二步
      • 友元是否有悖于OOP
    • 常用的友元:重载<<运算符
      • 第一步:<<的void重载版本
      • 第二步:<<的对象引用重载版本

运算符重载

函数重载是名称相同但是参数列表不同的函数。
运算符重载将重载的概念扩展到运算符上,允许赋予 C++ 运算符多种函数一。很多C++运算符已经被重载。例如,将*运算符用于地址,将得到存储在这个地址中的值;但将它用于两个数字时,得到的将是它们的乘积。C++根据操作数的数目和类型来决定使用哪种操作。
C++允许将运算符重载扩展到用户自定义的类型。

重载运算符的格式

要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下(以+为例):
cpp operator+(argument-list)

可重载的运算符

+-*/%^
&|~==<
>+=-=*=/=%=
^=&=|=<<>>>>=
<<===!=<=>=&&
||++,->*->
()[]newdeletenew[]delete[]

注意,上表中大多数运算符都可以通过成员或非成员函数进行重载,
但下面的运算符只能通过成员函数进行重载。

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

有关运算符重载的限制

  1. 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此,不能将减法运算符(-)重载为计算两个 double 值的和,而不是它们的差。虽然这种限制将对创造性有所影响,但可以确保程序正常运行。

  2. 使用运算符时不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成使用一个操作数

    int x;
    % x;  // invalid for modulus operator
    Time shiva;
    % shiva; // invalid or overloaded operator
    

    同样,不能修改运算符的优先级。因此,如果将加号运算符重载成两个类相加,则新的运算符与原来的加号具有相同的优先级。

  3. 不能创建新运算符。例如,不能定义 operator**() 函数来求幂。

  4. 不能重载下面的运算符

    sizeofsizeof运算符
    .成员运算符
    .*成员指针运算符
    ::作用域解析运算符
    ?:条件运算符
    typeid一个 RTTI 运算符
    const_cast强制类型转换运算符
    dynamic_cast强制类型转换运算符
    reinterpret_cast强制类型转换运算符
    static_cast强制类型转换运算符
  5. 除了这些正式限制外,还应在重载运算符时遵循一些明智的限制。例如,不要将* 运算符重载成交换两个 Time 对象的数据成员。表示法中没有任何内容可以表明运算符完成的工作,因此最好定义一个其名称具有说明性的类方法,如Swap()。

友元

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

友元有3种:

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

下面介绍友元函数,其它两种友元将在后面的章节介绍。

为何需要友元

在为类重载二元运算符时(带两个参数的运算符)常常需要友元。

重载二元运算符时常需要使用非成员函数来满足二元运算符的交换律。

非成员函数不是由对象调用的,它使用的所有值(包括对象)都是显式参数。

使用非成员函数来重载二元运算符时引发了一个问题,那就时非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。但是友元函数可以解决这个问题。

创建友元

第一步

创建友元函数的第一步是将其声明放在类声明中,并在声明前加上关键字friend

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

该声明意味着以下两点:

  1. 虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用;
  2. 虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同。

第二步

第二步是编写函数定义。因为它不是成员函数,所以不要使用 Time::限定符。另外,不要在定义中使用关键字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;
}

友元是否有悖于OOP

应将友元看作类的扩展接口的组成部分

只有类声明可以决定哪一个函数是友元,因此类声明仍然控制了哪些函数可以访问私有数据。总之,类方法和友元只是表达类接口的两种不同机制。

如果要为类重载运算符,并将非类的项作为其第一个操作数,则可以用友元函数来反转操作数。

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

一个很有用的特性是,可以对 << 运算符 进行重载,使之能与 cout 一起来显式对象的内容。

实际上,<< 已经被重载很多次了。最初 << 运算符是位运算符,用来将值中的位左移。ostream类对该运算符进行了重载,将其转换成一个输出工具。cout 是一个 ostream 对象,它能够识别所有的C++基本类型,因为对于每种基本类型,ostream 类声明中都包含了相应的重载的 operator<<() 定义。也就是说,一个定义使用 int 参数,一个定义使用 double 参数,等等。因此,要使 cout 能够识别 Time 对象,一种方法是将一个新的函数运算符定义添加到 ostream 类声明中。但修改 iostream 文件是个危险的主意,这样做会在标准接口上浪费时间。相反,可以通过 Time 类声明来让 Time 类知道如何使用 cout

第一步:<<的void重载版本

要使 Time 类知道如何使用 cout,必须使用友元函数。以为如果使用一个 Time 成员函数来重载<<,Time对象将是第一个操作数,这意味着必须这样使用<<

trip << cout;

这看起来很奇怪,使得程序变得更难读也更难写了。使用友元函数就能解决两个操作对象的顺序问题:

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

这样可以使用下面的语句:

cout << trip;

按下面这样的格式打印数据:

4 hours, 23 minutes

注意,这个重载函数是Time类的友元函数,但不是ostream类的友元函数,因为这个函数使用了Time对象的内部数据,但一直在把ostream类的对象当作一个整体在使用。

ostream的对象不止cout,还有比如 cerr,它将输出发送到标准输出流,但在 UNIX、Linux 和 Windows 命令行环境中,可将标准错误流重定向到文件。另外,第 6 章介绍的 ofstream 对象可用于将输出写入到文件中,通过继承,ofstream对象可以使用ostream的成员函数,这样便可以用operator<<()定义来将Time的数据写入到文件和屏幕上,为此,只需传递一个经过适当初始化的 ofstream 对象。

注意,cout<

第二步:<<的对象引用重载版本

void版本的重载存在一个问题,那就是只能连续使用一次 << 运算符。
像这样的语句可以正常工作:

cout << trip;

但下面的语句就不能使用了:

cout << "Trip time: " << trip < " (Tuesday)\n";  	//can't do

要理解这样做不可行的原因,首先需要了解关于cout操作的一些知识。
如下面的语句:

int x = 5;
int y = 8;
cout << x << y;

C++ 从左至右读取输出语句,意味着它等同于:

(cout << x) << y;

<< 运算符要求左边是一个 ostream对象。,因此,要求(cout << x)返回一个ostream对象的引用,iostream 中的定义就是这样的,所以可以对Time友元函数采用相同的方法:

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

注意,返回类型是 ostream &。这意味着该函数返回 ostream对象的引用。因为函数开始执行时,程序传递了一个对象引用给它,这样做的最终结果就是,函数的返回值就是传递给它的对象。

对于

cout << "Trip time: " << trip << " (Tuesday)\n"; 

执行的过程就是:

  1. cout << "Trip time: ";
    
  2.  cout << trip;
    
  3.  cout << " (Tuesday)\n";
    

这个 operator<<() 版本还可用于将输出写入到文件中:

#include
...
ofstream fout;
fout.open("savetime.txt");
Time trip(12, 40);
fout << trip;

其中,最有一条语句将被转换成这样:

operator << (fout, trip);

警告:只有在类声明中才能使用friend关键字,除非函数只有定义,否则已经声明过的函数,不能在函数定义中使用该关键字。

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...