1.重载运算符
//重载运算符的函数格式
operatorop(arguement-list)
operator+() //重载+运算符
operator-() //重载-运算符
operator[]() //重载[]运算符
operator@() //这种情况是不允许的,只能重载C++中拥有的运算符,而不能直接创造运算符
一个重载运算符的例子
- 普通实现方式
//文件一:myTime0.h
#ifndef MYTIME0_H_
#define MYTIME0_H_
class Time
{
private:
int hours;
int minute;
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
//文件二: mytime0.cpp
#include<iostream>
#include"mytime0.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::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;
}
void Time::Show() const
{
std::cout << hours << ":" << minutes << std::endl;
}
//测试文件 useTime0.cpp
#include<iostream>.
#include"mytime0.h"
int main()
{
using std::cout;
using std::endl;
Time planning;
Time coding(2,40);
Time fixing(5,55);
Time total;
cout << "planning time = ";
planning.show();
cout << endl;
cout << "coding time = ";
coding.show();
cout << endl;
cout<< "fixing time = ";
fixing.show();
cout << endl;
total = coding.Sum(fixing);
cout << "coding.Sum(fixing) = ";
total.show();
cout << endl;
return 0;
}
- 使用重载运算符实现
//文件一:myTime1.h
#ifndef MYTIME1_H_
#define MYTIME1_H_
class Time
{
private:
int hours;
int minute;
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;
void show() const;
};
#endif
//文件二: mytime1.cpp
#include<iostream>
#include"mytime0.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;
}
void Time::Show() const
{
std::cout << hours << ":" << minutes << std::endl;
}
//测试文件 useTime1.cpp
#include<iostream>.
#include"mytime0.h"
int main()
{
using std::cout;
using std::endl;
Time planning;
Time coding(2,40);
Time fixing(5,55);
Time total;
cout << "planning time = ";
planning.show();
cout << endl;
cout << "coding time = ";
coding.show();
cout << endl;
cout<< "fixing time = ";
fixing.show();
cout << endl;
total = coding + fixing;
cout << "coding.Sum(fixing) = ";
total.show();
cout << endl;
Time morefixing(3,28);
cout << "more fixing time = ";
morefixing.show();
cout << endl;
total = morefixing.operator+(total);
cout << "morefixing.operator+(total) = ";
total.show();
cout << endl;
return 0;
}
//重载运算符可以进行多个加法运算
int a,b,c;
Time A,B,C;
c = a + b;
C = A + B;
Time t1,t2,t3,t4;
t4 = t1 + t2 + t3; //是可以的
// t4 = t1.operator+(t2+t3);
// t4 = t1.operator+(t2.operator+(t3));
//首先t2.operator+(t3)返回一个Time对象为t2+t3的和
//然后使用这个对象与t1进行相加,返回的值就是t1+t2+t3的值
//正如同我们希望的那样,所以这种写法是可以的
2.重载运算符的限制
- 重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此,不能把减法运算符 ( - ) 重载为计算两个double类型的和,而非计算他们的差。
- 使用运算符时,不能违反运算符原来的句法规则。例如,不能将求模运算符重载成使用一个操作数。
- 不能创建新的运算符
- 不能够对sizeof运算符【sizeof】,成员运算符【.】,成员指针运算符【.*】,作用域解析运算符【::】,条件运算符【?:】,一个RTTI运算符【typeid】,强制类型转换运算符【const_cast】,【dynamic_cast】,【reinterpret_cast】,【static_cast】
- 赋值运算符【=】,函数调用运算符【()】,下标运算符【[]】,通过指针访问类成员的运算符【->】只能通过成员函数重载
- 其他没有提及的运算符即可以通过成员函数也可以通过非成员函数重载
3.友元
C++控制对类对象私有部分的访问,只能通过公有类方法进行访问,但是有时候这种限制过于严格,导致一些编程问题没办法友好的解决,所以C++提供了另一种形式的访问权限 -----友元。
友元有三种:
- 友元函数
- 友元类
- 友元成员函数
通过让函数称为类的友元,可以赋予该函数与类的成员函数相同的访问权限。这种函数就称为友元函数。
何时需要使用友元:在为类重载二元运算符的时候,时常需要使用友元,将Time对象乘以实数就属于这种情况,区别在于,对于加减法来说,使用的对象都是Time对象,而对于乘法来说,两个操作数一个是Time对象,另一个是double数,对于重载运算符的函数来说,左侧的操作数是调用对象。也就是说:
A = B * 2.75;
A = 2.75 * B;
二者并不相同,2.75并不能作为调用对象来调用重载的操作数方法,因为2.75不是对象。所以编译器不能使用成员函数调用来替换该表达式。
解决方法:
- 一:告知每个人,只能按照 B*2.75 的格式进行书写,不能写成 2.75*B。
- 二:使用非成员函数来重载运算符。
格式如下,不过需要注意的一点是,该函数是非成员函数,也就是说他是没有办法使用类中的私有数据的,而这时候就需要友元函数解决问题了。因为友元函数可以访问类的私有成员。
//原型成如下格式 --- 普通函数,无法使用类的私有成员
Time operator*(double m , const Time & t);
4.创建友元函数
//原型成如下格式 --- 友元函数,可以使用类的私有成员
//关键字 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 * mult * 60 + t.minutes * mult;
result.hours = totalminutes / 60;
result.minutes = totalminutes % 60;
return result;
}
//一种常用的友元函数使用方法
Time operator*(double m, const Time & t)
{
return t * m; //交换操作数顺序,使用类方法
}
5.常用的友元:重载<<运算符
对<<运算符进行重载,使之可以与cout一起来显示对象的内容。
- 方法一
之所以使用友元函数,是为了使第一个参数的内容是ostream对象cout而非我们的类对象。
void operator<<(ostream & os, const Time & t)
{
os << t.hours << ":" << t.minutes ;
}
Time try(5,20);
cout << try;
上面我们知道,operator<<()函数是Time类的友元函数,但是他并不是ostream类的友元函数,因为上述函数使用了Time类的私有成员,而对ostream类只是做整体调用,并没有使用ostream类中的私有成员,所以该函数必须是Time类的友元,但是不需要是ostream类的友元。
- 方法二
上述代码的返回值类型是空,所以这意味着他并不能使用类似cout<< x << y;的这种连串式的表示方法,而之所以普通的<<运算符可以进行这种方式,完全是因为正常从cout<<x;语句返回值的类型也是ostream类的对象,而这种类型的对象就可以调用<<方法来继续输出,所以如果我们也想使用这种方式的话,我们就需要让我们的友元函数也返回ostream类型的变量或者引用。
如下所示,返回值是引用类型,而返回的内容就是传入给函数的ostream对象。
ostream & operator<<(ostream & os, const Time & t)
{
os << t.hours << ":" << t.minutes ;
return os;
}
注意:这个operator<<()版本还可以用于将输出写到文件中,因为类继承书写让ostream引用能够指向ostream对象和ofstream对象。
6.案例编写 ---- 一个矢量类
//文件1 矢量类的头文件 vect.h
#ifndef VECTOR_H_
#define VECTOR_H_
namespace VECTOR
{
class Vector
{
public:
enum Mode {RECT, POL};
private:
double x;
double y;
double mag;
double ang;
Mode mode;
void set_mag();
void set_ang();
void set_x();
void set_y();
public:
Vector();
Vector(double n1, double n2, Mode form = RECT);
void reset(double n1, double n2, Mode form = RECT);
~Vector();
//内联函数
double xval() const {return x;}
double yval() const {return y;}
double magval() const {return mag;}
double angval() const {return ang;}
//一直到这上面这四个都是内联函数
void polar_mode();
void rect_mode();
Vector operator+(const Vector & b) const;
Vector operator-(const Vector & b) const;
Vector operator-() const;
Vector operator*(double n) const;
friend Vector operator*(double n, const Vector &a);
friend std::ostream & operator<<(std::ostream & os, const Vector &v);
};
}
#endif
//文件二 函数定义源文件 vector.cpp
#include<cmath>
#include"vect.h"
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;
namespace VECTOR
{
const double Rad_to_deg = 45/atan(1.0);
void Vector::set_mag()
{
mag = sqrt(x * x + y * y);
}
void Vector::set_ang()
{
if (x == 0.0 && y == 0.0)
ang = 0.0;
else
ang = atan2(y,x);
}
void Vector::set_x()
{
y = mag * cos(ang);
}
void Vector::set_y()
{
x = mag * sin(ang);
}
Vector::Vector()
{
x = y = mag = ang = 0.0;
mode = RECT;
}
Vector::Vector(double n1, double n2, Mode form)
{
mode = form;
if(form == RECT)
{
x = n1;
y = n2;
set_mag();
set_ang();
}
else if(form == POL)
{
mag = n1;
ang = n2 / Rad_to_deg;
set_x();
set_y();
}
else
{
cout << "Incorrect 3rd arguement to Vector() -- ";
cout << "Vector set to 0";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
void Vector::reset(double n1, double n2, Mode form)
{
mode = form;
if(form == RECT)
{
x = n1;
y = n2;
set_mag();
set_ang();
}
else if(form == POL)
{
mag = n1;
ang = n2 / Rad_to_deg;
set_x();
set_y();
}
else
{
cout << "Incorrect 3rd arguement to Vector() -- ";
cout << "Vector set to 0";
x = y = mag = ang = 0.0;
mode = RECT;
}
}
Vector::~Vector()
{
}
void Vector::polar_mode()
{
mode = POL;
}
void Vector::rect_mode()
{
mode = RECT;
}
Vector Vector::operator+(const Vector & b) const
{
return Vector(x + b.x , y+ b.y);
}
Vector Vector::operator-(const Vector & b) const
{
return Vector(x - b.x , y - b.y);
}
Vector Vector::operator-() const
{
return Vector(-x , -y);
}
Vector Vector::operator*(double n) const
{
return Vector(n * x , n * y);
}
Vector operator*(double n,const Vector & a)
{
return a * n;
}
std::ostream & operator<<(std::ostream & os, const Vector &v)
{
auto form = v.mode;
if(form == Vector::RECT)
{
os << v.x << " " << v.y;
}
else if(form == Vector::POL)
{
os << v.mag << " " << v.ang;
}
else
{
cout << "Incorrect 3rd arguement to Vector() -- ";
}
return os;
}
}
7.阿就那个继续编写案例 ----- 使用Vector类来实现随机漫步问题
//随机漫步问题程序 walkchicken.cpp
/*
问题简介:一只溜达鸡,放在原点,什么时候能够溜达到里原点远五十米的位置。
注意:每一步不能与上一步相同
*/
#include<iostream>
#include<cstdlib>
#include<ctime>
#include"vect.h"
int main()
{
using namespace std;
using VECTOR::Vector;
srand(time(0)); //设置随机数的种子,使用time(0)的结果作为随机数的种子值
//time(0)表示从某个日期开始到现在的秒数
double direction; //方向变量
Vector step; //Vector对象 一步所走的矢量
Vector result(0.0,0.0); //存放结果的Vector对象
unsigned long steps = 0;
double target; //目标距离变量
double dstep; //步伐大小变量
cout << "请输入目标距离:【按Q退出】";
while(cin >> target)
{
cout << "Enter step length: ";
if(!(cin >> dstep))
break;
while (result.magval() < target)
{
//表示生成一个在0~359之间的随机数
direction = rand() % 360;
//注意 因为前面使用了using导入了VECTOR::Vector
//所以这里可以直接使用Vector::POL
step.reset(dstep,direction,Vector::POL);
result = result + step;
steps++;
}
cout << "走了" << steps << "步";
cout << result << endl;
//转化为直角坐标模式进行展示
result.polar_mode();
cout << result << endl;
//展示距离原点的长度
cout << result.magval() << endl;
steps = 0;
result.reset(0.0,0.0);
cout << "请输入目标距离:【按Q退出】";
}
cout << "再见" << endl;
cin.clear();
while(cin.get() != '\n')
continue;
return 0;
}
8.类的自动转换和强制类型转换
可以将类定义成与基本类型或另一个类相关,使得从一种类型转换为另一种类型是有意义的。在这种情况下,程序员可以指示C++如何自动进行转换,或通过强制类型转换来完成。下面举个例子,来介绍类型转换。例子是关于两种重量表示方法的转换
// stonewt.h
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt
{
private:
//对于定义类中特定的常量,对于整数来说可以使用枚举的方式
//当然也可以使用 static const int Lbs_per_stn = 14;这种方式
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn, double lbs);
Stonewt();
~Stonewt();
void show_lbs() const;
void show_stn() const;
};
#endif
//stonewt.cpp
#include<iostream>
using std::cout;
#include"stonewt.h"
Stonewt::Stonewt(double lbs)
{
stone = int(lbs) / Lbs_per_stn;
pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
pounds = lbs;
}
Stonewt::Stonewt(int stn, double lbs)
{
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn + lbs;
}
Stonewt::Stonewt()
{
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt()
{
}
void Stonewt::show_lbs() const
{
cout << stone << " " << pds_left << endl;
}
void Stonewt::show_stn() const
{
cout << pounds << endl;
}
因为Stonewt对象表示一个重量,所以可以提供一些将整数或浮点值转换为Stonewt对象的方法。在C++中,接受一个参数的构造函数为将类型与改参数相同的值转换为类提供了蓝图。因此,下面的构造函数用于将double类型的值转换为Stonewt类型:
//下面的构造函数可以将double类型转化为Stonewt类型
Stonewt(double lbs);
Stonewt myCat;
myCat = 19.6;
//将19.6赋值给myCat对象表示的就是将19.6隐式的转化为Stonewt类型然后赋值
//下面的构造函数则不能进行转化
Stonewt::Stonewt(int stn, double lbs);
//原因是他拥有两个参数,所以不能进行这种转化
//不过如果将其中一个参数设置为默认参数即可
Stonewt::Stonewt(int stn, double lbs = 0);
//上述构造函数则可以进行int类型的转化
编译器在什么时候使用Stonewt(double)函数呢?如果在声明中使用了关键字explicit,则Stonewt(double)将只用于显式强制类型转换,否则还可以用于以下隐式转换。
- 将Stonewt对象初始化为double值时
- 将double值赋给Stonewt对象时
- 将double值传递给接受Stonewt参数的函数时
- 返回值被声明为Stonewt的函数试图返回double值时
- 上述任意一种情况下,使用可转化为double类型的内置类型时
这一条说明,函数原型华提供的参数匹配过程,允许使用Stonewt(double)构造函数来转化其他数值类型
Stonewt Jumb(7000); //使用Stonewt(double); int 转化为double
Stonewt Jumbs = 7000; //使用Stonewt(double); int 转化为double
注意:当且仅当这种转化不存在二义性的时候才会进行这种转化,也就是说如果有上面所述的那种有默认参数的Stonewt::Stonewt(int stn, double lbs = 0);函数原型,是不存在这种转化的。
9.转换函数
将数字转化为Stonewt对象可以通过类的类型转化完成,而使用转化函数,可以将类对象转化为其他数据类型。转换函数是用户定义的强制类型转化,可以像强制类型转换一样使用他们,如果定义了从Stonewt类转化成double类的转换函数,就可以进行下面的转换:
Stonewt wolfe(285.7);
double host = double(wolfe);
//或者写成
double thinker = (double) wolfe;
//也可以让编译器来决定怎么做
double star = wolfe;
//编译器发现右边是Stonewt类型,左边是double类型
//因此查看程序员是否定义了与之相匹配的转换函数
//如果有 按照转换函数进行
//如果没有则返回错误信息
10.创建转换函数
- 转换函数的创建模板为 operator typeName();
- 转换函数必须是类方法
- 转换函数不能指定返回类型
- 转换函数不能有参数
//Stonewt中的转化为double类型的函数
//是类方法,通过类对象进行调用,所以不需要参数
operator double();
//Stonewt中的转化为int类型的函数
operator int();
// stonewt1.h
#ifndef STONEWT1_H_
#define STONEWT1_H_
class Stonewt
{
private:
//对于定义类中特定的常量,对于整数来说可以使用枚举的方式
//当然也可以使用 static const int Lbs_per_stn = 14;这种方式
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn, double lbs);
Stonewt();
~Stonewt();
void show_lbs() const;
void show_stn() const;
operator double() const;
operator int() const;
};
#endif
//stonewt1.cpp
#include<iostream>
using std::cout;
#include"stonewt1.h"
Stonewt::Stonewt(double lbs)
{
stone = int(lbs) / Lbs_per_stn;
pds_left = int(lbs) % Lbs_per_stn + lbs - int(lbs);
pounds = lbs;
}
Stonewt::Stonewt(int stn, double lbs)
{
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn + lbs;
}
Stonewt::Stonewt()
{
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt()
{
}
void Stonewt::show_lbs() const
{
cout << stone << " " << pds_left << endl;
}
void Stonewt::show_stn() const
{
cout << pounds << endl;
}
Stonewt::operator int() const
{
return int (pounds +0.5);
}
Stonewt::operator double() const
{
return pounds;
}
因为隐式转换可能会带来一些意想不到的问题,解决这些问题有两种方法:
- 方法一:使用explicit关键字来修饰函数防止隐式转换
- 方法二:用一个功能相同的非转换函数来代替该转换函数
Stonewt pop(15);
//转换函数
Stonewt::operator int() { return int (pounds + 0.5); }
//功能相同的非转换函数
int Stonewt::Stone_to_int() { return int (pounds + 0.5); }
//对于非转换函数来说
//不合法 不允许隐式的转化
int plb = pop;
//合法 使用非转化函数转换
int plb = pop.Stone_to_int();
C++为类提供了下面的类型转换
- 只有一个参数的类构造函数用于将类型与该参数相同的值转化为类类型。例如将int值赋值给Stonewt对象时,接受int参数的Stonewt类构造函数将自动被调用。然而在构造函数声明中使用explicit可以防止隐式转换,而只允许显式转化。
- 被称为转换函数的特殊类成员运算符函数,用于将类对象转化为其他类型,转化函数是类成员,没有返回类型,也没有参数,名称为 operator typeName(),其中typeName是对象将被转化成的类型。将类对象赋值给typeName变量或将其强制转化为typeName类型时,该转换函数会自动调用,C++11中explicit关键字也可以用于转换函数中,用于防止隐式转换。
11.转换函数和友元函数
//类成员函数
Stonewt Stonewt::operator+(const Stonewt & st) const
{
double pds = pounds + st.pounds;
Stonewt sum(pds);
return sum;
}
//友元函数
Stonewt operator+(const Stonewt & t1, const Stonewt & t2)
{
double pds = st1.pounds + st2.pounds;
Stonewt sum(pds);
return sum;
}
//注意:可以提供友元函数定义或者方法定义,但是只能提供其中一种
//上述的任何一种都允许这么做:
Stonewt jennySt(9,12);
Stonewt bennySt(12,8);
Stonewt total;
total = jennySt + bennySt;
//在上述的这种语法中,jennySt,bennySt都是Stonewt对象
//类成员函数:jennySt.operator+(bennySt);
//友元函数:operator+(jennySt,bennySt);
//上述两种都能够使用,不存在二义性,也符合语法规则,结果也是正确的
Stonewt jennySt(9,12);
double kennyD = 176.0;
Stonewt total;
total = jennySt + kennyD;
//在上述的这种语法中,jennySt是Stonewt对象 kennyD是double类型数值
//类成员函数:jennySt.operator+(kennyD);
//友元函数:operator+(jennySt,kennyD);
//当没有定义operator double()转换函数的情况下:
//类成员函数的参数是double类型,可以通过构造函数自动转化为StoneWt类型,从而使用函数
//友元函数的参数是一个StoneWt类型,还有一个是double类型,会将double类型转化为StoneWt类型
//如果定义了operator double()转化函数的情况下:
//在这种情况下,存在二义性,因为可以从double转化为StoneWt,也可以从StoneWt转化为double
//系统不知道该往哪个方向进行转化,出现二义性
Stonewt jennySt(9,12);
double pennyD = 176.0;
Stonewt total;
total = pennyD + jennySt;
//在上述的这种语法中,jennySt是Stonewt对象 pennyD是double类型数值
//对于上述语法来说,只有友元函数可以使用
//类成员函数:pennyD.operator+(jennySt)
//友元函数:operator+(pennyD,jennySt);
//因为pennyD并不是StoneWt类的对象,所以不能通过他调用类的成员函数
//这种语法是错误的,所以只能使用友元函数
注意:实现加法的时候,将函数定义为友元函数,可以让程序更加容易的适应自动类型转换。
实现加法的两种方法:
- 使用友元函数
- 将加法运算符重载为一个显式使用double类型参数的函数
注意:第一种方法是自动类型转化,能将程序更加的简短,因为定义的函数较少,这意味着程序员需要完成的工作较少,出错的机会也较小。不过会增加内存开销和计算时间。第二种方法则是会增加程序员完成的工作量,不过在执行过程中会比较省时。