当前课程知识点:C++语言程序设计基础 >  第5章 数据的共享与保护 >  类的友元 >  类的友元(例5-6)

返回《C++语言程序设计基础》慕课在线视频课程列表

类的友元(例5-6)在线视频

类的友元(例5-6)

类的友元

友元是C++提供的一种破坏数据封装和数据隐藏的机制。

通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是被隐藏的信息。

可以使用友元函数和友元类。

为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。

友元函数

友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能够通过对象名访问 private 和 protected成员

作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。

访问对象中的成员必须通过对象名。

5-6 使用友元函数计算两点间的距离

#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类的私有、保护数据。




下一节:共享数据的保护(例5-7)

返回《C++语言程序设计基础》慕课在线视频列表

类的友元(例5-6)课程教案、知识点、字幕

大家好

欢迎回来继续学习

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授权才行

所以注意

程序中的友元关系

它是单向的

C++语言程序设计基础课程列表:

第1章 绪论

-导学

--第1章导学

-计算机系统简介

--计算机系统简介

--计算机系统简介 测试题

-计算机语言和程序设计方法的发展

--计算机语言和程序设计方法的发展

--计算机语言和程序设计方法的发展 测试题

-面向对象的基本概念

--面向对象的基本概念

--面向对象的基本概念 测试题

-程序的开发过程

--程序的开发过程

--程序的开发过程 测试题

-信息的表示和储存

--计算机中的信息与存储单位

--计算机的数字系统

--数据的编码表示

--信息的表示和储存 测试题

-实验指导

--实验一:VS开发环境介绍

第2章 C++简单程序设计(一)

-导学

--第二章导学

-C++语言概述

--C++的特点和程序实例

--C++字符集和词法记号

--C++语言概述 测试题

-基本数据类型、常量、变量

--基本数据类型、常量、变量

--程序举例

--基本数据类型、常量、变量 测试题

-运算与表达式

--算术运算与赋值运算

--逗号运算、关系运算、逻辑运算和条件运算

--Sizeof运算、位运算

--运算优先级、类型转换

--运算与表达式 测试题

-实验二:简单程序设计(上)

--实验二:简单程序设计(上)

第2章 C++简单程序设计(二)

-数据的输入和输出

--数据的输入和输出

--数据的输入和输出 测试题

-选择结构

--if语句

--switch语句

--选择结构 测试题

-循环结构

--循环结构——while语句

--do-while语句

--for语句

--嵌套的控制结构、其他控制语句

--循环结构 测试题

-自定义类型

--自定义类型

--自定义类型

-第2章小结

--第二章小结

-实验二:C++简单程序设计(下)

--实验二C++简单程序设计(下)

第3章 函数

-导学

--导学

-函数定义

--函数定义

--函数定义 测试题

-函数调用

--函数调用(例3-1)

--例3-2

--例3-3

--例3-4

--例3-5

--例3-6

--函数调用 测试题

-嵌套与递归

--函数的嵌套调用(例3-7)

--函数的递归调用(例3-8)

--例3-9

--例3-10

--嵌套与递归 测试题

-函数的参数传递

--函数的参数传递

--函数的参数传递 测试题

-引用类型

--引用类型(例3-11)

--引用类型 测试题

-含有可变参数的函数

--含有可变参数的函数

--含有可变参数的函数 测试题

-内联函数

--内联函数(例3-14)

--内联函数 测试题

-constexpr函数

--constexpr函数

--CONSTEXPR函数课后习题

-带默认参数值的函数

--带默认参数值的函数

--默认参数值例(3-15)

--带默认参数值的函数 测试题

-函数重载

--函数重载(例3-16)

--函数重载 测试题

-C++系统函数

--C++系统函数(例3-17)

--C++系统函数习题

-第3章小结

--第三章小结

-实验三(上)函数的应用

--实验三(上)函数的应用

-实验三(下)函数的应用

--实验三(下)函数的应用

第4章 类与对象

-导学

--导学

-面向对象程序的基本特点

--面向对象程序的基本特点

--面向对象程序的基本特点 测试题

-类和对象

--类和对象的定义

--类和对象的程序举例

--类和对象 测试题

-构造函数

--构造函数基本概念

--构造函数例题(1)——例4-1

--构造函数例题(2)——例4-2

--委托构造函数

--复制构造函数

--复制构造函数调用举例

--构造函数 测试题

-析构函数

--析构函数

--析构函数 测试题

-类的组合

--类的组合

--类的组合程序举例

--前向引用声明

--类的组合 测试题

-UML简介

--UML简介

--UML简介课后习题

-结构体与联合体

--结构体(例4-7)

--联合体(例4-8)

--结构体与联合体 测试题

-枚举类

--枚举类

--枚举类 测试题

-第4章小结

--第四章小结

-实验四(上)

--实验四(上)

-实验四(下)

--实验四(下)

第5章 数据的共享与保护

-导学

--导学

-标识符的作用域与可见性

--标识符的作用域与可见性

--标识符的作用域与可见性 测试题

-对象的生存期

--对象的生存期

--对象的生存期 测试题

-类的静态成员

--静态数据成员(例5-4)

--静态函数成员(例5-5)

--类的静态成员 测试题

-类的友元

--类的友元(例5-6)

--类的友元 测试题

-共享数据的保护

--共享数据的保护(例5-7)

--共享数据的保护 测试题

-多文件结构和预编译命令

--多文件结构和预编译命令(例5-10)

--多文件结构和预编译命令 测试题

-第5章小结

--小结

-实验五

--实验五

第6章 数组、指针与字符串(一)

-导学

--导学

-数组的定义与初始化

--数组的定义与使用

--数组的储存与初始化

--一维数组应用举例

--数组的定义与初始化 测试题

-数组作为函数的参数

--数组作为函数参数(例6-2)

--数组作为函数的参数 测试题

-对象数组

--对象数组

--对象数组 测试题

-基于范围的for循环

--基于范围的for循环

-指针的定义和运算

--指针的概念、定义和指针运算

--指针的初始化和赋值

--指针的算术运算、关系运算

--指针的定义和运算 测试题

-综合实例

--综合实例

-实验六(上)

--实验六上

第6章 数组、指针与字符串(二)

-指针与数组

--用指针访问数组元素

--指针数组

--指针与数组 测试题

-指针与函数

--以指针作为函数参数

--指针类型的函数

--指向函数的指针

--指针与函数 测试题

-对象指针

--对象指针

--对象指针 测试题

-动态内存分配

--动态分配与释放内存

--申请和释放动态数组(一)

--申请和释放动态数组(二)

--动态内存分配 测试题

-智能指针

--智能指针

-vector对象

--vector对象

--vector对象 测试题

-对象复制与移动

--深层复制与浅层复制

--移动构造

--对象复制与移动 测试题

-字符串

--C风格字符串

--string类

--字符串 测试题

-第6章小结

--第六章小结

-综合实例

--综合实例

-实验六(下)

--实验六(下)

类的友元(例5-6)笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。