友元入门
友元friend
可以将一个函数或类进行friend修饰
修饰后的函数和类可以直接在外部访问调用本类的所有成员
友元的位置无需在意是否为public还是private
class Point { public: friend void display(const Point& p); friend class Line; private: int _ix; int _iy; };
void display(const Point& p) { cout << p._ix; cout << p._iy << endl; }
class Line { public: void print(const Point& p) { cout << p._ix; } };
|
具体实现
#include <iostream> #include <cmath>
using std::cout; using std::endl;
class Point { public: friend void distance(const Point& p1, const Point& p2); friend void display(const Point& p); friend class Line;
Point(const int& ix, const int& iy) :_ix(ix) , _iy(iy) { cout << "Point(int,int)" << endl; } private: int _ix; int _iy; };
void display(const Point& p) { cout << '(' << p._ix << ' ' << p._iy << ')' << endl; } void distance(const Point& p1, const Point& p2) { cout << "distance:"<<hypot(p1._ix - p2._ix, p1._iy - p2._iy) << endl; }
class Line { public: void print(const Point& p) { cout << p._ix; } };
void test0() { Point p1(0, 0); Point p2(3, 4);
display(p1); display(p2);
distance(p1, p2); } int main() { test0(); return 0; }
|
输出结果
Point(int,int) Point(int,int) (0 0) (3 4) distance:5
|
友元的使用
在友元修饰类时,我们发现虽然代码没有语法错误,但在编译过程中往往无法执行
class Line { public: void distance(const Point& p1, const Point& p2); { cout << "distance:" << hypot(p1._ix - p2._ix, p1._iy - p2._iy) << endl; } };
class Point { public:
friend void Line::distance(const Point& p1, const Point& p2); friend void display(const Point& p);
Point(const int& ix, const int& iy) :_ix(ix) , _iy(iy) { cout << "Point(int,int)" << endl; } private: int _ix; int _iy; };
|
这是因为在编译过程中,Line类作为Point的友元,虽然能够调用Point的成员,但因为Line声明定义在Point的前面,我们目前还不清楚Point类中成员都有什么,无法调用Point的成员
这种情况在我们一开始认识命名空间时也有遇到过,解决方法称为带有命名空间的函数声明
带有命名空间的函数声明
无法执行的案例
namespace front { void display() { back::out(); } } namespace back{ void print() { front::display(); } void out() { cout << "out" << endl; } }
|
在编译过程中,front内部的print函数调用了back内的out函数,此时out函数和back命名空间还没有声明或者定义,编译器不知道调用的是什么,就出现了问题
由此可知问题可以概括为:当一个命名空间需要调用另一个命名空间的函数,而另一个命名空间也需要调用当前命名空间的函数
解决方法:将一开始命名空间内需要调用的另一个命名空间内函数提前声明
namespace back { void out(); } namespace front { void display() { back::out(); } } namespace back{ void print() { front::display(); } void out() { cout << "out" << endl; } }
|
上述解决方法我们称之为带有命名空间的函数声明
类的前向声明
根据带有命名空间的函数声明,我们可以用来解决当前Line类中不能调用Point成员的问题(前提条件是friend已经在Point类内部修饰好了Line)
#include <iostream> #include <cmath>
using std::cout; using std::endl;
class Point;
class Line { public: void distance(const Point& p1, const Point& p2); };
class Point { public:
friend void Line::distance(const Point& p1, const Point& p2); friend void display(const Point& p);
Point(const int& ix, const int& iy) :_ix(ix) , _iy(iy) { cout << "Point(int,int)" << endl; } private: int _ix; int _iy; };
void display(const Point& p) { cout << '(' << p._ix << ' ' << p._iy << ')' << endl; }
void Line::distance(const Point& p1, const Point& p2) { cout << "distance:" << hypot(p1._ix - p2._ix, p1._iy - p2._iy) << endl; }
void test0() { Point p1(0, 0); Point p2(3, 4); } int main() { test0(); return 0; }
|
此时我们发现将Point提前声明好,把下方实现部分加上作用域,整个代码就不会报错了(如果直接在Line内部实现,Line还是不知道Point内部有什么,类的前向声明仅仅是让该类知道该另一个的存在)
此时我们可以选择使用static修饰来调用distance函数,或者通过创建Line对象调用(不写static修饰了,省点字数)
Point p1(0, 0); Point p2(3, 4);
Line temp;
temp.distance(p1, p2);
|
对于临时对象调用distance更便捷的写法
Point p1(0, 0); Point p2(3, 4);
Line().distance(p1,p2);
|
输出结果
Point(int,int) Point(int,int) distance:5
|
简便的原因
一方面是作为临时对象,其生命周期明显缩短,由整个test函数的生命周期缩短到了一个语句的生命周期,另一方面,只需要写一行就可以实现两行的功能
friend直接修饰类的好处
在之前的代码中,我们用friend修饰了Line中distance函数,如果Line中有很多函数需要调用Point的成员,那么我们可以直接修饰类,此时可以大大降低代码的冗余
复杂写法
class Point;
class Line { public: double distance(const Point& pt1, const Point& pt2); void setPoint(Point& pt, int ix, int iy); void displayPoint(const Point& p); void checkPoint(const Point& p); private: };
class Point { friend void display(const Point& pt);
friend double Line::distance(const Point& pt1, const Point& pt2); friend void Line::setPoint(Point& pt, int ix, int iy); friend void Line::displayPoint(const Point& p); friend void Line::checkPoint(const Point& p);
|
简便写法
class Point;
class Line { public: double distance(const Point& pt1, const Point& pt2); void setPoint(Point& pt, int ix, int iy); void displayPoint(const Point& p); void checkPoint(const Point& p); private: };
class Point { friend void display(const Point& pt); friend class Line; };
|
友元特性
class Point{ friend class Line; };
|
友元的单向性
Point把Line认为是自己的朋友
Line因此可以随便用Point的东西
但是Line还没有认为Point是他的朋友,所以Line不许Point去用Line的东西
友元的非传递性
A认为B是他的朋友,B认为C是B的朋友,但是A如果不认识C那么A就不认为C是A的朋友。
友元的非继承性
a的父亲是A,B是A的朋友,但是B不一定是a的朋友
友元最为特殊,不受public/protected/private影响,可以在类的任何位置声明
友元重构(友元运算符重载)
假如有一个类专门用来创建复数对象,我们想让两个复数对象相加
#include <iostream>
using std::cout; using std::endl;
class Complex { public: Complex() :_dreal(0) ,_dimage(0) { cout << "Complex()" << endl; } Complex(const int& dreal, const int& dimage) :_dreal(dreal) , _dimage(dimage) { cout << "Complex(int,int)" << endl; }
~Complex() { cout << "~Complex" << endl; } void print() const { cout << _dreal << ' + ' << _dimage << 'i' << endl; } private: int _dreal; int _dimage; }; void test0() { Complex c1(1, 2); Complex c2(3, 4);
c1.print(); c2.print();
Complex c3 = c1 + c2; c3.print(); } int main() { test0();
return 0; }
|
我们发现想要用c3保存好相加的结果,是不可行的,无法想string对象一样直接相加,那么string类中是如何实现的
类重构
Complex operator +(const Complex& rhs) { Complex temp(0,0); temp._dreal = this->_dreal + rhs._dreal; temp._dimage = this->_dimage + rhs._dimage;
return temp; }
|
说明
1:+运算符为双目运算符,有左右两个参数,在类中重构时,this指针指向的对象为左参数,参数1指向右参数,this指针作为隐含的参数不需要我们在参数列表内加上他,所以在类中重构+运算符时,只有一个参数
2:函数类型不能带上引用符号,因为我们创建了一个临时对象,返回了右值,无法进行取地址操作,因此中间会多一步拷贝函数(因为一些优化,我们看不到拷贝函数的执行过程)
输出结果
Complex(int,int) Complex(int,int) 1 + 2i 3 + 4i Complex(int,int) 4 + 6i ~Complex ~Complex ~Complex
|
这种在类内部重构的方法一般来说不便于理解,在我们印象中,如果需要重构+应该自己写两个参数才对,而不是一个this指针来代替左参数
全局重构
顾名思义就是在全局区来定义函数,缺点是需要单独在类中开新的函数去访问数据成员
#include <iostream>
using std::cout; using std::endl;
class Complex { public: Complex() :_dreal(0) ,_dimage(0) { cout << "Complex()" << endl; } Complex(const int& dreal, const int& dimage) :_dreal(dreal) , _dimage(dimage) { cout << "Complex(int,int)" << endl; }
~Complex() { cout << "~Complex" << endl; } void print() const { cout << _dreal << " + " << _dimage << 'i' << endl; } int getdreal() const { return _dreal; } int getimage() const { return _dimage; } private: int _dreal; int _dimage; };
Complex operator +(const Complex& lhs,const Complex& rhs) { int dreal = lhs.getdreal() + rhs.getdreal(); int dimage = lhs.getimage() + rhs.getimage();
Complex temp(dreal,dimage);
return temp; } void test0() { Complex c1(1, 2); Complex c2(3, 4);
c1.print(); c2.print();
Complex c3 = c1 + c2; c3.print(); } int main() { test0();
return 0; }
|
输出结果
Complex(int,int) Complex(int,int) 1 + 2i 3 + 4i Complex(int,int) 4 + 6i ~Complex ~Complex ~Complex
|
那么有什么方法既可以保证函数内参数列表有两个参数,而且不需要另起新的函数去访问数据成员呢
友元重构
用到刚刚掌握的友元,可以在类的外部定义,并且可以直接访问所有成员
#include <iostream>
using std::cout; using std::endl;
class Complex { public: Complex() :_dreal(0) ,_dimage(0) { cout << "Complex()" << endl; } Complex(const int& dreal, const int& dimage) :_dreal(dreal) , _dimage(dimage) { cout << "Complex(int,int)" << endl; }
~Complex() { cout << "~Complex" << endl; } void print() const { cout << _dreal << " + " << _dimage << 'i' << endl; } friend Complex operator +(const Complex& lhs, const Complex& rhs); private: int _dreal; int _dimage; };
Complex operator +(const Complex& lhs,const Complex& rhs) { int dreal = lhs._dreal + rhs._dreal; int dimage = lhs._dimage + rhs._dimage;
Complex temp(dreal,dimage);
return temp; } void test0() { Complex c1(1, 2); Complex c2(3, 4);
c1.print(); c2.print();
Complex c3 = c1 + c2; c3.print(); } int main() { test0();
return 0; }
|
输出结果
Complex(int,int) Complex(int,int) 1 + 2i 3 + 4i Complex(int,int) 4 + 6i ~Complex ~Complex ~Complex
|