11 章 使用类
运算符重载
友元函数
重载<<运算符,以便于输出
状态成员
使用rand生成随机值
类的自动转换和强制类型转换
类转换函数
本章首先介绍运算符重载,允许标准C++运算符用于类对象->友元,这种机制使得非成员函数可以访问私有数据->如何命令C++对类执行自动类型转换。
学习本章和12章将对类构造函数和类析构函数有更深入的了解。
11.1 运算符重载
之前介绍了函数重载,运算符重载是一种形式的C++多态。
实际上,很多C++运算符已经被重载,比如*地址运算符和乘法,C++根据操作数的数目和类型来决定采用哪种操作。
可以定义一个表示数组的类,并重载+运算符。于是:
evening = sam + janet; // add two array objects;
要重载运算符,需遵守运算符函数的格式:
operator op(argument list)
eg:
operator +()
operator *()
op必须是有效的C++运算符,不能虚构一个新的符号。
假设有一个Salesperson类,并重载了+运算符,使得两个对象的销售额相加:
districts = sid + sara;
相当于:
districts = sid.operator+(sara);
然后该函数将隐式地使用sid(因为它调用了方法)而显式地使用sara对象(因为它被作为参数传递),来计算总和并返回。
11.2 计算时间,一个运算符重载示例
2小时35分钟和2小时40分钟相加的运算符重载代码示例,
第7张通过定义结构相加来处理类似的情况,现在推广,采用类以及运算符重载来实现:
time.h
#ifndef MYTIMED_H_
#define MYTIMED_H_
class Time
{
private:
int hours;
int minutes;
public:
Time();
Time(int h, int m = 0);
void AddMin(int m);
void AddHr(int h);
void Reset(int h = 0, int m = 0);
Time Sum(const Time & t) const;
void Show() const;
};
#endif
================
time.cpp
#include<iostream>
#include "time.h"
Time::Time(){
hours = minutes = 0;
}
Time::Time(int h, int m) {
hours = h;
minutes = m;
}
void Time::Time::AddMin(int m) {
// 1、分钟相加
// 2、分钟取模,结果为当前分钟
// 3、分钟除法,若结果大于0,小时加1
minutes += m;
hours += minutes / 60;
minutes = minutes % 60;
}
void Time::Time::AddHr(int h) {
// 1、小时相加
// 2、小时取模,结果为当前小时
hours += h;
hours = hours % 24;
}
void Time::Reset(int h, int m) {
hours = h;
minutes = m;
}
Time Time::Sum(const Time & t) const {
Time sum;
sum.minutes = minutes + t.minutes;
sum.hours = hours + t.hours + sum.minutes/60;
sum.minutes = sum.minutes % 60;
// hours = hours % 24;
return sum;
}
void Time::Show() const{
using std::cout;
using std::endl;
cout << "hours: " << hours <<endl;
cout << "minutes: " << minutes <<endl;
}
然而,返回值不能使引用,因为函数将创建一个新的Time对象。返回对象将创建对象的副本,而调用函数可以使用它。
然而,若返回类型为Time &,由于sum是局部变量,在函数结束时被删除,因此引用将指向一个不存在的对象。
使用返回类型Time意味着程序将在删除sum之前构造它的拷贝,调用函数将得到该拷贝。
usetime.cpp
#include<iostream>
#include"time.h"
int main()
{
using namespace std;
Time planning;
Time coding(2, 20);
Time fixing(4, 20);
Time sum;
cout << "planning: " << endl;
planning.Show();
cout << "coding: " << endl;
coding.Show();
cout << "fixing: " << endl;
fixing.Show();
cout << "sum: " << endl;
sum = coding.Sum(fixing);
sum.Show();
return 0;
}
11.2.1 添加加法运算符
只需将sum()的名称改成operator+()即可。
这样,可以像调用Sum()那样来调用operator+()方法:sum = coding.operator+(fixing);
将该方法命令为operator+()后,也可以使用运算符表示法:sum = coding+fixing;
time.h中
Time operator+(const Time & t) const;
time.cpp中
Time Time::operator+(const Time & t) const {
usetime.cpp中
sum = coding+fixing;
总之,operator+()函数的名称使得可以使用函数表示法或运算符表示法来调用它。
可以将两个以上的对象相加吗?
t4 = t1 + t2 + t3;yes
11.2.2 重载限制
运算符重载的限制:
至少有一个操作数是用户定义的类型;
使用运算符时不能违反运算符原来的句法规则;例如,不能将%重载成使用一个操作数;不能修改运算符的优先级;
不能创建新的运算符
不能重载下面的运算符
sizeof:sizeof运算符
.:成员运算符
*:成员指针运算符
:::作用域解析运算符
?::条件运算符
typeid:一个RTTI运算符
const_cast:强制类项转换运算符
dynamic_cast:强制类项转换运算符
reintcrpret_cast:强制类项转换运算符
static_case:强制类项转换运算符
表11.1中大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载:
=:赋值运算符
():函数调用运算符
[]:下标运算符
->:通过指针访问类成员的运算符
表11.1
。。。。。。。。
注意:在重载运算符时遵循一些明智的限制,eg:不能将*运算符重载成交换两个对象的数据成员。表示法中没有任何内容可以表明运算符完成的工作,因此最好定义一个其名称具有说明性的类方法,egswap();
11.2.3 其他重载运算符
比如乘法和减法;
time.h
Time operator-(const Time & t) const;
Time operator*(double a) const;
========================
time.cpp
Time Time::operator-(const Time & t) const {
Time diff;
int tol1;
int tol2;
tol1 = t.minutes + t.hours * 60;
tol2 = minutes + hours * 60;
diff.minutes = (tol2 - tol1) % 60;
diff.hours = (tol2 - tol1) / 60;
return diff;
}
Time Time::operator*(double a) const {
Time aa;
int total = hours * 60 * a + minutes * a;
aa.minutes = total % 60;
aa.hours = total / 60;
return aa;
}
========================
usetime.cpp
cout << "diff: " << endl;
diff = coding-fixing;
diff.Show();
cout << "result: " << endl;
result = coding * 2;
result.Show();
11.3 友元
公有类方法提供私有部分的唯一访问途径,但这种限制太严格。
C++提供了另一种形式的访问权限:友元。
友元函数;
友元类;
友元成员函数;
通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。只介绍友元函数,其他两种15章介绍。
为何需要友元?在为类重载二元运算符时常常需要友元。
例如,乘法运算符将一个Time值与一个double值结合在一起。这限制了该运算符的使用方式。
A\B为Time类
A = B * 2.75;//可以这么使用,相当于A = B.operator*(2.75);
A = 2.75 * B; //不可以,因为2.75不是Time类
解决办法:
一、限制使用方式
二、非成员函数
非成员函数不是由对象调用的,它使用的所有值都是显示参数。
这样A = 2.75 * B;
与下面的非成员函数匹配:
A = operator*(2.75, B);
该函数的原型如下:
Time operator*(double m, const Time & t);
对于非成员重载运算符函数来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数。而原来的成员函数则按相反的顺序处理操作数。
使用非成员函数可以按所需的顺序获得操作数,但引发一个新问题:即非成员函数不能直接访问类的私有数据,至少常规非成员函数不能访问。然而,有一个特殊的非成员函数可以访问类的私有成员,称为友元函数
11.3.1 创建友元
第一步:将其原型放在类声明中,并加上关键字friend
friend Time operator*(double m, const Time & t);
该原型意味着:
虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用;
虽然operator*()不是成员函数,但它与成员函数的访问权限相同。
第二步:编写函数定义,不要使用Time::限定符,不要在定义中使用关键字friend
Time operator*(double m, const Time & t)
{
Time result;
long totalminutes = (t.hours * 60 + t.minutes) * m;
result.hours = totalminutes / 60;
result.minutest =totalminutes % 60;
return result;
}
提示:如果要为类重载运算符,并将非类的项作为第一个操作数,则可以用友元函数来反转操作数的顺序。
11.3.2 常用的友元:重载<<运算符
可以对<<运算符重载,使之能与cout一起来显示对象的内容。eg:
cout << time;
实际上,它已经被重载很多次了。
最初,<<是位运算符;
ofstream类对该运算符进行了重载,用作输出,能识别所有的基本类型;
1、<<的第一种重载版本
要使Time类知道使用cout,必须使用友元函数。因为第一个操作数不是Time。
void operatorMM(ostream & os, const Time & t)
{
os << t.hours << " hours, " << t.minutes << " minutes";
}
cout << time;