C++学习笔记-第8单元 运算符重载

news2024/11/25 20:34:26

第8单元 运算符重载

文章目录

  • 第8单元 运算符重载
    • 单元导读
      • 单元导读1:深度学习与C++
      • 单元导读2:向量、矩阵、张量
    • 8.1 运算符重载与平面向量类
      • 8.1.1 运算符与函数的关系
      • 8.1.2 平面向量类
      • 8.1.3 C++运算符函数
      • 8.1.4 [C++11]左值、纯右值、将亡值
    • 8.2 重载二元运算符、数组下标运算符
      • 8.2.1 重载一般二元算术运算符
      • 8.2.2 重载复合二元算术运算符
      • 8.2.3 重载[]运算符
    • 8.3 重载一元运算符
      • 8.3.1 重载一元运算符
      • 8.3.2 重载负号运算符
      • 8.3.3 重载++和--运算符
    • 8.4 重载流操作运算符、类型转换运算符
      • 8.4.1 重载流插入/提取运算符(二元运算符)
      • 8.4.2 重载对象转换运算符
    • 8.5 重载赋值运算符及其他总结
      • 8.5.1 重载赋值运算符```=```
      • 8.5.2 运算符重载的更多说明
      • 8.5.3 更多编码规范

注:本部分内容主要来自中国大学MOOC北京邮电大学崔毅东的 《C++程序设计》课程。
注:94条 C++程序规范。


单元导读

  本单元主要介绍运算符的重载方法和使用方法。

  1. 本单元首先介绍了 Vec2D。为了学习运算符重载的方法,我们需要首先理解Vec2D这个类的成员,包括 add/subtract/dot/toString等。
  2. 要掌握 二元算术运算符的重载 方法。尤其要理解当我们调用运算符函数,执行类似 a+b 这种运算时,重载的运算符函数中的 *this是谁、函数参数是谁。
    1. 如果对this指针理解有疑问,那么需要重新回顾一下本课程第5单元的相关内容。
    2. 还要理解对于复合运算符,例如 c+=d,运算结果会对对象c产生改变。而且,该函数的返回结果是一个引用类型。
  3. 要掌握 一元运算符的重载 方法。其中特别需要注意的是,前置自增自减运算符,与后置自增自减运算符在函数原型方面的差异,要理解“dummy”参数的作用。
  4. 要掌握 数组下标运算符的重载 方法,要理解如何让数组下标运算符函数能够成为左值;
  5. 要掌握 流输出运算符的重载 方法,尤其要理解为何流输出运算符要重载为友元函数的原因。
  6. 在上述运算符重载中,大量使用了对象引用类型作为函数的参数或者返回值。要理解哪些引用类型是为了 提升性能,哪些引用类型是为了 提供左值性质

注:本单元所用的示例,是一个平面向量类。平面向量是最简单的向量,并且由此衍生出矩阵、张量以及人工智能。下面给出一些介绍。

单元导读1:深度学习与C++

首先来看下面几个概念:
   人工智能(AI, Artificial Intelligence) 包含广阔的分支:模糊逻辑、计算机视觉、自然语言处理、机器学习、推荐系统等。人工智能又分为弱人工智能(适用面窄,如小爱同学等语音助手)、强人工智能(适用面广,如与人类类似的情感表达)。
   机器学习(Machine Learning) 是人工智能的一种实现方法,也是人工智能的一种分支。简单来讲,机器学习就是设定一个任务,用算法从大量数据中学习如何完成任务,比如人口出生率的预测。
   深度学习(Deep Learning) 是机器学习的一种技术,指使用 “深度神经网络” + 海量数据 去解决某一问题的方法。深度神经网络可以近似理解为 “多层的神经网络”。
   神经网络 是指多个“神经元”连接在一起,组成一个网状的结构。这种网状的结构在数据结构中可以用 的形式表现。神经元是一个函数,具体可以参考维基百科 人工神经网络。

注:深度学习最终要落到矩阵运算/向量运算,所以最终一般都会体现在对算力的要求。

现在来探讨一下深度学习和C++之间的关系:
  从“效率”的角度来说,所有的编程语言都具有两种特性:生产效率(程序员编写程序的速度)、运行效率(完成同样一项工作的时间)。比如Python属于动态类型语言(构造块在运行时检查、库文件可以实现的功能多),所以生产效率高,并且支持跨平台;而C++属于静态类型语言(导致所有变量必须先声明),生产效率低、也不完全的支持跨平台,但是运行效率高,可以内嵌汇编语言的支持,所以能做底层优化。
  深度学习的领域主要有两类人,一类是面向应用者,使用深度学习的方法解决实际问题(如自动驾驶等),注重生产效率;另一类是面向系统开发者,开发深度学习底层系统(如Tensor Flow,使用C/C++编写),注重运行效率。

总结:因为相比于其他语言,C/C++的运行效率可以很高,所以深度学习的底层系统的编写,C++使用的最多。

单元导读2:向量、矩阵、张量

   向量(vector) 在物理学中也叫做矢量,是指一个有大小(magnitude)和方向(direction)的量。指的是。注意C++中的std::vector指的是可变长的数组,不是这个向量的意思。
  一个m*n的 矩阵(matrix) 是一个由m行(row)n列(column)元素排列成的矩形阵列。在C语言中,可以使用原生数组的方式定义一个二维矩阵 int A[4][3];而在C++中,就不再使用这种原生数组的形式,而是使用 std::array标准库中的数组。
   张量(Tensor) 指的是多维矩阵组合在一起,矩阵和矩阵之间也会有一些关系。2阶张量可以看作是矩阵,1阶张量可以看作是向量。目前流行的深度学习系统 TensorFlow 就是谷歌发起的开源项目,其使用了一套线性代数库Eigen。Eigen中的基本数据单元是矩阵(Matrix) 。它并不直接定义向量,而是将向量看作只有一列的矩阵。

8.1 运算符重载与平面向量类

8.1.1 运算符与函数的关系

//1.string类:使用“+”连接两个字符串,此时“+”不代表加法
string s1("Hello"), s2("World!");
cout << s1 + s2 << endl;
 
//2.array 与 vector类:使用[] 访问元素
array<char, 3> a{};
vector<char> v(3, 'a'); //'a', 'a', 'a'
a[0] v[1] = 'b';

//3.path类:使用“/”连接路径元素,此时“/”不代表除法
std::filesystem::path p{}; 
p = p / "C:" / "Users" / "cyd";

  在前面已经学过了运算符的一些特殊用法,需要配合某些特殊对象一起使用,比如上面的代码所示。可以看到,上述这些运算符的含义都与其原本的含义不同,也就是 运算符的重载。那运算符与函数的异同都是什么呢?如下:

  • 相同之处:
  1. 运算符本质上也是函数。只是运算符是编译器需要进行进一步解释,而函数是直接调用。
  • 不同之处:
  1. 语法上有区别。 运算符可以前缀、中缀、后缀表达,但是函数一般只能前缀表达。
3 * 2             //中缀式(常用)
* 3 2             //前缀式
3 2 *             //后缀式(RPN)
multiply(3, 2);   //前缀式
  1. 不能自定义新的运算符,只能重载。
3 ** 2      // Python中表示乘方,C/C++中报错
pow(3, 2)   // C/C++只能调用函数来计算平方
  1. 函数可overload, override产生任何想要的结果,但运算符作用于内置类型的行为不能修改。
multiply(3, 2) // 函数可以随便定义,返回什么都行
3 * 2          // 结果只能为6

老师推荐:

  • 同学们在学完编译型语言(C/C++、Java等),然后再学一门脚本语言(如Python、Perl等),此时程序设计就基本上入门了,最后强烈建议学一门函数式编程语言(Haskell)。虽然C++/Python都引入了函数式编程语言的一些概念,如C++11中的匿名、函数等,但都不全面,而函数式编程语言的思想可以开阔很多编程思路,比如Hskell可以定义新的运算符、甚至修改已有运算符的含义。

8.1.2 平面向量类

  本小节学习平面向量类(2D Vector Class),这个类是为进一步学习 运算符重载 做准备。那首先,如何在C++中描述平面向量呢?在C++的STL(标准库)中存在vector类,但这只是变长数组,并不是数学意义上的“向量”。于是,现在我们要自行定义一个平面向量类,首先定义两个维度的数据成员,有以下两种方法:

//方法一:在类中定义两个(因为是二维)私有数据成员
double x_, double y_
//方法二:将两个元素直接放在同一个数组中
std::array<double, 2> v_;

并期望二维向量能进行如下运算:

表8-1 平面向量类可以执行的运算
运算示例
加减乘(1,2)+(3,4) --> (4,7)
(1,2)-(3,4) --> (-1,-1)
(1,2)*3 --> (3,6) //数乘
(1,2)*(3,4) --> (3,8) //点积
求长度和方向|(1,2)| --> sqrt(1*1+2*2)
dir(1,2) --> arctan(1/2)
判等==、!=
比较长度<、<=、>、>=
类型转换double,也就是向量长度。
string,转换成字符串。
负值-(1,2) --> (-1,-2)
自加1、自减(1,2) --自加1-->(3,4)

于是将上述定义和运算总结一下,便可以得到平面向量类的基本示意图:

图8-1 Vec2D类示意图

唯一需要注意的是,函数参数类型设置为引用类型的一个重要目的是避免对象拷贝的开销,以提高代码运行效率。

那接下来根据 Kent Beck 在《测试驱动开发》中所提及的开发设计方法(而不是软件测试方法):测试驱动开发(Test-Driven Development,TDD)。给出下面创建平面向量类的步骤:

(1) 先编写测试代码,而不是功能代码。
(2) 编译运行测试代码,发现不能通过。
(3) 做一些小小的改动(编写功能代码),尽快地让测试程序可运行。
(4) 重构代码,优化设计。

注:具体到Vec2D来说,相当于先写main()函数,然后用到啥就在Vec2D中添加啥代码。

最后介绍一下可能会用到的数学函数 及 编码规范:

#include<cmath> //以下数学函数都包含在这个头文件中
double atan(double x); //返回x的反正切值,以弧度为单位。有其他类型重载
double sqrt(double x); //返回x的平方根。有其它类型重载
double pow (double b, double exp); // 返回b的exp次方。有其它类型重载

编码规范

  • 12: 一般变量的名字应该与变量的类型相同;特定变量都是有角色的,这类变量经常用角色加上类型命名,比如:
//两个例子:名称与类型相同
void setTopic(Topic* topic)
    // NOT: void setTopic(Topic* value)
    // NOT: void setTopic(Topic* aTopic)
    // NOT: void setTopic(Topic* t)
void connect(Database* database)
    // NOT: void connect(Database* db)
    // NOT: void connect (Database* oracleDB)
//两个例子:名称=角色+类型
Point startingPoint, centerPoint;
Name loginName;

下面展示一下Vec2D类的测试代码:

  • 注意整个开发流程遵循TDD,先写主函数,再写Vec2D类的声明和定义。而且是先写函数,再考虑运算符重载。
  • 添加Vec2D类的代码时,可以使用VS2019(及以上)的“类向导”功能,可以自动生成头文件和源文件,以防止代码遗漏。

头文件 VectorsD.h

#pragma once
#include<string>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    /************复合二元运算符************/
    //暂略
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception>//使用out_of_range异常类

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
/************复合二元运算符************/
//暂略
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//求向量与x+轴的夹角
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,5 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1.tostring() << endl;
    cout << "v2= " << v2.tostring() << endl;
    // 用户输入向量
    //暂略
    /************一般二元运算符************/
    cout << endl << "2. 一般二元运算符+、-、*" << endl;
    // 向量加法
    v3 = v1.add(v2);  //向量+向量
    v4 = v1.add(10.0);//向量+数
    cout << "v1+v2   = " << v3.tostring() << endl;
    cout << "v3+10.0 = " << v4.tostring() << endl;
    // 向量减法
    v3 = v2.subtract(v1);//向量-向量
    v4 = v2.subtract(3);//向量-数
    cout << "v2-v1  = " << v3.tostring() << endl;
    cout << "v2-3.0 = " << v4.tostring() << endl;
    // 向量点积、向量数乘
    double d1 = v1.dot(v2); //向量*向量
    v4 = v1.multiply(2.0); //向量*数
    cout << "v1*v2  = " << d1 << endl;
    cout << "v1*2.0 = " << v4.tostring() << endl;
    /************复合二元运算符************/
    cout << endl << "3. 复合二元运算符+=、-=、*=" << endl;
    /************数组下标运算符************/
    cout << endl << "4. 数组下标运算符[]" << endl;
    cout << "v1.x_ =" << v1.at(0) << endl;
    cout << "v1.y_ =" << v1.at(1) << endl;
    /************一元运算符************/
    //5. 负号运算符
    cout << endl << "5. 负号运算符" << endl;
    v3 = v2.negative();
    cout << "-v2 = " << v3.tostring() << endl;
    //6. 向量自增、自减
    cout << endl << "6. 自增、自减运算符" << endl;
    /************对象转换运算符************/
    //7. 向量长度、向量角度
    cout << endl << "7. 向量长度、向量角度" << endl;
    cout << "v1长度 = " << v1.magnitude() << endl;
    cout << "v1角度 = " << v1.direction() << endl;
    //8. 比较两个向量,输出-1/0/1
    cout << endl << "8. 比较两个向量" << endl;
    cout << "v1 > v2: " << v1.compareTo(v2) << endl;

    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3.000000,5.000000)
v2= (4.000000,6.000000)

2. 一般二元运算符+-*
v1+v2   = (7.000000,11.000000)
v3+10.0 = (13.000000,15.000000)
v2-v1  = (1.000000,1.000000)
v2-3.0 = (1.000000,3.000000)
v1*v2  = 42
v1*2.0 = (6.000000,10.000000)

3. 复合二元运算符+=-=*=

4. 数组下标运算符[]
v1.x_ =3
v1.y_ =5

5. 负号运算符
-v2 = (-4.000000,-6.000000)

6. 自增、自减运算符

7. 向量长度、向量角度
v1长度 = 5.83095
v1角度 = 1.03038

8. 比较两个向量
v1 > v2: -1

8.1.3 C++运算符函数

  本小节学习C++的运算符重载,主要是针对上一小节的代码进行改进。那上一小节已经对Vec2D类中的操作函数做出了定义,并且也都运行成功了,为什么还要用运算符重载呢?这是因为上一小节都是定义的函数,需要程序员记住特定的函数名,在调用起来并不方便、不直观。比如下面实现“两个平面向量比较”,显然运算符重载后的代码更直观且更符合比较逻辑:

cout << "v1.compareTo(v2): " << v1.compareTo(v2) << endl;//直接定义操作函数,不直观,返回-1/0/1
cout << "v1 < v2         : " << v1 < v2          << endl;//运算符重载,直观,可以返回布尔类型

  但是,并不是所有的运算符都可以重载的。下面给出可重载及不可重载的运算符,以及运算符重载时需要注意的原则:

可重载的运算符:

    1. 类型转换运算符:doubleintchar, ……。
    1. 内存分配和归还运算符:new/deletenew []/delete[]
    1. 用户自定义字面量运算符(自C++11起):""_suffix,其中suffix可以任意写,注意使用时引入头文件#include<string>
    1. 一般运算符:下图所示的40个运算符,其中<=>co_await是C++20后才引入的。

不可重载的运算符: 除了上面可重载运算符,其他都是不可重载。

    1. 类属关系运算符:.(点)。
    1. 成员指针运算符:.*(点+星号)。
    1. 作用域运算符:::(两个冒号)。
    1. 三元运算符:?:
    1. 编译预处理符号:#
    1. 一些关键字:sizeof()等。

运算符重载的限制:

    1. 优先级和结合性不变。
    1. 不可创造新的运算符,起码C++不支持。

  有了上面的了解后,接下来就来介绍如何定义和使用这些运算符函数。如下图,运算符重载会使用到C++关键字 operator,表示要对其后的运算符进行重载。注意“运算符”本身就是一个函数,只不过为了和普通函数区分开,才称之为“运算符函数”。

  • 调用:调用的方法和使用其原型没有区别,但当然也可以直接用其函数名operator<。下图中给出的两种调用方法等价,v1表示当前的类,v2表示参数。
  • 原型(函数定义):this指向v1对象、SecondVec2D就是v2本身。operator<指的是运算符重载,return语句中的<则是小于符号原本的定义。当然传参时可以不使用引用,但使用引用无需将实参重新拷贝成一个形参,可以提供函数调用效率。
图8-2 运算符函数的原型与调用

  现在来着重讨论运算符函数的“调用形式”,也就是该如何确定“谁”的运算符?“谁”是运算符函数的参数?前面提及,“运算符重载”最大的好处就是直观,也就是“调用形式”简洁直观,无需记忆复杂的函数名称,直接使用运算符即可。但上面例子中的<只是一个二元运算符,那其他的一些前缀运算符、后缀运算符、特殊运算符该怎么进行“调用”呢?于是下表就较为全面的介绍了几乎所有运算符的调用形式。

表8-2 运算符函数的调用形式
运算符名称表达式作为成员函数作为非成员函数示例
前缀@a(a).operator@ ( )operator@ (a)!std::cin 调用 std::cin.operator!()
二元a@b(a).operator@ (b)operator@ (a, b)std::cout<< 42 调用 std::cout.operator<<(42)
赋值a=b(a).operator= (b)不能是非成员std::string s; s = "abc"; 调用 s.operator=("abc")
函数调用a(b...)(a).operator()(b...)不能是非成员std::random_device r; auto n = r(); 调用 r.operator()()
数组下标a[b](a).operator[](b)不能是非成员std::map<int, int> m; m[1] = 2; 调用 m.operator[](1)
成员指针a->(a).operator-> ( )不能是非成员auto p = std::make_unique<S>(); p->bar() 调用 p.operator->()
后缀a@(a).operator@ (0)operator@ (a, 0)std::vector<int>::iterator i = v.begin(); i++ 调用 i.operator++(0)
此表中,@ 是表示所有匹配运算符的占位符:@a 为所有前缀运算符,a@ 为除 -> 以外的所有后缀运算符,a@b 为除 = 以外的所有其他运算符

注:后面写代码的时候还会详细介绍。

8.1.4 [C++11]左值、纯右值、将亡值

  本小节来学习C++11中对于表达式的分类。其实早在C++03中就存在左值右值的区别,通俗理解就是:

  1. 能放在等号左边的是lvalue。
  2. 只能放在等号右边的是rvalue,比如 int x=6中,x是左值,6是右值。
  3. lvalue可以作为rvalue使用,比如int y; y=x;中,x就从上面的左值变成了右值。

而在C++11中对于左值和右值则做出了更加详细的分类,这些详细分类涉及到“移动语义”的问题,目的在于提高程序的运行效率和运行性能。但下面先不介绍“移动语义”,而是介绍一下分类:左值、右值、将亡值。

  1. 左值(Left Value, Lvalue)指定了一个 可以取地址的 函数或者对象。

代码示例:

int lv1{ 42 };              //lv1是可取地址的对象,左值
int main() {
  int& lv2{ lv1 };          //左值lv2引用对象,lv1和lv2地址相同
  int* lv3{ &lv1 };         //左值指针lv3指向对象,也可以取地址
}
int& lv4() { return lv1; }  //函数返回引用,本质上就是lv1,可取地址,所以函数返回左值

左值例子:

    1. 解引用表达式*p:显然可以取地址&(*p)
    1. 字符串字面量"abc":首地址为指针显然可以取地址。
    1. 前置自增/自减表达式++i/--i:由于是先自增自减,所以&(++i)就是i的地址。所以后置自增/自减表达式i++/i--并不是左值。
    1. 赋值或复合运算符表达式(x=ym*=n等)。

更详细说明参考:Understanding Lvalues, PRvalues and Xvalues in C/C++ with Examples。

  1. 纯右值(Pure Right Value, PRvalue):是不和对象相关联的值(字面量)或者其求值结果是字面量或者一个匿名的临时对象。比如下面纯右值例子:
    1. 除字符串字面量以外的字面量,比如 32'a'。注意上面提到字符串字面量"abc"是左值。
    1. 返回非引用类型的函数调用 int f() { return 1;}
    1. 后置自增/自减表达式i++/i--
    1. 算术/逻辑/关系表达式:算术表达式(a+b/a&b/a<<b)、与或非(a&&b/a||b/~a)、判断(a==b/a>=b/a<b)。
    1. 取地址(&x)。
    1. 左值可以当成右值使用。

更详细说明参考:C++11 中的左值、右值和将亡值。

  1. 将亡值(eXpiring Value, Xvalue)也指定了一个对象,是一个 将纯右值转换为右值引用&& 的表达式。也就是说,将亡值一定会涉及到右值引用&&

代码示例:

/**********左值引用示例*********/
int a = 1;
int& b = a; //普通的左值引用
/**********右值引用示例*********/
int prv(int x) { return 6 * x; } //纯右值,非引用类型的函数调用
int main() {
  const int& lvr5{ 21 };    //常量左值引用,可引用纯右值。
  int& lvr6{ 22 };          //报错!非常量左值引用,不可引用纯右值。
  int&& rvr1{ 22 };         //右值引用,可以引用纯右值。
  int& lvr7{ prv(2) };      //报错!非常量左值引用,不可引用纯右值。
  int&& rvr2{ prv(2) };     //右值引用,普通函数返回值。
  rvr1 = ++rvr2;            //右值引用,做左值使用。
}

更详细的例子可以参考:
What are rvalues, lvalues, xvalues, glvalues, and prvalues?
Understanding Lvalues, PRvalues and Xvalues in C/C++ with Examples

最后总结一下,C++11将左值、右值做了一些层次化的分类,任何一个表达式都是属于某一类值类型的。表达式分类逻辑关系如下图:

表达式
泛左值glvalue
左值lvalue
将亡值xvalue
右值rvalue
纯右值prvalue
图8-3 表达式分类逻辑关系

这些东西会在未来学到右值引用和移动语义的时候用上。

8.2 重载二元运算符、数组下标运算符

8.2.1 重载一般二元算术运算符

  本节还是回到平面向量类Vec2D的创建中来,首先来看“一般二元算术运算符”的重载,也就是“加减乘除”的重载。注意下面的重载中,对于double浮点数+Vec2D类的形式(z + b),必须声明成友元函数friend,否则就是对double中的加法运算符+进行重载,所以必须是友元函数。

/**********函数调用**********/
Vec2D a{1,2}, b{3,6};
double z{1.3};
Vec2D c = a + b; // 等价于a.operator+(b); --对应的原型--> Vec2D Vec2D::operator+(Vec2D);
Vec2D d = a + z; // 等价于a.operator+(z); --对应的原型--> Vec2D Vec2D::operator+(double);
Vec2D e = z + b; // 等价于z.operator+(b); --对应的原型--> Vec2D double::operator+(Vec2D);  错误!
/**********函数原型**********/
struct Vec2D {
  Vec2D operator +(Vec2D);                //成员函数,对应上述a+b
  Vec2D operator +(double);               //成员函数,对应上述a+z
  friend Vec2D operator +(double, Vec2D); //非成员(友元)函数,对应上述z+b
};
Vec2D operator +(double, Vec2D) { //函数外定义就不需要再写friend关键字了
    //实现代码...
}

下面针对8.1.2小节代码中的“一般二元运算符”,进行运算符+ - *重载。
头文件 VectorsD.h

#pragma once
#include<string>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D operator+(const Vec2D& secondVec2D);  //重载+:向量+向量
    Vec2D operator+(const double num);          //重载+:向量+数
    friend Vec2D operator+(const double num, const Vec2D& secondVec2D);//重载+:数+向量
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    Vec2D operator-(const Vec2D& secondVec2D);  //重载-:向量-向量
    Vec2D operator-(const double num);          //重载-:向量-数
    friend Vec2D operator-(const double num, const Vec2D& secondVec2D);//重载-:数-向量
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    double operator*(const Vec2D& secondVec2D);  //重载*:向量*向量
    Vec2D operator*(const double num);          //重载*:向量*数
    friend Vec2D operator*(const double num, const Vec2D& secondVec2D);//重载*:数*向量
    /************复合二元运算符************/
    //暂略
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception>//使用out_of_range异常类

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//重载+:向量+向量
Vec2D Vec2D::operator+(const Vec2D& secondVec2D) {
    return Vec2D(x_ + secondVec2D.x_, y_ + secondVec2D.y_);
}
//重载+:向量+数
Vec2D Vec2D::operator+(const double num) {
    return Vec2D(this->x_ + num, this->y_ + num);
}
//重载+:数+向量
Vec2D operator+(const double num, const Vec2D& secondVec2D) {
    return Vec2D(secondVec2D.x_ + num, secondVec2D.y_ + num);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//重载-:向量-向量
Vec2D Vec2D::operator-(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//重载-:向量-数
Vec2D Vec2D::operator-(const double num) {
    return Vec2D(this->x_ - num, this->y_ - num);
}
//重载-:数-向量
Vec2D operator-(const double num, const Vec2D& secondVec2D) {
    return Vec2D(num - secondVec2D.x_, num - secondVec2D.y_);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
//重载*:向量*向量
double Vec2D::operator*(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//重载*:向量*数
Vec2D Vec2D::operator*(const double num) {
    return Vec2D(this->x_ * num, this->y_ * num);
}
//重载*:数*向量
Vec2D operator*(const double num, const Vec2D& vec2d) {
    return Vec2D(vec2d.x_ * num, vec2d.y_ * num);
}
/************复合二元运算符************/
//暂略
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//求向量与x+轴的夹角
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,5 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1.tostring() << endl;
    cout << "v2= " << v2.tostring() << endl;
    // 用户输入向量
    //暂略
    /************一般二元运算符************/
    cout << endl << "2. 一般二元运算符+、-、*" << endl;
    // 向量加法
    v3 = v1 + v2;  //向量+向量
    v4 = v1 + 10.0;//向量+数
    v5 = 10.0 + v1;//数+向量
    cout << "v1+v2   = " << v3.tostring() << endl;
    cout << "v1+10.0 = " << v4.tostring() << endl;
    cout << "10.0+v1 = " << v5.tostring() << endl;
    // 向量减法
    v3 = v2 - v1; //向量-向量
    v4 = v2 - 3.0;//向量-数
    v5 = 3.0 - v2;//数-向量
    cout << "v2-v1  = " << v3.tostring() << endl;
    cout << "v2-3.0 = " << v4.tostring() << endl;
    cout << "3.0-v2 = " << v5.tostring() << endl;
    // 向量点积、向量数乘
    double d1 = v1*v2; //向量*向量
    v3 = v1*2.0;       //向量*数
    v4 = 2.0*v1;       //数*向量
    cout << "v1*v2  = " << d1 << endl;
    cout << "v1*2.0 = " << v3.tostring() << endl;
    cout << "2.0*v1 = " << v4.tostring() << endl;
    
    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3.000000,5.000000)
v2= (4.000000,6.000000)

2. 一般二元运算符+-*
v1+v2   = (7.000000,11.000000)
v1+10.0 = (13.000000,15.000000)
10.0+v1 = (13.000000,15.000000)
v2-v1  = (1.000000,1.000000)
v2-3.0 = (1.000000,3.000000)
3.0-v2 = (-1.000000,-3.000000)
v1*v2  = 42
v1*2.0 = (6.000000,10.000000)
2.0*v1 = (6.000000,10.000000)

最后注:

【判断】对于 + - * /这四个运算符而言,一般情况下,重载这四个运算符后,运算符函数的返回值类型可以是左值引用类型。 错!!

8.2.2 重载复合二元算术运算符

  本小节来介绍如何重载“简写/复合运算符”,也就是:+=-=*=/=(其实还有左移等于运算符<<=、右移等于运算符>>=,但是本小节不考虑)。注意由于+=-=是可以连续调用的,如a+=b+=c,所以重载后函数返回类型应该为引用类型。

/**********函数调用**********/
v1 += v2; //语句执行后,v1的值被改变了,和单纯的加法操作不同
/**********函数原型**********/
//首先回顾一下成员函数 Vec2D::add
Vec2D Vec2D::add(const Vec2D& secondVec2D) { //纯右值
    double m = x_ + secondVec2D.getX()
    double n = y_ + secondVec2D.y_;
    return Vec2D(m, n);  //临时的匿名对象
}
//重载+=运算符
// Vec2D Vec2D::operator +=(const Vec2D& secondVec2D){//参数为“常量+引用”,提高程序运行效率
//     *this = this->add(secondVec2D); //关键点:改变了v1的值
//     return (*this);
//     //return this->add(secondVec2D);可行吗??
// }
//但是上述代码无法实现+=的连续调用,所以返回值改成引用类型:
Vec2D& Vec2D::operator +=(const Vec2D& secondVec2D){//参数为“常量+引用”,提高程序运行效率
    x_ += secondVec2D.x_;
    y_ += secondVec2D.y_;
    return (*this);
}

下面针对8.1.2小节代码中的“复合二元运算符”,进行运算符+=-=重载,*=由于类型会变换所以不考虑。下面的代码仅考虑两端都是向量的用法。
头文件 VectorsD.h

#pragma once
#include<string>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D operator+(const Vec2D& secondVec2D);  //重载+:向量+向量
    Vec2D operator+(const double num);          //重载+:向量+数
    friend Vec2D operator+(const double num, const Vec2D& secondVec2D);//重载+:数+向量
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    Vec2D operator-(const Vec2D& secondVec2D);  //重载-:向量-向量
    Vec2D operator-(const double num);          //重载-:向量-数
    friend Vec2D operator-(const double num, const Vec2D& secondVec2D);//重载-:数-向量
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    double operator*(const Vec2D& secondVec2D);  //重载*:向量*向量
    Vec2D operator*(const double num);          //重载*:向量*数
    friend Vec2D operator*(const double num, const Vec2D& secondVec2D);//重载*:数*向量
    /************复合二元运算符************/
    Vec2D& operator+=(const Vec2D& secondVec2D);  //重载+=:向量+向量
    Vec2D& operator-=(const Vec2D& secondVec2D);  //重载-=:向量-向量
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception>//使用out_of_range异常类

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//重载+:向量+向量
Vec2D Vec2D::operator+(const Vec2D& secondVec2D) {
    return Vec2D(x_ + secondVec2D.x_, y_ + secondVec2D.y_);
}
//重载+:向量+数
Vec2D Vec2D::operator+(const double num) {
    return Vec2D(this->x_ + num, this->y_ + num);
}
//重载+:数+向量
Vec2D operator+(const double num, const Vec2D& secondVec2D) {
    return Vec2D(secondVec2D.x_ + num, secondVec2D.y_ + num);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//重载-:向量-向量
Vec2D Vec2D::operator-(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//重载-:向量-数
Vec2D Vec2D::operator-(const double num) {
    return Vec2D(this->x_ - num, this->y_ - num);
}
//重载-:数-向量
Vec2D operator-(const double num, const Vec2D& secondVec2D) {
    return Vec2D(num - secondVec2D.x_, num - secondVec2D.y_);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
//重载*:向量*向量
double Vec2D::operator*(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//重载*:向量*数
Vec2D Vec2D::operator*(const double num) {
    return Vec2D(this->x_ * num, this->y_ * num);
}
//重载*:数*向量
Vec2D operator*(const double num, const Vec2D& vec2d) {
    return Vec2D(vec2d.x_ * num, vec2d.y_ * num);
}
/************复合二元运算符************/
//重载+=:向量+向量
Vec2D& Vec2D::operator+=(const Vec2D& secondVec2D) {
    x_ += secondVec2D.x_;
    y_ += secondVec2D.y_;
    return (*this);
}
//重载-=:向量-向量
Vec2D& Vec2D::operator-=(const Vec2D& secondVec2D) {
    x_ -= secondVec2D.x_;
    y_ -= secondVec2D.y_;
    return (*this);
}
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//求向量与x+轴的夹角
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,5 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1.tostring() << endl;
    cout << "v2= " << v2.tostring() << endl;
    // 用户输入向量
    //暂略
    /************复合二元运算符************/
    cout << endl << "3. 复合二元运算符+=、-=、*=" << endl;
    v3 = v1; v3 += v2;//向量+=向量
    v4 = v1; v4 -= v2;//向量-=向量
    cout << "v1+=v2 : " << v3.tostring() << endl;
    cout << "v1-=v2 : " << v4.tostring() << endl;
    v3 = v1; v4 = v2; v3 += v4 += v2;//连续+=
    cout << "运行 v3=v1; v4=v2; v3+=v4+=v2; 后:" << endl;
    cout << "v3 : " << v3.tostring() << endl;
    cout << "v4 : " << v4.tostring() << endl;

    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3.000000,5.000000)
v2= (4.000000,6.000000)

3. 复合二元运算符+=-=*=
v1+=v2 : (7.000000,11.000000)
v1-=v2 : (-1.000000,-1.000000)
运行 v3=v1; v4=v2; v3+=v4+=v2; 后:
v3 : (11.000000,17.000000)
v4 : (8.000000,12.000000)

8.2.3 重载[]运算符

  本小节来介绍如何重载数组下标运算符,也就是[][]重载后可用类似数组的语法格式访问对象内容,尤其是当类中的对象成员数量非常多的时候(比如80维向量),那么使用[]运算符就会很方便。下面给出重载后,使用[]运算符读出Vec2D中对应索引元素的场景:

/**********函数调用**********/
Vec2D v {8, 9};
cout << "v.x_: " << v[0] << "v.y_: " << v[1] << endl;
/**********函数原型**********/
double Vec2D::operator[](const int &index) {
    if (index == 0)
        return x_;
    else if (index == 1)
        return y_;
    else {
        cout << "index out of bound" << endl;
        exit(0);//直接退出程序,但实际可以抛出异常,下一章学习
    }
}

  但值得注意的是,上面仅给出了使用[]运算符读元素的场景。实际上,我们希望数组下标运算符[]可以既作为访问器(accessor)读出类成员,也可以作为修改器(mutator)写入相应位置的类成员。 所以按照上面的代码来写入数据,就会报错。进一步改进代码,使[]返回一个引用,就可以解决这个问题:

/**********函数报错**********/
double a = v2[ 0 ]; //访问器,读,没有报错
v2[ 1 ]=3.0;        //修改器,写,compile error:Lvalue required in function main()
/**********代码改进**********/
double& Vec2D::operator[](const int &index) { //此时函数返回引用,既可以当左值,也可以当右值
    //其他代码无需修改
    //现在,就可以使用[]运算符,修改Vec2D的成员对象了
}

【判断】返回一个非const引用类型的函数是可以放在等号左边参与赋值运算的。(对)

下面针对8.1.2小节代码中“数组下标运算符”,添加数组下标运算符[]的重载。注意要求能通过[]读取或者更改Vec2D的元素。

头文件 VectorsD.h

#pragma once
#include<string>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D operator+(const Vec2D& secondVec2D);  //重载+:向量+向量
    Vec2D operator+(const double num);          //重载+:向量+数
    friend Vec2D operator+(const double num, const Vec2D& secondVec2D);//重载+:数+向量
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    Vec2D operator-(const Vec2D& secondVec2D);  //重载-:向量-向量
    Vec2D operator-(const double num);          //重载-:向量-数
    friend Vec2D operator-(const double num, const Vec2D& secondVec2D);//重载-:数-向量
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    double operator*(const Vec2D& secondVec2D);  //重载*:向量*向量
    Vec2D operator*(const double num);          //重载*:向量*数
    friend Vec2D operator*(const double num, const Vec2D& secondVec2D);//重载*:数*向量
    /************复合二元运算符************/
    Vec2D& operator+=(const Vec2D& secondVec2D);  //重载+=:向量+向量
    Vec2D& operator-=(const Vec2D& secondVec2D);  //重载-=:向量-向量
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    double& operator[](const int& index);//重载[]
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception>//使用out_of_range异常类

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//重载+:向量+向量
Vec2D Vec2D::operator+(const Vec2D& secondVec2D) {
    return Vec2D(x_ + secondVec2D.x_, y_ + secondVec2D.y_);
}
//重载+:向量+数
Vec2D Vec2D::operator+(const double num) {
    return Vec2D(this->x_ + num, this->y_ + num);
}
//重载+:数+向量
Vec2D operator+(const double num, const Vec2D& secondVec2D) {
    return Vec2D(secondVec2D.x_ + num, secondVec2D.y_ + num);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//重载-:向量-向量
Vec2D Vec2D::operator-(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//重载-:向量-数
Vec2D Vec2D::operator-(const double num) {
    return Vec2D(this->x_ - num, this->y_ - num);
}
//重载-:数-向量
Vec2D operator-(const double num, const Vec2D& secondVec2D) {
    return Vec2D(num - secondVec2D.x_, num - secondVec2D.y_);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
//重载*:向量*向量
double Vec2D::operator*(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//重载*:向量*数
Vec2D Vec2D::operator*(const double num) {
    return Vec2D(this->x_ * num, this->y_ * num);
}
//重载*:数*向量
Vec2D operator*(const double num, const Vec2D& vec2d) {
    return Vec2D(vec2d.x_ * num, vec2d.y_ * num);
}
/************复合二元运算符************/
//重载+=:向量+向量
Vec2D& Vec2D::operator+=(const Vec2D& secondVec2D) {
    x_ += secondVec2D.x_;
    y_ += secondVec2D.y_;
    return (*this);
}
//重载-=:向量-向量
Vec2D& Vec2D::operator-=(const Vec2D& secondVec2D) {
    x_ -= secondVec2D.x_;
    y_ -= secondVec2D.y_;
    return (*this);
}
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
//重载[]
double& Vec2D::operator[](const int& index) {
    return this->at(index);
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//求向量与x+轴的夹角
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,5 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1.tostring() << endl;
    cout << "v2= " << v2.tostring() << endl;
    // 用户输入向量
    //暂略
    /************数组下标运算符************/
    cout << endl << "4. 数组下标运算符[]" << endl;
    cout << "v1.x_ =" << v1[0] << endl;
    cout << "v1.y_ =" << v1[1] << endl;
    v1[0] = 6.0;
    cout << "v1.x_ =" << v1[0] << endl;
    cout << "v1.y_ =" << v1[1] << endl;

    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3.000000,5.000000)
v2= (4.000000,6.000000)

4. 数组下标运算符[]
v1.x_ =3
v1.y_ =5
v1.x_ =6
v1.y_ =5

8.3 重载一元运算符

8.3.1 重载一元运算符

  本小节来介绍如何重载一元运算符(Unary Opeartor),所谓“一元运算符”就是只有一个操作数的运算符,比如--++-(负号)、*(解引用),通常使用@代表单目运算符。

单目运算符
前置单目运算符
后置单目运算符
图8-4 单目运算符分类

  在重载时,通常将运算符分成前置单目运算符(如-负号、*解引用)和后置单目运算符来考虑。当编译器遇到前置运算符@obj;时要注意函数的定义:

  1. operator@obj的类的成员函数:则调用obj.operator @() //无参。也就是说,重载前置单目运算符时,若将运算符重载为类的成员函数,则该运算符函数无需参数。
  2. operator@obj的类的friend函数(相当于类外函数):则调用operator @(obj)

8.3.2 重载负号运算符

  本小节来学习如何重载负号运算符-。当然正号+和负号-都是一元运算符,但是一般我们并不处理+,所以就只讨论负号运算符-。由于调用-时无需改变当前对象的值,只需返回一个Vec2D对象(设计为临时匿名对象),所以-属于“类的成员函数”,则其满足:

  1. 只对调用该运算符的对象起作用。
  2. 作为对象成员的一元运算符无参数!
/**********函数调用**********/
Vec2D v1(2, 3);
Vec2D v2 = -v1;   //向量v1求负值,v1的值不变!
cout << v1.toString();
/**********函数定义**********/
Vec2D Vec2D::operator-(){
    return Vec2D(-this->x_, -this->y_); //返回匿名临时对象 
}

下面针对8.1.2小节代码中“向量求负值”,添加负号运算符-的重载。
头文件 VectorsD.h

#pragma once
#include<string>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D operator+(const Vec2D& secondVec2D);  //重载+:向量+向量
    Vec2D operator+(const double num);          //重载+:向量+数
    friend Vec2D operator+(const double num, const Vec2D& secondVec2D);//重载+:数+向量
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    Vec2D operator-(const Vec2D& secondVec2D);  //重载-:向量-向量
    Vec2D operator-(const double num);          //重载-:向量-数
    friend Vec2D operator-(const double num, const Vec2D& secondVec2D);//重载-:数-向量
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    double operator*(const Vec2D& secondVec2D);  //重载*:向量*向量
    Vec2D operator*(const double num);          //重载*:向量*数
    friend Vec2D operator*(const double num, const Vec2D& secondVec2D);//重载*:数*向量
    /************复合二元运算符************/
    Vec2D& operator+=(const Vec2D& secondVec2D);  //重载+=:向量+向量
    Vec2D& operator-=(const Vec2D& secondVec2D);  //重载-=:向量-向量
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    double& operator[](const int& index);//重载[]
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D operator-();  //重载-
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception>//使用out_of_range异常类

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//重载+:向量+向量
Vec2D Vec2D::operator+(const Vec2D& secondVec2D) {
    return Vec2D(x_ + secondVec2D.x_, y_ + secondVec2D.y_);
}
//重载+:向量+数
Vec2D Vec2D::operator+(const double num) {
    return Vec2D(this->x_ + num, this->y_ + num);
}
//重载+:数+向量
Vec2D operator+(const double num, const Vec2D& secondVec2D) {
    return Vec2D(secondVec2D.x_ + num, secondVec2D.y_ + num);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//重载-:向量-向量
Vec2D Vec2D::operator-(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//重载-:向量-数
Vec2D Vec2D::operator-(const double num) {
    return Vec2D(this->x_ - num, this->y_ - num);
}
//重载-:数-向量
Vec2D operator-(const double num, const Vec2D& secondVec2D) {
    return Vec2D(num - secondVec2D.x_, num - secondVec2D.y_);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
//重载*:向量*向量
double Vec2D::operator*(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//重载*:向量*数
Vec2D Vec2D::operator*(const double num) {
    return Vec2D(this->x_ * num, this->y_ * num);
}
//重载*:数*向量
Vec2D operator*(const double num, const Vec2D& vec2d) {
    return Vec2D(vec2d.x_ * num, vec2d.y_ * num);
}
/************复合二元运算符************/
//重载+=:向量+向量
Vec2D& Vec2D::operator+=(const Vec2D& secondVec2D) {
    x_ += secondVec2D.x_;
    y_ += secondVec2D.y_;
    return (*this);
}
//重载-=:向量-向量
Vec2D& Vec2D::operator-=(const Vec2D& secondVec2D) {
    x_ -= secondVec2D.x_;
    y_ -= secondVec2D.y_;
    return (*this);
}
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
//重载[]
double& Vec2D::operator[](const int& index) {
    return this->at(index);
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//重载-
Vec2D Vec2D::operator-() {
    return Vec2D{ -this->x_, -this->y_};
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//求向量与x+轴的夹角
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,5 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1.tostring() << endl;
    cout << "v2= " << v2.tostring() << endl;
    // 用户输入向量
    //暂略
    /************一元运算符************/
    //5. 负号运算符
    cout << endl << "5. 负号运算符" << endl;
    cout << "-v1 = " << (-v1).tostring() << endl;

    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3.000000,5.000000)
v2= (4.000000,6.000000)

5. 负号运算符
-v1 = (-3.000000,-5.000000)

【判断】负号运算符函数可以返回一个引用类型。(错)

8.3.3 重载++和–运算符

  本小节来介绍如何重载自增/自减运算符++--。 自增/减运算符与其他运算符最大的不同是,自增/减运算符既可以前置(prefix)也可以后置(postfix)。我们定义平面向量类Vec2D的自增/减规则如下,重点在后置增/减

Vec2D v1(2, 3);
Vec2D v2 = ++v1;//v1先增,再赋值给v2
cout << "v1: " << v1.toString() << endl; //v1: (3, 4)
cout << "v2: " << v2.toString() << endl; // v2: (3, 4)

Vec2D v3(2, 3);
Vec2D v4 = v3++;//v3先赋值给v4,再增(重点!!)
cout << "v3: " << v3.toString() << endl; // v3: (3, 4)
cout << "v4: " << v4.toString() << endl; // v4: (2, 3)

  于是可以总结出,前置与后置的差别如下:

  1. 前置++var--var:先增减后取值,表达式是左值。前置重载无参数,返回引用类型。
  2. 后置var++var--:先取值后增减,表达式是纯右值。后置重载带参数 dummy,可以理解为一个占位符,其并不参加实际的运算。

  下面给出自增/自减运算符在重载的时候如何定义函数原型,并给出伪代码示例:

表8-3 自增/自减运算符重载函数原型
运算符名语法可重载原型示例(对于类 class T)
类内定义类外定义
前自增++aT& T::operator++();T& operator++(T& a);
前自减--aT& T::operator--();T& operator--(T& a);
后自增a++T T::operator++(int dummy);T operator++(T& a, int dummy);
后自减a--T T::operator--(int dummy);T operator--(T& a, int dummy);
/**********前置函数定义**********/
Vec2D& Vec2D::operator++(){
    x_ += 1;
    y_ += 1;
    return *this;
}
/**********后置函数定义**********/
Vec2D Vec2D::operator++(int dummy){
    Vec2D temp(this->x_, this->y_);
    x_ += 1;
    y_ += 1;
    return temp;
}
//注意后置返回纯右值,所以不能有引用符号

下面针对8.1.2小节代码中“向量自增、自减”,分别添加前置/后置的运算符重载。
头文件 VectorsD.h

#pragma once
#include<string>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D operator+(const Vec2D& secondVec2D);  //重载+:向量+向量
    Vec2D operator+(const double num);          //重载+:向量+数
    friend Vec2D operator+(const double num, const Vec2D& secondVec2D);//重载+:数+向量
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    Vec2D operator-(const Vec2D& secondVec2D);  //重载-:向量-向量
    Vec2D operator-(const double num);          //重载-:向量-数
    friend Vec2D operator-(const double num, const Vec2D& secondVec2D);//重载-:数-向量
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    double operator*(const Vec2D& secondVec2D);  //重载*:向量*向量
    Vec2D operator*(const double num);          //重载*:向量*数
    friend Vec2D operator*(const double num, const Vec2D& secondVec2D);//重载*:数*向量
    /************复合二元运算符************/
    Vec2D& operator+=(const Vec2D& secondVec2D);  //重载+=:向量+向量
    Vec2D& operator-=(const Vec2D& secondVec2D);  //重载-=:向量-向量
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    double& operator[](const int& index);//重载[]
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D operator-();  //重载-
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    Vec2D& operator++();//重载前置++
    Vec2D& operator--();//重载前置--
    Vec2D operator++(int dummy);//重载后置++
    Vec2D operator--(int dummy);//重载后置--
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception>//使用out_of_range异常类

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//重载+:向量+向量
Vec2D Vec2D::operator+(const Vec2D& secondVec2D) {
    return Vec2D(x_ + secondVec2D.x_, y_ + secondVec2D.y_);
}
//重载+:向量+数
Vec2D Vec2D::operator+(const double num) {
    return Vec2D(this->x_ + num, this->y_ + num);
}
//重载+:数+向量
Vec2D operator+(const double num, const Vec2D& secondVec2D) {
    return Vec2D(secondVec2D.x_ + num, secondVec2D.y_ + num);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//重载-:向量-向量
Vec2D Vec2D::operator-(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//重载-:向量-数
Vec2D Vec2D::operator-(const double num) {
    return Vec2D(this->x_ - num, this->y_ - num);
}
//重载-:数-向量
Vec2D operator-(const double num, const Vec2D& secondVec2D) {
    return Vec2D(num - secondVec2D.x_, num - secondVec2D.y_);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
//重载*:向量*向量
double Vec2D::operator*(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//重载*:向量*数
Vec2D Vec2D::operator*(const double num) {
    return Vec2D(this->x_ * num, this->y_ * num);
}
//重载*:数*向量
Vec2D operator*(const double num, const Vec2D& vec2d) {
    return Vec2D(vec2d.x_ * num, vec2d.y_ * num);
}
/************复合二元运算符************/
//重载+=:向量+向量
Vec2D& Vec2D::operator+=(const Vec2D& secondVec2D) {
    x_ += secondVec2D.x_;
    y_ += secondVec2D.y_;
    return (*this);
}
//重载-=:向量-向量
Vec2D& Vec2D::operator-=(const Vec2D& secondVec2D) {
    x_ -= secondVec2D.x_;
    y_ -= secondVec2D.y_;
    return (*this);
}
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
//重载[]
double& Vec2D::operator[](const int& index) {
    return this->at(index);
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//重载-
Vec2D Vec2D::operator-() {
    return Vec2D{ -this->x_, -this->y_};
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
//重载前置++
Vec2D& Vec2D::operator++(){
    this->x_ += 1;
    this->y_ += 1;
    return (*this);
}
//重载前置--
Vec2D& Vec2D::operator--() {
    this->x_ -= 1;
    this->y_ -= 1;
    return (*this);
}
//重载后置++
Vec2D Vec2D::operator++(int dummy) {
    return Vec2D{ this->x_ + 1, this->y_ + 1 };
}
//重载后置--
Vec2D Vec2D::operator--(int dummy) {
    return Vec2D{ this->x_ - 1, this->y_ - 1 };
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//求向量与x+轴的夹角
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,5 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1.tostring() << endl;
    cout << "v2= " << v2.tostring() << endl;
    // 用户输入向量
    //暂略
    ///************一元运算符************/
    5. 负号运算符
    //cout << endl << "5. 负号运算符" << endl;
    //cout << "-v1 = " << (-v1).tostring() << endl;
    //6. 向量自增、自减
    cout << endl << "6. 自增、自减运算符" << endl;
    cout << "v1++" << (v1++).tostring();
    cout << "  v1" << v1.tostring() << endl;
    cout << "v1--" << (v1--).tostring();
    cout << "  v1" << v1.tostring() << endl;
    cout << "++v1" << (++v1).tostring();
    cout << "  v1" << v1.tostring() << endl;
    cout << "--v1" << (--v1).tostring();
    cout << "  v1" << v1.tostring() << endl;

    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3.000000,5.000000)
v2= (4.000000,6.000000)

6. 自增、自减运算符
v1++(4.000000,6.000000)  v1(3.000000,5.000000)
v1--(2.000000,4.000000)  v1(3.000000,5.000000)
++v1(4.000000,6.000000)  v1(4.000000,6.000000)
--v1(3.000000,5.000000)  v1(3.000000,5.000000)
  • 当我们在重载运算符的时候,一定要注意运算符的返回值类型是什么,切记不能将纯右值当成左值来用。也就是上述后置增/减操作,不能返回对象的引用类型。
  • 【判断】后置++/–运算符重载时的整型参数仅仅是一个标记,并不实际参与函数的处理逻辑。(对)
  • 更多关于自增/自减运算符的说明可以参考cppreference文章“自增/自减运算符”。
  • 关于重载解引用运算符*的操作,可以参考CSDN博文“C++ 解引用操作符重载”。

8.4 重载流操作运算符、类型转换运算符

8.4.1 重载流插入/提取运算符(二元运算符)

  本小节来介绍如何重载流插入/提取运算符<<>>,重载的目的是将平面向量类Vec2D中的对象直接输出到屏幕上,而无需调用toString()函数。注意到流提取/插入运算符都是二元运算符<<>>是在ostreamistream类中定义(重载)的,最终期望达到的效果如下:

  • 调用cout << vec2d,将信息输出到ostream对象。
  • 调用cin >> vec2d,从istream对象中读取信息。

  和之前重载二元运算符的思路相同,由于流操作运算符是二元运算符,又想按照上面的形式进行调用,也就是将coutcin这两个流类写在前面,那就必须将<<>>重载为友元函数。并且和8.2.2节重载复合二元算术运算符相似,为了实现连续调用,<<>>重载函数的返回值建议也是引用类型。

/*********重载为成员函数(不推荐)*********/
class Vec2D {
public:
    ostream& operator<<(ostream& stream);
    istream& operator>>(istream& stream);
};
Vec2D v1;
v1 << cout; //Vec2D对象只能作为第一个操作数,看起来很奇怪
/*********重载为友元函数*********/
struct Vec2D {
    friend ostream& operator<<(ostream& stream, const Vec2D& v);//注意是友元函数、返回引用类型
    friend istream& operator>>(istream& stream, Vec2D& v);//注意这里输入Vec2D类必须是引用类型
};
Vec2D v1;
cout << v1; //符合正常的流操作顺序

最后值得说明的一点,就是关于输入参数。由于流插入运算符>>要改变输入的Vec2D类,所以输入Vec2D类必须是引用类型(实参)。当然流提取运算符<<无所谓,但是保险起见Vec2D也输入引用类型,并加上const关键字以保证其不被更改。

下面针对8.1.2小节代码中“流操作运算”,分别添加流提取、流插入运算符的重载。
头文件 VectorsD.h

#pragma once
#include<string>
#include<istream>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    friend std::ostream& operator<<(std::ostream& os, const Vec2D& vec2d);//重载<<
    friend std::istream& operator>>(std::istream& is, Vec2D& vec2d);//重载>>
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D operator+(const Vec2D& secondVec2D);  //重载+:向量+向量
    Vec2D operator+(const double num);          //重载+:向量+数
    friend Vec2D operator+(const double num, const Vec2D& secondVec2D);//重载+:数+向量
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    Vec2D operator-(const Vec2D& secondVec2D);  //重载-:向量-向量
    Vec2D operator-(const double num);          //重载-:向量-数
    friend Vec2D operator-(const double num, const Vec2D& secondVec2D);//重载-:数-向量
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    double operator*(const Vec2D& secondVec2D);  //重载*:向量*向量
    Vec2D operator*(const double num);          //重载*:向量*数
    friend Vec2D operator*(const double num, const Vec2D& secondVec2D);//重载*:数*向量
    /************复合二元运算符************/
    Vec2D& operator+=(const Vec2D& secondVec2D);  //重载+=:向量+向量
    Vec2D& operator-=(const Vec2D& secondVec2D);  //重载-=:向量-向量
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    double& operator[](const int& index);//重载[]
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D operator-();  //重载-
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    Vec2D& operator++();//重载前置++
    Vec2D& operator--();//重载前置--
    Vec2D operator++(int dummy);//重载后置++
    Vec2D operator--(int dummy);//重载后置--
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception>//使用out_of_range异常类

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
//重载<<
std::ostream& operator<<(std::ostream& os, const Vec2D& vec2d) {
    os << "(" << vec2d.x_ << "," << vec2d.y_ << ")";
    return os;
}
//重载>>
std::istream& operator>>(std::istream& is, Vec2D& vec2d) {
    is >> vec2d.x_ >> vec2d.y_;
    return is;
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//重载+:向量+向量
Vec2D Vec2D::operator+(const Vec2D& secondVec2D) {
    return Vec2D(x_ + secondVec2D.x_, y_ + secondVec2D.y_);
}
//重载+:向量+数
Vec2D Vec2D::operator+(const double num) {
    return Vec2D(this->x_ + num, this->y_ + num);
}
//重载+:数+向量
Vec2D operator+(const double num, const Vec2D& secondVec2D) {
    return Vec2D(secondVec2D.x_ + num, secondVec2D.y_ + num);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//重载-:向量-向量
Vec2D Vec2D::operator-(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//重载-:向量-数
Vec2D Vec2D::operator-(const double num) {
    return Vec2D(this->x_ - num, this->y_ - num);
}
//重载-:数-向量
Vec2D operator-(const double num, const Vec2D& secondVec2D) {
    return Vec2D(num - secondVec2D.x_, num - secondVec2D.y_);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
//重载*:向量*向量
double Vec2D::operator*(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//重载*:向量*数
Vec2D Vec2D::operator*(const double num) {
    return Vec2D(this->x_ * num, this->y_ * num);
}
//重载*:数*向量
Vec2D operator*(const double num, const Vec2D& vec2d) {
    return Vec2D(vec2d.x_ * num, vec2d.y_ * num);
}
/************复合二元运算符************/
//重载+=:向量+向量
Vec2D& Vec2D::operator+=(const Vec2D& secondVec2D) {
    x_ += secondVec2D.x_;
    y_ += secondVec2D.y_;
    return (*this);
}
//重载-=:向量-向量
Vec2D& Vec2D::operator-=(const Vec2D& secondVec2D) {
    x_ -= secondVec2D.x_;
    y_ -= secondVec2D.y_;
    return (*this);
}
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
//重载[]
double& Vec2D::operator[](const int& index) {
    return this->at(index);
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//重载-
Vec2D Vec2D::operator-() {
    return Vec2D{ -this->x_, -this->y_};
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
//重载前置++
Vec2D& Vec2D::operator++(){
    this->x_ += 1;
    this->y_ += 1;
    return (*this);
}
//重载前置--
Vec2D& Vec2D::operator--() {
    this->x_ -= 1;
    this->y_ -= 1;
    return (*this);
}
//重载后置++
Vec2D Vec2D::operator++(int dummy) {
    return Vec2D{ this->x_ + 1, this->y_ + 1 };
}
//重载后置--
Vec2D Vec2D::operator--(int dummy) {
    return Vec2D{ this->x_ - 1, this->y_ - 1 };
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//求向量与x+轴的夹角
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,5 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1 << endl;
    cout << "v2= " << v2 << endl;
    // 用户输入向量
    cout << "请输入一个二维向量,使用空格隔开即可" << endl;
    std::cin >> v1;
    cout << "v1= " << v1 << endl;

    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3,5)
v2= (4,6)
请输入一个二维向量,使用空格隔开即可
7 8
v1= (7,8)

8.4.2 重载对象转换运算符

  本小节来介绍如何重载对象转换运算符,其实也就是 类型转换,只不过是将对象转换成某一个类型。比如现在我们想求解向量长度(magnitude),就可以将Vec2D对象转换为double类型,这便是一个对象转换:

//定义一个运算符函数执行类型转换
Vec2D::operator double() {
  return magnitude();
}
Vec2D v1(3, 4);
double d = v1 + 5.1;                //d:10.1,会自动进行隐式类型转换
double e = static_cast<double>(v1); //e:5.0,强制类型转换

注意到这个double的重载是Vec2D的成员函数,并且不需要额外的输入参数,所以没有输入参数。另外由于函数的返回值类型和函数名称相同,都是double,所以返回值类型可以不写,这种现象与类的构造函数、析构函数类似。

下面针对8.1.2小节代码中“求向量长度和角度”,进行double类型转换的重载。
头文件 VectorsD.h

#pragma once
#include<string>
#include<istream>

class Vec2D{
public:
    Vec2D();
    Vec2D(double,double);
    ~Vec2D();
private:
    double x_;
    double y_;
public:
    /************流操作运算符************/
    std::string tostring();
    friend std::ostream& operator<<(std::ostream& os, const Vec2D& vec2d);//重载<<
    friend std::istream& operator>>(std::istream& is, Vec2D& vec2d);//重载>>
    /************一般二元运算符************/
    Vec2D add(const Vec2D& secondVec2D);        //向量+向量
    Vec2D add(double numeral);                  //向量+数
    Vec2D operator+(const Vec2D& secondVec2D);  //重载+:向量+向量
    Vec2D operator+(const double num);          //重载+:向量+数
    friend Vec2D operator+(const double num, const Vec2D& secondVec2D);//重载+:数+向量
    Vec2D subtract(const Vec2D& secondVec2D);   //向量-向量
    Vec2D subtract(double numeral);             //向量-数
    Vec2D operator-(const Vec2D& secondVec2D);  //重载-:向量-向量
    Vec2D operator-(const double num);          //重载-:向量-数
    friend Vec2D operator-(const double num, const Vec2D& secondVec2D);//重载-:数-向量
    double dot(const Vec2D& secondVec2D);       //向量*向量
    Vec2D multiply(double multiplier);          //向量*数
    double operator*(const Vec2D& secondVec2D);  //重载*:向量*向量
    Vec2D operator*(const double num);          //重载*:向量*数
    friend Vec2D operator*(const double num, const Vec2D& secondVec2D);//重载*:数*向量
    /************复合二元运算符************/
    Vec2D& operator+=(const Vec2D& secondVec2D);  //重载+=:向量+向量
    Vec2D& operator-=(const Vec2D& secondVec2D);  //重载-=:向量-向量
    /************数组下标运算符************/
    double& at(const int index);//读取或修改元素变量
    double& operator[](const int& index);//重载[]
    /************一元运算符************/
    Vec2D negative();   //向量求负值
    Vec2D operator-();  //重载-
    Vec2D& increase();  //向量自增1
    Vec2D& decrease();  //向量自减1
    Vec2D& operator++();//重载前置++
    Vec2D& operator--();//重载前置--
    Vec2D operator++(int dummy);//重载后置++
    Vec2D operator--(int dummy);//重载后置--
    /************对象转换运算符************/
    double magnitude();//求向量的范数(长度)
    operator double(); //重载double()对象,计算长度
    double direction();//求向量与x+轴的夹角
    int compareTo(Vec2D secondVec2D);//比较两个向量的长度。返回-1/0/1
    bool operator<(const Vec2D& v);//重载小于号<
    bool operator>(const Vec2D& v);//重载小于号>
    bool operator==(const Vec2D& v);//重载判等号==
};

源文件 Vec2D.cpp

#include"Vec2D.h"
#include<iostream>
#include<string>
#include<exception> //使用out_of_range异常类
#include<cmath>     //计算长度和角度

/************构造和析构函数************/
Vec2D::Vec2D() {
    x_ = 0.0;
    y_ = 0.0;
}
Vec2D::Vec2D(double x, double y) {
    x_ = x;
    y_ = y;
}
Vec2D::~Vec2D() {

}
/************流操作运算符************/
std::string Vec2D::tostring(){
    return std::string("("+std::to_string(x_)+"," +std::to_string(y_)+")");
}
//重载<<
std::ostream& operator<<(std::ostream& os, const Vec2D& vec2d) {
    os << "(" << vec2d.x_ << "," << vec2d.y_ << ")";
    return os;
}
//重载>>
std::istream& operator>>(std::istream& is, Vec2D& vec2d) {
    is >> vec2d.x_ >> vec2d.y_;
    return is;
}
/************一般二元运算符************/
// 向量+向量
Vec2D Vec2D::add(const Vec2D& secondVec2D)
{
    return Vec2D(x_+secondVec2D.x_,y_+secondVec2D.y_);
}
//向量+数
Vec2D Vec2D::add(double numeral) {
    return Vec2D(this->x_ + numeral, this->y_ + numeral);
}
//重载+:向量+向量
Vec2D Vec2D::operator+(const Vec2D& secondVec2D) {
    return Vec2D(x_ + secondVec2D.x_, y_ + secondVec2D.y_);
}
//重载+:向量+数
Vec2D Vec2D::operator+(const double num) {
    return Vec2D(this->x_ + num, this->y_ + num);
}
//重载+:数+向量
Vec2D operator+(const double num, const Vec2D& secondVec2D) {
    return Vec2D(secondVec2D.x_ + num, secondVec2D.y_ + num);
}
//向量-向量
Vec2D Vec2D::subtract(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//向量-数
Vec2D Vec2D::subtract(double numeral) {
    return Vec2D(x_ - numeral, y_ - numeral);
}
//重载-:向量-向量
Vec2D Vec2D::operator-(const Vec2D& secondVec2D) {
    return Vec2D(x_ - secondVec2D.x_, y_ - secondVec2D.y_);
}
//重载-:向量-数
Vec2D Vec2D::operator-(const double num) {
    return Vec2D(this->x_ - num, this->y_ - num);
}
//重载-:数-向量
Vec2D operator-(const double num, const Vec2D& secondVec2D) {
    return Vec2D(num - secondVec2D.x_, num - secondVec2D.y_);
}
//向量*向量
double Vec2D::dot(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//向量*数
Vec2D Vec2D::multiply(double multiplier) {
    return Vec2D(x_*multiplier, y_*multiplier);
}
//重载*:向量*向量
double Vec2D::operator*(const Vec2D& secondVec2D) {
    return (x_*secondVec2D.x_ + y_*secondVec2D.y_);
}
//重载*:向量*数
Vec2D Vec2D::operator*(const double num) {
    return Vec2D(this->x_ * num, this->y_ * num);
}
//重载*:数*向量
Vec2D operator*(const double num, const Vec2D& vec2d) {
    return Vec2D(vec2d.x_ * num, vec2d.y_ * num);
}
/************复合二元运算符************/
//重载+=:向量+向量
Vec2D& Vec2D::operator+=(const Vec2D& secondVec2D) {
    x_ += secondVec2D.x_;
    y_ += secondVec2D.y_;
    return (*this);
}
//重载-=:向量-向量
Vec2D& Vec2D::operator-=(const Vec2D& secondVec2D) {
    x_ -= secondVec2D.x_;
    y_ -= secondVec2D.y_;
    return (*this);
}
/************数组下标运算符************/
//读取或者修改向量元素
double& Vec2D::at(const int index) {
    if (0 == index) {
        return x_;
    }
    else if (1 == index) {
        return y_;
    }
    else {
        throw std::out_of_range("at() only accept 1 or 2 as parameter");
        //抛出异常,并给出提示信息
    }
}
//重载[]
double& Vec2D::operator[](const int& index) {
    return this->at(index);
}
/************一元运算符************/
//向量求负值
Vec2D Vec2D::negative() {
    return Vec2D(-x_, -y_);
}
//重载-
Vec2D Vec2D::operator-() {
    return Vec2D{ -this->x_, -this->y_};
}
//向量自增1
Vec2D& Vec2D::increase() {
    x_++; y_++;
    return (*this);
}
//向量自减1
Vec2D& Vec2D::decrease() {
    x_--; y_--;
    return *this;
}
//重载前置++
Vec2D& Vec2D::operator++(){
    this->x_ += 1;
    this->y_ += 1;
    return (*this);
}
//重载前置--
Vec2D& Vec2D::operator--() {
    this->x_ -= 1;
    this->y_ -= 1;
    return (*this);
}
//重载后置++
Vec2D Vec2D::operator++(int dummy) {
    return Vec2D{ this->x_ + 1, this->y_ + 1 };
}
//重载后置--
Vec2D Vec2D::operator--(int dummy) {
    return Vec2D{ this->x_ - 1, this->y_ - 1 };
}
/************对象转换运算符************/
//求向量的范数(长度)
double Vec2D::magnitude() {
    return sqrt(x_*x_ + y_ * y_);
}
//重载double()对象,计算长度
Vec2D::operator double() {
    return this->magnitude();
}
//求向量与x+轴的夹角(弧度制)
double Vec2D::direction() {
    return atan(y_ / x_);  
}
//比较两个向量的长度
//如果firstVec2D小于secondVec2D,返回-1,若大于则返回1,若等于则返回0
int Vec2D::compareTo(Vec2D secondVec2D) {
    double m1 = this->magnitude();
    double m2 = secondVec2D.magnitude();
    if (abs(m1 - m2) < 1e-10) {//注意浮点数不能判等
        return 0;
    }
    else {
        return (m1 > m2 ? 1 : -1);
    }
}
//重载小于号<
bool Vec2D::operator<(const Vec2D& v) {
    if (this->compareTo(v) == -1) {
        return static_cast<bool>(1);
    }
    else {
        return static_cast<bool>(0);
    }
}
//重载小于号>
bool Vec2D::operator>(const Vec2D& v) {
    if (this->compareTo(v) == 1) {
        return static_cast<bool>(1);
    }
    else {
        return static_cast<bool>(0);
    }
}
//重载判等号==
bool Vec2D::operator==(const Vec2D& v) {
    if (this->compareTo(v) == 0) {
        return static_cast<bool>(1);
    }
    else {
        return static_cast<bool>(0);
    }
}

源文件 TestVec2D.cpp

#include<iostream>
#include"Vec2D.h"

using std::cout;
using std::endl;

int main() {
    //创建Vec2D类对象
    Vec2D v1{ 3,4 }, v2{ 4,6 }, v3, v4, v5;

    /************流操作运算符************/
    cout << "1. 流操作运算符" << endl;
    // 向量输出
    cout << "v1= " << v1 << endl;
    cout << "v2= " << v2 << endl;
     用户输入向量
    //cout << "请输入一个二维向量,使用空格隔开即可" << endl;
    //std::cin >> v1;
    //cout << "v1= " << v1 << endl;
    ///************一般二元运算符************/
    //cout << endl << "2. 一般二元运算符+、-、*" << endl;
     向量加法
    //v3 = v1 + v2;  //向量+向量
    //v4 = v1 + 10.0;//向量+数
    //v5 = 10.0 + v1;//数+向量
    //cout << "v1+v2   = " << v3.tostring() << endl;
    //cout << "v1+10.0 = " << v4.tostring() << endl;
    //cout << "10.0+v1 = " << v5.tostring() << endl;
     向量减法
    //v3 = v2 - v1; //向量-向量
    //v4 = v2 - 3.0;//向量-数
    //v5 = 3.0 - v2;//数-向量
    //cout << "v2-v1  = " << v3.tostring() << endl;
    //cout << "v2-3.0 = " << v4.tostring() << endl;
    //cout << "3.0-v2 = " << v5.tostring() << endl;
     向量点积、向量数乘
    //double d1 = v1*v2; //向量*向量
    //v3 = v1*2.0;       //向量*数
    //v4 = 2.0*v1;       //数*向量
    //cout << "v1*v2  = " << d1 << endl;
    //cout << "v1*2.0 = " << v3.tostring() << endl;
    //cout << "2.0*v1 = " << v4.tostring() << endl;
    ///************复合二元运算符************/
    //cout << endl << "3. 复合二元运算符+=、-=、*=" << endl;
    //v3 = v1; v3 += v2;//向量+=向量
    //v4 = v1; v4 -= v2;//向量-=向量
    //cout << "v1+=v2 : " << v3.tostring() << endl;
    //cout << "v1-=v2 : " << v4.tostring() << endl;
    //v3 = v1; v4 = v2; v3 += v4 += v2;//连续+=
    //cout << "运行 v3=v1; v4=v2; v3+=v4+=v2; 后:" << endl;
    //cout << "v3 : " << v3.tostring() << endl;
    //cout << "v4 : " << v4.tostring() << endl;
    ///************数组下标运算符************/
    //cout << endl << "4. 数组下标运算符[]" << endl;
    //cout << "v1.x_ =" << v1[0] << endl;
    //cout << "v1.y_ =" << v1[1] << endl;
    //v1[0] = 6.0;
    //cout << "v1.x_ =" << v1[0] << endl;
    //cout << "v1.y_ =" << v1[1] << endl;
    ///************一元运算符************/
    5. 负号运算符
    //cout << endl << "5. 负号运算符" << endl;
    //cout << "-v1 = " << (-v1).tostring() << endl;
    6. 向量自增、自减
    //cout << endl << "6. 自增、自减运算符" << endl;
    //cout << "v1++" << (v1++).tostring();
    //cout << "  v1" << v1.tostring() << endl;
    //cout << "v1--" << (v1--).tostring();
    //cout << "  v1" << v1.tostring() << endl;
    //cout << "++v1" << (++v1).tostring();
    //cout << "  v1" << v1.tostring() << endl;
    //cout << "--v1" << (--v1).tostring();
    //cout << "  v1" << v1.tostring() << endl;
    /************对象转换运算符************/
    //7. 向量长度、向量角度
    cout << endl << "7. 向量长度、向量角度" << endl;
    cout << "v1长度 = " << static_cast<double>(v1) << endl;
    cout << "v1角度 = " << v1.direction() << " rad" << endl;
    //8. 比较两个向量,输出-1/0/1
    cout << endl << "8. 比较两个向量" << endl;
    cout << "v1 > v2: " << (v1>v2) << endl;
    cout << "v1 < v2: " << (v1<v2) << endl;
    cout << "v1 == v2: " << (v1==v2) << endl;

    std::cin.get();
    return 0;
}

运行结果:

1. 流操作运算符
v1= (3,4)
v2= (4,6)

7. 向量长度、向量角度
v1长度 = 5
v1角度 = 0.927295 rad

8. 比较两个向量
v1 > v2: 0
v1 < v2: 1
v1 == v2: 0

8.5 重载赋值运算符及其他总结

8.5.1 重载赋值运算符=

图8-5 Employee类示意图

  本小节来介绍如何重载赋值运算符,并且整个过程以上面这个Employee类作为例子进行介绍。默认情况下,赋值运算符=执行对象成员的一对一拷贝。但正如5.3.4节中所说,如果Employee类中存在指针类型的数据成员,就会存在深拷贝和浅拷贝的问题。如下所示,赋值运算符默认执行浅拷贝,指针类型对象复制后指向同一个地址:

Employee e1{"Jack", Date(1999, 5, 3),  Gender::male};
Employee e2{"Anna", Date(2000, 11, 8), Gender:female};
e2 = e1;  //赋值运算符默认执行一对一成员拷贝
//导致浅拷贝的原因是类中有指针类型的数据成员
图8-6 赋值运算符默认执行浅拷贝

只有重载赋值运算符,才能改变其默认工作方式。所以一般情况下,如果拷贝构造函数需要执行深拷贝,那么赋值运算符需要重载。可以看到,执行深拷贝之后,赋值后的对象会指向一个新的地址。当然,若返回值类型是引用类型,那么就支持连续赋值。

class Employee{
public:
    //重载赋值运算符
    Employee& operator=(const Employee& e){
        name = e.name;
        *birthdate = *(e.birthdate);//两个对象的赋值,不同的地址
    } // ...
};
Employee e1{"Jack", Date(1999, 5, 3), Gender::male};
Employee e2{"Anna", Date(2000, 11, 8),, Gender:female};
e2 = e1;  //会调用重载赋值运算符
图8-7 重载赋值运算符执行深拷贝

最后是一个小练习,注意下面代码的调用方式:

Circle c1;      //默认构造方式创建c1
Circle c2(c1);  //调用拷贝构造函数创建c2,将c1的值复制到c2。
Circle c3 = c2; //拷贝构造函数创建c3,将c2的值复制到c3。注意不是赋值运算符的重载。
c1 = c3;        //调用赋值运算符的重载,将c3的值赋给了c1。

下面是代码展示,在employee类中重载赋值运算符,整个代码是在第五单元5.3.4节“深拷贝、浅拷贝”的基础上进行改编的:
头文件 date.h

#pragma once
#include<iostream>
#include<string>
class date {
private:
    int year = 2019, month = 1, day = 1;
public:

    date() = default;
    date(int y, int m, int d);
    int getyear();
    int getmonth();
    int getday();
    void setyear(int y);
    void setmonth(int m);
    void setday(int d);
    std::string tostring();
};

头文件 employee.h

#include<iostream>
#include<string>
#include"date.h"

enum class Gender {
    male,
    female,
};

class employee {
private:
    std::string name;
    Gender gender;
    date* birthday;
    static int numberofobjects;
public:
    void setname(std::string name);
    void setgender(Gender gender);
    void setbirthday(date birthday);
    std::string getname();
    Gender getgender();
    date getbirthday();
    std::string tostring();
    employee(std::string name, Gender gender, date birthday);
    employee();
    employee(const employee& e);
    employee& operator=(const employee& e);//关键:赋值运算符重载
    ~employee();
};

源文件 date.cpp

#include"date.h"

int date::getyear() {
    return year;
}
int date::getmonth() {
    return month;
}
int date::getday() {
    return day;
}
void date::setyear(int y) {
    year = y;
}
void date::setmonth(int m) {
    month = m;
}
void date::setday(int d) {
    day = d;
}
//date::date() = default;

date::date(int y, int m, int d) :year{ y }, month{ m }, day{ d }{
    std::cout << "date: " << tostring() << std::endl;
}
std::string date::tostring() {
    return (std::to_string(year) + "-" + std::to_string(month) + "-" + std::to_string(day));
}

源文件 employee.cpp

#include"employee.h"

void employee::setname(std::string name) {
    this->name = name;
}
void employee::setgender(Gender gender) {
    this->gender = gender;
}
void employee::setbirthday(date birthday) {
    *(this->birthday) = birthday;
}
std::string employee::getname() {
    return name;
}
Gender employee::getgender() {
    return gender;
}
date employee::getbirthday() {
    return *birthday;
}
std::string employee::tostring() {
    return (name + (gender == Gender::male ? std::string("male/") : std::string("female/"))
        + birthday->tostring());
}
employee::employee(std::string name, Gender gender, date birthday)
    :name{ name }, gender{ gender }{
    numberofobjects++;
    this->birthday = new date{ birthday };
    std::cout << "now there are: "
        << numberofobjects
        << "employees "
        << std::endl;
}
//默认构造函数
employee::employee()
    :employee("Tong hua/", Gender::male, date(2001, 5, 6)) {}
//拷贝构造函数-深拷贝
employee::employee(const employee& e) {
    this->birthday = new date{ *(e.birthday) };
    this->name = e.name;
    this->gender = e.gender;
    numberofobjects++;
}
//重载赋值运算符-深拷贝
employee& employee::operator=(const employee& e) {
    this->name = e.name;
    this->gender = e.gender;
    *(this->birthday) = *(e.birthday);
    return (*this);
}
//析构函数
employee::~employee() {
    numberofobjects--;
    delete birthday;
    birthday = nullptr;
    std::cout << "now there are: " << numberofobjects << "employees" << std::endl;
}

源文件 OverLoadingAssignmentOperator.cpp

#include<iostream>
#include"date.h"
#include"employee.h"
//构造employee对象e1,拷贝构造e2
//调试模式观察e1和e2的birthday成员
//添加拷贝构造函数实现深拷贝
//调试模式观察e1和e2的birthday成员
int employee::numberofobjects = 0;
int main() {
    employee e1{ "Li yu/",Gender::male,{1999,9,17} };
    employee e2{ e1 };
    std::cout << e1.tostring() << std::endl;//e1.birthday地址:0x0113bb58
    std::cout << e2.tostring() << std::endl;//e2.birthday地址:0x0113bab0

    employee e3{};
    std::cout << e3.tostring() << std::endl;//e3.birthday地址:0x0113bb28
    e3 = e1;
    std::cout << e3.tostring() << std::endl;//e3.birthday地址:0x0113bb28

    std::cin.get();
    return 0;
}

运行结果:

date: 1999-9-17
now there are: 1employees
Li yu/male/1999-9-17
Li yu/male/1999-9-17
date: 2001-5-6
now there are: 3employees
Tong hua/male/2001-5-6
Li yu/male/1999-9-17

now there are: 2employees
now there are: 1employees
now there are: 0employees

8.5.2 运算符重载的更多说明

  本小节来补充一些关于运算符重载的更多说明,主要就是下面两点。

  1. 能不能使用运算符重载来操作基础数据类型?不能! 运算符重载要注意:
  1. 重载的运算符必须和用户定义的class类型一起使用。
  2. 重载的运算符的参数至少应有一个是类对象(或类对象的引用)。
  • 参数不能全部是C++的标准类型,以防止用户修改用于标准类型数据的运算符的性质,比如int operator+(int x); //NOT ALLOWED!
  1. 运算符除了定义成类的成员函数之外,还可以声明为友元函数,以保证二元运算符的调用顺序与习惯上相同。基本上大多数类的成员函数都可以声明成友元函数进行重载,当然也有例外,像某些一元运算符就不能修改顺序(比如[]后置++等)。
class Vec2D {
public:
    friend Vec2D operator+(Vec2D &firstVec, Vec2D &secondVec);
    // friend double& operator[](Vec2D &v, const int &index);
    // operator[] 不能重载为友元函数,只能重载为成员函数
    friend Vec2D operator++(Vec2D &v);
    friend Vec2D operator++(Vec2D &v, int dummy);
}

class Vec2D {
public:
    Vec2D operator+(Vec2D &secondVec);
    double& operator[](const int &index);
    Vec2D operator++();
    Vec2D operator++(int dummy);
}

8.5.3 更多编码规范

  1. 关于变量名称方面的规范
  • 25:迭代变量应使用i,j,k等字母。j,k等应仅用于嵌套循环中。
  • 14:大作用域的变量应使用长名字,小作用域的变量应使用短名字。
  1. 关于头文件

41:包含语句应排序并分组。排序时,按其在系统中的层次位置,较低层的头文件写在前面,较高层的头文件写在后面。并且要用空行分隔分组的包含语句。包含文件的路径中,也一定不要使用绝对路径。

#include <fstream>
#include <iomanip>

#include <qt/qbutton.h>
#include <qt/qtextfield.h>

#include "com/company/ui/PropertiesDialog.h"
#include "com/company/ui/MainWindow.h"

42:包含语句必须放在文件的顶部。将包含语句放到源代码的其它位置,可能会导致预期之外的编译副作用。

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

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

相关文章

极致呈现系列之终章:Vue3中封装Echarts组件

经过前面一系列博文的介绍&#xff0c;我已经详细地讲解了Echarts中涉及的各种图表类型。所以&#xff0c;我认为极致呈现系列文章到此基本告一段落了。作为这个系列文章的终章&#xff0c;我想探讨最后一个问题&#xff1a;如何封装Echarts组件。我觉得这是一个很好的结尾&…

黑马程序员-从0到1学习Linux-第一章 初识Linux

视频学习地址&#xff1a;黑马程序员新版Linux零基础快速入门到精通&#xff0c;全涵盖linux系统知识、常用软件环境部署、Shell脚本、云平台实践、大数据集群项目实战等_哔哩哔哩_bilibili 目录 操作系统概述 初识Linux系统 虚拟机介绍 VMware WorkStation安装 在VMware上…

【Langchain】GPT的高效插件

功能1&#xff1a;让模型具有memory 可以看到&#xff0c;langchain作为访问gpt的一个插件&#xff0c;可以让gpt模型通过memory变量将之前的对话记录下来&#xff0c;从而使模型具有记忆&#xff08;在不改变模型参数的情况下&#xff09;。 查看memory变量包含了什么&#…

在blender中使用python程序化建模

blender中&#xff0c;所有可以在Blender软件中的手动操作&#xff0c;基本都可以通过Python API 完成 那么就可以用这个完成程序化生成 下面我给出一个简单的方块建模程序&#xff1a; 在scripting中&#xff0c;可以添加file&#xff0c;然后向场景中心放置一个正方体 首…

前端面试题Vue答案

1.vue的原理? image.png 关键词: 虚拟DOM树访问器属性 解释一下:响应式原理? 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项&#xff0c;Vue 将遍历此对象所有的 property&#xff0c;并使用 Object.defineProperty把这些 property 全部转为 getter/setter…

开关电源PFC电路原理详解及matlab仿真

PFC全称“Power Factor Correction”&#xff0c;意为“功率因数校正”。PFC电路即能对功率因数进行校正&#xff0c;或者说能提高功率因数的电路。 在电学中&#xff0c;功率因数PF指有功功率P&#xff08;单位w&#xff09;与视在功率S&#xff08;单位VA&#xff09;的比值。…

【Linux】fdisk命令参数详解(图文超详细,内容来自官方文档)

目录 0.环境 1.背景 2.内容--官方文档对fdisk的介绍 1&#xff09;名称 2&#xff09;说明 3&#xff09;具体参数/选项 4&#xff09;举个栗子&#xff0c;我要查fdisk的版本 0.环境 windows linux虚拟机 1.背景 之前发表了一篇文章Linux 用fdisk进行磁盘分区&#xff…

Python语法基础03(输入与while)

用户输入 使用input()函数可以获取输入&#xff0c;同时应说清楚所期待的输入内容 #使用函数input时&#xff0c;要说清楚期望的输入 carinput("Please input your favourite car.\nAnd i will repeat:") print(f"your favourite car is {car}")## 也可…

多元分类预测 | Matlab灰狼算法(GWO)优化混合核极限学习机(HKELM)分类预测,多特征输入模型,GWO-HKELM分类预测

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元分类预测 | Matlab灰狼算法(GWO)优化混合核极限学习机(HKELM)分类预测,多特征输入模型,GWO-HKELM分类预测 多特征输入单输出的二分类及多分类模型。程序内注释详细,直接替换数据就可以用。程序语言为matlab…

NFTScan 与 Sender Wallet 达成合作伙伴,双方在多链 NFT 数据方面展开合作!

近日&#xff0c;NFT 数据基础设施 NFTScan 与 Web3 钱包 Sender Wallet 达成合作伙伴关系&#xff0c;成为其官方 NFT 数据供应商。NFTScan 将为 Sender Wallet 的 NFT 部分提供专业的多链 NFT 数据支持&#xff0c;确保用户可以跨多个区块链获得全面和实时的 NFT 数据。 Sen…

Latex 文献引用

来源&#xff1a; 引用 - 为什么叫 citet 和 citep&#xff1f;- TeX - LaTeX 堆栈交换 (stackexchange.com) 来源&#xff1a; latex \cite, \citet, \citep_latex citet_juliosun的博客-CSDN博客 来源&#xff1a;https://www.reddit.com/r/LaTeX/comments/5g9kn1/whats_th…

【网络安全带你练爬虫-100练】第1练:发送请求获取返回内容

目录 一、前置知识1 二、前置知识2 requests库的7个主要方法 语法&#xff1a; 注解&#xff1a; 三、扩展工具 四、网络安全小圈子 一、前置知识1 顾名思义网络爬虫第一步&#xff0c;爬取目标 URL的网页的信息 可以使用 urllib.request 和 requests发送请求&#xff0…

瞄准光储赛道的家电巨头,是没活“硬整”,还是有理有据?

2023年上半年即将结束&#xff0c;家电巨头再次带来跨界的重磅消息。 在A股公司合康新能月底发布的定增公告中&#xff0c;美的集团&#xff0c;成为合康新能定增的包揽者。后者将获得前者提供的高达14.73亿元的募集资金总额&#xff0c;用于电子设备业务能力提升项目、光伏产…

一部手机如何登录多个微信?教你一招轻松搞定

现在大部分人都不止2个微信&#xff0c;有的用于私人社交&#xff0c;有的用于工作&#xff0c;人手多个微信已经很普遍了。那么如何在一个手机上同时登录2个甚至更多微信呢&#xff1f; 01登录2个微信 找到手机上面的【设置】&#xff0c;找到【应用设置】-【应用双开】&am…

Redis通信协议

RESP协议 Redis是一个CS架构的软件&#xff0c;通信一般分两步&#xff08;不包括pipeline和PubSub&#xff09;&#xff1a; ① 客户端&#xff08;client&#xff09;向服务端&#xff08;server&#xff09;发送一条命令 ② 服务端解析并执行命令&#xff0c;返回响应结果…

拧螺丝需求:递归算法的极致应用

前言 在一个平平无奇的下午&#xff0c;接到一个需求&#xff0c;需要给公司的中台系统做一个json报文重组的功能。 因为公司的某些业务需要外部数据的支持&#xff0c;所以会采购一些其它公司的数据&#xff0c;而且为了保证业务的连续性&#xff0c;同一种数据会采购多方的数…

电子模块|航空插头简介

电子模块|航空插头简介 航空插头图片航空插头介绍为什么要用航插航空插头实例及参数 航空插头图片 航空插头介绍 航空插头定义&#xff1a; 它是针对复杂工业环境与户外环境等应用场景开发的一类连接器。正式一些的称呼是“工业连接器”&#xff0c;主要用于电气、电子设备的电…

react菜鸟教程学习笔记

目录 第一个react实例 react安装 对react机制最直观的理解 如果你第一次用npm 关于初始化package.json的问题 使用 create-react-app 快速构建 React 开发环境 项目目录结构 修改一下代码执行源头APP.js React元素渲染 将元素渲染到DOM中 更新元素渲染 关于vue的更新…

Dlib —— 对图片进行人脸检测(附C++源码)

效果 注意&#xff1a;Dlib检测人脸在Release版耗时与CPU有关,本人I7 10代约100ms左右。建议人脸检测可以考虑使用Yolov5进行&#xff0c;之后将检测到的人脸输入给Dlib做特征或其他。 代码 Vs2017下使用Dlib检测人脸&#xff0c;并通过OpenCv将结果绘制出来。&#xff08;由于…

推荐几个数据可视化工具汇总

数据的魅力在于其故事性和洞察力。让数据说话&#xff0c;我们汇集了一系列令人兴奋的数据可视化工具&#xff0c;为您提供展示和探索数据的无限可能。 分享一&#xff1a;Tangle Tangle是一个基于Web的数据可视化工具&#xff0c;旨在帮助大家以交互式和可视化的方式探索和解…