友元
C++控制对类私有部分发的访问,但是在有些时候这些控制过于严格,以至于不适用于特定的编程问题,在这种情况下C++提供类另外一种形式的访问权限友元
- 友元函数
- 友元类
- 友元成员函数
通过让函数成为友元可以让他拥有与类成员相同的访问权限,下面主要介绍友元函数。
需要注意的是友元函数仍是普通函数,不是成员函数所以没有this指针!!!。
为什么需要友元函数,我们不妨来思考下述,问题
存在一个时间类,需要重载*运算符,让其内部的时间可以按照给定的系数q,倍乘
如果重载了乘法运算符,这个主要差异在两者类型不同一个是double一个Time
Time operator*(double);
如果按照如下形式调用
a = b * 10;
会被转换为如下;
A = b.operator*(10); // 这样没有问题
但是如果形式如下;
a = 10 * b;
// 从乘法交换律来说这两者应该完成相同,但是在重载运算符中,左边的操作数是调用对象,但是显然10无法完成完成调用,这怎么办呢?
解决这个问题的一种方法是告诉所有人只能按照 B * 2.75 的方式编写,不能写成 2.75 * B,这是一种服务器友好,客户警惕的解决方法
然而第二方法就是非成员函数(大多数运算符多可以通过成员或者非成员函数重载)由于非成员函数不是由对象调用的,所以他使用的所有值都是现实函数,所以编译器能将上述两个表达式完美匹配。
但是出现了第二个问题,非成员无法访问类的私有数据,至少常规非成员函数不能访问。然而有一类特殊的非成员函数可以访问类的私有成员,被称为友元函数。
创建友元
创建友元函数的第一步就是将函数在类声明中声明,在函数原型前面加上friend
friend Time operator*(double m,const Time & t);
这意味两点
- 虽然函数是在类声明中声明的但是他不是成员函数也自然不具备this指针
- 虽然函数不是成员函数,但是他与成员函数访问权限相同,即可以可以通过该类对象访问私有成员
第二部编译函数定义,首先由于不是成员函数,不需要作用域修饰
Time operator*(double m,const Time & t){
// do something
}
总之类的友元函数是非成员函数,其访问权限与成员函数相同
如果要为类重载运算符,并非类的项作为第一个操作数,可以使用友元函数来自动反转和适配操作数
友元是否有悖于OOP?
结论是当然是不违背,可能乍一看,允许非成员函数访问类私有成员违反了OOP数据隐藏的原理,但是这个观点太片面了,相反应该讲友元函数看作类的拓展接口的组成接口的组成部分,而且只有类声明才可以决定那个友元函数,因此类声明仍然可以控制那些函数可以访问私有数据,总之类方法和友元只是表达类接口的两种不同机制
常用的友元: 重载 << 运算符
一个类很有用的特性就是重载 << 运算符使之与cout来一起显示内容
要想让类使用cout运算符必须要使用友元函数,否则则只能类似一下形式使用 <<
time << cout; // time.operator<<(cout) 形式很奇怪
cout << time; // cout.operator<<(time) 我们需要修改iostream 这是一个很愚蠢的决定
但是通过友元函数就可以很舒适使用
void operator<<(ostream & os,const Time & t){
os << t.hours << t.minutes << endl;
}
// 调用
cout << time ;// operator<<(cout,t) 完美!
前面的友元函数将会导致下面的语句出错
cout << time1 << time2;
其实原理很简单,根据从左往右读取输入的顺序 C++首先处理
(cout << time1) << time2
但是根据友元函数,返回void就变成如下
(void) << time2
像void输出显然是荒唐的,所以我们只需要让前一个输出项目返回一个ostream对象就可以解决这个问题
ostream & operator<<(ostream & os,const Time & t){
os << t.hours << t.minutes << endl;
return os;
}
那么流程就会成如下
cout << time1 << time2;
(cout << time1) << time2;
cin << time2;
cin; // 结束
由于类的继承属性,让ostream属性不仅可以用于标准输入输出也可以用于文件输入输出等等等等。。
我们可以得出一般化的重载<<模版
ostream & operator<<(ostream & os,const T & t){
// do something
return os;
}
记住友元函数只在声明时使用friend关键字,除非该函数在声明处定义,否则在定义处不能使用friend关键字
重载运算符作为成员函数还是非成员函数
对于很多运算符来说可以成员函数或者非成员函数来完成运算符重载,一般来说非成员函数时友元函数,这样他才能直接使用类私有数据。
一般来说如果两个操作数地位完全相同,但是类型不同的情况下,可以选择非成员函数实现,来处理操作数顺序的问题,如果处理的两个操作数的地位不等,不能随意交换,一般可以使用成员函数。
对于操作数数量而言,成员函数重载运算符少一个操作数,一个由this指针隐式传递,而非成员函数操作数数量完全与操作符所需操作数一致。
但是在定义运算符的时候必须选择其中一种格式,而不能选择两种,因为两种格式如果同时匹配,会导致二义性错误。
矢量类
包含大小和方向的量,一般由两种表达方式,(本类只描述二维矢量)
- 可以用大小和长度描述矢量
- 可以用x,y分量描述
有些时候第一种更方便,有些时候第二种更方便,我们可以通过使用类,提供抽象接口的方式来想用户提供任何一种形式的访问
// Vector.h
#ifndef VECTOR_H
#define VECTOR_H
#include <ostream>
namespace VECTOR{
typedef double Number_t;
class Vector
{
Number_t x{};
Number_t y{};
public:
enum class Vector_Mode {POL,RECT};
Vector(double v1,double v2,Vector_Mode mode = Vector_Mode::RECT);
void reset(double v1, double v2, Vector_Mode mode = Vector_Mode::RECT);
~Vector();
double get_x() const;
double get_y() const;
double get_ang() const;
double getmag() const;
Vector operator+(const Vector &) const;
Vector operator-(const Vector &) const;
Vector operator-() const;
Vector operator*(double ) const;
friend Vector operator*(double n,const Vector & a);
friend std::ostream & operator<<(std::ostream &,const Vector &);
};
}
#endif //VECTOR_H
#include "Vector.h"
#include <cmath>
#include <iostream>
VECTOR::Vector VECTOR::operator*(double n, const VECTOR::Vector & a)
{
return a * n;
}
std::ostream & VECTOR::operator<<(std::ostream & os, const VECTOR::Vector & a)
{
os << "x: " << a.x << " y: " << a.y << std::endl;
return os;
}
VECTOR::Vector::Vector(double v1, double v2, VECTOR::Vector::Vector_Mode mode)
{
reset(v1, v2, mode);
}
void VECTOR::Vector::reset(double v1, double v2, VECTOR::Vector::Vector_Mode mode)
{
if (mode == Vector_Mode::RECT) {
x = v1;
y = v2;
}
else if (mode == Vector_Mode::POL) {
constexpr double RAD_TO_DEG = 57.29577951;
v2 /= RAD_TO_DEG;
x = v1 * cos(v2);
y = v1 * sin(v2);
}
else {
std::cout << "Invalid vector mode,mode is setting to RECT,x,y is initalized to 0,0" << std::endl;
x = y = 0;
}
}
VECTOR::Vector::~Vector()
= default;
double VECTOR::Vector::get_x() const
{
return x;
}
double VECTOR::Vector::get_y() const
{
return y;
}
double VECTOR::Vector::get_ang() const
{
if (x == 0 && y == 0) {
return 0.0;
}
return atan2(y, x);
}
double VECTOR::Vector::getmag() const
{
return sqrt(pow(x, 2) + pow(y, 2));
}
VECTOR::Vector VECTOR::Vector::operator+(const VECTOR::Vector & a) const
{
return {x + a.x, y + a.y};
}
VECTOR::Vector VECTOR::Vector::operator-(const VECTOR::Vector & a) const
{
return (*this + (-a));
}
VECTOR::Vector VECTOR::Vector::operator-() const
{
return {-x, -y};
}
VECTOR::Vector VECTOR::Vector::operator*(double n) const
{
return Vector{x * n, y * n};
}
这个类的设计决策遵守了OOP的传统,即将类接口放在其本质上,也就是矢量的本质上,而隐藏细节,这样当用户使用该类的时候,无需关心类内部的实现结构,只需要考虑其如何使用,从而忽略其底部如何实现。