前言
??本人在阅读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。 ??友元函数和<<运算符重载示例:
mytime.h
#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
mytime.cpp
#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;
}
usetime.cpp
#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英尺。
vector.h
#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
vector.cpp
#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);
}
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;
}
Vector Vector::operator*(const double & d) const
{
Vector temp(mag * d, ang, POL);
temp.setmode(getmode());
return temp;
}
Vector Vector::operator-(const Vector & v) const
{
Vector temp(x - v.x, y - v.y);
temp.setmode(getmode());
return temp;
}
Vector Vector::operator-() const
{
Vector temp(-x, -y);
temp.setmode(getmode());
return temp;
}
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;
}
}
main.cpp
#include "vector.h"
#include <cstdlib>
#include <ctime>
#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;
}
operator int() const;
{
return int(pounds);
}
??在C++11中可以在转换函数前加上关键字explicit将其用于显示强制类型转换: ??explicit operator double() const; ??explicit operator int() const;
总结
??以上就是本文的内容——运算符重载、友元函数、一个模拟随机漫步问题的矢量类、类的自动转换和强制类型转换。
|