C++之友元机制(友元函数和友元类)
我们已知道类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。
为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率(即减少了类型检查和安全性检查等都需要的时间开销),但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。
1.友元函数的简单介绍
1.1为什么要使用友元函数
在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。
1.2使用友元函数的优缺点
优点:能够提高效率,表达简单、清晰。
缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。
2.友元函数的使用
2.1友元函数的参数:
因为友元函数没有this指针,则参数要有三种情况:
(1)要访问非static成员时,需要对象做参数;
(2)要访问static成员或全局变量时,则不需要对象做参数;
(3)如果做参数的对象是全局对象,则不需要对象做参数;
2.2友元函数的位置
因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。
2.3友元函数的调用
可以直接调用友元函数,不需要通过对象或指针
2.4友元函数的分类:
根据这个函数的来源不同,可以分为三种方法:
普通函数友元函数
目的:使普通函数能够访问类的友元
语法:
声明: friend + 普通函数声明
实现位置:可以在类外或类中
实现代码:与普通函数相同
调用:类似普通函数,直接调用
/// /// @file Point.cc /// @author AlexCthon(AlexCthon@163.com) /// @date 2018-06-12 09:40:14 /// #include <math.h> #include <iostream> using std::cout; using std::endl; class Point { public: Point(int ix = 0, int iy = 0) : _ix(ix) , _iy(iy) { cout << "Point(int=0, int=0)" << endl; } void print() const { cout << "(" << _ix << "," << _iy << ")" << endl; } int getX() const { return _ix; } int getY() const { return _iy; } //友元之普通函数 friend float distance(const Point & lhs, const Point & rhs); private: int _ix; int _iy; }; #if 0 float distance(const Point & lhs, const Point & rhs) { return sqrt((lhs.getX() - rhs.getX()) * (lhs.getX() - rhs.getX()) + (lhs.getY() - rhs.getY()) * (lhs.getY() - rhs.getY())); } #endif float distance(const Point & lhs, const Point & rhs) { return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) + (lhs._iy - rhs._iy) * (lhs._iy - rhs._iy)); } int main(void) { Point pt1(1, 2); Point pt2(3, 4); cout << "pt1和pt2之间的距离: " << distance(pt1, pt2) << endl; return 0; }
类Y的一个成员函数为类X的友元函数
目的:使类Y的一个成员函数成为类X的友元,具体而言:在类Y的这个成员函数中,借助参数X,可以直接以X的私有变量
语法:
声明位置:声明在公有中 (本身为函数)
声明:friend + 成员函数的声明
调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制
代码:
#include <math.h> #include <iostream> using std::cout; using std::endl; class Point;//类的前向声明 class Line { public: float distance(const Point & lhs, const Point & rhs); }; class Point { public: Point(int ix = 0, int iy = 0) : _ix(ix) , _iy(iy) { cout << "Point(int=0, int=0)" << endl; } void print() const { cout << "(" << _ix << "," << _iy << ")" << endl; } //友元之成员函数 friend float Line::distance(const Point & lhs, const Point & rhs); private: int _ix; int _iy; }; #if 0 float distance(const Point & lhs, const Point & rhs) { return sqrt((lhs.getX() - rhs.getX()) * (lhs.getX() - rhs.getX()) + (lhs.getY() - rhs.getY()) * (lhs.getY() - rhs.getY())); } #endif float Line::distance(const Point & lhs, const Point & rhs) { return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) + (lhs._iy - rhs._iy) * (lhs._iy - rhs._iy)); } int main(void) { Point pt1(1, 2); Point pt2(3, 4); Line line; cout << "pt1和pt2之间的距离: " << line.distance(pt1, pt2) << endl; return 0; }
类Y的所有成员函数都为类X友元函数—友元类
目的:使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。
语法:
声明位置:公有私有均可,常写为私有(把类看成一个变量)
声明: friend + 类名(不是对象)
补充: 当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。类的前置声明。
#include <math.h> #include <iostream> using std::cout; using std::endl; class Point;//类的前向声明 class Line { public: float distance(const Point & lhs, const Point & rhs); void setPoint(int ix, int iy, Point & pt); private: int _iz; }; class Point { public: Point(int ix = 0, int iy = 0) : _ix(ix) , _iy(iy) { cout << "Point(int=0, int=0)" << endl; } void print() const { cout << "(" << _ix << "," << _iy << ")" << endl; } //友元之友元类 //friend class Line; friend Line;// 一定是破坏了类的封装性 //友元是单向的, 不具备传递性, 不能继承 // //A -> B, B -> C ==> A -> C void setZ(Line & line, int iz) {//Point不能访问Line的私有成员 line._iz = iz; } private: int _ix; int _iy; }; float Line::distance(const Point & lhs, const Point & rhs) { return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) + (lhs._iy - rhs._iy) * (lhs._iy - rhs._iy)); } void Line::setPoint(int ix, int iy, Point & pt) { pt._ix = ix; pt._iy = iy; } int main(void) { Point pt1(1, 2); Point pt2(3, 4); Line line; cout << "pt1和pt2之间的距离: " << line.distance(pt1, pt2) << endl; line.setPoint(5, 6, pt1); cout << "pt1 = "; pt1.print(); return 0; }
小结:其实一些操作符的重载实现也是要在类外实现的,那么通常这样的话,声明为类的友元是必须滴。
4.友元函数和类的成员函数的区别
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
成员函数有this指针,而友元函数没有this指针。这点其实和静态成员函数一样,静态成员函数也是没有this指针的,所以它只能访问静态成员变量或者通过对象访问非静态成员变量。
友元类是单向的,不可传递,不能被继承。
class Rect { public: Rect() // 构造函数,计数器加1 { count++; } //Rect(const Rect& r) //{ // width = r.width; // height = r.height; // count++; //} ~Rect() // 析构函数,计数器减1 { count--; } static int getCount() // 返回计数器的值 { return count; } friend int get(); private: int width; int height; static int count; // 一静态成员做为计数器 }; int Rect::count = 0; // 初始化计数器 ,静态成员变量必须要在类外部初始化 int get() { return Rect::count;//友元函数通过类访问私有静态成员变量 } int main() { Rect rect1; cout<<"The count of Rect: "<<Rect::getCount()<<endl;//通过类访问公有静态成员函数,输出1 Rect rect2(rect1); // 使用rect1复制rect2,此时应该有两个对象 cout<<"The count of Rect: "<<Rect::getCount()<<endl; //输出1 cout << get() << endl;//输出1 //cout << Rect::count << endl;//不能编译通过,不能访问私有成员。this只能访问类中的非静态成员变量或成员函数 system("pause"); return 0; }
注意:怎么理解友元类是单向的,不可传递,不能被继承?
1、友元类不能被继承。这个不多说,很容易理解。
2、友元类是单向的。A->B,B->A?这是错误的。
3、友元类是不可传递的。A->B,B->C, A=>C?这是错误的。
补充:当类的成员变量很多时,需要提供许多的get/set方法来实现成员变量的存取,在这种情况下,不妨用友元的方法。
/// /// @file Complex.cc /// @author AlexCthon(AlexCthon@163.com) /// @date 2018-06-12 10:10:03 /// #include <iostream> using std::cout; using std::endl; class Complex { public: Complex(double dreal = 0, double dimag = 0) : _dreal(dreal) , _dimag(dimag) {} void display() { cout << _dreal << " + " << _dimag << "i" << endl; } //成员函数的形式 Complex operator+(const Complex & rhs) { return Complex(_dreal + rhs._dreal, _dimag + rhs._dimag); } private: double _dreal; double _dimag; }; int main(void) { Complex c1(1, 2); Complex c2(3, 4); Complex c3 = c1 + c2; c3.display(); Complex c4 = c1 + 5;// c1.operator+(5); c4.display(); Complex c5 = 5 + c1;//operator+(5, c1); c5.display(); return 0; }
有关运算符重载更多细节,参考这篇博客:https://www.cnblogs.com/cthon/p/9181404.html