“学习是人类进步的阶梯,也是个人成功的基石。” - 罗伯特·肯尼迪
文章目录
- 友元函数
- 利用友元函数重载<<运算符
- 重载部分示例:矢量类
友元函数
先看看在上一章中我们作为例子的代码:
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();//将模式设为POL
void 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;
else
ang = 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 << ")";
}
else
os << "Vector object mode is invaild";
return os;
}
//display rectangular coordinates if mode is RECT
//else display polar coordinates if mode is POL
Vector 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;
}
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!