第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++程序规范。
单元导读
本单元主要介绍运算符的重载方法和使用方法。
- 本单元首先介绍了
Vec2D
类。为了学习运算符重载的方法,我们需要首先理解Vec2D
这个类的成员,包括add/subtract/dot/toString
等。 - 要掌握 二元算术运算符的重载 方法。尤其要理解当我们调用运算符函数,执行类似 a+b 这种运算时,重载的运算符函数中的
*this
是谁、函数参数是谁。- 如果对this指针理解有疑问,那么需要重新回顾一下本课程第5单元的相关内容。
- 还要理解对于复合运算符,例如 c+=d,运算结果会对对象c产生改变。而且,该函数的返回结果是一个引用类型。
- 要掌握 一元运算符的重载 方法。其中特别需要注意的是,前置自增自减运算符,与后置自增自减运算符在函数原型方面的差异,要理解“
dummy
”参数的作用。 - 要掌握 数组下标运算符的重载 方法,要理解如何让数组下标运算符函数能够成为左值;
- 要掌握 流输出运算符的重载 方法,尤其要理解为何流输出运算符要重载为友元函数的原因。
- 在上述运算符重载中,大量使用了对象引用类型作为函数的参数或者返回值。要理解哪些引用类型是为了 提升性能,哪些引用类型是为了 提供左值性质。
注:本单元所用的示例,是一个平面向量类。平面向量是最简单的向量,并且由此衍生出矩阵、张量以及人工智能。下面给出一些介绍。
单元导读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";
在前面已经学过了运算符的一些特殊用法,需要配合某些特殊对象一起使用,比如上面的代码所示。可以看到,上述这些运算符的含义都与其原本的含义不同,也就是 运算符的重载。那运算符与函数的异同都是什么呢?如下:
- 相同之处:
- 运算符本质上也是函数。只是运算符是编译器需要进行进一步解释,而函数是直接调用。
- 不同之处:
- 语法上有区别。 运算符可以前缀、中缀、后缀表达,但是函数一般只能前缀表达。
3 * 2 //中缀式(常用) * 3 2 //前缀式 3 2 * //后缀式(RPN) multiply(3, 2); //前缀式
- 不能自定义新的运算符,只能重载。
3 ** 2 // Python中表示乘方,C/C++中报错 pow(3, 2) // C/C++只能调用函数来计算平方
- 函数可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_;
并期望二维向量能进行如下运算:
运算 | 示例 |
---|---|
加减乘 | (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) |
于是将上述定义和运算总结一下,便可以得到平面向量类的基本示意图:
唯一需要注意的是,函数参数类型设置为引用类型的一个重要目的是避免对象拷贝的开销,以提高代码运行效率。
那接下来根据 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;//运算符重载,直观,可以返回布尔类型
但是,并不是所有的运算符都可以重载的。下面给出可重载及不可重载的运算符,以及运算符重载时需要注意的原则:
可重载的运算符:
- 类型转换运算符:
double
、int
、char
, ……。
- 内存分配和归还运算符:
new/delete
、new []/delete[]
。
- 用户自定义字面量运算符(自C++11起):
""_suffix
,其中suffix可以任意写,注意使用时引入头文件#include<string>
。
- 一般运算符:下图所示的40个运算符,其中
<=>
、co_await
是C++20后才引入的。
不可重载的运算符: 除了上面可重载运算符,其他都是不可重载。
- 类属关系运算符:
.
(点)。
- 成员指针运算符:
.*
(点+星号)。
- 作用域运算符:
::
(两个冒号)。
- 三元运算符:
?:
。
- 编译预处理符号:
#
。
- 一些关键字:
sizeof()
等。运算符重载的限制:
- 优先级和结合性不变。
- 不可创造新的运算符,起码C++不支持。
有了上面的了解后,接下来就来介绍如何定义和使用这些运算符函数。如下图,运算符重载会使用到C++关键字 operator
,表示要对其后的运算符进行重载。注意“运算符”本身就是一个函数,只不过为了和普通函数区分开,才称之为“运算符函数”。
- 调用:调用的方法和使用其原型没有区别,但当然也可以直接用其函数名
operator<
。下图中给出的两种调用方法等价,v1
表示当前的类,v2
表示参数。- 原型(函数定义):
this
指向v1
对象、SecondVec2D
就是v2
本身。operator<
指的是运算符重载,return
语句中的<
则是小于符号原本的定义。当然传参时可以不使用引用,但使用引用无需将实参重新拷贝成一个形参,可以提供函数调用效率。
现在来着重讨论运算符函数的“调用形式”,也就是该如何确定“谁”的运算符?“谁”是运算符函数的参数?前面提及,“运算符重载”最大的好处就是直观,也就是“调用形式”简洁直观,无需记忆复杂的函数名称,直接使用运算符即可。但上面例子中的<
只是一个二元运算符,那其他的一些前缀运算符、后缀运算符、特殊运算符该怎么进行“调用”呢?于是下表就较为全面的介绍了几乎所有运算符的调用形式。
运算符名称 | 表达式 | 作为成员函数 | 作为非成员函数 | 示例 |
---|---|---|---|---|
前缀 | @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中就存在左值和右值的区别,通俗理解就是:
- 能放在等号左边的是lvalue。
- 只能放在等号右边的是rvalue,比如
int x=6
中,x
是左值,6
是右值。- lvalue可以作为rvalue使用,比如
int y; y=x;
中,x
就从上面的左值变成了右值。
而在C++11中对于左值和右值则做出了更加详细的分类,这些详细分类涉及到“移动语义”的问题,目的在于提高程序的运行效率和运行性能。但下面先不介绍“移动语义”,而是介绍一下分类:左值、右值、将亡值。
- 左值(Left Value, Lvalue)指定了一个 可以取地址的 函数或者对象。
代码示例:
int lv1{ 42 }; //lv1是可取地址的对象,左值 int main() { int& lv2{ lv1 }; //左值lv2引用对象,lv1和lv2地址相同 int* lv3{ &lv1 }; //左值指针lv3指向对象,也可以取地址 } int& lv4() { return lv1; } //函数返回引用,本质上就是lv1,可取地址,所以函数返回左值
左值例子:
- 解引用表达式
*p
:显然可以取地址&(*p)
。
- 字符串字面量"abc":首地址为指针显然可以取地址。
- 前置自增/自减表达式
++i
/--i
:由于是先自增自减,所以&(++i)
就是i
的地址。所以后置自增/自减表达式i++
/i--
并不是左值。
- 赋值或复合运算符表达式(
x=y
或m*=n
等)。更详细说明参考:Understanding Lvalues, PRvalues and Xvalues in C/C++ with Examples。
- 纯右值(Pure Right Value, PRvalue):是不和对象相关联的值(字面量)或者其求值结果是字面量或者一个匿名的临时对象。比如下面纯右值例子:
- 除字符串字面量以外的字面量,比如
32
、'a'
。注意上面提到字符串字面量"abc"
是左值。
- 返回非引用类型的函数调用
int f() { return 1;}
。
- 后置自增/自减表达式
i++
/i--
。
- 算术/逻辑/关系表达式:算术表达式(
a+b
/a&b
/a<<b
)、与或非(a&&b
/a||b
/~a
)、判断(a==b
/a>=b
/a<b
)。
- 取地址(
&x
)。
- 左值可以当成右值使用。
更详细说明参考:C++11 中的左值、右值和将亡值。
- 将亡值(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将左值、右值做了一些层次化的分类,任何一个表达式都是属于某一类值类型的。表达式分类逻辑关系如下图:
这些东西会在未来学到右值引用和移动语义的时候用上。
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),所谓“一元运算符”就是只有一个操作数的运算符,比如--
、++
、-
(负号)、*
(解引用),通常使用@
代表单目运算符。
在重载时,通常将运算符分成前置单目运算符(如-
负号、*
解引用)和后置单目运算符来考虑。当编译器遇到前置运算符@obj;
时要注意函数的定义:
- 若
operator@
是obj
的类的成员函数:则调用obj.operator @() //无参
。也就是说,重载前置单目运算符时,若将运算符重载为类的成员函数,则该运算符函数无需参数。- 若
operator@
是obj
的类的friend函数(相当于类外函数):则调用operator @(obj)
。
8.3.2 重载负号运算符
本小节来学习如何重载负号运算符-
。当然正号+
和负号-
都是一元运算符,但是一般我们并不处理+
,所以就只讨论负号运算符-
。由于调用-
时无需改变当前对象的值,只需返回一个Vec2D对象(设计为临时匿名对象),所以-
属于“类的成员函数”,则其满足:
- 只对调用该运算符的对象起作用。
- 作为对象成员的一元运算符无参数!
/**********函数调用**********/
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)
于是可以总结出,前置与后置的差别如下:
- 前置
++var
、--var
:先增减后取值,表达式是左值。前置重载无参数,返回引用类型。- 后置
var++
、var--
:先取值后增减,表达式是纯右值。后置重载带参数dummy
,可以理解为一个占位符,其并不参加实际的运算。
下面给出自增/自减运算符在重载的时候如何定义函数原型,并给出伪代码示例:
运算符名 | 语法 | 可重载 | 原型示例(对于类 class T) | |
---|---|---|---|---|
类内定义 | 类外定义 | |||
前自增 | ++a | 是 | T& T::operator++(); | T& operator++(T& a); |
前自减 | --a | 是 | T& 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()
函数。注意到流提取/插入运算符都是二元运算符。 <<
和>>
是在ostream
和istream
类中定义(重载)的,最终期望达到的效果如下:
- 调用
cout << vec2d
,将信息输出到ostream
对象。- 调用
cin >> vec2d
,从istream
对象中读取信息。
和之前重载二元运算符的思路相同,由于流操作运算符是二元运算符,又想按照上面的形式进行调用,也就是将cout
和cin
这两个流类写在前面,那就必须将<<
、>>
重载为友元函数。并且和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 重载赋值运算符=
本小节来介绍如何重载赋值运算符,并且整个过程以上面这个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; //赋值运算符默认执行一对一成员拷贝
//导致浅拷贝的原因是类中有指针类型的数据成员
只有重载赋值运算符,才能改变其默认工作方式。所以一般情况下,如果拷贝构造函数需要执行深拷贝,那么赋值运算符需要重载。可以看到,执行深拷贝之后,赋值后的对象会指向一个新的地址。当然,若返回值类型是引用类型,那么就支持连续赋值。
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; //会调用重载赋值运算符
最后是一个小练习,注意下面代码的调用方式:
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 运算符重载的更多说明
本小节来补充一些关于运算符重载的更多说明,主要就是下面两点。
- 能不能使用运算符重载来操作基础数据类型?不能! 运算符重载要注意:
- 重载的运算符必须和用户定义的class类型一起使用。
- 重载的运算符的参数至少应有一个是类对象(或类对象的引用)。
- 参数不能全部是C++的标准类型,以防止用户修改用于标准类型数据的运算符的性质,比如
int operator+(int x); //NOT ALLOWED!
。
- 运算符除了定义成类的成员函数之外,还可以声明为友元函数,以保证二元运算符的调用顺序与习惯上相同。基本上大多数类的成员函数都可以声明成友元函数进行重载,当然也有例外,像某些一元运算符就不能修改顺序(比如
[]
、后置++
等)。
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 更多编码规范
- 关于变量名称方面的规范
- 25:迭代变量应使用i,j,k等字母。j,k等应仅用于嵌套循环中。
- 14:大作用域的变量应使用长名字,小作用域的变量应使用短名字。
- 关于头文件
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:包含语句必须放在文件的顶部。将包含语句放到源代码的其它位置,可能会导致预期之外的编译副作用。