运算符重载的意义是:将常见的运算符重载出其他的含义:比如将*重载出指针的含义,将<<与cout联合使用重载出输出的含义,但需要认识到的问题是:运算符的重载:本质仍然是成员函数,即你可以认为:发生运算符重载时,实际上,是调用了成员函数,只不过重新换了个名字,叫运算符重载。

友元函数的意义:使得我们能够像成员函数一样访问私有成员变量,不会受到“权限”。下面通过一个函数来认识这一点:

 # ifndef MYTIME3_H_H
# define MYTIME3_H_H
# include "iostream"
class Time
{
private:
int hours;
int minutes;
public:
Time();
Time(int h, int m = );
void Addmin(int m);
void AddHr(int h);
void Reset(int h = , int m = );
Time operator+(const Time & t) const;//const放在后面表示不能修改成员函数
Time operator-(const Time & t) const;
Time operator*(double n) const;
friend Time operator*(double m, const Time & t)
{
return t*m;
}//类声明中定义的函数都是内联函数???
friend std::ostream & operator<< (std::ostream & os, const Time & t);
};
#endif

上述是一个关于类声明的头文件,在此,再次强调这么几个东西:

1  10,11行描述了构造函数(运用了函数重载),构造函数的存在意义是完成对类对象的初始化,在定义自己的类对象的时候,一定要保证了自己的类对象经过了初始化(实际上,在穿件类的时候,首先就会调用构造函数,完成初始化,即使没定义初始化函数,系统会自定生成一个初始化函数)

2  对于15-22行的代码,实际上都运用了运算符的重载,+,-,*,<<都被重载,至少从这里可以看出,运算符的重载,本质上,仍然是函数的调用。

3  18,22行的函数声明为友元函数,可以发现,friend 为关键字。同时注意:17行,18行代码,构成了函数重载!!!因为他们使用了同一个函数名,虽然这其中使用了运算符重载。这两个函数都描述了*运算,这也可以看出,重载函数,往往描述了同一种功能。

4   一个有趣的做法是:22行 的函数返回了一个ostream &,在之后的调用中,我们可以体会到这种妙用。

继续看成员函数定义:

 # include "mytime3.h"

 Time::Time()
{
hours = minutes = ;
} Time::Time(int h, int m)
{
hours = h;
minutes = m;
} void Time::Addmin(int m)
{
minutes = minutes + m;
hours = minutes / ;
minutes = minutes % ;
} void Time::AddHr(int h)
{
hours = 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 / ;
sum.minutes = sum.minutes % ;
return sum;//思考这里有引入Time对象的必要吗
} Time Time::operator-(const Time& t) const
{
Time diff;
int tot1,tot2;
tot1 = t.minutes + * t.hours;
tot2 = minutes + * hours;
diff.hours = (tot2 - tot1) / ;
diff.minutes = (tot2 - tot1) % ;
return diff;
} Time Time::operator*(double mult) const
{
Time result;
long totalminutes = hours*mult * + minutes*mult;
result.hours = totalminutes / ;
result.minutes = totalminutes % ;
return result;
} std::ostream & operator<<(std::ostream& os, const Time & t)
{
os << t.hours << "hours," << t.minutes << "minutes";
return os;
}

需要注意的是:51行的*并不是描述友元函数那个,而是另外一个。同时注意到63行,返回了一个类对象的引用,返回引用,本质上其实就是返回了传递到引用的参数!!!

我们注意到:62中,我们直接访问了t.hour和t.minutes,这是因为我们定义的是友元函数,同时注意到,前面并没有Time::类作用域的限定。这说明了友元函数并不是成员函数。注意:函数定义中并没有使用friend关键字。

最后看实际上,调用了这个类的代码:

 # include <iostream>
# include "mytime3.h" int main()
{
using std::cout;
using std::endl;
Time aida(, );
Time tosca(, );
Time temp; cout << "Aida and Tosca:" << endl;
cout << aida << ";" << tosca << endl;
temp = aida + tosca;
cout << "Aida + Tosca:" << temp << endl;
temp = aida*1.17;
cout << "Aida *1.17:" << temp << endl;
cout << "10.0*Tosca::" << 10.0*tosca << endl;
system("pause");
return ;
}

注意:第十行的 对象,被“隐“初始化

这里尤其要注意的是16行的代码,和18行的区别,当执行16行的代码时,系统会调用第一个*函数,当执行18行的代码时,执行的却是二个*函数。同时,我们注意:cout可以用来打印类对象,根本原因是std::ostream & operator<<(std::ostream& os, const Time & t)导致的,同时注意,该函数中返回了一个类对象引用,返回对象引用的目的是:返回cout本身,这就导致了比如定义了两个对象:Time A ,Time B ,当执行 cout<<A<<B时不会出错,因为(cout<<A)执行完毕返回了一个cout继续作用于B。你可以尝试删除return 。但是要运用这个的前提是:你认识到运算符重载本质上,仍然是函数的调用!!!只有认识到这点,才会将cout<<A<<B看成((cout<<A)<<B)

但问题在于:友元函数存在的意义又何在呢?

在这个程序中,有两个地方使用了友元函数,一个地方是:在计算诸如类对象 A*1.17或者1.17* 类对象 A,站在我们的角度看,这两者应该是相同的,但是在程序执行的时候,却完全不同。

比如:在执行A*1.17的时候,我们看会发生什么:上面讲过,运算符重载本质是一种函数调用,因此当看到(类*double)的时候,很明显基本规则中并没有这种方式的运算(因为我们都知道在基本类型中有一种数据类型要匹配的概念)。那么此时,程序就会认为这是一个运算符重载的案例,需要调用其函数。而这里涉及到了函数重载(两个operatorb*),此时会根据函数重载的准则选择最匹配的进行函数调用,这里选择了函数定义中的51行的定义:Time Time::operator*(double mult) const,很明显,这里没有调用友元函数那个。

此时,来解读这个函数:我们可以看到,这个函数定义只有一个显示参数mult。我们看调用时,发生了什么:当执行A*1.17,实际上,执行了A operator*(1.17),站在函数的角度可以认为:A对象作为隐式参数被传递到了函数中。也就是是:左侧的操作数是调用的对象,而对象被隐式传参了。

当我们要执行1.17*A的时候,又当如何呢???

当我们去试图去调用 Time Time::operator*(double mult) const,发现1.17并不是对象,再次声明:运算符重载中左侧的操作数是调用对象!!!此时,我们只能调用另一个operator*函数。但问题在于:似乎就算我们不需要这个友元函数,在成员函数中进行函数重载,定义两个非运算符重载的函数,让重载函数的特征标的顺序不同,照样可以完成这个功能。因此这里并不能很好的说明友元函数的存在意义。

但如果我们要完成cout<<类型A<<类型B;且必须采用这种方式,又当如何呢???

对于此处友元函数定义中,我们采用了friend std::ostream & operator<< (std::ostream & os, const Time & t);   因为要连续使用cout,因此函数的返回类型是ostream & ,.假如,我们去掉friend,变成:std::ostream & operator<< (std::ostream & os, const Time & t); 则编译器报错,说:operator运算符的参数太多,很明显,我们在这里要显示t,假如像之前一样,将t作为隐式参数是否可以呢? 即std::ostream & operator<< (std::ostream & os);此时倒是没报错,但是:这时请再次记住这句话:运算符重载中左侧的操作数是调用对象。也就是说这个时候变成了t<<cout!!!,显然这并不是我们想要的结果。我们甚至可以通过修改函数定义,来满足t<<cout,比如将函数定义改为:

 std::ostream & Time:: operator<<(std::ostream& os)
{
os << hours << "hours," << minutes << "minutes";
return os;
}

这样,我们可以使用t<<cout了,但如果我们要实现:cout\<<A<<B这样的东西,却发现实现不了(读者可自行尝试)。而且采用t<<cout,显然有悖程序设计原则:简洁而统一。

因此,我们必须使用友元函数。来实现cout<<A<<B.这里来回顾一下,使用友元函数实现这一目的的根本原因:

我们要使用运算符重载来实现显示,而运算符重载必然导致了左侧的操作数是调用的对象,而且运算符重载,显示参数只能有一个,隐式参数是对象本身,且位于运算符左侧,这才是根本限制!!

说明一下:非成员版本的重载运算符函数所需的形参数目与运算符使用的操作数数目相同(如友元函数),而成员函数所需参数少一个,因为其中一个参数是被隐式地传递的调用对象(通过this指针)。

c++入门之—运算符重载和友元函数的更多相关文章

  1. C++运算符重载(友元函数方式)

    我们知道,C++中的运算符重载有两种形式:①重载为类的成员函数(见C++运算符重载(成员函数方式)),②重载为类的友元函数. 当重载友元函数时,将没有隐含的参数this指针.这样,对双目运算符,友元函 ...

  2. C++抽象编程·运算符重载与友元函数

    运算符重载(Operator overloading) 从我们在几个前篇的类的层次介绍中可以知道,C++可以扩展标准运算符,使其适用于新类型.这种技术称为运算符重载. 例如,字符串类重载+运算符,使其 ...

  3. C++ 学习笔记(五)类的知识小结一(重载,友元函数,静态成员,new)

    ---恢复内容开始--- 学习C++类知识点还是挺多的,每个知识点学习的时候都觉得这个知识点咋那么多东西,其实真学完了再回头看好像也就那么点.这次用程序写一个黑猫揍白猫的故事总结一下这段时间学习的零碎 ...

  4. C++运算符重载形式——成员函数or友元函数

    运算符重载是C++多态的重要实现手段之一.通过运算符重载对运算符功能进行特殊定制,使其支持特定类型对象的运算,执行特定的功能,增强C++的扩展功能. 运算符重载的我们需要坚持四项基本原则: (1)不可 ...

  5. 初探C++运算符重载学习笔记&lt;2&gt; 重载为友元函数

    初探C++运算符重载学习笔记 在上面那篇博客中,写了将运算符重载为普通函数或类的成员函数这两种情况. 以下的两种情况发生.则我们须要将运算符重载为类的友元函数 <1>成员函数不能满足要求 ...

  6. C++运算符重载(成员函数方式)

    一.运算符重载 C++中预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多用户自定义类型,也需要有类似的运算操作.如果将C++中这些现存的运算符直接作用于用户自定义的类型数据上,会得到什么样 ...

  7. c++运算符重载和虚函数

    运算符重载与虚函数 单目运算符 接下来都以AClass作为一个类例子介绍 AClass{ int var } 区分后置++与前置++ AClass operator ++ () ++前置 一般设计为返 ...

  8. C++多态性----运算符重载与虚函数

    一.多态性 ①概述:多态是指同样的消息被不同类型的对象接收时导致的不同行为. ②类型: 可以分为四类:重载多态.强制多态.包含多态.参数多态. ------------------------ --- ...

  9. c++入门之运算符重载

    c++函数重载:可以将一个函数名用于不同功能的函数.从而处理不同的对象.对于运算符,同样也有这样的用途,即对同一个标志符的运算符,可以运用到不同的功能中去. 首先引入:运算符重载,在C语言中甚至都有运 ...

随机推荐

  1. win8系统电脑自动关机怎么取消

    在使用win8系统的用户会遇到电脑自动关机的情况,这是win8自带的自动关机功能,如果想取消这个功能,只需要通过执行一个命令即可实现.下面小编来为大家讲解一下具体步骤. 1.组合键:win+R,然后在 ...

  2. LeetCode算法题-Rotate Array(Java实现)

    这是悦乐书的第184次更新,第186篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第43题(顺位题号是189).给定一个数组,将数组向右旋转k步,其中k为非负数.例如: ...

  3. Django Form和ModelForm组件

    Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否 ...

  4. python collection模块

    一.模块的认识 定义:模块就是我们把装有特定功能的代码进行归类的结果. 说明:从代码编写的单位来看我们的城西,从小到大:一条代码 -> 语句块 - >代码块(函数.类)-> 模块. ...

  5. Nginx的configure各项中文说明

    –prefix=<path> – Nginx安装路径.如果没有指定,默认为 /usr/local/nginx. –sbin-path=<path> – Nginx可执行文件安装 ...

  6. nginx: worker process is shutting down

    正常情况下,nginx进程状态如下: 当修改配置文件,reload之后: PID=17114的wroker有正在处理的连接,等处理结束,该worker就会退出(退出之前,该worker不会处理新的连接 ...

  7. 微信H5开发,页面被缓存,不更新

    原文:https://blog.csdn.net/qq_27471405/article/details/79295348  这里只是备份 前言:每一次请求,我们都知道浏览器会做一定处理,其中就包括对 ...

  8. Jenkins+Ansible+Gitlab自动化部署三剑客-gitlab本地搭建

    实际操作 准备linux初始环境 关闭防火墙 systemctl stop firewalld 开机自己关闭 systemctl disable firewalld 设置安全配置 为关闭 vim /e ...

  9. 深度学习之从RNN到LSTM

    1.循环神经网络概述 循环神经网络(RNN)和DNN,CNN不同,它能处理序列问题.常见的序列有:一段段连续的语音,一段段连续的手写文字,一条句子等等.这些序列长短不一,又比较难拆分成一个个独立的样本 ...

  10. Oracle使用数据泵 (expdp/impdp)实施迁移

    实验环境: 1.导出环境:RedHat6.4+Oracle 11.2.0.4.0,利用数据库自带的scott示例用户进行试验测试. Directory:wjq à /tmp/seiang_wjq 2. ...