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语言中甚至都有运 ...
随机推荐
- IIS ip访问限制插件
Dynamic IP Restrictions Overview The Dynamic IP Restrictions Extension for IIS provides IT Professio ...
- LINQ的求和 平均 最大 最小 分组 计数 等等
1.简单形式: var q = from p in db.Products group p by p.CategoryID into g select g; 语句描述:使用Group By按Categ ...
- Elasticsearch拼音和ik分词器的结合应用
一.创建索引时,自定义拼音分词和ik分词 PUT /my_index { "index": { "analysis": { "analyzer&quo ...
- Python 面向对象的特性2-----继承
面向对象的三大特性 1.封装 根据职责将属性和方法封装到一个抽象的类中,然后类创建一个实实在在的对象,有了对象以后,就可以访问到对象内部的属性,或者让对象来调用一个已经封装好的方法. 2.继承 实现代 ...
- kafka_2.11-2.0.0_常用操作
参考博文:Kafka消费组(consumer group) 参考博文:kafka 1.0 中文文档(九):操作 参考博文:kafka集群管理工具kafka-manager部署安装 以下操作可以在min ...
- June 1. 2018 Week 22nd Friday
What makes life dreary is the want of motive. 没有了目的,生活便暗淡无光. We all have dreams about our future, we ...
- Mac各种数据库安装和启动【笔记】
MongoBD 一个基于分布式文件存储的数据库. 下载 https://www.mongodb.com/download-center#community 安装 解压包 mongodb 数据默认存在/ ...
- Spring的事务管理1
事务的回顾: 事务:逻辑上的一组操作,组成这组事务的各个单元,要么全部成功,要么全部失败 事务的特性:ACID 原子性(Atomicity):事务不可分割 一致性(Consistency):事务执行前 ...
- Spring的jdbc模板2:使用开源的连接池
上篇简要介绍了如何在spring中配置默认的连接池和jdbc模板,这篇来介绍开源的连接池配置与属性引入 C3P0连接池配置: 引入jar包 配置c3p0连接池 <?xml version=&qu ...
- WebAPI HelpPage支持Area
WebAPI原生的HelpPage文档并不支持Area的生成,需进行如下改造: WebApiConfig: public static class WebApiConfig { public stat ...