《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; …

如何实现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…

MATLAB入门-字符串操作

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

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

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

新手如何开始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…

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

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

小节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…

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

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

idea装载jerbel以及文件上传下载

一、JRebel 1.1 Jrebel介绍 JRebel是一个Java开发工具&#xff0c;它是一款用于实时代码重载的插件。它的主要功能是在不重新启动应用程序的情况下&#xff0c;将修改后的Java代码实时应用到正在运行的应用程序中&#xff0c;从而加快开发周期&#xff0c;提高开发效率。 实…

Sqlserver如何调试存储过程

前提&#xff1a;需要使用Sql Server Managerment Studio 工具进行调试。 步骤&#xff1a; 1.选择存储过程&#xff0c;右键选择“执行存储过程”。操作过后&#xff0c;会生成一个调用存储过程的代码块。 2.以编辑模式打开所以需要调试的存储过程。 3.点击调试按钮进行调…

【视觉检测】电源线圈上的导线弯直与否视觉检测系统软硬件方案

 检测内容 线圈上的导线弯直与否检测系统。  检测要求 检测线圈上的导线有无弯曲&#xff0c;弯曲度由客户自己设定。检测速度5K/8H625PCS/H。  视觉可行性分析 对样品进行了光学实验&#xff0c;并进行图像处理&#xff0c;原则上可以使用机器视觉进行测试测量…

数据结构与算法--排序算法复习

目录 1.三种常见的简单排序&#xff1a; 1.1冒泡排序 1.2 选择排序 1.3 插⼊排序 2 常见高级排序算法 2.1 希尔排序 2.2 快速排序 2.3 归并排序 2.4计数排序 先上结论&#xff1a; 1.三种常见的简单排序&#xff1a; 1.1冒泡排序 1.⾸先在未排序数组的⾸位开始&#…

Vue 3的革命性新特性:深入了解Composition API

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

安装深度(Deepin)系统

Deepin系统安装 Deepin是和Ubuntu一样&#xff0c;是一个基于Debian的Linux的发型版本。 Deepin相对于Ubuntu&#xff0c;Deepin更适合中国用户的使用习惯。 一 官网工具制作启动盘 制作启动盘、和安装系统&#xff0c;操作非常简单&#xff0c;nice&#xff01; 官网提供了…

C语言 -- 零基础入门详解

文章目录 引言1. 第一个C语言程序&#xff1a;HelloWorld1.1 编写C语言代码&#xff1a;hello.c1.2 代码分析 2. 数据类型2.1 常量与变量2.1.1 关键字2.1.2 数据类型2.1.3 常量2.1.4 变量2.1.5 使用示例 2.2 整型&#xff1a;int2.2.1 整型变量的定义和输出2.2.2 整型变量的输入…

VRTK4⭐二.VRTK4的项目基础配置

文章目录 &#x1f7e5; 硬件基本配置&#x1f7e7; 设置XR Plug-in Management&#x1f7e8; 添加项目Tilia&#x1f7e9; 配置项目Hierarchy &#x1f7e5; 硬件基本配置 解决使用OpenXR,HTC头显正常追踪,但手柄无法使用的问题. 问题如下: 当我们按照官方的标准流程配置完Op…