c++入门之—运算符重载和友元函数
运算符重载的意义是:将常见的运算符重载出其他的含义:比如将*重载出指针的含义,将<<与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++入门之—运算符重载和友元函数的更多相关文章
- C++运算符重载(友元函数方式)
我们知道,C++中的运算符重载有两种形式:①重载为类的成员函数(见C++运算符重载(成员函数方式)),②重载为类的友元函数. 当重载友元函数时,将没有隐含的参数this指针.这样,对双目运算符,友元函 ...
- C++抽象编程·运算符重载与友元函数
运算符重载(Operator overloading) 从我们在几个前篇的类的层次介绍中可以知道,C++可以扩展标准运算符,使其适用于新类型.这种技术称为运算符重载. 例如,字符串类重载+运算符,使其 ...
- C++ 学习笔记(五)类的知识小结一(重载,友元函数,静态成员,new)
---恢复内容开始--- 学习C++类知识点还是挺多的,每个知识点学习的时候都觉得这个知识点咋那么多东西,其实真学完了再回头看好像也就那么点.这次用程序写一个黑猫揍白猫的故事总结一下这段时间学习的零碎 ...
- C++运算符重载形式——成员函数or友元函数
运算符重载是C++多态的重要实现手段之一.通过运算符重载对运算符功能进行特殊定制,使其支持特定类型对象的运算,执行特定的功能,增强C++的扩展功能. 运算符重载的我们需要坚持四项基本原则: (1)不可 ...
- 初探C++运算符重载学习笔记<2> 重载为友元函数
初探C++运算符重载学习笔记 在上面那篇博客中,写了将运算符重载为普通函数或类的成员函数这两种情况. 以下的两种情况发生.则我们须要将运算符重载为类的友元函数 <1>成员函数不能满足要求 ...
- C++运算符重载(成员函数方式)
一.运算符重载 C++中预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多用户自定义类型,也需要有类似的运算操作.如果将C++中这些现存的运算符直接作用于用户自定义的类型数据上,会得到什么样 ...
- c++运算符重载和虚函数
运算符重载与虚函数 单目运算符 接下来都以AClass作为一个类例子介绍 AClass{ int var } 区分后置++与前置++ AClass operator ++ () ++前置 一般设计为返 ...
- C++多态性----运算符重载与虚函数
一.多态性 ①概述:多态是指同样的消息被不同类型的对象接收时导致的不同行为. ②类型: 可以分为四类:重载多态.强制多态.包含多态.参数多态. ------------------------ --- ...
- c++入门之运算符重载
c++函数重载:可以将一个函数名用于不同功能的函数.从而处理不同的对象.对于运算符,同样也有这样的用途,即对同一个标志符的运算符,可以运用到不同的功能中去. 首先引入:运算符重载,在C语言中甚至都有运 ...
随机推荐
- SMB协议原理抓包分析
SMB协议原理抓包分析 目录: 1.SMB概述 2.SMB原理 3.SMB配置 一.SMB概述 SMB(全称是Server Message Block)是一个协议名,可用于在计算机间共享文件.打印机. ...
- 十大经典排序算法的python实现
十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序.包括:冒泡排序.选择排序.归并排序.快速 ...
- 基于centOS7:新手篇→tomcat的部署方式
一.自动部署 将项目直接拷贝到webapps目录下,通过项目名直接访问 二.在server.xml中指定项目 打开Tomcat/conf/server.xml文件,在host标签中加入以下参数并重启T ...
- Java入门(五):控制流程
在Java中,使用条件语句和循环结构确定控制流程,在本文中,主要包括块作用域.条件语句.循环结构.中断循环这四部分. 一.块作用域 块,也叫复合语句,是指由一对大括号括起来的若干条Java语句.块决定 ...
- 如何写 go 代码 (How to Write Go Code 翻译)
目录 1. 写在前面的话 2. 介绍 3. 代码组织 3.1. 工作区 3.2. GOPATH 环境变量 3.3. Package 路径 3.4. 第一个 GO 程序 3.5. 第一个 GO 库 3. ...
- 【2018.04.27 C与C++基础】关于switch-case及if-else的效率问题
对于这个问题自己是比较清楚的,在分支比较多时,switch-case的效率肯定比if-else的要高许多,其原理类似于我们在优化某些程序时使用查表来代替算法计算一样. 如果想进一步深究的话,可以查看G ...
- 2018-10-18读文献总结之DCB码分多址、零基线、信号产生
---恢复内容开始--- 今天心血来潮,想开始把自己读文献的过程和每篇文献的收获总结一下,不知道CSDN怎么回事,一直登陆不进去,搞得我注册了一个博客园的账户,博客园新注册的还需要认证,但是很快,所以 ...
- nginx + uwsgi 部署 Django+Vue项目
nginx + uwsgi 部署 Django+Vue项目 windows 本地 DNS 解析 文件路径 C:\Windows\System32\drivers\etc 单机本地测试运行方式,调用dj ...
- A1制作文件夹目录
第一步 在文件夹目录下建立bat文件,填写以下内容: dir *.* /B >目录.txt 最后双击bat文件. 第二步 运行后复制目录.txt文件内容到空白excel 使用hyperlink函 ...
- 安装站点时出现“连接数据库出现数据库server或登录password无效,无法连接数据库,请又一次设定”解决方法
在安装站点时出现 "连接数据库出现数据库server或登录password无效,无法连接数据库,请又一次设定" 可是数据库username和password是正确的,在serv ...