对象友元的概念及应用

news/2024/11/8 16:36:44/

  • 友元入门
  • 友元的使用
    • 带有命名空间的函数声明
    • 类的前向声明
      • friend直接修饰类的好处
  • 友元特性
  • 友元重构(友元运算符重载)
    • 类重构
    • 全局重构
    • 友元重构

友元入门

友元friend

可以将一个函数或类进行friend修饰
修饰后的函数和类可以直接在外部访问调用本类的所有成员
友元的位置无需在意是否为public还是private

class Point {
public://在类中用friend声明该函数,在外部定义friend void display(const Point& p);//在类中修饰另一个类,另一个类可直接访问该类对象的成员friend class Line;
private:int _ix;int _iy;
};//friend修饰声明需要friend开头,定义时不需要带
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)
{//hypot函数可以返回两个参数的平方后的和在开根的值,在应用两点之间距离很常用,包含库为mathcout << "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 class Line;*/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 class Line;*/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);//为了防止代码冗余,可以直接让Line类成为Point的友元(Line可以使用Point的所有成员)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

http://www.ppmy.cn/news/77935.html

相关文章

国内有替代Intel的选择吗?

国内有替代Intel的选择吗&#xff1f; 前段时间&#xff0c;国内服务器厂商被无端制裁&#xff0c;无法进口高端处理器等元器件&#xff0c;看的笔者是义愤填膺。不过事物总有两面性&#xff0c;虽然制裁会短期影响业务开展&#xff0c;但长远看反倒有利于国内CPU、服务器产业…

2.搭建Fabric区块链网络环境——前提条件和fabric的安装

(1)安装前提条件: 这些前提条件的满足确保了你可以顺利地搭建和运行 Fabric 区块链网络,并进行链码的开发、部署和执行。 安装 Docker:确保系统上已经安装了 Docker,并且 Docker 服务正在运行。Docker:Fabric 使用 Docker 容器化技术来部署和管理区块链网络的各个组件。…

如何在Windows系统中安装Hadoop分布式文件系统(HDFS)

在Windows系统中安装Hadoop分布式文件系统(HDFS) Hadoop分布式文件系统(HDFS)是一个分布式文件系统,用于存储和处理大数据集。本文将介绍如何在Windows系统中安装和配置HDFS。 1. 安装Java 在Windows系统上安装Hadoop,首先需要安装Java。可以从Oracle官网下载Java安装…

通过Python的pdfplumber库提取pdf中的文字

文章目录 前言一、pdfplumber库是什么&#xff1f;二、安装pdfplumber库三、查看pdfplumber库版本四、pdfplumber和PyPDF2区别是什么&#xff1f;五、使用方法1.引入库2.定义pdf路径3.打开PDF文件4.获取PDF文件中的页数5.遍历每一页6.获取当前页内容7.提取文本内容8.打印文本内…

钓鱼网站Permit 离线签名之实战案例和原理讲解

目录 背景 现象分析 正确应对 原理分析之Permit 参考 背景 5 月 11 日,0x9f4a15.....56e8f8因误点钓鱼网站(大家千万别点!

QxRibbon 知:搭建 PyQt5 环境

文章目录 安装 Python安装 PyQt5安装 PyCharm构建 QxRibbon参考资料 安装 Python 参考其它教程 安装 PyQt5 PyQt5 版本&#xff1a;5.15.2 运行 windows cmd.exe 命令提示符&#xff0c;通过下列方法进行安装&#xff08;友情提醒&#xff1a;关闭流氓软件 360&#xff09; …

K210学习 (三)串口

前言 uart 模块主要用于驱动开发板上的异步串口&#xff0c;可以自由对 uart 进行配置。k210 一共有3个 uart&#xff0c;每个 uart 可以进行自由的引脚映射 一、K210串口使用步骤 1.库的导入 from machine import UART from board import board_info from fpioa_manager im…

【国产虚拟仪器】基于AD9172/AD9176的4 通道12.6GSPS 采样率16 位DA 播放FMC JESD204B 接口子卡模块

板卡概述 FMC_XM131 是一款4 通道12.6GSPS 采样率16 位DA 播放FMC子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4 规范&#xff0c;可以作为一个理想的IO 模块耦合至FPGA 前端&#xff0c;16 通道的JESD204B 接口通过FMC连接器连接至FPGA 的高速串行端…