本人在阅读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; //运算符表示法
在运算符表示法中,运算符左侧的对象是调用对象,右侧的对象是作为参数被传递的对象。
+ | - | * | / | % | ^ |
---|---|---|---|---|---|
& | | | ~= | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | << | >> | >>= |
<<= | == | != | <= | >= | && |
|| | ++ | -- | , | ->* | -> |
() | [] | 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)可以使用。然而非成员函数不能直接访问类的私有数据,但是特殊的非成员函数——友元函数可以。
首先需要将友元函数的原型放在类声明中,并在原型前加上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; //调用成员函数
}
修改定义后该函数不再访问类的私有数据成员,因此可以将其编写为非友元函数。
<<运算符是需要两个操作数的运算符,如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
#include
……
ofstream fout;
fout.open(“saving.txt”);
Time trip(3, 25);
fout << trip;
可行的原因是ofstream继承自ostream。
友元函数和<<运算符重载示例:
#pragma once
#ifndef MYTIME_H_
#define MYTIME_H_
#include
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
#include
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 //包含rand()和srand()函数的原型
#include //包含time()函数原型
#include
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;
以上就是本文的内容——运算符重载、友元函数、一个模拟随机漫步问题的矢量类、类的自动转换和强制类型转换。