前言:友元对于我来说一直是一个难点,最近看了一些有关友元的课程与博客,故在此将自己的学习收获做一个简单的总结

一、什么是友元

在C++的自定义类中,一个常规的成员函数声明往往意味着:

  • 该成员函数能够访问其所在类的私有部分

  • 该成员函数位于其所在类的作用域之中

  • 该成员函数必须由一个对象去激活从而被调用(通过this指针来实现)

如果将一个函数声明为另一个类的友元,则可以使该函数只具有上面的第一个特性,即可以使该函数访问类中的私有部分。

友元函数的语法:friend+普通函数声明
友元类的语法: friend
+类名(不是对象名)
友元成员函数的语法:friend
+成员函数的声明

[注]:如果将类A声明为类B的友元类,则类A中的所有成员函数都可以访问类B的私有部分,即类A中的成员函数都是类B的友元函数。

 #include<iostream>
#include<string>
using namespace std; 5 class Student;
6 void show(Student &);//一个函数或类在类中被声明为友元时,其在类外必须要有声明!

class Student{
public:
9 friend void show(Student &);//将函数show声明为类Student的友元函数
Student()=default;
Student(string name,int age):Name(name),Age(age){}
private:
string Name;
int Age;
}; 17 void show(Student &stu){
18 cout<<"Name:"<<stu.Name<<endl;
19 cout<<"Age:"<<stu.Age<<endl;
20 }

int main(){
Student stu("Tomwenxing",);
show(stu);
return ;
}

特别注意:

1.友元函数不是类的成员函数。和一般函数相比友元函数可以访问类中的所有成员,而一般函数只能访问类中的非公有成员

2.友元函数不受类中的访问权限关键字的限制,因此可以把友元函数或友元类的声明放在类的任意位置(不管该位置是公有、私有还是受保护),其结果是相同的(建议将友元函数或友元类的声明放在类定义最开始的地方)

3.友元函数的作用域并非其声明所在类的作用域。如果友元函数是另一个类的成员函数,则其作用域和另一个类的作用域;否则该友元函数的作用域和一般函数的作用域相同

二、为什么使用友元

1.使用友元可以实现类之间的数据共享,减少系统的开销,提高效率

 #include<iostream>
#include<string>
using namespace std; 5 class Age;//声明类
class Name{
public:
8 friend Age;//将类Age声明为类Name的友元类
Name()=default;
Name(string name){
this->name=name;
}
void show1(const Age&);
private:
string name;
}; class Age{
public:
20 friend Name;//将类Name声明为类Age的友元类
Age()=default;
Age(int age){
this->age=age;
}
void show2(const Name&);
private:
int age;
};
void Name::show1(const Age& age){
cout<<"调用类Name中的成员函数show1"<<endl;
cout<<"Name:"<<name<<endl;
cout<<"Age:"<<age.age<<endl;
}
void Age::show2(const Name& name){
cout<<"调用类Age中的成员函数show2"<<endl;
cout<<"Name:"<<name.name<<endl;
cout<<"Age:"<<age<<endl;
} int main(){
Name name("Tomwenxing");
Age age();
name.show1(age);
cout<<"------------分界线----------------"<<endl;
age.show2(name);
return ;
}

上例中Name类和Age类互为友元类,从而实现了类Age和类Name之间的数据共享。

2.运算符重载的某些场合需要使用友元

Example 1:重载+号时利用友元实现“加法的交换律”

先来看一个有关复数加法的例子:

 #include<iostream>
#include<string>
using namespace std; 5 class Complex;//对类Comple进行声明
class Complex{
public:
Complex():real(),img(){} //默认构造函数
Complex(int r,int i):real(r),img(i){} //带参数的构造函数
void show(){ //打印复数
cout<<"("<<real<<","<<img<<")"<<endl;
}
13 Complex operator+(const Complex &c){ //对+进行重载
14 return Complex(real+c.real,img+c.img);
15 }
16 Complex operator+(const int &value){
17 return Complex(real+value,img);
18 }
private:
int real; //复数实部
int img; //复数虚部
}; int main(){
Complex c1(,);
Complex c2(,);
27 Complex sum1=c1+c2;
sum1.show();
29 Complex sum2=c1+10;
sum2.show();
return ;
}

上例中Comple对象和Complex对象或int型整数的加法本质上是调用类中的成员函数,即语句sum1=c1+c2等价于sum1=c1.operator+(c2),语句sum2=c1+10等价于sum2=c1.operator+(10) ,因此这两条语句可以在系统中可以顺利执行。但如果main函数中出现如下语句时,编译器会报错:

1 Comple sum3=10+c1; //错误!
2 sum3.show();

这是由于10是int型整数而非Complex类的对象,因而无法调用类中的成员函数operator+()来完成Complex对象和int型整数的加法,换句话说就是如果仅仅在类中对+进行重载是无法使对象在和int型整数进行加法时满足加法的交换律。这时候如果想解决这个问题,就需要借助友元的力量了,如下:

 #include<iostream>
#include<string>
using namespace std; 5 class Complex;//对类Comple进行声明
6 Complex operator+(const int&,const Complex&); //对函数进行声明

class Complex{
public:
9 friend Complex operator+(const int&,const Complex&);
Complex():real(),img(){} //默认构造函数
Complex(int r,int i):real(r),img(i){} //带参数的构造函数
void show(){ //打印复数
cout<<"("<<real<<","<<img<<")"<<endl;
}
Complex operator+(const Complex &c){ //对+进行重载
16 return Complex(real+c.real,img+c.img);
17 }
18 Complex operator+(const int &value){
19 return Complex(real+value,img);
20 }
private:
int real; //复数实部
int img; //复数虚部
}; 26 Complex operator+(const int &value,const Complex &c){
27 return Complex(value+c.real,c.img);
28 }
int main(){
Complex c(,);
Complex sum1=c+10;
sum1.show();
cout<<"----------分界线-------------"<<endl;
Complex sum2=10+c;
sum2.show();
return ;
}

此时语句sum2=10+c相当于sum2=operator+(10,c),从而实现了加法的交换律

Example 2:对>>和<<的重载

我们希望对>>和<<进行重载,从而使Comple对象可以直接使用cout和cin。那么只在类中对运算符>>和<<进行重载是否可以?我们可以先来试一下:

 #include<iostream>
#include<string>
using namespace std; class Complex{
public:
Complex():real(),img(){} //默认构造函数
Complex(int r,int i):real(r),img(i){} //带参数的构造函数
void show(){ //打印复数
cout<<"("<<real<<","<<img<<")"<<endl;
}
12 ostream& operator<<(ostream &out) const{
13 out<<"("<<real<<","<<img<<")";
14 return out;
15 }
16 istream& operator>>(istream &in){
17 in>>real>>img;
18 return in;
19 }
private:
int real; //复数实部
int img; //复数虚部
}; int main(){
Complex c;
cout<<"请输入复数:";
28 c>>cin; //输入复数
29 c<<cout;//输出对象
return ;
}

由上面的例子可以看出其实是可以的,但由于对运算符>>和<<的使用本质上是对类中成员函数的调用,因此完成对复数进行输入操作的语句是c>>cin(相当于c.operator>>(cin)),而完成对复数进行输出操作的语句时c<<out(相当于c.operator<<(cout)),但这和我们平时的操作习惯有很大不同,并且可读性也很差。为了解决这个问题,我们需要借助友元的力量:

 #include<iostream>
#include<string>
using namespace std; 5 class Complex;//声明类
6 ostream& operator<<(ostream&,const Complex&);//声明函数
7 istream& operator>>(istream&,Complex&);//声明函数

class Complex{
public:
10 friend ostream& operator<<(ostream &out,const Complex &c);//声明为友元函数
11 friend istream& operator>>(istream &in,Complex &c);
Complex():real(),img(){} //默认构造函数
Complex(int r,int i):real(r),img(i){} //带参数的构造函数
void show(){ //打印复数
cout<<"("<<real<<","<<img<<")"<<endl;
}
private:
int real; //复数实部
int img; //复数虚部
};
21 ostream& operator<<(ostream &out,const Complex &c){
22 out<<"("<<c.real<<","<<c.img<<")";
23 return out;
24 }
25 istream& operator>>(istream &in,Complex &c){
26 in>>c.real>>c.img;
27 return in;
28 }
int main(){
Complex c;
cout<<"请输入复数:";
32 cin>>c;//输入复数
33 cout<<c;//输出对象
return ;
}

此时语句cin>>c相当于operator>>(cin,c),而语句cout<<c相当于operator<<(cout,c),从而完成所期望的功能

三、友元的特别注意事项

1.切记友元函数不是类的成员函数,故编译器不会在友元函数中隐式地插入this指针

2.友元是不能被继承的,原因很简单: “父亲的朋友不一定也是儿子的朋友”

3.友元破坏了类的封装性,因此使用友元时必须要是是十分慎重

C++:友元的更多相关文章

  1. C++的友元类和友元函数实例

    #include <math.h> #include<iostream> using namespace std; class Point { public: Point(do ...

  2. C++学习笔记 构造&析构 友元 new&delete

    构造&析构函数 构造函数 定义:与类同名,可以有参可以无参,主要功能用于在类的对象创建时定义初始化的状态,无返回值,也不能用void修饰,构造函数不能被直接调用,必须通过new运算符在创建对象 ...

  3. c++友元函数

    c++友元函数分两类: 一://友员全居函数 /*#include <iostream>using namespace std;class aaa{    friend void prin ...

  4. 重载运算符:类成员函数or友元函数

    类成员函数: bool operator ==(const point &a)const { return x==a.x; } 友元函数: friend bool operator ==(co ...

  5. C++之友元

    友元提供了不同类的成员函数之间.类的成员函数与一般函数之间进行数据共享的机制.通过友元,一个不同函数或另一个类中的成员函数可以访问类中的私有成员和保护成员.C++中的友元为封装隐藏这堵不透明的墙开了一 ...

  6. 不可或缺 Windows Native (20) - C++: 友元函数, 友元类

    [源码下载] 不可或缺 Windows Native (20) - C++: 友元函数, 友元类 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 友元函数 友元类 示例演 ...

  7. InternalsVisibleToAttribute——把internal成员暴露给指定的友元程序集

    友元程序集简介 我们知道一个类中被定义为internal的成员(包括类型.方法.属性.变量.事件)是只能在同一个程序集中被访问到的(当然了,我这里说的是正常的方式,不包括通过反射来访问).这个规则在. ...

  8. c++ 操作符重载和友元

    操作符重载(operator overloading)是C++中的一种多态,C++允许用户自定义函数名称相同但参数列表不同的函数,这被称为函数重载或函数多态.操作符重载函数的格式一般为: operat ...

  9. [Reprint]C++友元函数与拷贝构造函数详解

    这篇文章主要介绍了C++友元函数与拷贝构造函数,需要的朋友可以参考下   一.友元函数 1.友元函数概述: (1)友元函数是定义在一个类外的普通函数.友元函数和普通函数的定义一样;在类内必须将该普通函 ...

  10. C++——友元、异常和其他

    一.友元 类并非只能拥有友元函数,也可以将类作为友元.在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员.另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元.哪些函数. ...

随机推荐

  1. MVC view操作(Razor语法)

    Razor,很有意思的剃刀.相较与之前开发WEB的方式,Razor真是轻松惬意. 下面说一下我使用Razor的一些心得. 页面中归根结底是一个输出字符串(HTML CSS JS等混合的字符串)的过程. ...

  2. socket编程小问题:地址已经被使用——Address already in use

    很多socket编程的初学者可能会遇到这样的问题:如果先ctrl+c结束服务器端程序的话,再次启动服务器就会出现Address already in use这个错误,或者你的程序在正常关闭服务器端so ...

  3. JS form跳转到新标签页并用post传参

    通过js实现跳转到一个新的标签页,并且传递参数.(使用post传参方式) 1 超链接<a>标签  (get传参)  <a href="http://www.cnblogs. ...

  4. MariaDB中文乱码之解决思路

    首先出现乱码的原因就是编码不一致问题引起的,那么就从以下2个方面入手: 1.应用层:前提条件数据库服务端存储的中文数据是对的,但是页面上显示乱码,这里只需要检查你的项目的编码格式,设置成一致就行. 2 ...

  5. rem布局简介

    移动端常见布局: 1.流式布局 高度固定,宽度自适应 2.响应式布局 能够用一套代码适应不同尺寸屏幕 3.rem布局 宽高自适应,能实现整个页面像一张图片一样缩放且不失真的效果. rem布局: em: ...

  6. TMS Xdata Server

    Xdata 在TMS中扮演的桥的角色,一年前仔细看过TMS 的源码,当时对流程很清晰,随着时间慢慢的过去,现在该忘记的都忘记了.所以用此文章来记录自己对Xdata还剩下的一点点的记忆... 光有xda ...

  7. Android签名验证漏洞POC及验证

    poc实际上就是一段漏洞利用代码,以下是最近炒得很火Android签名验证漏洞POC,来自https://gist.github.com/poliva/36b0795ab79ad6f14fd8 #!/ ...

  8. JavaScript总结(一)

    什么是JavaScript? 首先我们要知道它是什么?JavaScript是一门专门用来对网页进行编程的脚本语言:因为我是学习Java之后再来学习JavaScript的,所以我的第一疑问JavaScr ...

  9. log4j.properties配置文件详解

    Log4J的配置文件(Configuration File)就是用来设置记录器的级别.存放器和布局的,它可接key=value格式的设置或xml格式的设置信息.通过配置,可以创建出Log4J的运行环境 ...

  10. 洛咕 P2468 [SDOI2010]粟粟的书架

    强行二合一啊... 前面直接二分最小值,二维前缀和.后面用主席树查最小值.注意要写\(nlogn\). // luogu-judger-enable-o2 #include<bits/stdc+ ...