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语言中甚至都有运 ...
随机推荐
- mssql sqlserver 下文分享一种新颖的字符串截取方法
原文地址:http://www.maomao365.com/?p=7307 摘要: 以前分割字符串时,都使用类似split函数的方式处理,下文分享一种对有规律的字符串的分隔方式, 即:1. ...
- Spring入门详细教程(一)
一.spring概述 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的 ...
- 数据库之mysql篇(6)—— mysql常用函数函数/自定义函数
常用函数 运算函数 我相信你都能看懂,所以以上的不再做过多解释 然后还有个,前面漏掉的between and: 意指10是否在0到20之间,如果是返回1,否则返回0 日期函数 这个要稍微注意一下参数, ...
- shell 在终端中打开另一个终端执行命令
gnome-terminal -x bash -c "/home/XX/cc.sh; exec bash"
- 简单的bfs
这里主要是写的一个简单的bfs,实例运行了RMAT10无向图,总共有1024个顶点.这种简单的bfs算法存在很明显的缺陷,那就是如果图数据过大,那么进程将会直接被系统杀死. 代码如下: #includ ...
- Unity琐碎(3) UGUI 图文混排解决方案和优化
感觉使用Unity之后总能看到各种各样解决混排的方案,只能说明Unity不够体恤下情啊.这篇文章主要讲一下个人在使用过程中方案选择和优化过程,已做记录.顺便提下,开源很多意味着坑,还是要开实际需求. ...
- eclipse使用CXF3.1.*创建webservice服务端客户端以及客户端手机APP(二)
eclipse使用CXF3.1.*创建webservice服务端客户端以及客户端手机APP(二) 接上篇博客,本篇博客主要包含两个内容: 4.使用Android studio创建webservice客 ...
- 微服务---Eureka注册中心(服务治理)
在上一篇的初识SpringCloud微服务中,我们简单讲解到服务的提供者与消费者,当服务多了之后,会存在依赖与管理之间混乱的问题,以及需要对外暴露自己的地址,为了解决此等问题,我们学习Eureka注册 ...
- 《Java大学教程》—第6章 类和对象
6.2 对象:结构化编程-->数据-->封装(聚合,信息隐藏)-->对象(方法及其操作的数据都聚合在一个单元中,作为更高层的组织单元)-->类(创建对象的模板)6.3 类:* ...
- 【Linux基础】Linux常用命令汇总
3-1文件目录操作命令(cd pwd mkdir rmdir rm) 绝对路径:由根目录(/)开始写起的文件名或目录名称, 例如 /home/dmtsai/.bashrc: 相对路径:相对于目前路径的 ...