“学习是人类进步的阶梯,也是个人成功的基石。” - 罗伯特·肯尼迪
文章目录
- 友元函数
- 利用友元函数重载<<运算符
- 重载部分示例:矢量类
友元函数
先看看在上一章中我们作为例子的代码:
class Student{string name;int grade;int operator+(Student s){return this->grade+s.grade;}int operator+(int a){return this->grade+a;}
}void test(Student a,Student b)
{a.operator+(b);int c=a+b;int d=a+c;
}
注意最后一行:
int d=a+c;
这一行的意义在上文已经说明,但是这是“+”运算符,也就是说,如果我编写以下的代码,在逻辑上依旧是正确的:
int d=c+a;
虽然在逻辑上正确,但在语法上并不能通过编译,因为编译器会认为是int类型的c调用了重载的函数。为了解决这个问题,我们可以用多种方法:
- 编写一层非成员函数接口。观察以下代码:
int operator+(int a,Student b)//非成员函数也可以重载运算符
{return b+a;
}
当成员函数进行运算符重载时,编译器默认调用这个函数的对象是这个函数的第一个操作数;非成员函数进行运算符重载时,编译器严格按照传参顺序决定操作数顺序。加入这个函数后,上面的代码就可以被编译器解释为传入int类型的c和Student类型的a作为参数,调用非成员函数,这就是接口的思想。
2. 事实上,C++的友元函数语法可以解决这个问题。友元函数是在类声明内定义的非成员函数,但是可以访问类的保护和私有成员,可以理解为具有成员函数视野的非成员函数。当想把一个函数声明为友元函数时,在函数声明开头加friend关键字。观察以下代码:
class Student{string name;int grade;int operator+(Student s){return this->grade+s.grade;}int operator+(int a){return this->grade+a;}//友元函数在类内声明friend int operator+(int a,Student b)//因为本质上是非成员函数,因此编译器按照传参顺序决定操作数顺序{return a+b.grade;//也不能用this指针,因为对象不能用.运算符调用友元函数}friend int operator+(Student a,Student b);
}int operator+(Student a,Student b)//因为不是成员函数,所以在类外定义时不使用::运算符,而且也不用额外加friend关键字
{return a.grade+b.grade;
}
利用友元函数重载<<运算符
观察以下代码:
cout<<i;
这行代码使用了<<运算符,第一个操作数是ostream(输出流)类的对象cout,第二个操作数是一个不定类型的变量i。从上一篇我们知道大部分运算符都能进行重载,因此我们可以重载能用Student类对象作为操作数的函数。
首先,我们排除在类内重载<<运算符的可能性,因为这样就需要以Student类对象作为第一操作数,需要用以下代码来使用:
i<<cout;//i是一个Studnet类对象
代码的可读性很低,也很不习惯。因此,我们用友元函数来实现重载。
接下来,我们确定这个函数的参数和返回值。参数很好确定,第一个参数是ostream类对象,第二个参数是Student类对象。至于返回值,我们先看对于内置类型,<<运算符是怎么运作的:
cout<<a<<b<<c;//a,b,c都是int类型
对于上面这个语句的行为,我们可以理解为:
((cout<<a)<<b)<<c
- 第一个运算符调用函数,以ostream类对象为第一个参数,int类对象为第二个参数,输出a
- 第二个运算符应该也调用相同的函数,这意味着参数也相同,第一个参数也应该为ostream对象,所以第一个运算符调用的函数应该返回一个ostream类的对象
拓展到Student类型的重载,道理也是一样的,应该返回一个ostream类对象。以下是该友元函数的一种实现方式:
ostream operator*(ostream& os,Student s)
{return os<<s.name<<' '<<s.grade;//输出学生的名字和年级
}
重载部分示例:矢量类
作为一个例子,我们构建一个表示矢量的类。矢量是一个有长度的方向的量。我们可以用直角坐标和极坐标两种方式来表示。下面是这个类的声明:
namespace VECTOR {class Vector{public:enum Mode { RECT, POL };//RECT为直角坐标系模式表示矢量,POL为极坐标系模式表示矢量private:double x;//x轴的值double y;//y轴的值double mag;//矢量的长度double ang;//矢量偏转的角度Mode mode;//选择哪种模式(RECT或POL)//设置值的私有方法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();//将模式设为POLvoid rect_mode();//将模式设为RECT//运算符重载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, Vector& a);friend std::ostream& operator<<(std::ostream& os, const Vector& v);};
}//end namespace VECTOR
- 使用VECTOR命名空间
- 使用enum枚举,带有RECL(直角坐标)和POL(极坐标)两个枚举量,表示这个对象用哪种方式描述矢量。
- private下有四种私有方法,用于直接控制类的私有变量,只有公共接口能使用这四个方法
- 注意第二种构造函数和reset函数的参数:
Vector(double n1, double n2, Mode form = RECT);
void reset(double n1, double n2, Mode form = RECT);
这两个函数的最后一个参数,即表示模式,有默认值,这代表在使用这个函数的时候不一定需要输入三个参数,编译器会自动采取默认参数值,但如果有恰当的参数值传入,则编译器使用新的参数值:
Vector v1(30,40)//此时编译器采用默认参数RECT
Vector v1(30,40,VECTOR::POL)//编译器采用传入的参数POL,注意POL在命名空间VECTOR中,要用::运算符
- 内联函数,忘记的可以看前几章
接下来是这个类的实现:
//定义表示一弧度的度数的一个变量const double Rad_to_deg = 45.0 / atan(1.0);//私有方法//从输入的x和y计算长度void Vector::set_mag(){mag = sqrt(x * x + y * y);}void Vector::set_ang(){if (x == 0.0 && y == 0.0)ang = 0.0;elseang = atan2(x, y);}//极坐标下设置x值void Vector::set_x(){x = mag * cos(mag);}//极坐标下设置y值void Vector::set_y(){y = 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;set_x();set_y();}else{cout << "Incorrect 3rd argument to Vector() -- ";cout << "vector set to 0.\n";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;set_x();set_y();}else{cout << "Incorrect 3rd argument to Vector() -- ";cout << "vector set to 0.\n";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);}std::ostream& operator<<(std::ostream& os, const Vector& v){if (v.mode == Vector::RECT){os << "(x,y) = (" << v.x << "," << v.y << ")";}else if (v.mode == Vector::POL){os << "(m,a)=(" << v.mag << "," << v.ang * Rad_to_deg << ")";}elseos << "Vector object mode is invaild";return os;}//display rectangular coordinates if mode is RECT//else display polar coordinates if mode is POLVector operator*(double n, const Vector &a)//友元函数{return a * n;}
- C++中的函数在角度方面使用弧度制,但在我们构造的类中使用角度制,因此需要定义一个从弧度到角度的变量。
- 其中一些陌生的函数进行一些数学运算,头文件为< cmath >,具体如下:
- atan函数:接收一个正切值(直线的斜率)输出直线与x轴的夹角(-90~90度)
- sqrt函数:输入一个数,输出这个数的平方根
- atan2函数:接收一个点的横纵坐标,输出横纵坐标与原点的连线延伸出的直线与x轴正方向的夹角(-180~180度)
- cos、sin函数:输入一个角度,输出这个角度的余弦/正弦值
- 带有参数的构造函数和reset函数会根据模式的不同,进行不同行为的初始化。
- 注意运算符重载和友元函数的逻辑
下面是一个使用矢量类的实例,建议自己复制下来或者敲一遍运行一下(不要忘记给声明和定义加上头文件):
//randwalk.cpp -- using the Vector class
//compile with the vect.cpp file
#include<iostream>
#include<cstdlib>//rand(),srand()函数的头文件
#include<ctime>//time()函数的头文件
#include "vector.h"
int main(void)
{using namespace std;using VECTOR::Vector;srand(time(0));//随机种子生成器double direction;Vector step;Vector result(0.0, 0.0);unsigned long steps = 0;double target;double dstep;cout << "Enter target distance (q to quit):";while (cin >> target){cout << "Enter step length:";if (!(cin >> dstep)){break;}while (result.magval() < target){direction = rand() % 360;step.reset(dstep, direction, Vector::POL);result = result + step;steps++;}cout << "After " << steps << " steps,the subject has the following location.\n";cout << result << endl;result.polar_mode();cout << "or\n" << result << endl;cout << "Average outward distance per step="<< result.magval() / steps << endl;steps = 0;result.reset(0.0, 0.0);cout << "Enter target diatance(q to quit):";}cout << "Bye!\n";cin.clear();while (cin.get() != '\n')continue;return 0;
}
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!