#C++ Day28 November 12 2025

//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;

}