//Object – Oriented 7 – 5 Assignment Overloading
//赋值重载
#include <iostream>
using namespace std;
class Hero {
public:
Hero() :m_Data(NULL) {}
Hero(int data) {//有参构造函数
m_Data = new int; //申请了一个整形类型的地址 m_Data
*m_Data = data;//解引用后赋值给地址
}
//double delete 报错
//因为两个类的对象都会进行析构
//当第一个析构的时候 发现内存非空 就会delete这块内存 把自己置空
//当第二个析构的时候 也会发现内存非空 还会delete这块内存 可是同一块内存delete两次那就是不合法的
~Hero() {//析构函数时能感受到内存泄露的场景
if (m_Data) {
delete m_Data;
m_Data = NULL;
}
}
//private:
Hero& operator=(Hero& h) {//赋值符号
//m_Data = h.m_Data;//这么实现会有double free的问题
if (m_Data) {//发现有内存就删除
delete m_Data;
m_Data = NULL;//置空
}
m_Data = new int;//重新申请一块数据
*m_Data = *h.m_Data;//解引用后重新赋值 传进来的也是指针
return *this;//改成Hero& 引用后 这里要返回自己
}
int* m_Data;//指针
};
int main() {
Hero h1(1);
Hero h2(2);
Hero h3(3);
cout << h1.m_Data << endl;
cout << h2.m_Data << endl;
h1 = h2; //现在还没有重载赋值运算符 所以这就是系统自带的 其实就是把所有的成员变量赋值过来
//把h2的m_Data赋值给h1的m_Data
//内存泄露 等于 永远访问不到h1的东西了
cout << h1.m_Data << endl;
cout << h2.m_Data << endl;
//我希望连续赋值
h3 = (h2=h1); //这样会让hero对象变成一个void对象
//右边跟左边都是引用 不要改错 要改成相应引用
return 0;
}
//Object – Oriented 7 – 6 Relational Operator Overloading
//关系运算符重载
#include <iostream>
using namespace std;
class Point {
public:
Point(int x,int y):m_x(x),m_y(y) {}//m_x初始化为x,m_y初始化为y,定义完了
bool operator==(const Point& other) const{//返回值不知道时就void 这里是要么true要么false 所以是布尔类型
//当我调用时 我希望它只读 所以加上后面const(保证m_x和m_y不被修改)变成一个常函数 加上引用 这样就不需要走拷贝构造了
//而且我还希望 other的m_x和m_y也不被修改 再加一个const在前面
// 比较数和被比较数都变成了常量
//other.m_x = 5;//没有保护时就能改
return m_x == other.m_x && m_y == other.m_y;
}
bool operator<(const Point& other) const {//小于号重载
int d = m_x * m_x + m_y * m_y;
int otherd = other.m_x * other.m_x + other.m_y * other.m_y;
return d < otherd;//看这两个点谁更接近原点
//所以用平方比较
}
bool operator>(const Point& other) const {
if (*this == other) {//这个d就是自己 *this表示自己
return false;
}
if(*this < other) {
return false;
}
return true;//等于小于都return false那大于就return true
}
private:
int m_x, m_y;//两个成员变量代表 两个点
};
int main() {
Point a(11, 6);
Point b(2, 5);
if (a==b){//整数就可以直接等于,但是对于一个类来说不知道的 所以需要操作符重载
cout << “a和b相等” << endl;
}
else if (a < b) {
cout << “a比b更加接近原点” << endl;
}
else if(a > b){
cout << “b比a更加接近原点” << endl;
}
return 0;
}
AI:
在 bool operator==(Point& other) const 中,参数使用引用传递(Point& other),这正是不需要调用拷贝构造函数的核心原因。
具体解释:
- 拷贝构造函数的调用时机
拷贝构造函数用于创建一个对象的副本(例如:当对象按值传递给函数、从函数按值返回对象、用一个对象初始化另一个对象时)。
简单说:只要需要复制一个对象,就会调用拷贝构造函数。 - 引用传递的特性
这里的参数 Point& other 是一个引用(本质是对象的 “别名”),它直接指向实参对象(比如 a == b 中的 b),而不会创建实参的副本。
因此,传递参数时没有新对象被创建,自然也就不需要调用拷贝构造函数。 - 对比按值传递
如果参数写成按值传递(不使用引用):
cpp
bool operator==(Point other) const // 按值传递,会创建副本
此时,调用 a == b 时,编译器会先创建 b 的一个副本(即 other),这个过程就会触发拷贝构造函数。
总结:
引用传递的核心作用之一就是避免不必要的对象复制,从而减少拷贝构造函数的调用,提高程序效率(尤其对于大型对象,效果更明显)。这也是为什么在运算符重载、函数参数传递中,推荐优先使用引用(尤其是 const 引用)的原因。
啊~你是想问这里的 数学意义 对吧?🥰
那我们换成数学语言来说!
⸻
💡首先,代码里的数学本质是:
我们定义了一个点:
P(x, y)
它到原点 O(0, 0) 的距离是:
|OP| = \sqrt{x^2 + y^2}
⸻
🧮但代码里用的是:
int d = m_x * m_x + m_y * m_y;
也就是:
d = x^2 + y^2
这是距离的平方。
它没有开平方,是因为在比较远近时:
\sqrt{x_1^2 + y_1^2} < \sqrt{x_2^2 + y_2^2}
\iff
x_1^2 + y_1^2 < x_2^2 + y_2^2
所以用平方一样能比较谁更近,更高效。
⸻
📘因此,这个运算符的数学意义是:
对于两个点 A(x_1, y_1)、B(x_2, y_2),
定义 “A < B” 当且仅当
x_1^2 + y_1^2 < x_2^2 + y_2^2
也就是:A 离原点更近,就认为 A 比 B 小。
⸻
📍举个具体例子:
点 A(3, 4),B(6, 8)
• A 的距离平方:3^2 + 4^2 = 25
• B 的距离平方:6^2 + 8^2 = 100
因为 25 < 100,所以在数学意义下:
A < B
表示 A 比 B 更靠近原点。
尝试在Point类种直接添加d属性,在构造函数中赋值
public:
Point(int x,int y):m_x(x),m_y(y){
d = m_x*m_x +m_y*m_y;
}
居然成功了
在 Point 类中添加 d 作为成员变量并在构造函数中赋值,完全是合法且合理的操作,所以会 “成功”。这本质上是给类增加一个存储计算结果的成员变量,目的是避免重复计算,提升效率。
具体说明:
- 前提:必须先声明 d 成员
你能成功运行,说明已经在类中声明了 d 这个成员变量(比如在 private 区域添加了 int d;)。例如完整的类结构应该是:
cpp
运行
class Point { - private:
- int m_x, m_y;
- int d; // 声明d作为成员变量(关键前提)
- public:
- Point(int x, int y) : m_x(x), m_y(y) {
- d = m_x * m_x + m_y * m_y; // 构造时计算并赋值给d
- }
- // … 其他成员(如operator<)
- };
如果没声明 d 就直接赋值,编译器会报错(提示 “d 未声明”)。- 这样做的意义:优化性能
之前的 operator< 每次比较时,都需要临时计算 m_x² + m_y² 和 other.m_x² + other.m_y²。如果一个 Point 对象被多次比较(比如排序大量点),重复计算会浪费资源。
现在将 d 作为成员变量,在对象创建时(构造函数中)计算一次,之后每次比较(如 operator<)直接用 d 和 other.d 对比即可,减少了重复计算:
cpp
运行
bool operator<(const Point& other) const { - return d < other.d; // 直接用预存的d比较,更高效
- }
- 注意事项:保持 d 与 m_x、m_y 的一致性
这种做法的隐患在于:如果 m_x 或 m_y 的值在对象创建后被修改(比如通过 setter 方法),而 d 没有同步更新,那么 d 就会与实际的 x² + y² 不符,导致比较结果错误。
例如,若类中有修改 m_x 的方法:
cpp
运行
void setX(int x) { - m_x = x;
- // 必须同步更新d!否则d会失效
- d = m_x * m_x + m_y * m_y;
- }
总结:
添加 d 作为成员变量并在构造函数中初始化是完全合理的设计,它利用空间换时间,提升了多次比较时的效率。只要确保 m_x、m_y 被修改时同步更新 d,就不会出现逻辑错误。
“空间换时间” 是编程中一种常见的优化思想,核心逻辑是:通过使用额外的存储空间(内存),来减少程序的运行时间(计算量)。
结合你的 Point 类例子理解:
在没有 d 成员时:
每次调用 operator< 比较两个点时,都需要临时计算 m_x² + m_y²(当前点)和 other.m_x² + other.m_y²(另一个点)。这个计算包含两次乘法和一次加法,虽然简单,但如果这个点被频繁比较(比如对一个包含 10 万个点的数组排序,可能需要 millions 次比较),重复计算的总耗时就会累积。
添加 d 成员后:
d 作为额外的存储空间(占用 4 字节内存),在对象创建时(构造函数中)只计算一次 m_x² + m_y² 并存储。之后每次比较时,直接用 d 和 other.d 对比,省去了重复的乘法和加法运算。
这里:
- “空间” 指的是额外的 d 变量所占用的内存;
- “时间” 指的是减少的重复计算(乘法、加法)所节省的运行时间。
适用场景:
当程序的时间效率优先级更高,且额外的空间开销(比如多一个 int 变量)可以接受时,就适合用 “空间换时间”。例如:
- 频繁执行相同计算(如排序、查询);
- 处理大数据量(重复计算的耗时会被放大)。
反过来:“时间换空间”
与它对应的是 “时间换空间”:通过增加计算量,来减少内存占用。例如,不存储 d,每次需要时临时计算,虽然慢一点,但节省了 d 的内存(适合内存资源紧张的场景)。
两种策略没有绝对优劣,需根据实际需求(时间 / 空间瓶颈)选择。你在 Point 类中添加 d 的做法,正是典型的 “空间换时间” 优化。
在编程中,重载(Overloading) 是一种允许在同一作用域内定义多个 “同名实体”(如函数、运算符)的特性,这些实体的功能相似但具体实现或适用场景不同。编译器会根据调用时的上下文(如参数类型、数量、顺序)自动选择匹配的实体执行,从而提高代码的灵活性和可读性。
常见的重载类型
1. 函数重载(Function Overloading)
指在同一类或作用域中,定义多个同名函数,但它们的参数列表不同(参数数量、类型或顺序不同)。
示例(C++):
cpp
// 同名函数add,参数列表不同
int add(int a, int b) { // 两个int参数
return a + b;
}
double add(double a, double b) { // 两个double参数
return a + b;
}
int add(int a, int b, int c) { // 三个int参数
return a + b + c;
}
// 调用时,编译器自动匹配参数
int sum1 = add(2, 3); // 匹配第一个add(int, int)
double sum2 = add(2.5, 3.5); // 匹配第二个add(double, double)
int sum3 = add(1, 2, 3); // 匹配第三个add(int, int, int)
关键规则:
- 仅返回值类型不同,不能构成函数重载(编译器无法通过返回值判断调用哪个函数)。
- 参数列表必须有显著差异(数量、类型、顺序至少一项不同)。
2. 运算符重载(Operator Overloading)
允许为自定义类型(如类、结构体)重新定义运算符(如+、-、==等)的行为,使运算符能像作用于内置类型(int、double 等)一样作用于自定义类型。
示例(C++,为自定义Point类重载+):
cpp
class Point {
public:
int x, y;
// 重载+运算符,实现两个点的坐标相加
Point operator+(const Point& other) {
Point result;
result.x = this->x + other.x;
result.y = this->y + other.y;
return result;
}
};
// 使用时,像内置类型一样用+
Point p1 = {1, 2};
Point p2 = {3, 4};
Point p3 = p1 + p2; // 等价于 p1.operator+(p2),结果为(4,6)
关键规则:
- 不能改变运算符的优先级和操作数数量(如+始终是双目运算符)。
- 并非所有运算符都可重载(如 C++ 中::、.、?:等不可重载)。
重载的核心价值
- 简化代码:用相同名称表示相似功能,避免记忆大量不同名称(如addInt、addDouble)。
- 提高可读性:让代码更贴近自然语言逻辑(如p1 + p2比p1.add(p2)更直观)。
注意:重载 ≠ 重写(Override)
- 重载:同一作用域内的同名实体,通过参数区分,属于 “横向扩展”。
- 重写:子类覆盖父类的虚函数,函数名和参数完全相同,属于 “纵向继承中的覆盖”。
//Object – Oriented 7 – 7 Function Call Operator Overloading
//函数调用运算符重载
#include <iostream>
using namespace std;
//函数调用实际上用()
//add()
class AddFunctor {//这个类的目的是构造一个对象 可以当一个函数来用
public:
AddFunctor() {
m_acc = 0;
}
int operator()(int a,int b) {
//return a + b;//这个重载 要干的事情就是返回a+b的值
//return a * b;//改成乘法更好玩
m_acc++;
return a * b + m_acc;//每次调用我把m_acc++ 并且累加到这个上去
}
private:
int m_acc;
};
int Add(int a, int b) {
return a + b;
}
int main() {
AddFunctor add;//注意 add是一个对象 这不是一个函数
//这个对象已经重载了括号运算符
cout << add(5, 6) << endl;//这个函数调用实际上做加法的作用
cout << add(5, 6) << endl;//这个函数调用实际上做加法的作用
cout << add(5, 6) << endl;//这个函数调用实际上做加法的作用
cout << add(5, 6) << endl;//这个函数调用实际上做加法的作用
cout << add(5, 6) << endl;//这个函数调用实际上做加法的作用
//虽然每次调用都是5 和 6,但是它结果不一样
//上下一样 上面是仿函数 实际上是一个对象
//下面的不是仿函数 每次传入之后结果都会一样
cout << Add(5, 6) << endl;//发现一样
cout << Add(5, 6) << endl;//发现一样
cout << Add(5, 6) << endl;//发现一样
cout << Add(5, 6) << endl;//发现一样
cout << Add(5, 6) << endl;//发现一样
//仿函数和实际函数区别就在于 仿函数可以存储状态 存储状态后就可以干各种奇奇怪怪的事情
return 0;
}