文章目录
前言
本人在阅读C++ primer plus(第六版)的过程中做的笔记,写这篇文章既是为了分享,也是为了以后查阅。以下是本篇文章正式内容。
一、运算符重载
  要重载运算符,需要使用运算符函数:
   operatorop(argument-list);
   例如,operator+()重载+运算符,operator*()重载*运算符,operator重载[]运算符。例如将两个Time对象相加:
Time Time::Sum(const Time &t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}
 
使用重载+运算符的函数只需将函数名改为operator+:
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;
}
 
  和Sum()一样,operator+()也是由对象调用的,它将第一个对象当作调用对象,第二个对象当作参数,可以像调用Sum()那样调用operator+():
   total = coding.operator+(fixing);   //函数表示法
   total = coding + fixing;      //运算符表示法
   在运算符表示法中,运算符左侧的对象是调用对象,右侧的对象是作为参数被传递的对象。
重载限制
- 重载后的运算符必须至少有一个操作数是用户定义的类型;
 - 不能违反运算符原来的句法规则,例如不能将求模运算符%重载为使用一个操作数;不能修改运算符的优先级;
 - 不能创建新运算符;
 - 不能重载下面的运算符:
(1)sizeof : sizeof运算符;
(2). : 成员运算符;
(3)* : 成员指针运算符;
(4):: : 作用域解析运算符;
(5)?: : 条件运算符;
(6)typeid : 一个RTTI运算符;
(7)const_cast : 强制类型转换运算符;
(8)dynamics_cast : 强制类型转换运算符;
(9)reinterpret_cast : 强制类型转换运算符;
(10)static_cast : 强制类型转换运算符。 - 下表中的运算符都可以通过成员函数或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载:
(1)= : 赋值运算符;
(2)() : 函数调用运算符;
(3)[] : 下标运算符;
(4)-> : 通过指针访问类成员的运算符。 
| + | - | * | / | % | ^ | 
|---|---|---|---|---|---|
| & | | | ~= | ! | = | < | 
| > | += | -= | *= | /= | %= | 
| ^= | &= | |= | << | >> | >>= | 
| <<= | == | != | <= | >= | && | 
| || | ++ | -- | , | ->* | -> | 
| () | [] | new | delete | new[] | delete[] | 
二、友元
  友元有三种:友元函数、友元类、友元成员函数。对于上面的Time类,如果让Time对象与一个double值相乘,则需要重载*运算符,而且*运算符左边的操作数是调用重载*函数的Time对象,右边的操作数是double值,也就是说对于Time对象t和double值m,只能这样使用:Time total = t * m,而不能Time total = m * t。成员函数和非成员函数都可以重载运算符,因此可以定义这样的非成员函数:
   Time operator*(const double &m, const Time &t);
   使Time total = m * t即Time total = operator*(m, t)可以使用。然而非成员函数不能直接访问类的私有数据,但是特殊的非成员函数——友元函数可以。
1.创建友元
  首先需要将友元函数的原型放在类声明中,并在原型前加上friend关键字:
   friend Time operator*(const double &m, const Time &t);
   有下面两点需要说明:
   ♦虽然函数在类声明中声明,但不是成员函数,不能使用成员运算符来调用;
   ♦关键字friend表明虽然它不是成员函数,但它与成员函数具有同样的访问权限。
   接下来是进行定义,友元函数不是成员函数,所以不使用Time::限定符,另外不使用关键字friend:
Time operator*(const double &m, const Time &t)
{
	Time result;
	long totalminutes = t.hours * m * 60 + t.minutes * m;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
 
  有了上述声明和定义后,Time total = 2.75 * t可以使用,即Time total = operator*(2.75, t)。
   实际上,可以像下面这样修改定义:
Time operator*(const double &m, const Time &t)
{
	return t * m;									//调用成员函数
}
 
修改定义后该函数不再访问类的私有数据成员,因此可以将其编写为非友元函数。
2.常用的友元:重载<<运算符
  <<运算符是需要两个操作数的运算符,如cout << x,cout是左操作数,x是右操作数。假设trip是Time类的一个对象,为了显示trip的值,可以使用cout << trip最好,所以我们需要重载<<运算符。但是左操作数必须是调用重载函数的对象,显然cout是ostream的对象,而不是Time类对象,而trip << cout太奇怪,所以我们需要通过友元函数来重载<<运算符,并且重载<<函数的第一个参数是cout的引用,第二个参数是trip的引用。而且函数返回类型必须是ostream &,否则下面的语句不能正常执行:
   cout << trip << “ Done!\n”;
   因为<<左操作数必须是ostream对象,重载<<运算符函数的返回类型为ostream &可以使cout << trip返回ostream对象用于输出字符串” Done!”。函数原型如下:
   friend ostream & operator<<(ostream &os, const Time &t);
   函数定义如下:
ostream & operator<<(ostream &os, const Time &t)
{
	os << t.hours << “ hours, ” << t.minutes << “ minutes.\n”;
	return os;
}
 
  为什么可以返回ostream &?os在函数代码执行结束后不是会被释放吗?不是的,os不是在函数代码块中被创建的,而是以按引用传递的方式作为参数传递到函数中的,函数所返回的os其实就是作为函数参数被传递进来的os。
   有趣的是,重载<<运算符还可用于将输出写入到文件中:
#include <iostream>
#include <fstream>
……
ofstream fout;
fout.open(“saving.txt”);
Time trip(3, 25);
fout << trip;
 
  可行的原因是ofstream继承自ostream。
   友元函数和<<运算符重载示例:
#pragma once
#ifndef MYTIME_H_
#define MYTIME_H_
#include <iostream>
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 operator+(const Time &t) 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
 
#include "mytime.h"
Time::Time()
{
	hours = minutes = 0;
}
Time::Time(int h, int m)
{
	hours = h;
	minutes = m;
}
void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}
void Time::AddHr(int h)
{
	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 / 60;
	sum.minutes %= 60;
	return sum;
}
Time Time::operator-(const Time &t) const
{
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}
Time Time::operator*(double mult) const
{
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
std::ostream & operator<<(std::ostream &os, const Time &t)
{
	os << t.hours << " hours, " << t.minutes << " minutes";
	return os;
}
 
#include "mytime.h"
int main()
{
	using std::cout;
	using std::endl;
	Time aida(3, 35);
	Time tosca(2, 48);
	Time temp;
	cout << "Aida and Tosca:\n";
	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;
	return 0;
}
 
三、一个矢量类
下面的程序清单模拟了随机漫步问题。其意思是,将一个人领到街灯住下。这个人开始走动,但每一步的方向都是随机的(与前一步不同)。这个问题的一种表述是,这个人走到离灯柱50英尺处需要多少步。从矢量的角度看,这相当于不断将方向随机的矢量相加,直到长度超过50英尺。
#pragma once
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
#include <cmath>
namespace VECTOR
{
	class Vector
	{
	public:
		enum Mode { RECT, POL };
	private:
		double x;
		double y;
		double mag;
		double ang;
		Mode mode;
	public:
		Vector();
		Vector(double n1, double n2, Mode form = RECT);
		~Vector() {};
		void setx();
		void sety();
		void setmag();
		void setang();
		void setmode(Mode md);
		double getx() const { return x; }
		double gety() const { return y; }
		double getmag() const { return mag; }
		double getang() const { return ang; }
		Mode getmode() const { return mode; }
		friend std::ostream & operator<<(std::ostream &os, const Vector &v);
		Vector operator+(const Vector &v) const;
		Vector operator*(const double &d) const;
		friend Vector operator*(const double &d, const Vector &v);
		Vector operator-(const Vector &v) const;
		Vector operator-() const;
		void reset(double n1, double n2);
	};
}
#endif
 
#include "vector.h"
namespace VECTOR
{
	Vector::Vector()
	{
		x = y = mag = ang = 0.0;
		mode = RECT;
	}
	Vector::Vector(double n1, double n2, Mode form)
	{
		if (form == RECT)
		{
			mode = form;
			x = n1;
			y = n2;
			setmag();
			setang();
		}
		else if (form == POL)
		{
			mode = form;
			mag = n1;
			ang = n2;
			setx();
			sety();
		}
		else
		{
			std::cout << "出现了既不是直角坐标又不是极坐标的情况。\n";
		}
	}
	void Vector::setx()
	{
		x = mag * sin(ang);
	}
	void Vector::sety()
	{
		y = mag * cos(ang);
	}
	void Vector::setmag()
	{
		mag = sqrt(x * x + y * y);
	}
	void Vector::setang()
	{
		if (x == 0.0 && y == 0.0)
			ang = 0.0;
		else
			ang = atan2(y, x);								//atan2()是内置数学函数,由x, y值求得弧度
	}
	void Vector::setmode(Mode md)
	{
		mode = md;
	}
	Vector Vector::operator+(const Vector & v) const
	{
		Vector temp(x + v.x, y + v.y);
		temp.setmode(getmode());
		return temp;
		//return Vector(x + v.x, y + v.y);
	}
	Vector Vector::operator*(const double & d) const
	{
		Vector temp(mag * d, ang, POL);
		temp.setmode(getmode());
		return temp;
		//return Vector(mag * d, ang, POL);
	}
	Vector Vector::operator-(const Vector & v) const
	{
		Vector temp(x - v.x, y - v.y);
		temp.setmode(getmode());
		return temp;
		//return Vector(x - v.x, y - v.y);
	}
	Vector Vector::operator-() const
	{
		Vector temp(-x, -y);
		temp.setmode(getmode());
		return temp;
		//return Vector(-x, -y);
	}
	void Vector::reset(double n1, double n2)
	{
		if (mode == RECT)
		{
			x = n1;
			y = n2;
			setmag();
			setang();
		}
		else if (mode == POL)
		{
			mag = n1;
			ang = n2;
			setx();
			sety();
		}
		else
		{
			std::cout << "出现了既不是直角坐标又不是极坐标的情况。\n";
		}
	}
	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 << "(mag, ang) = (" << v.mag << ", " << v.ang << ")\n";
		}
		else
			std::cout << "出现了既不是直角坐标又不是极坐标的情况。\n";
		return os;
	}
	Vector operator*(const double & d, const Vector & v)
	{
		return v * d;
	}
}
 
#include "vector.h"
#include <cstdlib>												//包含rand()和srand()函数的原型
#include <ctime>												//包含time()函数原型
#include <fstream>
int main()
{
	//模拟随机漫步问题
	using std::cout;
	using std::cin;
	using std::endl;
	using std::ofstream;
	ofstream outFile;
	outFile.open("VECTOR.txt");
	using VECTOR::Vector;
	Vector step(0.0, 0.0, Vector::POL);							//单步
	Vector result(0.0, 0.0, Vector::POL);						//到达指定地点后的方向和距离
	double stepLength;											//步长
	double stepAngle;											//方向(单位为弧度)
	unsigned long stepCounts = 0;								//步数
	double targetLength;										//指定距离
	cout << "输入随机漫步的距离:";
	cin >> targetLength;
	srand(time(0));												//设置种子值
	outFile << "目标距离:" << targetLength << endl;
	outFile << stepCounts << ": " << result << endl;
	while (result.getmag() < targetLength)
	{
		stepLength = rand() % 3;
		stepAngle = rand() % 360;
		step.reset(stepLength, stepAngle);
		result = result + step;
		stepCounts++;
		outFile << stepCounts << ": " << result << endl;
	}
	cout << "模拟结束:" << result;
	cout << "一共走了" << stepCounts << "步。\n";
	outFile << "模拟结束:" << result;
	outFile << "一共走了" << stepCounts << "步,平均每步"
		<< result.getmag() / stepCounts << endl;
	outFile.close();
	return 0;
}
 
谈谈程序中的随机数。库中有一个rand()函数,它返回一个从0到某个值的随机整数,使用求模操作符来实现。例如求从0到100(不包含100)的随机整数:rand() % 100。rand()函数用一个初始种子生成随机数,10次连续的使用通常将生成10个一样的随机数。然而,srand()函数允许覆盖默认的种子值,本程序使用time(0)的返回值作为种子,time(0)返回当前时间,通常为从某个日期开始的秒数,因此,srand(time(0))在每次程序运行时都将设置不同的种子值。rand()函数和srand()函数的原型在头文件cstdlib中,time()函数的原型在头文件ctime中。
四、类的自动转换和强制类型转换
  可以将构造函数用作自动类型转换函数,比如下面的构造函数将double类型的值转换为Stonewt类型:
   Stonewt(double lbs)
   {
     Stonewt myCat;
     myCat = 19.6;
   }
   程序将使用构造函数Stonewt(double)来创建一个临时的Stonewt对象,并将19.6作为初始值。随后采用逐成员赋值的方式将临时对象的内容复制到myCat中。这一过程被称为隐式转换,因为它是自动进行的。
   只有一个参数的构造函数可以用于自动转换,下面的构造函数有两个参数,不能用来转换类型:
   Stonewt(int stn, double lbs);
   如果给第二个参数提供默认值,它便可以用来转换int:
   Stonewt(int stn, double lbs = 0);
   关键字explicit可以关闭构造函数的自动转换特性:
   explicit Stonewt(double lbs);
   Stonewt myCat;
   myCat = 16.9;        //不允许,构造函数被声明为explicit
   myCat = Stonewt(16.9);   //允许,显示强制类型转换
   当构造函数只接受一个参数时,可以使用下面的格式来初始化类对象:
   Stonewt incognito = 275;
   它等价于另外两种格式:
   Stonewt incognito(275);
   Stonewt incognito = Stonewt(275);
转换函数
  数字可以转换为Stonewt类型,是否可以做相反的转换呢?也就是说,能否把Stonewt类型转换为double类型或者是int类型呢?就像下面的这样:
   Stonewt wolfe(285.7);
   double host = wolfe;    //?
   可以这样做,但不是使用构造函数,构造函数只用于从某种类型转换为类类型,要做相反的转换,需要使用特殊的C++运算符函数——转换函数。转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。
   Stonewt wolfe(285.7);
   double host = double(wolfe);    //第一种方式
   double host = (double)wolfe;    //第二种方式
   转换函数是这样创建的:
   operator typeName();      //typeName是要转换成的类型
   注意以下几点:
   ♦转换函数必须是类方法;
   ♦转换函数不能指定返回类型;
   ♦转换函数不能有参数。
   例如,要转换为double类型的转换函数是这样的:
   operator double();
   typeName指出了要转换为的类型,因此不需要返回类型;必须是类方法意味着它必须通过类对象来调用,从而告知函数要转换的值,因此不需要参数。
   如果需要将类类型转换为double和int类型,需要在类声明中加入以下这两个函数:
operator double() const;
{
	return pounds;						//pounds是double类型的数据成员
}
operator int() const;
{
	return int(pounds);
}
 
  在C++11中可以在转换函数前加上关键字explicit将其用于显示强制类型转换:
   explicit operator double() const;
   explicit operator int() const;
总结
以上就是本文的内容——运算符重载、友元函数、一个模拟随机漫步问题的矢量类、类的自动转换和强制类型转换。










