当前课程知识点:C++语言程序设计基础 > 第5章 数据的共享与保护 > 类的友元 > 类的友元(例5-6)
类的友元
友元是C++提供的一种破坏数据封装和数据隐藏的机制。
通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是被隐藏的信息。
可以使用友元函数和友元类。
为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。
友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 private 和 protected成员
作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。
访问对象中的成员必须通过对象名。
#include <iostream>
#include <cmath>
using namespace std;
class Point { //Point类声明
public: //外部接口
Point(int x=0, int y=0) : x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
friend float dist(Point &a, Point &b);
private: //私有数据成员
int x, y;
};
float dist( Point& a, Point& b) {
double x = a.x - b.x;
double y = a.y - b.y;
return static_cast<float>(sqrt(x * x + y * y));
}
int main() {
Point p1(1, 1), p2(4, 5);
cout <<"The distance is: ";
cout << dist(p1, p2) << endl;
return 0;
}
若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。
声明语法:将友元类名在另一个类中使用friend修饰说明。
class A {
friend class B;
public:
void display() {
cout << x << endl;
}
private:
int x;
};
class B {
public:
void set(int i);
void display();
private:
A a;
};
void B::set(int i) {
a.x=i;
}
void B::display() {
a.display();
};
如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数却不能访问B类的私有、保护数据。
大家好
欢迎回来继续学习
C++语言程序设计
这一节来学习类的友元
友元这个关键字很有意思
就是英文的朋友friend
也就是说一个类
它可以在类体中声明
类外的哪些函数
哪些类是它的朋友
一旦声明了是朋友
就给了一种授权
让它能够访问自己的私有成员
友元是C++提供的一种
破坏数据封装
和数据隐藏的机制
那么既然是破坏
为什么我们还需要它呢
有的时候我们需要在数据的隐藏
和效率之间去找一个平衡
那么友元呢
是这样一种机制
它通过将一个模块
声明为另外一个模块的友元
也就是朋友
使得友元模块可以访问
原本是被隐藏的这些信息
我们可以使用友元函数
也可以使用友元类
既然这是一种破坏封装
和隐藏的机制
所以我们要很小心地使用
为了确保数据的完整性
以及数据的封装
与隐藏这种原则呢
建议尽量不使用
或者少使用友元
现在我们首先来看一看
什么是友元函数
友元函数就是在类声明中
由关键字friend修饰说明的
这种类外的非本类
成员函数的这种函数
在它的函数体中呢
就可以通过对象名
来访问private和protected成员
那么如果不是友元的话
那这是肯定不行的
身为友元就可以这样访问
为什么我们需要这样呢
它作用是可以增加灵活性
使得程序员呢可以在封装
和快速性
也就是封装和效率方面
做一个合理的选择
达到一种合理的平衡
但是友元函数
它毕竟不是类的成员
所以它虽然有权限访问
对象中的私有数据
但是你得告诉它访问哪个对象
所以如果需要友元函数
来处理对象中的私有数据的话呢
就要将对象作为参数
传给这个函数
下面我们来看一个
友元函数的例题
在这个例题中呢
我们希望能够计算两个点对象
之间的距离
那计算两个点的距离
我们希望用一个函数来实现
这个函数名字是dist
这个函数我们并没有把它
作为类的成员函数
而是在类外
作为一个独立的全局函数
将两个点的引用作为参数
传递给它
那么这个时候有个什么问题呢
点类的数据成员x和y
它都是私有成员
在外部是不能直接访问的
不能用a.x b.x
这种方式来访问的
都要用什么
用这个类体规的外部接口
getx gety来访问
但是呢
这里我们用计算点的坐标
来模拟一种
计算密集型的这种函数
也就是模拟某个函数中
要大量地用到点的x坐标 y坐标
做很多复杂的计算
我们用这种简单例题
来模拟这样一种情况
这样的情况下
如果你不断地调用getx gety
大家知道这个调用函数
和返回
是要有时间和空间开销的
如果说你在计算中
大量地用到x y
这些地方你都去调用
这个函数的话呢
会影响一点这种执行效率
当然了
如果不太介意执行效率的话
最好对这个类的成员
隐藏保护的越严格越好
那么如果我们想提高一点效率
怎么办呢
就是用友元函数的方式
在类里面将这个距离函数
声明为当前类的友元
这种声明就是给dist函数
给了一个授权
让它可以访问x坐标 y坐标
但是它不是成员函数
所以它要访问点的时候
我们必须把两个点
都作为参数传过去
否则它不知道该访问哪个点
所以它的两个参数是点a的引用
点b的引用
这样两个参数
为什么这里用引用
而不传值呢
这又牵扯到一个效率的问题
当然了
具体到这个点类的对象
它占的空间并不是多
只是两个int数据成员而已
但是很多对象它是挺大的
所以一般来说呢
我们要用对象作参数的时候
宁愿传对象的引用
也就是传个别名过去
这样效率会比较高
而传整个对象过去
你需要的时间 空间
这个成本就比较高了
这个引用的成本比较低
所以我们传引用作参数
由于将这个dist函数设成友元了
所以我们看在这个函数体中
就可以直接访问x坐标
y坐标了
这样执行效率会比较高
但是也有一些问题
什么问题呢
我们看既然传了引用
在第三章中我们学过了
如果传引用作参数的话
它是什么效果
是可以双向传递的效果
也就是说
如果在这个函数中
有什么失误
哪些地方写错了 不合适了
将参数a 参数b的x坐标
y坐标修改了也是有可能的
那这一种情况肯定不是
这个类授权给dist函数的本意
这个类将dist函数设为友元
授权给它能访问私有成员
只是希望
它能够读取这些数据来用
并且使用的效率高一点
但是真的不希望在类体里面
因为一些什么意外情况
将来a和b这两个点的数据修改了
肯定不希望这样
但是现在确实有这个隐患
这个隐患呢
后面的例题中我们会看到
会把它解决好的
现在我们看主函数中
是怎么使用它的
定义两个点p1 p2
然后调用dist函数
将p1 p2作为实参
去初始化引用
这两个引用形参
然后就把引用传过去了
然后就可以把距离计算出来
传回来在这儿输出
我们说了
不仅可以定义一个类外的函数
声明一个类外的函数
为本类的朋友
我们还可以将另外一个整个的类
都声明为友元
那个类是我们这个类的朋友
可以这样声明
如果一个类是另一个类的友元
那么友元类里面的所有成员
就都能够访问授权的那个类的
它的私有成员 保护成员了
那么声明友元类呢
就是在类体中
用friend去说明一下
另外一个类是友元
现在我们来看一个友元类的例子
这个简单的例题演示了一个友元类
大家看
我们定义了一个类a
然后又定义一个类b
注意到呢
这个类b里面
它的成员是a的对象
这是一个类的组合的问题
b是组合类 a是部件类
那么在b类中呢
我们定义了一个set函数
想用这个函数来设置
它成员的值
按说虽然a是b的组件
但是a里面的数据成员x
它是私有成员
所以b类的函数是没有权限
直接访问对象a的数据成员x的
假设在某种情况下
我们特别希望能够直接像这样
用a.x的方式去访问x给它赋值
那怎么办呢
如果特别有这种需要的话呢
可以在a类的设计中予以考虑
将b类说明成a类的友元
这就是一种授权
授权b类的所有函数
能够直接访问a类的私有成员
所以经过这友元的声明以后呢
我们看在b类的set函数中
就可以直接给a.x赋值了
所以定义这个友元
是一种对外的授权
当然这个授权
要在十分必要的时候
才这样用
要慎用
如果你授权给很多友元类
友元函数以后呢
那么你类里面的私有成员
它的安全性就有问题了
我们看到了友元类
是不是觉得挺方便的
但一定要注意一点
友元关系它是单向的
这跟我们人类的朋友关系
不太一样
我们人类一般来说
我是你的朋友
你会说郑老师也会是我的朋友
对吧
很少会出现这种情况
我说某某同学是我的好朋友
那个同学说
他是谁啊 我不认识他
我跟他不是朋友
这种情况很少出现
所以一般来讲我们说朋友
那肯定是双向的
你以我为友 我以你为友
但是这样的定义
在程序中就显得不严格了
程序中去定义友元
必须是严谨的
每个类只有资格去授权
只有资格去认可
别人是自己的朋友
那么就授权
别人访问自己的私有数据成员
私有函数成员
但是你不能强行别人给你授权
所以a类说了b类是友元
那么b类就有资格访问
a类私有成员了
但是并不等同于
a类同时有权限
访问b类的私有成员
如果需要这样的话
你还要b给a授权才行
所以注意
程序中的友元关系
它是单向的
-导学
--第1章导学
-计算机系统简介
--计算机系统简介
--计算机系统简介 测试题
-计算机语言和程序设计方法的发展
--计算机语言和程序设计方法的发展 测试题
-面向对象的基本概念
--面向对象的基本概念 测试题
-程序的开发过程
--程序的开发过程
--程序的开发过程 测试题
-信息的表示和储存
--计算机的数字系统
--数据的编码表示
--信息的表示和储存 测试题
-实验指导
-导学
--第二章导学
-C++语言概述
--C++语言概述 测试题
-基本数据类型、常量、变量
--程序举例
--基本数据类型、常量、变量 测试题
-运算与表达式
--运算与表达式 测试题
-实验二:简单程序设计(上)
-数据的输入和输出
--数据的输入和输出
--数据的输入和输出 测试题
-选择结构
--if语句
--switch语句
--选择结构 测试题
-循环结构
--for语句
--循环结构 测试题
-自定义类型
--自定义类型
--自定义类型
-第2章小结
--第二章小结
-实验二:C++简单程序设计(下)
-导学
--导学
-函数定义
--函数定义
--函数定义 测试题
-函数调用
--例3-2
--例3-3
--例3-4
--例3-5
--例3-6
--函数调用 测试题
-嵌套与递归
--例3-9
--例3-10
--嵌套与递归 测试题
-函数的参数传递
--函数的参数传递
--函数的参数传递 测试题
-引用类型
--引用类型 测试题
-含有可变参数的函数
--含有可变参数的函数 测试题
-内联函数
--内联函数 测试题
-constexpr函数
--CONSTEXPR函数课后习题
-带默认参数值的函数
--带默认参数值的函数 测试题
-函数重载
--函数重载 测试题
-C++系统函数
--C++系统函数习题
-第3章小结
--第三章小结
-实验三(上)函数的应用
-实验三(下)函数的应用
-导学
--导学
-面向对象程序的基本特点
--面向对象程序的基本特点 测试题
-类和对象
--类和对象的定义
--类和对象 测试题
-构造函数
--构造函数基本概念
--委托构造函数
--复制构造函数
--构造函数 测试题
-析构函数
--析构函数
--析构函数 测试题
-类的组合
--类的组合
--类的组合程序举例
--前向引用声明
--类的组合 测试题
-UML简介
--UML简介
--UML简介课后习题
-结构体与联合体
--结构体与联合体 测试题
-枚举类
--枚举类
--枚举类 测试题
-第4章小结
--第四章小结
-实验四(上)
--实验四(上)
-实验四(下)
--实验四(下)
-导学
--导学
-标识符的作用域与可见性
--标识符的作用域与可见性 测试题
-对象的生存期
--对象的生存期
--对象的生存期 测试题
-类的静态成员
--类的静态成员 测试题
-类的友元
--类的友元 测试题
-共享数据的保护
--共享数据的保护 测试题
-多文件结构和预编译命令
--多文件结构和预编译命令 测试题
-第5章小结
--小结
-实验五
--实验五
-导学
--导学
-数组的定义与初始化
--数组的定义与使用
--一维数组应用举例
--数组的定义与初始化 测试题
-数组作为函数的参数
--数组作为函数的参数 测试题
-对象数组
--对象数组
--对象数组 测试题
-基于范围的for循环
-指针的定义和运算
--指针的定义和运算 测试题
-综合实例
--综合实例
-实验六(上)
--实验六上
-指针与数组
--指针数组
--指针与数组 测试题
-指针与函数
--指针类型的函数
--指向函数的指针
--指针与函数 测试题
-对象指针
--对象指针
--对象指针 测试题
-动态内存分配
--动态内存分配 测试题
-智能指针
--智能指针
-vector对象
--vector对象
--vector对象 测试题
-对象复制与移动
--移动构造
--对象复制与移动 测试题
-字符串
--C风格字符串
--string类
--字符串 测试题
-第6章小结
--第六章小结
-综合实例
--综合实例
-实验六(下)
--实验六(下)