《C++ primer plus》精炼(OOP部分)——对象和类(4)

“学习是人类进步的阶梯,也是个人成功的基石。” - 罗伯特·肯尼迪

文章目录

  • 友元函数
    • 利用友元函数重载<<运算符
    • 重载部分示例:矢量类

友元函数

先看看在上一章中我们作为例子的代码:

class Student{string name;int grade;int operator+(Student s){return this->grade+s.grade;}int operator+(int a){return this->grade+a;}
}void test(Student a,Student b)
{a.operator+(b);int c=a+b;int d=a+c;
}

注意最后一行:

int d=a+c;

这一行的意义在上文已经说明,但是这是“+”运算符,也就是说,如果我编写以下的代码,在逻辑上依旧是正确的:

int d=c+a;

虽然在逻辑上正确,但在语法上并不能通过编译,因为编译器会认为是int类型的c调用了重载的函数。为了解决这个问题,我们可以用多种方法:

  1. 编写一层非成员函数接口。观察以下代码:
int operator+(int a,Student b)//非成员函数也可以重载运算符
{return b+a;
}

当成员函数进行运算符重载时,编译器默认调用这个函数的对象是这个函数的第一个操作数;非成员函数进行运算符重载时,编译器严格按照传参顺序决定操作数顺序。加入这个函数后,上面的代码就可以被编译器解释为传入int类型的c和Student类型的a作为参数,调用非成员函数,这就是接口的思想。
2. 事实上,C++的友元函数语法可以解决这个问题。友元函数是在类声明内定义的非成员函数,但是可以访问类的保护和私有成员,可以理解为具有成员函数视野的非成员函数。当想把一个函数声明为友元函数时,在函数声明开头加friend关键字。观察以下代码:

class Student{string name;int grade;int operator+(Student s){return this->grade+s.grade;}int operator+(int a){return this->grade+a;}//友元函数在类内声明friend int operator+(int a,Student b)//因为本质上是非成员函数,因此编译器按照传参顺序决定操作数顺序{return a+b.grade;//也不能用this指针,因为对象不能用.运算符调用友元函数}friend int operator+(Student a,Student b);
}int operator+(Student a,Student b)//因为不是成员函数,所以在类外定义时不使用::运算符,而且也不用额外加friend关键字
{return a.grade+b.grade;
}

利用友元函数重载<<运算符

观察以下代码:

cout<<i;

这行代码使用了<<运算符,第一个操作数是ostream(输出流)类的对象cout,第二个操作数是一个不定类型的变量i。从上一篇我们知道大部分运算符都能进行重载,因此我们可以重载能用Student类对象作为操作数的函数。
首先,我们排除在类内重载<<运算符的可能性,因为这样就需要以Student类对象作为第一操作数,需要用以下代码来使用:

i<<cout;//i是一个Studnet类对象

代码的可读性很低,也很不习惯。因此,我们用友元函数来实现重载。
接下来,我们确定这个函数的参数和返回值。参数很好确定,第一个参数是ostream类对象,第二个参数是Student类对象。至于返回值,我们先看对于内置类型,<<运算符是怎么运作的:

cout<<a<<b<<c;//a,b,c都是int类型

对于上面这个语句的行为,我们可以理解为:

((cout<<a)<<b)<<c
  1. 第一个运算符调用函数,以ostream类对象为第一个参数,int类对象为第二个参数,输出a
  2. 第二个运算符应该也调用相同的函数,这意味着参数也相同,第一个参数也应该为ostream对象,所以第一个运算符调用的函数应该返回一个ostream类的对象

拓展到Student类型的重载,道理也是一样的,应该返回一个ostream类对象。以下是该友元函数的一种实现方式:

ostream operator*(ostream& os,Student s)
{return os<<s.name<<' '<<s.grade;//输出学生的名字和年级
}

重载部分示例:矢量类

作为一个例子,我们构建一个表示矢量的类。矢量是一个有长度的方向的量。我们可以用直角坐标和极坐标两种方式来表示。下面是这个类的声明:

namespace VECTOR {class Vector{public:enum Mode { RECT, POL };//RECT为直角坐标系模式表示矢量,POL为极坐标系模式表示矢量private:double x;//x轴的值double y;//y轴的值double mag;//矢量的长度double ang;//矢量偏转的角度Mode mode;//选择哪种模式(RECT或POL)//设置值的私有方法void set_mag();void set_ang();void set_x();void set_y();public:Vector();Vector(double n1, double n2, Mode form = RECT);void reset(double n1, double n2, Mode form = RECT);~Vector();//内联函数double xval()const {return x;}double yval()const {return y;}double magval()const {return mag;}double angval()const {return ang;}void polar_mode();//将模式设为POLvoid rect_mode();//将模式设为RECT//运算符重载Vector operator+(const Vector& b)const;Vector operator-(const Vector& b)const;Vector operator-()const;Vector operator*(double n)const;//友元函数friend Vector operator*(double n, Vector& a);friend std::ostream& operator<<(std::ostream& os, const Vector& v);};
}//end namespace VECTOR
  1. 使用VECTOR命名空间
  2. 使用enum枚举,带有RECL(直角坐标)和POL(极坐标)两个枚举量,表示这个对象用哪种方式描述矢量。
  3. private下有四种私有方法,用于直接控制类的私有变量,只有公共接口能使用这四个方法
  4. 注意第二种构造函数和reset函数的参数:
Vector(double n1, double n2, Mode form = RECT);
void reset(double n1, double n2, Mode form = RECT);

这两个函数的最后一个参数,即表示模式,有默认值,这代表在使用这个函数的时候不一定需要输入三个参数,编译器会自动采取默认参数值,但如果有恰当的参数值传入,则编译器使用新的参数值:

Vector v1(30,40)//此时编译器采用默认参数RECT
Vector v1(30,40,VECTOR::POL)//编译器采用传入的参数POL,注意POL在命名空间VECTOR中,要用::运算符
  1. 内联函数,忘记的可以看前几章

接下来是这个类的实现:

//定义表示一弧度的度数的一个变量const double Rad_to_deg = 45.0 / atan(1.0);//私有方法//从输入的x和y计算长度void Vector::set_mag(){mag = sqrt(x * x + y * y);}void Vector::set_ang(){if (x == 0.0 && y == 0.0)ang = 0.0;elseang = atan2(x, y);}//极坐标下设置x值void Vector::set_x(){x = mag * cos(mag);}//极坐标下设置y值void Vector::set_y(){y = mag * sin(ang);}//公共方法Vector::Vector(){x = y = mag = ang = 0.0;mode = RECT;}Vector::Vector(double n1, double n2, Mode form){mode = form;if (form == RECT){x = n1;y = n2;set_mag();set_ang();}else if (form == POL){mag = n1;ang = n2;set_x();set_y();}else{cout << "Incorrect 3rd argument to Vector() -- ";cout << "vector set to 0.\n";x = y = mag = ang = 0.0;mode = RECT;}}void Vector::reset(double n1, double n2, Mode form){mode = form;if (form == RECT){x = n1;y = n2;set_mag();set_ang();}else if (form == POL){mag = n1;ang = n2;set_x();set_y();}else{cout << "Incorrect 3rd argument to Vector() -- ";cout << "vector set to 0.\n";x = y = mag = ang = 0.0;mode = RECT;}}Vector::~Vector(){}void Vector::polar_mode(){mode = POL;}void Vector::rect_mode(){mode = RECT;}Vector Vector::operator+(const Vector& b)const{return Vector(x + b.x, y + b.y);}Vector Vector::operator-(const Vector& b)const{return Vector(x - b.x, y - b.y);}Vector Vector::operator-()const//返回相反数,和上面的不是一个运算符{return Vector(-x, -y);}Vector Vector::operator* (double n)const{return Vector(n * x, n * y);}std::ostream& operator<<(std::ostream& os, const Vector& v){if (v.mode == Vector::RECT){os << "(x,y) = (" << v.x << "," << v.y << ")";}else if (v.mode == Vector::POL){os << "(m,a)=(" << v.mag << "," << v.ang * Rad_to_deg << ")";}elseos << "Vector object mode is invaild";return os;}//display rectangular coordinates if mode is RECT//else display polar coordinates if mode is POLVector operator*(double n, const Vector &a)//友元函数{return a * n;}
  1. C++中的函数在角度方面使用弧度制,但在我们构造的类中使用角度制,因此需要定义一个从弧度到角度的变量。
  2. 其中一些陌生的函数进行一些数学运算,头文件为< cmath >,具体如下:
  • atan函数:接收一个正切值(直线的斜率)输出直线与x轴的夹角(-90~90度)
  • sqrt函数:输入一个数,输出这个数的平方根
  • atan2函数:接收一个点的横纵坐标,输出横纵坐标与原点的连线延伸出的直线与x轴正方向的夹角(-180~180度)
  • cos、sin函数:输入一个角度,输出这个角度的余弦/正弦值
  1. 带有参数的构造函数和reset函数会根据模式的不同,进行不同行为的初始化。
  2. 注意运算符重载和友元函数的逻辑

下面是一个使用矢量类的实例,建议自己复制下来或者敲一遍运行一下(不要忘记给声明和定义加上头文件):

//randwalk.cpp -- using the Vector class
//compile with the vect.cpp file
#include<iostream>
#include<cstdlib>//rand(),srand()函数的头文件
#include<ctime>//time()函数的头文件
#include "vector.h"
int main(void)
{using namespace std;using VECTOR::Vector;srand(time(0));//随机种子生成器double direction;Vector step;Vector result(0.0, 0.0);unsigned long steps = 0;double target;double dstep;cout << "Enter target distance (q to quit):";while (cin >> target){cout << "Enter step length:";if (!(cin >> dstep)){break;}while (result.magval() < target){direction = rand() % 360;step.reset(dstep, direction, Vector::POL);result = result + step;steps++;}cout << "After " << steps << " steps,the subject has the following location.\n";cout << result << endl;result.polar_mode();cout << "or\n" << result << endl;cout << "Average outward distance per step="<< result.magval() / steps << endl;steps = 0;result.reset(0.0, 0.0);cout << "Enter target diatance(q to quit):";}cout << "Bye!\n";cin.clear();while (cin.get() != '\n')continue;return 0;
}

请添加图片描述
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/79898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十九章、【Linux】开机流程、模块管理与Loader

19.1.1 开机流程一览 以个人计算机架设的 Linux 主机为例&#xff0c;当你按下电源按键后计算机硬件会主动的读取 BIOS 或 UEFI BIOS 来载入硬件信息及进行硬件系统的自我测试&#xff0c; 之后系统会主动的去读取第一个可开机的设备 &#xff08;由 BIOS 设置的&#xff09; …

Android NDK 中有导出 sp智能指针吗?如果没有,可以用什么方法代替 android::sp 智能指针

Android NDK 中有导出 sp智能指针吗&#xff1f;如果没有&#xff0c;可以用什么方法代替 android::sp 智能指针 Author: Lycan Note: 以下问题解答通过大模型生成&#xff0c;主要用于个人学习和备忘&#xff0c;仅供参考&#xff0c;若有错误或者侵权&#xff0c;请联系我修…

如何实现wingftpserver部署到外网访问?快解析p2p内网穿透

不少朋友选择用wing FTP来搭建部署FTP服务管理文件共享。Wing FTP Server是一个跨平台ftp服务器端&#xff0c;它有不错的可靠性和一个友好的配置界面&#xff0c;Wing FTP Server除了能提供FTP的基本服务功能以外&#xff0c;还能提供管理员终端、任务计划、基于Web的管理端和…

中国智能客服发展历程

中国智能客服的发展历程&#xff1a; 在2000年以前&#xff0c;互联网尚未普及&#xff0c;客服主要以电话沟通为主。从2000年到2010年&#xff0c;得益于计算机技术、计算机电话集成技术&#xff08;CTI&#xff09;、网络技术、多媒体机技术以及CRM、BI、ERP、OA等企业信息化…

基于SSM+Vue的校园教务系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

AK 9.12 百度Java后端研发B卷 笔试

T1(博弈论) #include <bits/stdc.h>#define endl \nusing namespace std;typedef long long LL;const int N 1e5 10;int n, m, t;void solve() {cin >> n >> m; t n m - 2;if(t & 1) cout << "Yes" << endl;else cout <&l…

MATLAB入门-字符串操作

MATLAB入门-字符串操作 注&#xff1a;本篇文章是学习笔记&#xff0c;课程链接是&#xff1a;link MATLAB中的字符串特性&#xff1a; 无论是字符还是字符串&#xff0c;都要使用单引号来‘’表示&#xff1b;在MATLAB中&#xff0c;字符都是在矩阵中存储的&#xff0c;无论…

Leetcode: 645.错误的集合 题解【超详细】

题目 集合 s 包含从 1 到 n 的整数。不幸的是&#xff0c;因为数据错误&#xff0c;导致集合里面某一个数字复制了成了集合里面的另外一个数字的值&#xff0c;导致集合 丢失了一个数字 并且 有一个数字重复 。 给定一个数组 nums 代表了集合 S 发生错误后的结果。 请你找出重复…

在WinForms应用程序中创建一个定时任务以监听鼠标左键点击事件可以通过以下步骤实现

在WinForms应用程序中创建一个定时任务以监听鼠标左键点击事件可以通过以下步骤实现&#xff1a; 1. 打开您的WinForms应用程序或创建一个新的WinForms项目。 2. 在窗体上添加一个Timer控件&#xff0c;用于定时检查鼠标左键点击事件。 3. 在窗体的构造函数或Load事件处理程…

新手如何开始Microstation CE版二次开发

一步步学习MicroStation CE MDL&#xff08;C&#xff09;开发 - 技术资料库 - Bentley 中国优先社区 - Bentley Communities https://communities.bentley.com/communities/other_communities/chinafirst/w/chinawiki/57704/microstation-ce-mdl-c一步步学习MicroStation CE A…

VS2022 开发.net 4.0的方法

原文连接&#xff08;如果安装.net 4.0 把文中的4.5改为4.0即可&#xff09; Visual Studio 2022 如何安装低版本的 .Net Framework - 一颗路边小石头 - 博客园 (cnblogs.com)

代码随想录二刷day29

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣491. 递增子序列二、力扣47. 全排列 II三、力扣46. 全排列 前言 一、力扣491. 递增子序列 class Solution {List<List<Integer>> res ne…

算法:合并两个有序数组---双指针[1]

文章来源&#xff1a; https://blog.csdn.net/weixin_45630258/article/details/132673462 欢迎各位大佬指点、三连 1、题目&#xff1a; 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元…

java关于文件记录篇章之文件夹创建篇

今天&#xff0c;创建一个文件夹目录的时候&#xff0c;创建多级目录的时候发现&#xff0c;自己老是创建失败&#xff0c;但是系统显示文件夹创建成功&#xff0c;但是你去找文件夹的时候&#xff0c;又发现创建失败&#xff0c;这里在我成功之后封装了一个创建文件夹的创建对…

小节5:Python列表list常用操作

1、对列表的基本认知&#xff1a; 列表list&#xff0c;是可变类型。比如&#xff0c;append()函数会直接改变列表本身&#xff0c;往列表里卖弄添加元素。所以&#xff0c;list_a list_a.append(123)就是错误的。如果想删除列表中的元素&#xff0c;可以用remove()函数&…

Android RecyclerView BaseSectionQuickAdapter实现分组功能

详情网站&#xff1a;手把手教你使用BaseSectionQuickAdapter实现分组功能&#xff0c;史上最详细Adapter使用教程_basequickadapter 分组_杨阿程的博客-CSDN博客 //加入二个包implementation com.android.support:recyclerview-v7:26.0.0-beta1implementation com.github.Cym…

2023/9/15 -- C++/QT

作业&#xff1a; 1> 将工程文件进行注释 2> 03login_box.pro: QT core gui #core核心库 gui图形开发库greaterThan(QT_MAJOR_VERSION, 4): QT widgets #4.0版本以上自动包含widgets库CONFIG c11 #支持C11版本# The following define makes your compiler em…

npm 发布包、更新包,同步到 cnpm (taobao镜像)

如果还没有npm账号&#xff0c;请直接到 https://www.npmjs.com 注册。已有账号&#xff0c;根据下面的流程登录&#xff1a; 1. 登录 执行命令&#xff1a; npm login根据步骤输入你的用户名(Username)&#xff0c;密码(Password)&#xff0c;邮箱(Email)&#xff0c;邮箱接…

C# byte转bit bit组装成byte

bit组装成Byte public void BitToByte(){List<char[]> chars new List<char[]>();char mm1 1;char mm2 1;char mm3 1;char mm4 1;char mm 0;char[] chars0 new char[]{ mm1, mm2, mm3, mm4, mm, mm, mm, mm };//0-7 chars.Add(chars0);byte[] bytes new b…

深度学习推荐系统(八)AFM模型及其在Criteo数据集上的应用

深度学习推荐系统(八)AFM模型及其在Criteo数据集上的应用 1 AFM模型原理及其实现 沿着特征工程自动化的思路&#xff0c;深度学习模型从 PNN ⼀路⾛来&#xff0c;经过了Wide&#xff06;Deep、Deep&#xff06;Cross、FNN、DeepFM、NFM等模型&#xff0c;进⾏了大量的、基于不…