学习笔记 C++ Primer Plus 第11章 使用类

关键词:
① 运算符重载
② 友元函数
③ 重载<<运算符,以便用于输出
④ 状态成员
⑤ 使用rand()生成随机值
⑥ 类的自动转换和强制类型转换 / 类转换函数
⑦ 转换函数和友元函数


一、运算符重载
1. 功能介绍
运算符重载可以让用户自定义类的对象直接使用运算符(如+,-,*,/)执行某些操作。例如,将两个数组相加是一种常见的运算。在C语言中,通常需要使用以下for循环来实现:

for (int i = 0; I < 20; i++)
	total[i] = a[i] + b[i];	// add element by element

但在C++中,可以定义一个表示数组的类,然后重载+运算符。就可以有如下操作:

total = a + b;		// add two array objects

这种方法隐藏了内部机理,强调了实质,是OOP的目标之一。

2. 语法格式
重载运算符,需要使用运算符函数。运算符函数格式如下:
type operatorop(argument-list)
其中 type 一般为定义该运算符函数的类本身,op 为需要重载的运算符,argument-list 为参与运算的参数。调用该函数时,参与运算的参数有:1.调用该函数的对象本身。2.argument-list
下面示例重载了+和*,+的操作数为2个类,而*的操作数为一个类和一个double。

// mytime.h
#include <iostream>
class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	Time operator+(const Time & t) const;
	Time operator*(double n) const;
	void Show() const;
};
// mytime.cpp
#include "mytime.h"
Time::Time()
{
	hours = minutes = 0;
}
Time::Time(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 / 60;
	sum.minutes %= 60;
	return sum;
}
Time Time::operator*(double n) const
{
	Time result;
	long totalminutes = hours * n * 60 + minutes * n;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
void Time::Show() const
{
	std::cout << hours << " hours, " << minutes << " minutes";
}
// usetime.cpp
#include "mytime.h"
int main()
{
	Time coding(2, 40);
	Time fixing(5, 55);
	Time total;
	Time mult;
	
	total = coding + fixing;
	std::cout << "coding + fixing = ";
	total.Show();
	std::cout << std::endl;

	mult = coding * 2;
	std::cout << "coding * 2 = ";
	mult.Show();
	std::cout << std::endl;
	
	return 0;
}

运行结果是:
在这里插入图片描述
3. 重载限制
(1) 重载后的运算符必须至少有一个操作数是用户定义的类型。
(2) 使用运算符时不能违反运算符原来的句法规则。
(3) 不能创建新运算符。例如,不能定义 operator**()函数来表示求幂。
(4) 下列运算符不能进行重载
sizeof: sizeof运算符
.:成员运算符
.*:成员指针运算符
:::作用域解析运算符
?::条件运算符
typeid:一个RTTI运算符
const_cast:强制类型转换运算符
dynamic_cast:强制类型转换运算符
reinterpret_cast:强制类型转换运算符
static_cast:强制类型转换运算符

(5) 下列运算符只能通过成员函数进行重载
=:赋值运算符
( ):函数调用运算符
[ ]:下标运算符
->:通过指针访问类成员的运算符

评价:该特性可以帮我们在相同类型的类对象和类对象或类对象和标准类型变量之间更方便的进行运算。


二、友元函数
1. 说明由来
介绍友元之前,先介绍为何需要友元。在前面为Time重载 * 运算符时,调用该重载函数的方法是:
mult = coding * 2;
*号运算符前面的coding是Time类的对象,因此它调用Time中对 * 号运算符重载的函数。如果写成:
mult = 2 * coding;
会怎么样呢?编译器无法识别到该运算符重载。为了解决这个问题,就引出了友元函数。

2. 语法格式
在上面的mytime.h中添加一个 * 运算符友元函数声明。
friend Time operator*(double m, const Time & t);
注意:
1. 虽然operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用。
2. 虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同。
再在上面的mytime.cpp中添加该函数的定义,定义处不需要friend关键字。

Time operator*(double m, const Time & t)
{
	return t * m;	// use t.operator*(m)
}

如此就能在usetime.cpp中使用 mult = 2 * coding 的写法了。


三、重载<<运算符,以便用于输出
1. 功能说明
我们可以重载<<运算符,使之能用cout显示对象中的内容。

2. 示例代码
在上面的mytime.h中添加一个 << 运算符重载友元函数声明
friend std::ostream & operator<<(std::ostream & os, const Time & t);
然后再在mytime.cpp中添加该函数的定义

std::ostream & operator<<(std::ostream & os, const Time & t)
{
	os << t.hours << " hours, " << t.minutes << " minutes";
	return os;
}

如此就可以在usetime.cpp中使用 cout << total; 来代替 total.Show(); 函数了。

评价:友元函数特性可以帮我们在不同类型的类对象之间更变得进行发送消息。


四、状态成员
1. 说明由来
有些类可以有多种等价的表达方式,如"矢量“,它是一个既有方向也有大小的量。它有两种数学表达形式:
(1) 直角坐标(x, y); (2) 极坐标(mag, ang)。
如果我们声明了一个表示”矢量“的类,又定义了一系列该类的对象,那么这些表示”矢量“的对象究竟是直角坐标形式还是极坐标形式呢?因此,我们需要在声明"矢量”类的时候,添加一个Mode成员变量,来判断其是哪一种形式的矢量。

2. 具体实例
下面是一个包含状态成员矢量类的声明,定义及使用。构造函数、成员函数或友元函数可以根据状态成员变量的值来执行不同的语句。

// vector.h
#include <iostream>
class Vector
{
public:
	enum Mode {RECT, POL};
	const double Rad_to_deg = 57.2957795130823;
private:
	double x;
	double y;
	double mag;
	double ang;
	Mode mode;		// 状态成员,使用枚举变量来表示对象的状态。
public:
	Vector();
	Vector(double n1, double n2, Mode form = RECT);		//构造函数可以根据第三个参数来判断前两个参数表达的意思
	friend std::ostream & operator<<(std::ostream & os, const Vector & v);
};
// vector.cpp
#include "vector.h"
Vector::Vector()
{
	x = y = mag = ang = 0.0;
	mode = RECT;
}
Vector::Vector(double n1, double n2, Mode form)
{
	using std::cout;
	mode = form;
	if (form == RECT) {
		x = n1;
		y = n2;
	} else if (form = POL) {
		mag = n1;
		ang = n2;
	} else {
		cout << "Incorrect 3rd argument to Vector() -- ";
		cout << "vector set to 0\n";
		x = y = mag = ang = 0.0;
		mode = RECT;
	}
}
std::ostream & operator<<(std::ostream & os, const Vector & v)
{
	if (v.mode == Vector::RECT) {
		os << "(x, y) = (" << v.x << ", " << v.y << ")\n";
	} else if (v.mode == Vector::POL) {
		os << "(m, a) = (" << v.mag << ", " << v.ang * v.Rad_to_deg << ")\n";
	} else {
		os << "Vector object mode is invalid\n";
	}
	return os;
}
// usevector.cpp
#include "vector.h"
int main()
{
	using std::cout;
	Vector vect1(1, 2);
	Vector vect2(5, 0.9273, Vector::POL);
	cout << vect1 << vect2;

	return 0;
}

运行结果是:
在这里插入图片描述

评价:状态成员变量可以帮助我们在调用类的方法时,可以根据状态成员变量的值来执行某些特定的语句。


五、使用rand()生成随机值
有时我们需要系统给出一系列的随机数,这时我们需要用到rand(), srand(), 和time() 函数。比如,我们需要100以内的随机数,具体操作如下:

#include <iostream>
#include <cstdlib>		// rand(), srand() prototypes
#include <ctime>		// time() prototype
int main()
{
	using namespace std;
	double random;
	srand(time(0));		// seed random-number generator
	for (int i = 0; i < 5; i++) {
		cout << rand() % 100 << endl;
	}
	return 0;
}

运行结果为:
在这里插入图片描述
说明:这里如果只执行 rand() 函数,系统每次都会给出一样的固定值,在执行之前先执行一次 srand(time(0)) 函数,它可以将当前系统的时间作为参数传入,生成一个随机数种子,再执行 rand() 时,每次给出的值就不一样了。虽然它是伪随机数,但其程度是可以接受的。


六、类的自动转换和强制类型转换 / 类转换函数
1. 类的转换分两类
第一种:将标准类型变量转换成类变量。(通过只有一个参数的构造函数实现)
第二种:将类变量转换成标准类型变量。(通过类转换函数实现)

2. 标准类型——>类(单参数构造函数)
下面有一个表示身高的类,我们希望当给这个类赋值数值的时候,该数值可以转换成这个类的类型变量。

// heigth.h
#include <iostream>
class Height
{
public:
	enum Mode {CM, M};
private:
	int cm;
	double m;
	Mode mode;
public:
	Height(int h);				// construct from int
	Height(double h);			// construct from double
	friend std::ostream & operator<<(std::ostream & os, const Height & h);
};
// height.cpp
#include "height.h"
Height::Height(int h)
{
	cm = h;
	m = 1.0 * h / 100;
	mode = CM;
}
Height::Height(double h)
{
	m = h;
	cm = h * 100;
	mode = M;
}
std::ostream & operator<<(std::ostream & os, const Height & h)
{
	using std::cout;
	using std::endl;
	if (h.mode == Height::CM) {
		cout << h.cm << " cm" << endl;
	} else if (h.mode == Height::M) {
		cout << h.m << " m" << endl;
	} else {
		cout << "Error" << endl;
	}
}
// useheight.cpp
#include "height.h"
int main()
{
	using std::cout;
	Height one = 176;
	Height two = 1.6;
	cout << one << two;
	return 0;
}

运行结果:
在这里插入图片描述
可以看到这里我们给Height类型的变量赋值整数176和小数1.6,最终cout都能正常输出其身高的正确表达式。这里正是通过单个参数的构造函数进行的自动类型转换。如果不希望系统自动进行这样的类型转换,可以在声明构造函数的时候,加上explicit关键字,这样就需要显示类型转换 Height one = Height(176),才能将标准类型变量转换成类的类型变量。

评价:该特性可以帮我们更方便的给一些类的对象赋值。

3. 类——>标准类型(转换函数)
前面我们将标准类型通过单参数构造函数转换成了类,现在我们要将类转换成标准类型,这只能通过特殊的C++运算符函数,转换函数来实现。转换函数有以下3个注意点:
(1) 转换函数必须是类的方法;
(2) 转换函数不能指定返回类型;
(3) 转换函数不能有参数。
语法格式: operator typeName()
下面依然是 Height 这个类的例子,在上面的基础上进行了添加。

// heigth1.h
#include <iostream>
class Height
{
public:
	enum Mode {CM, M};
private:
	int cm;
	double m;
	Mode mode;
public:
	Height(int h);					// construct from int
	Height(double h);				// construct from double
	operator int() const;			// conversion function
	operator double() const;		// conversion function
	friend std::ostream & operator<<(std::ostream & os, const Height & h);
};
// height1.cpp
#include "height1.h"
Height::Height(int h)
{
	cm = h;
	m = 1.0 * h / 100;
	mode = CM;
}
Height::Height(double h)
{
	m = h;
	cm = h * 100;
	mode = M;
}
Height::operator int() const
{
	return cm;
}
Height::operator double() const
{
	return m;
}
std::ostream & operator<<(std::ostream & os, const Height & h)
{
	using std::cout;
	using std::endl;
	if (h.mode == Height::CM) {
		cout << h.cm << " cm" << endl;
	} else if (h.mode == Height::M) {
		cout << h.m << " m" << endl;
	} else {
		cout << "Error" << endl;
	}
}
// useheight1.cpp
#include "height1.h"
int main()
{
	using std::cout;
	Height one = 176;
	Height two = 1.6;
	int a = int(one);
	double b = (double)two;
	cout << "a = " << a << "\n";
	cout << "b = " << b << "\n";
	return 0;
}

运行结果:
在这里插入图片描述
上面总共定义了2个类型转换函数,并且均使用的时显示类型转换,这样有助于代码的可读性。
若只定义一个 operator int() const 转换函数,并且没有重载<<运算符,则可以直接用隐式转换的方式 cout << one,直接输出one的整数值。但是如果同时也定义了 operator double() const 转换函数的话,cout 类将不知道应该接收哪一个转换函数的结果,代码存在了二义性。因此,一般情况下,建议使用显式类型转换。


七、转换函数和友元函数
最后我们再来总结一下为 Height 类重载+运算符的方法。
可以使用成员函数的方法:

Height Height::operator+(const Height & hei) const
{
	Height sum;
	sum.cm =  cm + hei.cm;
	sum.m = m + hei.m;
	return sum;
}

也可以将加法作为友元函数来实现:

Height operator+(const Height & hei1, const Height & hei2)
{
	Height sum;
	sum.cm = hei1.cm + hei2.cm;
	sum.m = hei1.m + hei2.m;
	return sum;
}

(1) 当我们需要使用 class = class + class 时,上述两种方法均可以使用。
(2) 当我们需要使用 class = class + double or int 时,需要再提供一个单参数构造函数Height(double)或者Height(int),它可以将里面的 double 或 int 转变为 Height,然后就变成了 (1) 这种形式。此时不能定义类转换函数operator double()或者operator int(),因为这样编译器将不清楚究竟是将 class 转换成 double or int,还是将double or int 转换成 class。
(3) 当我们需要使用 class = double or int + class 时,有两种方法:
第一种是使用单参数构造函数友元函数,先调用 Height(double) 或 Height(int) 将 double or int 转换成功 class,再调用 operator+(const Height &, const Height &) 将两个 class 相加。
第二种是将+运算符重载为一个显式使用 double or int 类型参数的函数
Height operator+(double x); 或者 Height operator+(int x); 的成员函数。
friend Height operator+(double x, Height & hei); 或者 friend Height operator+(int x, Height & hei); 的友元函数。
如此,class = class + double or int 将与成员函数完全匹配,而不需要进行隐式类型转换。
class = double or int + class 将于友元函数完全匹配,而不需要进行隐式类型转换。
第一种方法的优点是工作量小,不容易出错,但是每次做加法的时候都需要调用单参数构造函数,增加了内存的开销。
第二种方法的优缺点正好和第一种相反,它工作量大,但是它运行速度更快。
如果程序经常要将 class 和 double or int 相加的话,推荐使用第二种方法。但如果只是偶尔使用的话,则使用第一种更简单,但为了保险起见,建议使用显式转换。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值