C++ 运算符重载:深入剖析与实现
I. 引言
A. 什么是运算符重载
运算符重载是C++中一种特殊的函数重载机制,它允许我们对已有的运算符赋予新的含义以适应不同数据类型的操作。通过运算符重载,我们可以使用自定义的类或结构体类型进行运算,从而提高代码的可读性和整洁性。运算符重载的实质是编写一个或多个特殊的成员函数或友元函数,这些函数负责处理特定运算符的操作。
B. 为什么要使用运算符重载
运算符重载的主要目的是使自定义数据类型的操作更加直观和易于理解。通过运算符重载,我们可以使用与内置数据类型相同的语法和形式来操作自定义数据类型,从而简化代码并提高代码可读性。此外,运算符重载还可以提高代码的整洁性,使代码更符合数学或其他领域的表达习惯。例如,在实现一个复数类时,通过重载加法运算符,我们可以直接使用 “+” 来表示复数的加法,而不需要调用特定的成员函数。
C. C++运算符重载的优缺点
- 优点:
提高代码可读性:运算符重载使得自定义数据类型的操作更加直观,从而提高代码的可读性。
代码整洁性:通过运算符重载,我们可以简化代码,使其更符合数学或其他领域的表达习惯。
一致性:运算符重载提供了一种与内置数据类型相同的操作方式,使得自定义数据类型的操作更加一致。
- 缺点:
滥用可能导致代码难以理解:如果滥用运算符重载,可能导致代码变得难以理解。重载运算符的含义应该符合预期的逻辑,否则可能引起混淆。
学习成本:对于初学者来说,理解和掌握运算符重载的概念和用法可能需要一定的学习成本。
无法重载所有运算符:C++不允许重载某些运算符,例如条件运算符(?:)和逗号运算符(,)。这在某些情况下可能会限制我们的编程灵活性
II. 运算符重载基本概念
A. 运算符重载的定义
运算符重载是通过为已有的运算符定义新的操作来适应不同数据类型的一种机制。运算符重载实质上是在定义一个特殊的函数,这个函数负责处理指定运算符的操作。运算符重载可以通过成员函数或友元函数来实现。
B. 运算符重载的分类
1. 一元运算符
一元运算符是只需要一个操作数的运算符,例如递增(++)、递减(–)和取反(!)。一元运算符可以通过成员函数或友元函数来实现。
2. 二元运算符
二元运算符是需要两个操作数的运算符,例如加法(+)、减法(-)和乘法(*)。二元运算符通常通过成员函数或友元函数来实现。
C. 限制与规范
1. 无法重载的运算符
C++不允许重载以下运算符:
- 条件运算符(?:)
- 作用域解析运算符(::)
- 成员选择运算符(.*)
- 成员指针选择运算符(->*)
- 大括号初始化运算符({})
- 逗号运算符(,)
2. 重载运算符的规范与建议
为了避免混淆和误解,使用运算符重载时需要遵循一定的规范和建议:
- 重载的运算符应该符合其原始含义和预期行为。
- 避免过多地重载运算符,以免使代码变得难以理解。
- 在可能的情况下,为了保持一致性和易用性,重载的运算符应该具有与内置类型相似的优先级和结合性。
- 使用友元函数进行重载时,应谨慎选择,以防止破坏类的封装性。
3. C++ 运算符重载的两种方式
- 类内重载
//运算符重载的格式为: 返回值类型 operator 运算符名称 (形参表列) { /** code **/ } //重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。 Point operator+(const Point &);
- 类外重载
运算符重载函数不仅可以作为类的成员函数,还可以作为全局函数.
C++ 只会对成员函数的参数进行类型转换,而不会对调用成员函数的对象进行类型转换
friend 返回值类型 operator 运算符名称 (形参表列);
- 临时返回
返回值类型 类名::operator 运算符名称 (const 类名 &S)const { return 类名(this->变量名A + S.变量名B, this->变量名A + S.变量名B); } //return 语句中会创建一个临时对象,这个对象没有名称,是一个匿名对象。 //在创建临时对象过程中调用构造函数,return语句将该临时对象作为函数返回值。
- 规则
- 并不是所有的运算符都可以重载。能够重载的运算符包括:
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> <<= >>= == != <= >= && || ++ -- , ->* -> () [] new new[] delete delete[]
上述运算符中,[]是下标运算符,()是函数调用运算符。自增自减运算符的前置和后置形式都可以重载。
长度运算符sizeof、条件运算符: ?、成员选择符.和域解析运算符::不能被重载
//不能被重载的运算符 .、.*、::、? :、sizeof //必须以成员函数重载的运算符 )、[]、->、=
- 重载不能改变运算符的优先级和结合性。
- 重载不会改变运算符的用法,原有有几个操作数、操作数在左边还是在右边,这些都不会改变。
- 运算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数,这显然是错误的。
- <<和>>是在 iostream 中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的。
- 类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象被自动转换为某种类型。
- 运算符重载函数既可以作为类的成员函数,也可以作为全局函数。
- 运算符重载的实质是将运算符重载为一个函数,使用运算符的表达式就被解释为对重载函数的调用。
- 运算符为全局函数时,此时函数的参数个数就是运算符的操作数个数,运算符的操作数就成为函数的实参。
- C++ 规定,箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载。
III. 运算符重载实例
A. 重载一元运算符
1. 重载前缀递增/递减运算符
//重载前缀递增/递减运算符通常通过成员函数实现,函数没有参数并返回引用。 class Counter { public: Counter(int value = 0) : value(value) {} Counter& operator++() { // 前缀递增 ++value; return *this; } Counter& operator--() { // 前缀递减 --value; return *this; } private: int value; };
2. 重载后缀递增/递减运算符
//重载后缀递增/递减运算符也是通过成员函数实现,函数带有一个int类型的参数(不使用),并返回值。 class Counter { public: Counter(int value = 0) : value(value) {} Counter operator++(int) { // 后缀递增 Counter temp = *this; ++value; return temp; } Counter operator--(int) { // 后缀递减 Counter temp = *this; --value; return temp; } private: int value; };
3. 重载取反运算符
//重载取反运算符通过成员函数实现,函数没有参数并返回值。 class Boolean { public: Boolean(bool value = false) : value(value) {} Boolean operator!() const { // 取反 return Boolean(!value); } private: bool value; };
B. 重载二元运算符
1. 重载算术运算符
a. 加法运算符
//重载加法运算符可以通过成员函数或友元函数实现,以下是一个复数类的例子。 class Complex { public: Complex(double real = 0, double imaginary = 0) : real(real), imaginary(imaginary) {} Complex operator+(const Complex& rhs) const { // 加法 return Complex(real + rhs.real, imaginary + rhs.imaginary); } friend Complex operator+(const Complex& lhs, const Complex& rhs) { return Complex(lhs.real + rhs.real, lhs.imaginary + rhs.imaginary); } private: double real; double imaginary; };
b. 减法运算符
重载减法运算符的实现与加法运算符类似。
c. 乘法运算符
class Complex { public: Complex(double real = 0, double imaginary = 0) : real(real), imaginary(imaginary) {} Complex operator*(const Complex& rhs) const { // 乘法 double newReal = real * rhs.real - imaginary * rhs.imaginary; double newImaginary = real * rhs.imaginary + imaginary * rhs.real; return Complex(newReal, newImaginary); } friend Complex operator*(const Complex& lhs, const Complex& rhs) { double newReal = lhs.real * rhs.real - lhs.imaginary * rhs.imaginary; double newImaginary = lhs.real * rhs.imaginary + lhs.imaginary * rhs.real; return Complex(newReal, newImaginary); } private: double real; double imaginary; };
d. 除法运算符
重载除法运算符的实现与乘法运算符类似。
e. 模运算符
重载模运算符的实现通常与加法和减法运算符类似。
2. 重载关系运算符
a. 等于运算符
class String { public: bool operator==(const String& rhs) const { // 等于 return strcmp(data, rhs.data) == 0; } friend bool operator==(const String& lhs, const String& rhs) { return strcmp(lhs.data, rhs.data) == 0; } private: char* data; };
b. 不等于运算符
重载不等于运算符的实现通常基于等于运算符。
c. 大于运算符
重载大于运算符的实现通常与等于运算符类似。
d. 小于运算符
重载小于运算符的实现通常与等于运算符类似。
e. 大于等于运算符
重载大于等于运算符的实现通常基于大于和等于运算符。
f. 小于等于运算符
重载小于等于运算符的实现通常基于小于和等于运算符。
3. 重载赋值运算符
a. 重载复合赋值运算符
//重载复合赋值运算符通常通过成员函数实现,函数带有一个常量引用参数并返回引用。 class Integer { public: Integer(int value = 0) : value(value) {} Integer& operator+=(const Integer& rhs) { // 复合加法赋值 value += rhs.value; return *this; } private: int value; };
b. 重载移动赋值运算符
//重载移动赋值运算符通过成员函数实现,函数带有一个右值引用参数并返回引用。 class String { public: String& operator=(String&& rhs) { // 移动赋值 if (this != &rhs) { delete[] data; data = rhs.data; rhs.data = nullptr; } return *this; } private: char* data; };
4. 重载输入/输出运算符
a. 重载流插入运算符
//重载流插入运算符通常通过友元函数实现,函数带有一个ostream引用参数和一个自定义类型的常量引用参数,并返回ostream引用。 class Point { public: friend std::ostream& operator<<(std::ostream& os, const Point& point) { // 流插入 os << "(" << point.x << ", " << point.y << ")"; return os; private: int x; int y; };
b. 重载流提取运算符
//重载流提取运算符通常通过友元函数实现,函数带有一个istream引用参数和一个自定义类型的引用参数,并返回istream引用。 class Point { public: friend std::istream& operator>>(std::istream& is, Point& point) { // 流提取 is >> point.x >> point.y; return is; } private: int x; int y; };
IV. 运算符重载的实际应用案例
在本节中,我们将讨论运算符重载在实际编程中的应用案例,以及它们在类设计中的作用。我们还将讨论运算符重载与友元函数以及类成员函数的关系。
A. 运算符重载在类的设计中的作用
运算符重载在类设计中的主要作用是提供自然、直观的语法来表示自定义数据类型的操作。以下是一些典型的运算符重载应用案例:
- 矩阵类:实现矩阵的加法、减法、乘法等操作。
- 分数类:实现分数的加法、减法、乘法、除法以及约分等操作。
- 复数类:实现复数的加法、减法、乘法、除法等操作。
- 字符串类:实现字符串的连接、比较、查找等操作。
- 向量类:实现向量的加法、减法、点积、叉积等操作。
B. 运算符重载与友元函数
在实现运算符重载时,我们有时需要在类之外定义运算符函数。这种情况下,我们可以使用友元函数。友元函数可以访问类的私有和保护成员,同时具有全局函数的调用方式。以下是一些典型的运算符重载与友元函数的应用案例:
- 输入/输出运算符:通常通过友元函数实现,以便在类之外访问私有成员。
- 二元运算符:在需要访问两个不同对象的私有成员时,可以使用友元函数。
- 关系运算符:当需要访问两个对象的私有成员进行比较时,可以使用友元函数。
C. 运算符重载与类成员函数
运算符重载可以作为类的成员函数实现,这使得我们可以直接访问类的私有成员。以下是一些典型的运算符重载与类成员函数的应用案例:
- 一元运算符:通常通过成员函数实现,因为它们只涉及一个对象的操作。
- 赋值运算符:通常通过成员函数实现,因为它们涉及修改对象的内部状态。
- 复合赋值运算符:通常通过成员函数实现,因为它们涉及修改对象的内部状态。
在实际应用中,我们可以根据需要选择友元函数或成员函数来实现运算符重载。通过合理地使用运算符重载,我们可以提高代码的可读性和整洁性,使得自定义数据类型的操作更加直观和自然。
V. 运算符重载与类的设计
在本节中,我们将讨论运算符重载在类设计中的应用,以及如何利用运算符重载实现复数类、向量类、矩阵类和字符串类。
A. 复数类的实现
class Complex { public: Complex(double real = 0.0, double imaginary = 0.0) : real(real), imaginary(imaginary) {} // 加法运算符重载 Complex operator+(const Complex& rhs) const { return Complex(real + rhs.real, imaginary + rhs.imaginary); } // 减法运算符重载 Complex operator-(const Complex& rhs) const { return Complex(real - rhs.real, imaginary - rhs.imaginary); } // 乘法运算符重载 Complex operator*(const Complex& rhs) const { double r = real * rhs.real - imaginary * rhs.imaginary; double i = real * rhs.imaginary + imaginary * rhs.real; return Complex(r, i); } // ... 其他运算符重载 private: double real; double imaginary; };
B. 向量类的实现
class Vector { public: Vector(double x = 0.0, double y = 0.0) : x(x), y(y) {} // 加法运算符重载 Vector operator+(const Vector& rhs) const { return Vector(x + rhs.x, y + rhs.y); } // 减法运算符重载 Vector operator-(const Vector& rhs) const { return Vector(x - rhs.x, y - rhs.y); } // 点积运算符重载 double operator*(const Vector& rhs) const { return x * rhs.x + y * rhs.y; } // ... 其他运算符重载 private: double x; double y; };
C. 矩阵类的实现
class Matrix { public: Matrix(int rows, int cols) : rows(rows), cols(cols), data(rows, std::vector<double>(cols, 0.0)) {} // 加法运算符重载 Matrix operator+(const Matrix& rhs) const { Matrix result(rows, cols); for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { result.data[i][j] = data[i][j] + rhs.data[i][j]; } } return result; } // ... 其他运算符重载 private: int rows; int cols; std::vector<std::vector<double>> data; };
D. 字符串类的实现
class String { public: String(const char* str = "") : data(str) {} // 加法运算符重载 String operator+(const String& rhs) const { return String((data + rhs.data).c_str()); } // 赋值运算符重载 String& operator=(const String& rhs) { if (this != &rhs) { data = rhs.data; } return *this; } // ... 其他运算符重载 private: std::string data; };
E. 分数类的实现
class Fraction { public: Fraction(int numerator = 0, int denominator = 1) : numerator(numerator), denominator(denominator) { simplify(); } // 加法运算符重载 Fraction operator+(const Fraction& rhs) const { int newNumerator = numerator * rhs.denominator + rhs.numerator * denominator; int newDenominator = denominator * rhs.denominator; return Fraction(newNumerator, newDenominator); } // ... 其他运算符重载 private: void simplify() { // 约分操作(使用辗转相除法求最大公约数) } int numerator; int denominator; };
F. 二维点类的实现
class Point2D { public: Point2D(double x = 0.0, double y = 0.0) : x(x), y(y) {} // 加法运算符重载 Point2D operator+(const Point2D& rhs) const { return Point2D(x + rhs.x, y + rhs.y); } // 减法运算符重载 Point2D operator-(const Point2D& rhs) const { return Point2D(x - rhs.x, y - rhs.y); } // ... 其他运算符重载 private: double x; double y; };
G. 三维向量类的实现
class Vector3D { public: Vector3D(double x = 0.0, double y = 0.0, double z = 0.0) : x(x), y(y), z(z) {} // 加法运算符重载 Vector3D operator+(const Vector3D& rhs) const { return Vector3D(x + rhs.x, y + rhs.y, z + rhs.z); } // 减法运算符重载 Vector3D operator-(const Vector3D& rhs) const { return Vector3D(x - rhs.x, y - rhs.y, z - rhs.z); } // 点积运算符重载 double operator*(const Vector3D& rhs) const { return x * rhs.x + y * rhs.y + z * rhs.z; } // 叉积运算符重载 Vector3D operator^(const Vector3D& rhs) const { double newX = y * rhs.z - z * rhs.y; double newY = z * rhs.x - x * rhs.z; double newZ = x * rhs.y - y * rhs.x; return Vector3D(newX, newY, newZ); } // ... 其他运算符重载 private: double x; double y; double z; };
Ⅵ 运算符重载在设计模式中的运用
运算符重载在设计模式中的应用不是非常普遍,因为设计模式通常关注更高层次的架构和行为。然而,在某些情况下,运算符重载可以用于简化设计模式的实现。以下是几个示例:
A. 代理模式
代理模式中,代理类通常会代替实际类处理某些操作。在这种情况下,我们可以利用运算符重载来实现代理类与实际类之间的交互。
class RealObject { public: int value() const { return value_; } void setValue(int value) { value_ = value; } private: int value_; }; class Proxy { public: Proxy(RealObject* realObject) : realObject_(realObject) {} // 重载->运算符,使代理类可以像实际类一样使用 RealObject* operator->() { return realObject_; } private: RealObject* realObject_; }; int main() { RealObject realObj; Proxy proxy(&realObj); proxy->setValue(42); std::cout << "Value: " << proxy->value() << std::endl; return 0; }
B. 享元模式
享元模式中,我们可以使用运算符重载来实现对共享对象的引用计数。
class SharedObject { public: SharedObject() : refCount_(0) {} void addRef() { ++refCount_; } void release() { if (--refCount_ == 0) { delete this; } } private: int refCount_; }; class ObjectHandle { public: ObjectHandle(SharedObject* obj = nullptr) : obj_(obj) { if (obj_) { obj_->addRef(); } } ObjectHandle(const ObjectHandle& rhs) : obj_(rhs.obj_) { if (obj_) { obj_->addRef(); } } ~ObjectHandle() { if (obj_) { obj_->release(); } } // 重载赋值运算符,实现引用计数 ObjectHandle& operator=(const ObjectHandle& rhs) { if (obj_ != rhs.obj_) { if (obj_) { obj_->release(); } obj_ = rhs.obj_; if (obj_) { obj_->addRef(); } } return *this; } private: SharedObject* obj_; };
C. 装饰器模式
装饰器模式中,装饰器类通常会为被装饰类添加额外的功能。在这种情况下,我们可以使用运算符重载来实现装饰器类对被装饰类的功能扩展。
class Component { public: virtual void operation() const = 0; }; class ConcreteComponent : public Component { public: void operation() const override { // 实现具体操作 } }; class Decorator : public Component { public: Decorator(Component* component) : component_(component) {} void operation() const override { // 在调用component_->operation()之前或之后添加额外功能 component_->operation(); } private: Component* component_; };
Ⅶ 运算符重载的底层逻辑
运算符重载的底层逻辑是通过为运算符提供自定义实现,从而实现用户自定义类型的支持。编译器在处理运算符重载时,会将运算符的调用转换为对应的函数调用。下面我们来具体了解一下编译器在实现运算符重载时的一些基本原理。
当编译器遇到一个运算符表达式时,它会检查操作数的类型。如果操作数是用户自定义类型(如类或结构体)并且对应的运算符重载函数已经定义,编译器会将运算符的调用替换为该运算符重载函数的调用。运算符重载函数可以是类的成员函数或者非成员函数(通常是友元函数)。
以下是一个简单的示例,说明了编译器如何处理运算符重载:
class Complex { public: Complex(double real = 0.0, double imaginary = 0.0) : real_(real), imaginary_(imaginary) {} Complex operator+(const Complex& rhs) const { return Complex(real_ + rhs.real_, imaginary_ + rhs.imaginary_); } private: double real_; double imaginary_; }; int main() { Complex a(1, 2); Complex b(3, 4); Complex c = a + b; // 编译器将这个表达式转换为:Complex c = a.operator+(b); return 0; }
在这个例子中,Complex 类有一个成员函数 operator+,用于重载加法运算符。当编译器遇到表达式 a + b 时,它会将这个表达式转换为对应的运算符重载函数调用 a.operator+(b)。
编译器处理运算符重载的基本步骤如下:
- 解析表达式,确定操作数的类型和运算符。
- 搜索与操作数类型匹配的运算符重载函数。这可能包括成员函数和非成员函数(如友元函数)。
- 如果找到了匹配的运算符重载函数,则将运算符调用替换为该函数调用。
- 如果没有找到匹配的运算符重载函数,但操作数类型支持隐式类型转换,则尝试进行类型转换并重复步骤 2 和 3。
- 如果仍然没有找到匹配的运算符重载函数,则报告编译错误。
需要注意的是,运算符重载并不改变运算符的优先级或结合性。这些特性在编译期间由编译器根据语言规范处理。运算符重载只是允许我们为用户自定义类型提供自定义的运算符实现。
Ⅷ 常见问题与解答
以下是关于C++运算符重载的一些常见问题及解答,以帮助您进一步巩固和深化对运算符重载的理解。
问题:运算符重载是否会影响程序性能?
答:运算符重载通常不会对程序性能产生显著影响。事实上,由于运算符重载是在编译时解析的,因此它不会引入额外的运行时开销。然而,不恰当地使用运算符重载可能导致代码变得难以理解和维护,所以我们需要权衡可读性和性能之间的平衡。
问题:为什么不能重载内置类型的运算符?
答:C++不允许为内置类型重载运算符,因为这可能导致代码混乱和意料之外的行为。运算符重载的主要目的是提供一种方式来定义自定义数据类型的操作,而不是改变内置类型的行为。
问题:成员函数和友元函数在运算符重载中有什么区别?
答:成员函数和友元函数在运算符重载中的主要区别在于访问权限和调用方式。成员函数可以直接访问类的私有成员,而友元函数需要类的显式授权。此外,成员函数的调用方式是基于对象的,而友元函数的调用方式是基于参数的。通常情况下,我们可以根据需要选择成员函数或友元函数进行运算符重载。
问题:为什么有些运算符不能重载?
答:C++不允许重载某些运算符,主要是因为这些运算符具有特定的语义,重载它们可能导致代码混乱和意料之外的行为。例如,条件运算符(?:)和作用域解析运算符(::)具有特定的语法结构和语义,重载它们将破坏语言的一致性。
问题:运算符重载是否有助于提高代码可读性?
答:运算符重载可以提高代码可读性,因为它允许我们使用自然的数学符号表示自定义数据类型的操作。然而,不恰当地使用运算符重载可能导致代码变得难以理解和维护。因此,在使用运算符重载时,我们需要遵循相关规范和建议,确保代码可读性和可维护性。
Ⅸ. 总结
C++中的运算符重载提供了一种灵活的方法来定义自定义数据类型的操作。通过合理地使用运算符重载,我们可以提高代码的可读性和整洁性。然而,运算符重载也有其局限性和需要注意的规范。在实际开发中,我们需要根据实际需求和项目特点来判断是否需要使用运算符重载,以及如何合理地使用运算符重载。总的来说,运算符重载是C++编程的一个重要特性,掌握和运用好运算符重载,可以提高我们编程的效率和代码质量。
常用运算符重载的总结
以下是一些常用运算符重载的简要总结,帮助您快速回顾和理解运算符重载的应用。
一元运算符
- 前缀递增/递减:通过成员函数实现,无参数,返回引用。
- 后缀递增/递减:通过成员函数实现,带一个int类型参数(不使用),返回值。
- 取反:通过成员函数实现,无参数,返回值。
二元运算符- 算术运算符:可以通过成员函数或友元函数实现,带一个常量引用参数,返回值。
- 关系运算符:可以通过成员函数或友元函数实现,带一个常量引用参数,返回布尔值。
- 赋值运算符:通常通过成员函数实现,带一个常量引用参数或右值引用参数,返回引用。
输入/输出运算符- 流插入运算符:通过友元函数实现,带一个ostream引用参数和一个自定义类型的常量引用参数,返回ostream引用。
- 流提取运算符:通过友元函数实现,带一个istream引用参数和一个自定义类型的引用参数,返回istream引用。
在使用运算符重载时,请注意遵循相关的规范和建议,避免滥用运算符重载,确保代码可读性和可维护性。通过以上内容的学习,您应该对C++运算符重载有了较为全面的了解。在实际编程过程中,不断练习和思考,加深对运算符重载的理解和运用,将有助于您更好地利用C++的强大功能,提高编程效率和代码质量。
Ⅹ. 实践练习
为了帮助您更好地掌握C++运算符重载,我们为您准备了一些实践练习。通过完成这些练习,您将巩固对运算符重载的理解,并提高编程能力。
为一个表示分数的类实现加法、减法、乘法和除法运算符。此外,实现关系运算符(如等于、不等于、大于、小于等)以及输入/输出运算符。
实现一个矩阵类,支持加法、减法、乘法和转置等操作。同时实现关系运算符和输入/输出运算符。
为一个表示复数的类实现算术运算符、关系运算符和输入/输出运算符。确保复数的实部和虚部都能正确处理。
实现一个简单的字符串类,支持拼接(+)、比较(==、!=、>、<等)和赋值(=)等操作。同时实现输入/输出运算符。
实现一个表示二维点的类,支持加法(向量相加)、减法(向量相减)、点积(内积)、叉积(外积)以及关系运算符和输入/输出运算符。
在完成这些练习时,请注意遵循C++运算符重载的相关规范和建议。通过实际操作,您将更加深入地了解C++运算符重载的原理和应用,从而提高编程效率和代码质量。