P14-c++使用类-02友元详细介绍,详细的例子演示!

1. 友元基本概念以及诞生原因

通常,公有类方法提供唯一的访问途径,但是有时候这种制太严格,以致于不适合特定的编程问题。
在这种情况下,C+提供了另外一种形式的访问权限: 友元
友元有3种:

  • 友元函数;
  • 友元类
  • 友元成员函数

过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
这篇文章主要讨论友元函数

在为类重载二元运算符时(带两个参数的运算符)常常友元。
将Demo对象乘以实数就属于这种情况,下面来看看Demo类的定义。

class Demo {
private:
	int iAge;
	string name;
public:
	Demo(int i, const string & name);
	void Show();	
	Demo operator+(const Demo &d) const;
	Demo operator*(int i) const;	
};

并有下面这样一个例子

#include <iostream>
#include <<tring>
 /*
    author:梦悦foundation
    公众号:梦悦foundation
    可以在公众号获得源码和详细的图文笔记
*/

using namespace std;

class Demo {
private:
	int iAge;
	string name;
public:
	Demo(int i, const string & name);
	void Show();	
	Demo operator+(const Demo &d) const;
	Demo operator*(int i) const;	
};

void Demo::Show()
{
	cout << "iAge:" << iAge << endl;
}

Demo::Demo(int i = 0, const string & name = "null")
{
	this->iAge = i;
	this->name = name;
}
Demo Demo::operator+(const Demo &d) const
{

	Demo result;
	cout << "this->name:" << this->name << ", this->age: " << this->iAge << ", d.name:" << d.name  << ", d.iAge: " << d.iAge << endl; 
	result.iAge = this->iAge + d.iAge;
	return result;
}

Demo Demo::operator*(int i) const
{
	Demo result;
	result.iAge = this->iAge * i;
	return result;
}


int main(int argc, char *argv[]) 
{
	cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
	Demo d0 = Demo(0, "d0");
	Demo d1(1, "d1");

	d0 =  d1 * 4;
	d0.Show();
	cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
    return 0;
}

这里是能够运行成功的。

meng-yue@ubuntu:~/MengYue/c++/use_class/02$ ./friend
---------------开始--->公众号:梦悦foundation---------------
iAge:4
---------------结束--->公众号:梦悦foundation---------------

d0 = d1 * 4; 这一行代码 会调用 d1的 重载 * 函数(d1.operator**(4))。也就是说类对象必须放到前面。
假设是下面这样呢?
d0 = 4 * d1
会出现什么问题??

他会提示没有找到一个 具有 int类型的 Demo类型的 * 重载函数。

meng-yue@ubuntu:~/MengYue/c++/use_class/02$ g++ -o friend friend.cpp
friend.cpp: In function ‘int main(int, char**):
friend.cpp:55:9: error: no match foroperator*(operand types are ‘intand ‘Demo’)
  d0 = 4 * d1;
       ~~^~~~
meng-yue@ubuntu:~/MengYue/c++/use_class/02$

记住,左侧的操作数应是调用对象,但2.75不是对象。因此,编译器不能使用成员函数调用来
表达式决这个难题的一种方式是,告知每个人(包括程序员自己),只能按d0 = d1 * 4这种格式编写,不能写 d0 = d1 * 4
然而,还有另一种解决方式——非成员函数
(记住,大多数运算符都可以通过成员或非成员函数来重载, 非成员函数不是由对象调用的,它使用的所有值(包括对象) 都是显式参数。这样,编译器能够将下面的表达式
d0 = 4 * d1
与下面的非成员函数调用匹配
d0 = operator*(4, d1);
该函数的原型如下:
Demo operator*(int i, const Demo &d);
对于非成员重载运算符函数来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算达式右边的操作数对应于运算符函数的第二个参数。

使用非成员函数可以按所需的顺序获得操作数(先是 double,然后是Demo),但引发了一个新问题函数不能直接访问类的私有数据,至少常规非成员函数不能访问(不能使用d.iAge访问变量了)。然而,有一类特殊的非成员函数方问类的私有成员,它们被称为友元函数。

2. 创建友元

创建友元元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字 friend
friend Demo operator+(double m, const Demo & t); //goes in class declaration
原型意味着下面两点:

  • 虽然 operator*()函数是在类声明中声明的,但它不是成员函数,因此不能使用成员运算符来调用
  • 虽然 operator*()函数不是成员函数,但它与成员函数的访问权限相同。
    第二步是编写函数定义。
    因为它不是成员函数,所以不要使用Demo::限定符。另外,不要在定义中使用关键字 friend,定义应该如下:
Demo operator*(int i, const Demo &d)//friend not used in definition

有了上述声明和定义后,下面的语句
d0 = 4 * d1;
将转换为如下语句,从而调用刚才定义的非成员友元函数
d0 = operator*(4, d1);
总之,类的友元函数是非成员函数,其访问权限与成员函数相同
friend01.cpp
定义一个这样的函数
Demo operator*(int i, const Demo &d)//friend not used in definition;

#include <iostream>
#include <<tring>
 /*
    author:梦悦foundation
    公众号:梦悦foundation
    可以在公众号获得源码和详细的图文笔记
*/

using namespace std;

class Demo {
private:
	int iAge;
	string name;
public:
	Demo(int i, const string & name);
	void Show();	
	Demo operator+(const Demo &d) const;
	Demo operator*(int i) const;	
};

void Demo::Show()
{
	cout << "iAge:" << iAge << endl;
}

Demo::Demo(int i = 0, const string & name = "null")
{
	this->iAge = i;
	this->name = name;
}
Demo Demo::operator+(const Demo &d) const
{

	Demo result;
	cout << "this->name:" << this->name << ", this->age: " << this->iAge << ", d.name:" << d.name  << ", d.iAge: " << d.iAge << endl; 
	result.iAge = this->iAge + d.iAge;
	return result;
}

Demo Demo::operator*(int i) const
{
	Demo result;
	result.iAge = this->iAge * i;
	return result;
}

Demo operator*(int i, const Demo &d)
{	
	cout << "operator*(int i, const Demo &d)" << endl; 
}

int main(int argc, char *argv[]) 
{
	cout << "---------------开始--->公众号:梦悦foundation---------------" << endl;
	Demo d0 = Demo(0, "d0");
	Demo d1(1, "d1");

	d0 = 4 * d1;
	d0.Show();
	cout << "---------------结束--->公众号:梦悦foundation---------------" << endl;
    return 0;
}

编译运行的结果:

meng-yue@ubuntu:~/MengYue/c++/use_class/02$ ./friend01
---------------开始--->公众号:梦悦foundation---------------
operator*(int i, const Demo &d)
iAge:-1791702336
---------------结束--->公众号:梦悦foundation---------------
Segmentation fault (core dumped)
meng-yue@ubuntu:~/MengYue/c++/use_class/02$

从结果来看,这个定义的函数成功的被调用了。再对它进行修改成友元函数。
friend02.cpp

class Demo {
private:
	int iAge;
	string name;
public:
	Demo(int i, const string & name);
	void Show();	
	Demo operator+(const Demo &d) const;
	Demo operator*(int i) const;		
	friend Demo operator*(int i, const Demo &d);
};

Demo operator*(int i, const Demo &d)
{	
	Demo result;
	result.iAge = d.iAge * i;
	cout << "operator*(int i, const Demo &d)" << endl; 
	return result;
	
}

运行结果!

meng-yue@ubuntu:~/MengYue/c++/use_class/02$ ./friend02
---------------开始--->公众号:梦悦foundation---------------
operator*(int i, const Demo &d)
iAge:4
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/use_class/02$

友元函数和 重载的 *成员函数 结果是一样的!

3. 常用的友元: 重载 << 运算符

一个很有用的类特性是,可以对<<运算符进行重载,使之能与cout一起来显示对象的内容。
假设trip是一个 Demo 对象,为显示trip的值, 前面使用的是Show()。
然而,如果可以像下面这样操作将更好
cout << trip: // make cout recognize Demo class?
之所以可以这样做,是因为<<是可被重载的C+运算符之一。
实际上,它已经被重载很多次了。最初,<<运算符是C和C++的位运算符,将值中的位左移。 ostream类对该运算符进行了重载,将其转换为一个输出工具。
cout是一个 ostream对象,它是智能的,能够识别所有的C++基本类型。
这是因为对于每种基本类型, ostream类声明中都包含了相应的重载的 operator<<()定义。也就是说一个定义使用int参数,一个定义使用 double参数,等等。
因此,要使cout能够识别Demo对象,一种方法是将一个新的函数运算符定义添加到 ostream类声明中。
但修改 iostream文件是个危险的主意,这样做会在标准接口上浪费时间。
相反,通过Demo类声明来让Demo类知道如何使用cout

1. <<的第一种重载版本

要使Demo类知道使用cout,必须使用友元函数。这是什么原因呢?因为下面这样的语句使用两个对象,其中第一个是 ostream类对象(cout):
cout << trip;
如果使用一个Demo成员函数来重载<<, Demo对象将是第一个操作数,就像使用成员函数重载*运算符那样。这意味着必须这样使用
trip << cout: //if operator<<() were a Demo member function
这样会令人迷惑。但通过使用友元函数,可以像下面这样重载运算符:

void operator<<(ostream & os, const Demo &d)
{
	os << "iAge:" << d.iAge << endl;
}

friend03.cpp

class Demo {
private:
	int iAge;
	string name;
public:
	Demo(int i, const string & name);
	void Show();	
	Demo operator+(const Demo &d) const;
	Demo operator*(int i) const;		
	friend Demo operator*(int i, const Demo &d);
	friend void operator<<(ostream &os, const Demo &d);
};
void operator<<(ostream &os, const Demo &d)
{
	os << "operator<<, iAge:" << d.iAge << endl;
}

使用cout << d0; 可以直接操作 类,非常方便
编译运行的结果:

meng-yue@ubuntu:~/MengYue/c++/use_class/02$ ./friend03
---------------开始--->公众号:梦悦foundation---------------
operator*(int i, const Demo &d)
iAge:4
operator<<, iAge:4
---------------结束--->公众号:梦悦foundation---------------
meng-yue@ubuntu:~/MengYue/c++/use_class/02$

2. <<的第二种重載版本

前面介绍的实现存在一个问题。像下面这样的语句可以正常工作:
cout << trip;
但这种实现不允许像通常那样将重新定义的 << 运算符与cout一起使用:
cout << " Trip time:" << trip << "(Tuesday)\n"; //can't do
要理解这样做不可行的原因以及必须如何做才能使其可行,首先需要了解关于cout操作的一点知识。
请看下面的语句:

int x = 5;
int y =8;
cout << x << y;

C++从左至右读取输出语句,意味着它等同于:
(cout <<) << y;
正如 iostream中定义的那样,<<运算符要求左边是一个 ostream对象。
显然,因为cout是 ostream对象,所以表达式cout<<x满足这种要求。
然而,因为表达式cout<<x位于<<y的左侧,所以输出语句也要求该表达式是一个 ostream类型的对象。因此, ostream类将 operator<<()函数实现为返回一个指向 ostream对象的引用。
具体地说,它返回一个指向调用对象(这里是cout)的引用。
因此,表达式(cout<<x)本身就是ostream对象cout,从而可以位于<<运算符的左侧。
可以对友元函数采用相同的方法。只要修改 operators<<()函数,让它返回 ostream对象的引用即可

ostream & operator<<(ostream &os, const Demo &d)
{
	os << "operator<<, iAge:" << d.iAge << endl;
	return os;
}

注意,返回类型是 ostream &
这意味着该函数返回 ostream对象的引用。因为函数开始执行时,程序传递了一个对象引用给它,这样做的最终结果是,函数的返回值就是传递给它的对象。
也就是说,下面的语句

cout << trip;

将被转换为下面的调用

将被转换为下面的调用:
operator<<(cout, trip;
而该调用返回cout对象。因此,下面的语句可以正常工作
cout < " Trip time:" << trip << "(Tuesday)\n"; //can't do
我们将这条语句分成多步,来看看它是如何工作的。
首先,下面的代码调用 ostream中的<<定义,它显示字符串并返回cout对象:
cout <<Trip time:
因此表达式 cout <“ Trip time:”将显示字符串,然后被它的返回值cout所替代。
原来的语句被简化为下面的形式
cout < trip << "(Tuesday)\n"; //can't do
接下来,程序使用Demo声明的<< 显示trip值,并再次返回cout对象。
这将语句简化为
cout << "(Tuesday)\n";
现在,程序使用 ostream中用于字符串的<<定义,来显示最后一个字符串,并结束运行。

一般来说,要重载<<运算符来显示 class_name的对象,可使用一个友元函数,其定义如下:

ostream & operator<<(ostream &os, const class_name &obj)
{
	os << ...;//display object contents
	return os;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值