7.1.2 定义改进的Sales_date类
改进后的类的数据成员将与之前定义的版本保持一致,它们包括:bookNO,string类型,表示ISBN编号;units_sold,unsigned类型,表示某本书的销量;以及revenue,double类型,表示这本书的总销售收入。
如前所述,我们的类将包含两个成员函数:combine和isbn。此外,我们还将赋予Sales_date另一个成员函数用于返回售出书籍的平均价格,这个函数被命名为avg_price。因为avg_price的目的并非通用,所以它应该属于类的实现的一部分,而非接口的一部分。
定义和声明成员函数的方式与普通函数差不多。成员函数的声明必须在类的内部,它的定义则可以在类的内部也可以在类的外部。作为接口组成部分的非成员函数,例如add、read和print等,它们的定义和声明都在类的外部。
由此可知,改进的Sales_data类型应该如下所示:
struct Sales_data{// 新成员:关于Sales_data对象的操作std :: string isbn() const { return bookNo; }Sales_data & combine(const Sales_data&);double avg_price() const;// 数据成员相之前没有变化sd :: string bookNo;unsigned units_sold = 0;double revenue = 0.0; }; // Sales_data的非成员接口函数 Sales_data add(const Sales_data&, const Sales_data&); std :: ostream &print(std :: ostream&, const Sales_data&); std :: istream &read(std :: istream, Sales_data&);
注:定义在类的内部的函数是隐式的inline函数。
定义成员函数
尽管所有成员都必须在内部声明,但是成员函数体可以定义在类内也可以定义在类外。对于Sales_data类来说,isbn函数定义在了类内,而combine和avg_price定义在了类外。
我们首先介绍isbn函数,它的形参列表为空,返回值是一个string对象:
std :: string isbn( ) const { return bookNo; }
和其他函数一样,成员函数体也是一个语句快。在此例中只有一条return语句,用于返回Sales_data对象的bookNo数据成员。关于isbn函数一间很有意思的事情是:它是如何获得bookNo成员所依赖的对象呢?
引入this
让我们再一次观察对isbn成员函数的调用:
total.isbn( )
在这里,我们使用了点运算符来访问total对象的isbn成员,然后调用它。
在后面我们将介绍一种例外的形式,当我们调用成员函数时,实际上是在替某个对象调用它。如果isbn指向Sales_data的成员(例如bookNo),则它隐式地指向调用该函数的对象的成员。在上面所示的调用中,当isbn返回bookNo时,实际上它隐式地返回total.bookNo。
成员函数通过一个名为this的额外的隐式的参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。例如,如果调用
total.isbn( )
则编译器负责把total的地址传递给isbn的隐式形参this,可以等价地认为编译器将该调用重写成了如下的形式:
// 伪代码,用于说明调用成员函数的实际执行过程
Sales_data :: isbn(&total)
其中,调用Sales_data的isbn成员时传入了对象total的地址。
在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看作this的隐式引用,也就是说,当isbn使用bookNo时,它隐式地使用this指向的成员,就像我们书写了this->bookNo一样。
对于我们来说,this形参是隐式定义的。实际上,任何自定义名为this的参数或变量的行为都是非法的。我们可以在成员函数体内部使用this,因此尽管没必要,但我们还是能够把isbn定义成如下的形式:
std :: string isbn( ) const { return this ->bookNo; }
因为this的目的总是指向“这个”对象,所以this是一个常量指针,我们不允许改变this中保存的地址。
引入const成员函数
isbn函数的另一个关键之处是紧随参数列表之后的const关键字,这里,const的作用是修改隐式this指针的类型。
默认情况下,this的类型是指向类类型非常量版本的常量指针。例如在Sales_data成员函数中,this的类型是Sales_data *const。尽管this是隐式的,但是它仍然需要遵循初始化规则,意味着(在默认情况下)我们不能把this绑定到一个常量对象上。这一情况也就使得我们不能在一个常量对象上调用普通的成员函数。
如果isbn是一个普通函数而且this是一个普通的指针参数,则我们应该把this声明成const Sales_data * const。毕竟在isbn函数体内不会改变this所指的对象,所以把this设置为指向常量的指针有助于提高函数的灵活性。
然而,this是隐式的并且不会出现在参数列表中,所以在哪儿将this声明成指向常量的指针就成为我们必须面对的问题。C++语言的做法是允许把关键字const放在成员函数的参数列表之后,此时,紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数。
可以把isbn的函数体想象成如下的形式:
// 伪代码,说明隐式的this指针是如何使用的
// 下面的代码是非法的:因为我们不能显示地定义自己的this指针
// 谨记此处的this是一个指向常量的指针,因为isbn是一个常量成员
std :: string Sales_data :: isbn(const Sales_data *const this) { return this -> bookNo; }
注:常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
类的作用域和成员函数
回顾之前所学的知识,类本身就是一个作用域。类的成员函数的定义嵌套在类的作用域之内,因此,isbn中用到的名字bookNo其实就是定义在Sales_data内的数据成员。
值得注意的是,即使bookNo定义在isbn之后,isbn也还是能够使用bookNo。就如后面将要学习的那样,编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体(如果有的话)。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。
在类的外部定义成员函数
像其他函数一样,当我们在类的外部定义成员函数时,成员函数的定义必须与它的声明匹配。也就是说,返回类型、参数列表和函数名都得与类内部的声明保持一致。如果成员被声明成常量成员函数,那么它的定义也必须在参数列表后明确指定const属性。同时,类外部定义的成员的名字必须包含它所属的类名:
double Sales_data :: avg_price( ) const {
if (units_sold)
return revenue / units_sold;
else
return 0;
}
函数名Sales_data :: avg_price使用作用域运算符来说明如下事实:我们定义了一个名为avg_price的函数,并且该函数被声明在类Sales_data的作用域内。一旦编译器看到这个函数名,就能理解剩余的代码是位于类的作用域内的。因此,当avg_price使用revenue和units_sold时,实际上它隐式地使用了Sales_data的成员。
定义一个返回this对象的函数
函数combine的设计初衷类似于复合赋值运算符+=,调用该函数的对象代表的是赋值运算符左侧的运算对象,右侧运算对象则是通过显示的实参被传入函数:
Sales_data & Sales_data :: combine(const Sales_data &rhs)
{
units_sold += rhs.units_sold; // 把rhs的成员加到this对象的成员上
revenue += rhs.revenue;
return *this; // 返回调用该函数的对象
}
当我们的交易处理程序调用如下的函数时,
total.combine(trans); // 更新变量total当前的值
total的地址被绑定到隐式的this参数上,而rhs绑定到了trans上。因此,当combine执行下面的语句时,
units_sold += rhs.units_sold; // 把rhs的承压un添加到this对象的成员中
效果等同于求total.units_sold和trans.units_sold的和,然后把结果保存到total.units_sold中。
该函数一个值得关注的部分是它的返回类型和返回语句。一般来说,当我们定义的函数类似于某个内置运算符时,应该令该函数的行为尽量模仿这个运算符。内置的赋值运算符把它的左侧运算对象当成左值返回,因此为了与它保持一致,combine函数必须返回引用类型。因为此时的左侧运算对象是一个Sales_data的对象,所以返回类型应该是Sales_data&。
如前所述,我们无须使用隐式的this指针访问函数调用者的某个具体成员,而是需要把调用函数的对象当成一个整体来访问:
return *this; // 返回调用该函数的对象
其中,return语句解引用this指针以获得执行该函数的对象,换句话说,上面的这个调用返回total的引用。
7.1.2 定义改进的Sales_date类相关推荐
- 【学习笔记】35、定义自己的异常类
定义自己的异常类 定义自己的错误类型有很多好处,比如可以清楚地显示出潜在的错误,让函数和模块更具可维护性.自定义错误类型还可以用来提供额外的调试信息.这都有助于改进Python代码,使其更易于理解.调 ...
- 首先定义一个Point (点)类,包含属性x,y(x,y为坐标点),方法有setPoint、getX、getY和OprintInfo...python编程题练习
复习python编程题集合的第五题,若有错误和改进意见,欢迎评论交流! 首先定义一个Point (点)类: (1)包含属性x,y(x,y为坐标 ...
- java编译会产生多少个类文件,编译一个定义了三个类和四个方法的Java源程序文件,总共会产生多少个字节码文件 ? ( )...
编译一个定义了三个类和四个方法的Java源程序文件,总共会产生多少个字节码文件 ? ( ) 更多相关问题 论述风化作用基本概念及其主要类型. 什么是药用植物 学?其 研究任务是什么 ? 庐山瀑布很有名 ...
- 《JAVA练习题目7》 定义一个素数生成器类PrimeGenerator,用于生成给定区间内的所有素数。(类PrimeGenerator都由类Main代替)
题目内容: 定义一个素数生成器类PrimeGenerator,用于生成给定区间内的所有素数.要求PrimeGenerator类具有: 属性:start(区间起始值),end(区间终止值),两个属性均为 ...
- 构造方法与重载:定义一个网络用户类,信息有用户 ID、用户密码、 email 地址。在建立类的实例时把以上三个信息都作为构造函数的参数输入
构造方法与重载:定义一个网络用户类,信息有用户 ID.用户密码. email 地址.在建立类的实例时把以上三个信息都作为构造函数的参数输入, 其中用户 ID 和用户密码时必须缺省时 email地址是用 ...
- DCMTK:测试程序中定义的功能和类 ofmem.h(OF shared_ptr)
DCMTK:测试程序中定义的功能和类 ofmem.h(OF shared_ptr) 测试程序中定义的功能和类 ofmem.h(OF shared_ptr) 测试程序中定义的功能和类 ofmem.h(O ...
- 3.【练习题】构造方法与重载 定义一个网络用户类,要处理的信息有用户ID、用户密码、email地址。拓展:判断密码长度
package day09;/*3.[练习题]构造方法与重载 定义一个网络用户类,要处理的信息有用户ID.用户密码.email地址. 在建立类的实例时,把以上三个信息都作为构造函数的参数输入, 其中用 ...
- 定义一个 圆形 Circle类 , 定义其中的长度length属性,定义一个求面积getArea()的方法。 并编写一个测试类,进行长度的赋值和展示,并调用求面积方法展示面积值。
定义一个 圆形 Circle类 , 定义其中的长度length属性,定义一个求面积getArea()的方法. 并编写一个测试类,进行长度的赋值和展示,并调用求面积方法展示面积值. public cla ...
- python方法定义..._解析Python类中的方法定义
最近在学习类过程中,绑定方法这个概念没有理解透彻,所以在网上找了很多相关博客.文章研究到底是怎么一回事.因为有的文章所陈述与我在python3.5版本实际实验中有些出入,所以经过实践后总结出以下结论. ...
最新文章
- ubuntu系统安装mysql二进制压缩包(tar.gz)以及navicat远程连接服务器(linux系统)
- php 搜索名称或者编号,ECSHOP商品关键词模糊分词搜索插件,商品列表关键字加红功能-ecshop插件网...
- 034:DTL常用过滤器(3)
- 如何用 Netty 设计一个百万级推送服务?
- Linux停止后台运行Django项目
- linux防火墙停用,关闭LINUX防火墙
- 微信小程序获取当前时间戳、获取当前时间、时间戳加减
- Linux内存管理slub分配器
- 并发编程应用场景_linux网络编程之select函数的并发限制和poll函数应用举例
- 数据结构HashMap(Android SparseArray 和ArrayMap) 1
- GO程序设计语言学习笔记
- python编写翻译器_用Python做一个简单的翻译工具
- 130、总结:华为、H3C、锐捷三家交换机配置命令详解
- 雅虎邮箱客户端服务器设置
- 安卓开发——视频播放器
- 【EXLIBRIS】随笔记 008
- Windows平台搭建Mantis服务器
- 打了一台滴滴D1后,我开始思考今后还要买车吗?
- 教你从零开始搭建阿里云ESC服务器(建站)新手必看!
- 【钛晨报】字节跳动硬件业务调整,原锤子团队被合并;蔚来回应特斯拉降价冲击:退订是有组织的水军谣言...
热门文章
- 编写高性能Web应用程序的10个技巧
- align_center在JAVA_margin:0 auto与body{text-align:center;}实现元素居中的区别
- [Python从零到壹] 五.网络爬虫之BeautifulSoup基础语法万字详解
- [LeetCode] Binary Tree Paths - 二叉树基础系列题目
- 2017年第八届蓝桥杯 - 省赛 - C/C++大学A组 - G. 正则问题
- LeetCode 多线程 1117. H2O 生成
- 2020年第十一届蓝桥杯 - 省赛 - Python大学组 - B.寻找2020
- PAT (Basic Level) Practice (中文)C++ python 语言实现 —— 题解目录
- Python学习笔记(五) Python高级特性
- 【C/C++13】天气APP:数据挖掘/HTTP协议/非结构化数据存储(filetoblob.cpp),数据管理/监控告警(hsmtable.cpp,tbspaceinfo.cpp)