C++友元,为什么需要友元,友元函数,友元类,小结【C++】(zf)

友元

为什么需要友元

get/set Method

get 方法和 set 方法,是常见的获取和设置数据成员的方式。比如游戏角色遭到对方攻击后血量减少,就需要用到 set 方法,而要实时的显示就需要 set 方法。

我们先给出例子:

#include <iostream>

using namespace std;

class Sprite    //设置角色类
{
public:
    Sprite(int lb = 100) :_lifeBlood(lb) {}  //构造角色血量
    int getLiseBlood()     //返回当前血量
    {
        return _lifeBlood;
    }

    void setLiseBlood(int lb)    //设置遭到攻击之后的血量
	{
        _lifeBlood = lb;      
    }

private:
    int _lifeBlood;      //定义角色血量

};

void fight(Sprite & sp)  //参加战斗
{
    sp.setLiseBlood(sp.getLiseBlood() - 20);    //修改变化之后的剩余血量
    cout << sp.getLiseBlood() << endl;    //输出血量

}
int main()
{
    Sprite sp;
    fight(sp);
    return 0;
}

运行结果为:

在这里插入图片描述

上面实现是没有问题的,但是效率很低,假设一下很多游戏里里面,角色受到了万箭穿心,就会频繁的使用set方法和get方法。调用函数就会频繁的压栈出栈。那么我们就需要做一些提高效率的工作。

如果我们对于fight函数进行修改:

void fight(Sprite & sp)  //参加战斗
{
    //sp.setLiseBlood(sp.getLiseBlood() - 20);    //修改变化之后的剩余血量
    //cout << sp.getLiseBlood() << endl;    //输出血量

    //提高效率的方法
    sp._lifeBlood = sp._lifeBlood - 20;
    cout << sp.getLiseBlood() << endl;

}

但是上面的使用方法必须函数是类内函数才能访问私有成员,但是fight函数是全局函数,所以还是无法访问类内的私有数据成员。我们也不会把fight函数放到类内,因为fight函数可能需要操作多个角色,fight函数的形参是可以变化的根据当时参加战斗的人数来决定的,所以一般不写到类内。那么如果提高效率呢,就需要用到友元。

fight 全局接口,通过战斗来参加或是减少血量,对于这种频繁的操作,可以每次不通过对象,而直接操作。
方法很简单,将函数 fight 设置为类 Sprite 的友元,通过友元这层关系,就可以直接访
问类的私有数据成员,从面大大提高的访问的效率

我们在之前的操作中想要访问类内的私有成员,就必须使用成员函数,现在全局函数也需要去访问,我们就使用友元。

实现代码:

#include <iostream>

using namespace std;

class Sprite    //设置角色类
{
    friend void fight(Sprite& sp);   //将函数 fight 设置为类 Sprite 的友元
public:
    Sprite(int lb = 100) :_lifeBlood(lb) {}  //构造角色血量
    int getLiseBlood()     //返回当前血量
    {
        return _lifeBlood;
    }

    void setLiseBlood(int lb)    //设置遭到攻击之后的血量
	{
        _lifeBlood = lb;      
    }

private:
    int _lifeBlood;      //定义角色血量

};

void fight(Sprite & sp)  //参加战斗
{
    //sp.setLiseBlood(sp.getLiseBlood() - 20);    //修改变化之后的剩余血量
    //cout << sp.getLiseBlood() << endl;    //输出血量

    //提高效率的方法
    sp._lifeBlood = sp._lifeBlood - 30;
    cout << sp.getLiseBlood() << endl;

}
int main()
{
    Sprite sp;
    fight(sp);
    return 0;
}

运行结果为:

在这里插入图片描述

get方法和set方法是标准封装的结果,但是上面使用友元会破环这种封装。

为什么使用友元

采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。
但是,有时需要定义一些函数,这些函数不是类的一部分,但又需要 频繁地访问类的数据成员,这时可以将这些函数定义为 该类的友元函数。除了友元函数外,还有友元类,两者统称为友元。
友元的作用是 提高了程序的运行效率(即减少了类型和安全性检查及调用的时间开销),
但它破坏了类的封装性和隐藏性,但是带来了效率的提高,这种破坏也不是全面性的破坏,是通过类授权许可的,并不是完全性的破坏,又能提高效率,使得非成员函数可以访问类的私有成员。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。

关系辨别

同类间无私处

一个类可以访问本类所有对象的私有成员。先前,我们自实现 mystring 的时候,发现other._str,我们直接使用了,要知道_str 是 mystring 类的私有成员。为什么没有报错呢?

因为他们属于同类间的访问,所以 private 对其没有限制。
例如我们前面博客实现的mystring:

mystring::mystring(const mystring & other)
{
int len = strlen(other._str);
this->_str = new char[len+1];
strcpy(this->_str,other._str);
}

当前对象就可以直接访问同一类其他对象的私有成员。

异类间有友元

但是,如果上层关系,不是发生在同一类之间,就麻烦了。private 属性会限制类对象私有成员的访问。
struct 对象其成员默认是 public 的,所以 operator+函数中是可以成立的,但是 struct换为 class 后,其成员默认是 private 的,就会编译出错。
若不想通过 get/set 方法来获取私有成员,怎么办呢,答案是友元。

使用struct通过+运算符重载实现;

#include <iostream>

using namespace std;

struct Comlpex
{
    double real;
    double image;
};

Comlpex operator+(Comlpex& c1, Comlpex& c2)
{
    Comlpex tmp;
    tmp.real = c1.real + c2.real;
    tmp.image = c1.image + c2.image;
    return tmp;
}

int main()
{
    Comlpex c1 = { 1,2 }, c2 = { 3,4 };
    Comlpex sum = { 0,0 };
    sum = c1 + c2;
    return 0;
}

在class中使用友元实现。

#include <iostream>

using namespace std;

class Comlpex
{
    friend  Comlpex operator+(Comlpex& c1, Comlpex& c2);
public:
    Comlpex(double r = 0, double i = 0):real(r), image(i) {}
    void DumpFromat()
    {
        cout << "(" << real << "," << image << ")" << endl;
    }

private:
    double real;
    double image;
};

Comlpex operator+(Comlpex& c1, Comlpex& c2)
{
    Comlpex tmp;
    tmp.real = c1.real + c2.real;
    tmp.image = c1.image + c2.image;
    return tmp;
}

int main()
{
    Comlpex c1(1,2), c2(3,4);
    Comlpex sum = { 0,0 };
    sum = c1 + c2;
    sum.DumpFromat();
    return 0;
}

运行结果为:

在这里插入图片描述

友元不是成员

友元函数终究不是成员函数,成员中有隐参 this 指针,可以直接访问成员,而友元中则没有,必须得通过对象来访问。
友元仅是打破了外部访问中的 private 权限。 声明为谁的友元,就可以通过谁的对象 ,访问谁的私有成员。

友元函数

全局函数作友元

全局函数作友元,只需要在相应的类内作友元函数声明即可。一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
友 元 的 声 明 位 置 , 可 以 是 类 中 任 意 位 置 , 且 其 声 明 不 受 访 问 权 限 关 键 字(public/protected/private)的影响。

语法格式

class A
{
friend void globalfunction(A &,...)
public:
.... 
}

实战操练:求两点之间的距离

#include<iostream>
#include<cmath>
using namespace std;
class Point
{
public:
Point(double x,double y)
{
_x = x;
_y = y;
}
void getFormatxy();
friend double distance(Point &a, Point &b);
private:
double _x, _y;
};
void Point::getFormatxy()
{
cout <<"(" << _x << "," << _y << ")" <<endl;
}
double distance(Point &a, Point &b)
{
double dx = a._x - b._x;
double dy = a._y - b._y;
return sqrt(dx*dx + dy*dy);
}
int main(void)
{
Point p1(3.0,4.0), p2(6.0,8.0);
p1.getFormatxy();
p2.getFormatxy();
double d = distance(p1, p2);
cout<< "distance is " << d <<endl;
return 0;
}

运行结果为:

在这里插入图片描述

类的成员函数作友元

一个类的成员函数作友员,在声明为另一个类的友员时,要将原类的作用域加上,其它属性同全局函数。

语法格式

class A
{
friend void One::globalfunction(A&,...);
public:
...
}

实战操练:求两点之间的距离

#include<iostream>
#include<cmath>
using namespace std;
class Point;
class ManagerPoint
{
public:
double distance(Point &a, Point &b);
};
class Point
{
public:
Point(double x, double y)
{
_x = x;
_y = y;
}
void getFormatxy();
friend double ManagerPoint::distance(Point &a, Point &b);
private:
double _x, _y;
};
void Point::getFormatxy()
{
cout << "(" << _x << "," << _y << ")" << endl;
}
double ManagerPoint::distance(Point &a, Point &b)
{ double dx = a._x - b._x;
double dy = a._y - b._y;
return sqrt(dx*dx + dy*dy);
}
int main(void)
{
Point p1(3.0, 4.0), p2(6.0, 8.0);
p1.getFormatxy();
p2.getFormatxy();
ManagerPoint mp;
float d = mp.distance(p1,p2);
cout << "distance is" << d<< endl;
return 0;
}

运行结果为:

在这里插入图片描述

我们可以看到实现了相同的功能。

上面的代码得以实现是因为存在前向声明。

前向声明

前向声明,是一种不完全型(incomplete type)声明,即只需提供类名(无需提供类实现)
即可。正因为是类型不完全,功能也很有限:不能定义对象,可以定义指针和引用做参数或者返回值,但是用作函数声明。
在这里插入图片描述

指针和引用的大小均是 4,对于编译器来说是可以确定的,但是对于一个不完类型编译器是无法确定其大小的。所以上例中的 distance(Point &a, Point &b)中的引用改为 distance(Point a, Point b)是不可行的。若改为指针类型,distance(Point *a, Point *b)则是可行的。前向声明常见于头文件中,而在其头文件所在的 cpp 文件中被包含。

友元类

当希望一个类中所有成员函数,均可存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
友元类,导致封装破坏的面积扩大化,但由于其简易操作的特性,而常常在实战开发
中被使用。

声明

class A
{
friend class B;
public:
private:};

应用:求两点之间的距离

#include<iostream>
#include<cmath>
using namespace std;
class Point
{
	friend class ManagerPoint;
public:
	Point(double x, double y)
	{
		_x = x;
		_y = y;
	}
	void getFormatxy();
private:
	double _x, _y;
};
void Point::getFormatxy()
{
	cout << "(" << _x << "," << _y << ")" << endl;
}
class ManagerPoint
{
public:
	double distance(Point& a, Point& b);
};
double ManagerPoint::distance(Point& a, Point& b)
{
double dx = a._x - b._x;
double dy = a._y - b._y;
return sqrt(dx * dx + dy * dy);
}
int main(void)
{
	Point p1(3.0, 4.0), p2(6.0, 8.0);
	p1.getFormatxy();
	p2.getFormatxy();
	ManagerPoint mp;
	float d = mp.distance(p1, p2);
	cout << "distance is" << d << endl;
	return 0;
}

运行结果为:
在这里插入图片描述

应用:求三点构成的面积

上面运行结果没有问题,但是一但使用友元类,相当于之前撕开的小口子,使用友元类破坏性更大,从逻辑上友元类应该少用,但是实际工作中,我们进程会使用友元类,因为比较简单方便。

上面代码假设在 ManagerPoint 中再加一个函数成员,求三点的面积。doubel area(Point &a,Point &b, Point &c);是否一个友元类就可以搞定了呢?
那么知道了三个点的距离就可以使用海伦公式求三角形面积。

#include<iostream>
#include<cmath>

using namespace std;

class Point
{
	friend class ManagerPoint;
public:
	Point(double x, double y)
	{
		_x = x;
		_y = y;
	}
	void getFormatxy();
private:
	double _x, _y;
};
void Point::getFormatxy()
{
	cout << "(" << _x << "," << _y << ")" << endl;
}

class ManagerPoint
{
public:
	double distance(const Point& a, const Point& b);
	double getArea(const Point& a, const Point& b, const Point& c);
};

double ManagerPoint::distance(const Point& a, const Point& b)
{
double dx = a._x - b._x;
double dy = a._y - b._y;
return sqrt(dx * dx + dy * dy);

}

double ManagerPoint::getArea(const Point& a, const Point& b, const Point& c)
{
	double dx = distance(a, b);
	double dy = distance(b, c);
	double dz = distance(a, c);
	double p = (dx + dy + dz) / 2;
	return sqrt(p * (p - dx) * (p - dy) * (p - dz));
}
int main(void)
{
	Point p1(0.0, 0.0), p2(4.0, 0.0), p3(0.0, 4.0);
	p1.getFormatxy();
	p2.getFormatxy();
	p3.getFormatxy();
	ManagerPoint mp;
	cout << "area is" << mp.getArea(p1,p2,p3) << endl;
	return 0;
}

运行结果为:

在这里插入图片描述

小结

声明位置
友元声明以关键字 friend 开始,它只能出现在类定义中。因为友元 不是类授权的成员,
所以它 不受其所在类的声明区域 public private 和 protected 的影响。通常我们选择把所
有友元声明组织在一起并放在类头之后。具体放的位置,要看团队的一致风格

友元利弊
友元不是类成员,但是它可以通过对象访问类中的私有成员。友元的作用在于提高程
序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。不过,类的访问权限确实在某些应用场合显得有些呆板,从而 容忍了友元这一特语法现象。

注意事项

友元关系不能被继承。

友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元,要看在类中是否有相应的声明。

友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是类B 的友元,类 C 不一定 是
类A 的友元,同样要看类中是否有相应的声明。

发布了163 篇原创文章 · 获赞 94 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43648751/article/details/104710589