作者: supervito

  • #C++ Day33 Basic Data Structure Chapter2 and C++ Generic Programming Day2 November 18 2025

    // C++ Generic Programming – Chapter 2 – Function Templates’ differences in Calling Rules between Regular Functions-2.4.1-Prioritize Calling General Functions

    // C++ 泛型编程 -第二章-函数模版与普通函数调用规则区别 -2.4.1-优先调用普通函数

    #include <iostream>

    using namespace std;

    // 普通函数和函数模版的调用规则区别

    // 普通函数和函数模版可以重名

    // 当普通函数和函数模版 函数名相同 数量也相同的时候 就会优先调用普通函数

    int add(int a, int b)

    {

        cout << “调用普通函数” << endl;

        return a + b;

    }

    // int add(int a, int b);

    // 就算这个普通函数是声明 编译器也会优先调这里 而不是函数模版

    // 显示无法解析的外部符号 普通函数没有实现

    template <typename T>

    T add(T a, T b)

    {

        cout << “调用函数模版” << endl;

        return a + b;

    } // 跟模版在不在没关系

    int main()

    {

        int a = 1, b = 2;

        add(a, b);

        // 编译器规则:如果普通函数和函数模版都可以调用的情况下 会优先调用普通函数

        return 0;

    }

    // C++ Generic Programming – Chapter 2 – Function Templates’ differences in Calling Rules between Regular Functions-2.4.2 Forced Calling Function Templates

    //  C++ 泛型编程 -第二章-函数模版与普通函数调用规则区别 -2.4.2-优先调用函数模版

    #include <iostream>

    using namespace std;

    // 普通函数和函数模版的调用规则区别

    // 普通函数和函数模版可以重名

    // 当普通函数和函数模版 函数名相同 数量也相同的时候 就会优先调用普通函数

    int add(int a, int b)

    {

        cout << “调用普通函数” << endl;

        return a + b;

    }

    // int add(int a, int b);

    // 就算这个普通函数是声明 编译器也会优先调这里 而不是函数模版

    // 显示无法解析的外部符号 普通函数没有实现

    template <typename T>

    T add(T a, T b)

    {

        cout << “调用函数模版” << endl;

        return a + b;

    } // 跟模版在不在没关系

    int main()

    {

        int a = 1, b = 2;

        // add(a, b);

        // 怎么强制调用函数模版 只要在这里显式指定类型就可以了

        // add<int>(a, b); // 普通函数没有这个玩意  加上<int>就是显式指定类型

        add<>(a, b); // 也可以不写int 变成空参数列表

        // 编译器会先指定一个函数模版 再进行自动类型推导

        // 结论:当普通函数和函数模版都可以调用时 可以加上参数列表让编译器强制调用函数模版

        // 编译器规则:如果普通函数和函数模版都可以调用的情况下 会优先调用普通函数

        return 0;

    }

    // C++ Generic Programming – Chapter 2 – Function Templates’ differences in Calling Rules between Regular Functions-2.4.3 Priority Matching of Function Templates

    //  C++ 泛型编程 -第二章-函数模版与普通函数调用规则区别 -2.4.3 – 函数模版的优先匹配

    #include <iostream>

    using namespace std;

    // 即使当不显式指定类型的时候 也能让它调用函数模版

    int add(int a, int b)

    {

        cout << “调用普通函数” << endl;

        return a + b;

    }

    template <typename T>

    T add(T a, T b)

    {

        cout << “调用函数模版” << endl;

        return a + b;

    }

    int main()

    {

        double a = 1, b = 2;

        add(a, b);

        // 普通函数支持隐式类型转换 double转成int 走隐式类型转换

        // 如果是第二个函数 走自动类型推导 可以推导成double

        // 这里的规则:这里是调用函数模版,原因是首先编译器会尝试最匹配的普通函数的版本

        // 找到普通函数的版本 能够完美匹配参数类型 就会调用普通函数

        // 因为这里发生了隐式转换 所以这里不叫完美匹配了

        // 要没有任何转换的情况下才叫完美匹配

        // 所以实际上没有找到合适的版本 这时候编译器就会考虑进行实例化 生成一个合适的函数版本进行调用 所以才会调用这个函数模版

        return 0;

    }

    // C++ Generic Programming – Chapter 2 – 2.5 Function Templates with Multiple Parameters

    //  C++ 泛型编程 -第二章 – 2.5 – 多参数函数模版

    #include <iostream>

    using namespace std;

    /*

    template <typename T1, typename T2, typename T3> // 这里三个类型 当然可以继续往后加

    T1 add(T2 a, T3 b)

    {

        T1 c = a + b;

        return c;

    }

    */

    template <typename T2, typename T3> // 这里三个类型 当然可以继续往后加

    T2 add(T2 a, T3 b)

    {

        // 在正确匹配之后 我想看看T2和T3到底是什么类型 可以通过typeid(T2).name()关键字打印出来

        cout << typeid(T2).name() << endl;

        cout << typeid(T3).name() << endl;

        T2 c = a + b;

        return c;

    }

    int main()

    {

        // double r = add(4.0, 8);

        // 首先T1我们认为它是一个double类型 然后调用add函数 并且传一个double类型和一个整型进去

        // 这样编译通不过 提示没有与参数类型匹配的模版

        // 根据4.0 我们可以推导T2它是个double,根据8 我们可以推导T3它是个int

        // 但是这个T1的类型是推不出来的

        // 结论一:返回值类型(double)无法作为(T1)推导依据

        // 结论二:一旦有类型参数不能推导 就会编译失败

        // 解决:把T1去掉 然后把T1改为T2 这样就完美解决问题了

        double r = add(4.0f, 88881281881); // float类型被推导成float T3类型被推导成了int64 这个int64其实就是longlong 代表 64位整型

        /*

        1、函数模版支持多个类型参数

        2、一旦有类型不能推导,就会导致编译失败

        3、返回值类型无法作为推导依据

        */

        return 0;

    }

    // C++ Generic Programming – Chapter 3 – Class Templates – 3.1 Dynamic Array Class

    //  C++ 泛型编程 -第三章 -类模版- 3.1 – 动态数组类

    #include <iostream>

    using namespace std;

    // 在C++中 你如果定义一个原生的静态数组 数组大小必须是常量 不能是变量

    class DynamicArray

    {

    private:

        int* elements; // 这个成员就是p

        int size;      // 代表数组的一个大小

    public:

        DynamicArray(int n) : size(n)

        { // 构造函数 名字一样 传参是一个整数n 初始化列表 把这个n赋值给这个size

            // 在构造函数里面 把这个elements动态内存给它申请出来

            elements = new int[n];

            // size和n都可以 ,因为n和size的大小是一模一样的

            // 那么在这个构造函数里面 就相当于申请了长度为size的动态数组的内存

        }

        ~DynamicArray()

        {

            // 申请了还需要进行销毁 不然容易导致内存泄漏

            // 我们把销毁放在析构函数里面

            delete[] elements;

            // 因为销毁的是一块数组内存 所以调用delete[]

        }

        // 由于我们模拟的是一个数组 所以对于这个类 我们要实现一个中括号的操作 运算符重载

        // 其实就是实现一个函数 这个函数它有个返回值 返回值是个整数 那就返回一个整型

        int& operator[](int index)  // 如果不写引用 我得到的只是一个值 我不能对它进行赋值 但是我得返回这个元素本身 而不能只是一个值

            //引用去掉后 我用没关系 但是无法对它进行赋值了

        {                           // 加上一个引用 传参是一个索引

            return elements[index]; // 返回一个elements[index]的元素就可以了

        }

    };

    int main()

    {

        int n = 10;

        // int a[n];//报错

        // 在C++中 你如果定义一个原生的静态数组 数组大小必须是常量 不能是变量 标准C++过不了

        // 那么如何在标准C++中实现 给定一个变量n 生成一个长度为n的数组

        // 我们只需要定义一个指针 然后用new运算符实现就可以了

        int* p = new int[n]; // 相当于在堆上申请了 n乘上sizeof(int)的字节 赋值给了指针p

        // 这时候这个p实际上就是动态数组的首地址了 我就可以通过对p进行下标操作或者指针偏移,访问到这块连续内存中的数据了

        // 这个就是动态数组

        // 而动态数组类就是把它进行一层封装 把p变成类的成员变量

        // 动态数组类实现出来了

        DynamicArray da(100);

        da[1] = 3;

        da[9] = 4;

        cout << da[0] << ‘ ‘ << da[1] << endl;//因为数组没有初始化 所以da[0]是一个乱的值

        //如果要实现char double 或者其他类型 的动态数组 而不多写冗余的代码,我该怎么办: 使用类模板

        return 0;

    }

  • #C++ Day32 Basic Data Structure Chapter2 and C++ Generic Programming Day1  November 17 2025

    Sequential List

    Coding 

    //Basic Data Structure Day1 Sequential List

    //顺序表

    #include <iostream>

    using namespace std;

    #define eleType int

    struct SequentialList {//定义顺序表结构

    eleType* elements;

    int size; //顺序表当前的大小,元素的个数

    int capacity; //顺序表的容量,就是数组的大小 

    };

    void intializeList(SequentialList* list, int capacity) {//初始化顺序表

    list->elements = new eleType[capacity];

    list->size = 0;

    list->capacity = capacity;

    }

    void destroyList(SequentialList* list) {//删除整个顺序表

    delete[] list->elements;

    }

    int main() {

    return 0;

    }

    “C++ 泛型编程” 的标准英文表述是 C++ Generic Programming

    补充说明:

    • 核心概念解析
      • Generic Programming(泛型编程):是一种编程范式,核心思想是 “编写与具体数据类型无关的代码,在使用时再指定具体类型”,从而实现代码的复用性、灵活性和类型安全性。
      • 在 C++ 中,泛型编程的主要实现手段是 Templates(模板),包括函数模板(Function Templates)和类模板(Class Templates),例如 std::vector、std::sort 等标准库组件均基于模板实现,支持不同数据类型(如 int、std::string)的通用操作。
    • 常见搭配与场景
      • 描述技术特性:Generic Programming in C++(C++ 中的泛型编程)、C++ Generic Programming Techniques(C++ 泛型编程技巧)。
      • 关联组件:C++ Template-based Generic Programming(基于 C++ 模板的泛型编程)、Generic Data Structures in C++(C++ 中的泛型数据结构)。
    • 与相关概念的区分
      有时容易与 “泛型” 混淆的是 “多态(Polymorphism)”,但二者本质不同:
      • 泛型编程(Generic Programming):编译期确定类型,通过模板生成特定类型的代码,属于 “静态多态”;
      • 面向对象的多态(OOP Polymorphism):运行期确定类型,通过虚函数实现,属于 “动态多态”。

    例如,以下是 C++ 泛型编程(基于模板)的简单示例:

    cpp

    // 函数模板:通用的加法函数,支持任意可相加的类型

    template <typename T>

    T add(T a, T b) {

        return a + b;

    }

    int main() {

        int num1 = 3, num2 = 5;

        double num3 = 2.1, num4 = 3.5;

        std::cout << add(num1, num2) << std::endl;  // 编译期生成 int 版本的 add

        std::cout << add(num3, num4) << std::endl;  // 编译期生成 double 版本的 add

        return 0;

    }

    //C++ Generic Programming – Chapter 2 – Function Template – 2.1 Function Template Definition

    //C++ 泛型变成 – 二、函数模板 2.1 函数模板定义 

    #include <iostream>

    using namespace std;

    //函数模板

    //就是建立一个通用的函数

    //这个通用的函数 它的返回值和参数列表的类型 在定义的时候可以不指定 等到函数调用时再去指定

    //相当于把形参类型和返回值的类型给它抽象出来 定义的时候不知道它的类型 只有在调用的时候才真正确定数据类型

    /*

    template <typename T>

    函数的定义

    T是通用类型

    */

    int addInt(int a, int b) { //整型

    int c = a + b;

    return c;

    }

    double addDouble(double a, double b) {//浮点型

    double c = a + b;

    return c;

    }

    //这两个函数 除了类型不一样 其它都一样 框架一样

    //小学提取公因式

    //T是虚拟类型

    template<typename T>//在前面加上函数模板定义 T变成自己定义的类型 编译就不会报错了

    T add(T a, T b) {

    T c = a + b;

    return c;

    }

    int main() {

    int a = 1, b = 2;

    int c = addInt(a, b);

    cout << c << endl;

    double aa = 1.1, bb = 1.91;

    double cc = addDouble(aa, bb);

    cout << cc << endl;

    return 0;

    }

    “自动类型推导” 的英文是 Automatic Type Deduction

    补充说明:

    • 核心含义:指编程语言中由编译器或解释器根据变量的初始化值、表达式结果等信息,自动推断出变量或表达式数据类型的机制,无需开发者显式声明类型。
    • 常见应用场景:在 C++(如 auto 关键字)、Python(动态类型语言,天生支持类型推导)、Java(Java 10+ 的 var 关键字)、Go 等语言中广泛使用。
      • 示例(C++):auto num = 10;(编译器自动推导 num 为 int 类型)
      • 示例(Python):name = “Alice”(解释器自动推导 name 为字符串类型)
    • 相关术语
      • 若特指某类语言特性,可能会搭配具体关键字表述,如 auto Type Deduction(C++ 中基于 auto 的推导)、var Type Inference(Java/JavaScript 中基于 var 的推导,“Inference” 与 “Deduction” 在此语境下可通用,前者更侧重 “推断逻辑”,后者更侧重 “推导过程”)。

    //C++ Generic Programming – Chapter 2 – 2.2.1 Function Template Calling – Automatic Type Deduction

    //C++ 泛型编程 2.2.1 函数模板调用 – 自动类型推导

    #include <iostream>

    using namespace std;

    //调用函数模板的方法有两种

    //1.自动类型推导

    //2.显式指定类型

    //函数模板

    //就是建立一个通用的函数

    //这个通用的函数 它的返回值和参数列表的类型 在定义的时候可以不指定 等到函数调用时再去指定

    //相当于把形参类型和返回值的类型给它抽象出来 定义的时候不知道它的类型 只有在调用的时候才真正确定数据类型

    /*

    template <typename T>

    函数的定义

    T是通用类型

    */

    int addInt(int a, int b) { //整型

    int c = a + b;

    return c;

    }

    double addDouble(double a, double b) {//浮点型

    double c = a + b;

    return c;

    }

    //这两个函数 除了类型不一样 其它都一样 框架一样

    //小学提取公因式

    //T是虚拟类型

    template<typename T>//在前面加上函数模板定义 T变成自己定义的类型 编译就不会报错了

    T add(T a, T b) {

    T c = a + b;

    return c;

    }

    int main() {

    int a = 1, b = 2;

    int c = addInt(a, b);

    cout << c << endl;

    double aa = 1.1, bb = 1.91;

    double cc = addDouble(aa, bb);

    cout << cc << endl;

    //自动类型推导

    // 自动类型推导 编译器根据a和b的数据类型 去自己推导 并且找到合适的函数模板进行调用 只要没有不确定的数据类型就可以正常编译输出

    // 好处就是把addInt和addDouble的函数变成一个函数去定义,这样就可以减少冗余的代码

    //调用一个add,把a和b传进来

    c=add(a, b);

    cout << c << endl;

    cc = add(aa, bb);

    cout << cc << endl;

    return 0;

    }

    //C++ Generic Programming – Chapter 2 – 2.2.2 Function Template Calling – Explicitly Specify the Type

    //C++ 泛型编程 2.2.2 函数模板调用 – 显式指定类型

    #include <iostream>

    using namespace std;

    //调用函数模板的方法有两种

    //1.自动类型推导

    //2.显式指定类型

    //函数模板

    //就是建立一个通用的函数

    //这个通用的函数 它的返回值和参数列表的类型 在定义的时候可以不指定 等到函数调用时再去指定

    //相当于把形参类型和返回值的类型给它抽象出来 定义的时候不知道它的类型 只有在调用的时候才真正确定数据类型

    /*

    template <typename T>

    函数的定义

    T是通用类型

    */

    int addInt(int a, int b) { //整型

    int c = a + b;

    return c;

    }

    double addDouble(double a, double b) {//浮点型

    double c = a + b;

    return c;

    }

    //这两个函数 除了类型不一样 其它都一样 框架一样

    //小学提取公因式

    //T是虚拟类型

    template<typename T>//在前面加上函数模板定义 T变成自己定义的类型 编译就不会报错了

    T add(T a, T b) {

    T c = a + b;

    return c;

    }

    int main() {

    int a = 1, b = 2;

    int c = addInt(a, b);

    cout << c << endl;

    double aa = 1.1, bb = 1.91;

    double cc = addDouble(aa, bb);

    cout << cc << endl;

    //自动类型推导

    // 自动类型推导 编译器根据a和b的数据类型 去自己推导 并且找到合适的函数模板进行调用 只要没有不确定的数据类型就可以正常编译输出

    // 好处就是把addInt和addDouble的函数变成一个函数去定义,这样就可以减少冗余的代码

    //调用一个add,把a和b传进来

    /*

    c = add(a, b);

    cout << c << endl;

    cc = add(aa, bb);

    cout << cc << endl;

    */

    //显式指定类型

    //显式指定类型就是在调用的时候直接告诉编译器 我这里的T是个什么类型 不需要编译器进行自动推导

    c=add<int>(a, b); //这个<int>实际上就是程序员告诉编译器 接下来我要调用的这个函数 指定了T的这个类型是尖括号里面的这个int

    cout << c << endl;

    cc = add<double>(aa, bb);//我只要指定了这个类型 我这个add函数 只需要实现一个

    cout << cc << endl;

    //甚至以后出现了char类型

    add<char>(‘a’, ‘b’);

    return 0;

    }

    //C++ Generic Programming – Chapter 2 – 2.3.1 The Differences between Regular Functions – Regular Functions has Implicit Type Conversion

    //C++泛型编程 第二章-函数模板 2.3.1 与普通函数区别 – 普通函数有隐式类型转换

    #include <iostream>

    using namespace std;

    //对于普通函数 在实参变成形参的过程中 变量的类型实际上有可能产生隐式转换

    int addInt(int a, int b) { //整型

    int c = a + b;

    return c;

    }

    double addDouble(double a, double b) {//浮点型

    double c = a + b;

    return c;

    }

    template<typename T>//在前面加上函数模板定义 T变成自己定义的类型 编译就不会报错了

    T add(T a, T b) {

    T c = a + b;

    return c;

    }

    int main() {

    int a = 1, b = 2;

    //虽然这两个是整型变量 但是我依然可以调用addDouble这个函数 并且把a和b传进来 赋值给一个变量c

    double c = addDouble(a, b);

    cout << c << endl;

    //正常运行 因为整型 a 作为传参的时候 在传的过程中强制转换成这个double 类型

    //本来a和b都是整型 在传的过程中强制转换成这个double 类型

    //普通函数在调用的时候 如果实参的参数类型和形参的参数类型不匹配 就可能发生隐式类型转换

    return 0;

    }

    是的,在许多编程语言中(如 JavaScript、C++ 等),当函数调用时实参类型与形参类型不匹配时,会发生隐式类型转换(也称为自动类型转换)。

    这种转换由编译器或解释器自动完成,目的是尝试让不匹配的类型能够兼容。

    以 JavaScript 为例:

    javascript

    function add(a, b) {

      return a + b;

    }

    console.log(add(5, “10”));  // 输出 “510”,数字5被转换为字符串”5″,然后进行字符串拼接

    console.log(add(“5”, 10));  // 输出 “510”,数字10被转换为字符串”10″

    console.log(add(true, 5));  // 输出 6,布尔值true被转换为数字1

    console.log(add(null, 5));  // 输出 5,null被转换为数字0

    在 C++ 中也有类似的隐式转换:

    cpp

    #include <iostream>

    using namespace std;

    void printNumber(int num) {

        cout << “Number: ” << num << endl;

    }

    int main() {

        printNumber(‘A’);  // 字符’A’被转换为ASCII码值65

        printNumber(3.14); // 浮点数3.14被转换为整数3

        return 0;

    }

    隐式类型转换虽然方便,但也可能导致意外的结果,尤其是在 JavaScript 等弱类型语言中。因此,在编写代码时,最好显式地进行类型转换,以确保代码的可读性和正确性。

    //C++ Generic Programming – Chapter 2 – Function Templates’ differences between regular functions  2.3.2 Function Templates Automatic Type Deduction  without Implicit Type Conversion

    //C++ 泛型编程 – 第二章 – 函数模板 – 函数模板与普通函数的区别 2.3.2 函数模板自动类型推导无隐式类型转换

    #include <iostream>

    using namespace std;

    //在函数模板进行自动类型推导时 无法进行隐式类型转换

    int addInt(int a, int b) { //整型

    int c = a + b;

    return c;

    }

    double addDouble(double a, double b) {//浮点型

    double c = a + b;

    return c;

    }

    template<typename T>//在前面加上函数模板定义 T变成自己定义的类型 编译就不会报错了

    T add(T a, T b) {

    T c = a + b;

    return c;

    }

    int main() {

    int a = 1, b = 2;

    int c = add(a, b);//这是自动类型推导

    cout << c << endl;

    double d = 2.5;

    //int e = add(a, d);//这里出问题了 说没有与参数列表匹配的函数模板

    // 错误!函数模板自动类型推导 无法进行 隐式类型转换

    //a参数传入时 由于a是int类型 所以它把形参类型T推导成了 int

    //当d参数传入时 因为它是double类型 所以它把形参类型T推导成double类型 

    //然后这时候就冲突了 因为T不可能又是int又是double

    //函数模板进行自动类型推导时 无法进行隐式类型转换

    return 0;

    }

    //C++ Generic Programming – Chapter 2 – Function Templates’ differences between regular functions-2.3.3-Explicitly Specifying the Type with Implicit Type Conversion

    //C++ 泛型编程 – 第二章 – 函数模板 – 函数模板与普通函数的区别 2.3.3 函数模板显式指定类型有隐式类型转换

    #include <iostream>

    using namespace std;

    //在函数模板进行自动类型推导时 无法进行隐式类型转换

    int addInt(int a, int b) { //整型

    int c = a + b;

    return c;

    }

    double addDouble(double a, double b) {//浮点型

    double c = a + b;

    return c;

    }

    template<typename T>//在前面加上函数模板定义 T变成自己定义的类型 编译就不会报错了

    T add(T a, T b) {

    T c = a + b;

    return c;

    }

    int main() {

    int a = 1, b = 2;

    double d = 2.1;

    //int e = add(a, d);//这里出问题了 说没有与参数列表匹配的函数模板

    // 错误!函数模板自动类型推导 无法进行 隐式类型转换

    //a参数传入时 由于a是int类型 所以它把形参类型T推导成了 int

    //当d参数传入时 因为它是double类型 所以它把形参类型T推导成double类型 

    //然后这时候就冲突了 因为T不可能又是int又是double

    //函数模板进行自动类型推导时 无法进行隐式类型转换

    int c = add<int>(a, d);//这样没问题 显式指定类型相当于这个T已经变成 int 

    //T变成int之后 就算我这个是double 它也能自己进行隐式类型转换 

    // 函数相当于明确地告诉编译器 请把我这个T推导成int 

    //这里已经变成普通函数 那就沿用我们之前普通函数的规则:当传入的数据是什么类型不重要 它会在传参的过程中进行隐式的类型转换

    //double c = add<double>(a, d);//也可以写 因为无论它转成什么类型 当我传一个类型进来 他都能变成隐式类型转换 根据自己的需要选择合适的数据类型就可以了

    //函数模板的 【显式指定类型】 调用时的 【隐式类型转换】

    cout << c << endl;

    return 0;

    }

  • #C++ Day31 Basic Data Structure   November 15 2025

    Sequential List

    Basic knowledge 

    //Basic Data Structure Day1 Sequential List

    //顺序表

    #include <iostream>

    using namespace std;

    #define eleType int

    struct SequentialList {//定义顺序表结构

    eleType* elements;

    int size; //顺序表当前的大小,元素的个数

    int capacity; //顺序表的容量,就是数组的大小 

    };

    void intializeList(SequentialList* list, int capacity) {//初始化顺序表

    list->elements = new eleType[capacity];

    list->size = 0;

    list->capacity = capacity;

    }

    void destroyList(SequentialList* list) {//删除整个顺序表

    delete[] list->elements;

    }

    int main() {

    return 0;

    }

  • #C++ Day30 November 14 2025

    //Object – Oriented 9 – 1 Syntax of Polymorphism

    //多态的语法

    #include <iostream>

    using namespace std;

    //多态分为静态多态和动态多态

    //静态多态主要是函数重载

    //我们现在主要学习动态多态 主要是利用派生类和虚函数 来实现一个运行时多态

    class Animal {

    public:

    //虚函数 这样写 就可以调用子类的函数了

    virtual void eat() {

    cout << “动物在吃东西” << endl;

    }

    };

    class Cat :public Animal {

    public:

    void eat() {

    cout << “猫在吃东西” << endl;

    }

    };

    class Pig :public Animal {

    public:

    void eat() {

    cout << “猪在吃东西” << endl;

    }

    };

    //调用链:main -> test -> eat -> Animal::eat

    //函数传参是个动物,但是传入不同的动物,会产生不同的行为,这就叫多态

    void eat(Animal& a) {//eat函数里面调用一个Animal的引用

    //子类对象通过隐式转换 转换成了父类对象 那么肯定调用父类的东西 

    //在父类的函数前面可以加virtual 关键字 引入虚函数

    a.eat();

    }

    void Test() {

    Cat c;

    Pig p;

    //这里是多态:子类重写了父类的虚函数

    //多态还表现在函数传参中,通过传入不同的动物产生不同的行为

    eat(c);

    eat(p);

    }

    int main() {

    Test();

    return 0;

    }

    //Object – Oriented 9 – 2 Virtual Function

    //虚函数

    #include <iostream>

    using namespace std;

    class Animal {

    public:

    virtual void eat() {//可以认为这个函数不占空间 没有成员变量时 类只占1字节

    //但是不占用1字节的话 就可以无限创建没有成员的类 这显然是不合理的

    cout << “动物在吃东西” << endl;

    }

    virtual void run() {

    cout << “动物在跑” << endl;

    }

    };

    class Cat:public Animal {

    public:

    void eat() {//函数存在另一个地方 它跟成员变量分开存储

    cout << “猫在吃东西” << endl;

    }

    };

    void eat(Animal& a) {//函数传进来时直接把Cat转成Animal 隐式转换了

    //调试是发现a没有东西 那就是因为这个存储在这个对象本身上面

    Animal b;//这个b本身就是Animal,只有一个虚函数指针

    a.eat();//这个a是由Cat转换过来的,除了虚函数指针还会有Cat

    //如果当我们产生继承的时候 如果子类覆盖了父类的虚函数 在虚函数指针里面只需要把这条记录覆盖掉就好了

    //覆盖掉的好处,当我在调用a.eat()的时候,我实际上是在虚函数表里面去找这个函数的,去找Cat::eat 这就是我想要的函数,所以这才是我想调用的不是已经被覆盖的父类的函数,所以才会产生多态

    //子调用的时候实际会生成一个虚函数指针,虚函数指针保存了这个类上所有虚函数的地址,并且在子类继承父类时会覆盖掉这个虚函数

    }

    void Test() {

    Cat c;

    eat(c);

    cout << “Animal’s size = ” << sizeof(Animal) << endl;//如果没有任何成员变量 Animal的大小是1字节 这个1字节是占位用的

    //如果Animal变成virtual虚函数 这里就会占8字节 在64位机上 double long long 指针变量就会占8字节  这里是指针(虚函数指针)

    //虚函数指针可以认为是一个数组的首地址 这个数组是所有虚函数组成的一个数组

    }

    int main() {

    Test();

    return 0;

    }

    //Object – Oriented 9 – 3 Pure Virtual Function and Abstract Class

    //纯虚函数和抽象类

    #include <iostream>

    using namespace std;

    //有时在写代码时父类里面的函数有可能没有任何实现,都需要子类去实现,子类去重写,然后纯虚函数的目的就是这个

    class Animal {

    public:

    virtual void eat() =0;//定义一个虚函数 如果不实现就会报错,解决方法加上=0就可以了

    //只要有纯虚函数的类 我们叫它抽象类 抽象类无法实例化对象 这个类是为继承而生的

    };

    class Cat : public Animal {

    public:

    /*

    virtual void eat() {//子类的eat()前面还要加virtual是因为子类也可能被继承

    cout << “猫在吃东西” << endl;

    //如果不重写纯虚函数Animal,Cat也会变成一个抽象类

    }

    */

    //当Cat同样变成了一个抽象类时 实例化Cat时就会报错

    };

    int main() {

    //Animal a;//定义一个虚函数 如果不实现就会报错 有纯虚函数的类也无法实例化对象

    //Cat c;//继承抽象类Animal的类Cat可以实例化对象

    //c.eat();

    return 0;

    }

    //Object – Oriented 9 – 4 Virtual Destructor and Pure Virtual Destructor

    //虚析构和纯虚析构

    #include <iostream>

    using namespace std;

    //虚析构 主要是解决内存泄露的问题

    //我现在有个父类的指针 指向派生类的对象 那么在对它进行销毁的时候 如果父类的析构函数 不是virtual的 就会调用不到子类的析构函数 从而导致内存泄露

    class BaseA {

    public: 

    //只有一个构造函数和一个析构函数,并且析构函数和构造函数都是空实现 因为没有任何成员变量

    BaseA(){}

    ~BaseA(){ cout << “BaseA 销毁了” << endl; }

    };

    class SonA : public BaseA {

    public:

    SonA() : m_Value(NULL) {//构造函数,先把m_Value初始化为一个空指针

    m_Value = new int(10);//在构造函数里把这个内存申请出来

    }

    ~SonA() {

    cout << “SonA 销毁了” << endl;

    delete m_Value;//在析构函数里面把这个内存销毁掉

    }

    int* m_Value;//这个是它的成员变量 并且需要在堆上分配内存的

    };

    class BaseB {

    public:

    //只有一个构造函数和一个析构函数,并且析构函数和构造函数都是空实现 因为没有任何成员变量

    BaseB() {}

    /*

    virtual ~BaseB() { cout << “BaseB 销毁了” << endl; }//把基类的这个地方改成 virtual 变成虚析构

    */

    //虚析构可以这么干

    virtual ~BaseB() = 0;//纯虚析构也可以这么干

    };

    BaseB::~BaseB(){

    //纯虚析构需要在外部被实现一下 因为加上纯虚会变成 抽象类不能实例化对象的

    cout << “BaseB 销毁了” << endl;

    }

    class SonB : public BaseB {

    public:

    SonB() : m_Value(NULL) {//构造函数,先把m_Value初始化为一个空指针

    m_Value = new int(10);//在构造函数里把这个内存申请出来

    }

    ~SonB() {

    cout << “SonB 销毁了” << endl;

    delete m_Value;//在析构函数里面把这个内存销毁掉

    }

    int* m_Value;//这个是它的成员变量 并且需要在堆上分配内存的

    };

    int main() {

    BaseA* a = new SonA();//首先new一个SonA的对象出来,然后把它指向基类的指针

    delete a;//然后把指针delete掉 实际上是调用了SonA的析构 

    //调用时会发现只有基类的析构函数被调用了 子类的析构函数没有被调用

    //带来问题是 子类的堆上的 分配的内存 m_Value  已经new了但是没有被delete 这样就会导致内存泄露

    BaseB* b = new SonB();//这里可能只是定义了一个指针 并没有真正的实例化

    delete b;

    //BaseB x;//发现这里无法进行实例化 因为已经变成抽象类了

    //抽象类无法进行实例化

    return 0;

    }

  • #C++ Day29 November 13 2025

    //Object – Oriented 8 – 1 Inheritance grammar

    //继承的语法

    #include <iostream>

    using namespace std;

    //你可以把继承理解成 数据结构中的一颗树   

    /*

    动物

      / \

      猫     狗

    */

    /*

    继承的语法

    class 子类 : 继承方式 父类{}

    子类 -> 派生类

    父类 -> 基类

    写了两个类 发现他们有一些共性 把共性抽出来实现一个新的类 那两个类成为新类的派生类 这样能省掉一些冗余代码

    */

    class Animal {

    public:

    void eat() {

    cout << “吃” << endl;

    }

    };

    class Cat : public Animal {

    //类 类名: public 继承的类

    public:

    /*void eat() {

    cout << “吃” << endl;

    }*/

    void sayHi() {

    cout << “喵喵~” << endl;

    }

    };

    class Dog : public Animal {

    public:

    /*void eat() {

    cout << “吃” << endl;

    }*///放动物类

    void sayHi() {

    cout << “汪汪汪~” << endl;

    }

    };

    int main() {

    Cat c;

    Dog d;

    c.eat();

    d.eat();

    c.sayHi();

    d.sayHi();

    return 0;

    }

    //Object – Oriented 8 – 2 Inheritance Mode

    //继承方式

    #include <iostream>

    using namespace std;

    /*

    * 继承方式

    公共 public

    保护 protected

    私有 private

    继承方式有三种 访问权限也有三种 组合一下 就是 3*3=9种

    class 子类名 : 继承方式 父类名{};

    上面代表父类访问权限

    下面代表子类继承方式

    | public | protected | private |

    | public | public | protected | 无法访问 |

    | protected | protected | protected | 无法访问 |

    | private | private | private | 无法访问 |

    public:类内可以访问,类外也可以访问

    protected:类内可以访问,类外也可以访问,且子类可以访问

    private:类内可以访问,类外不可访问,且子类不可访问

    */

    class Animal {

    public:

    int m_pub;

    protected:

    int m_pro;

    private:

    int m_pri;

    };

    class Cat : public Animal {

    public:

    Cat() {

    m_pub = 1;

    m_pro = 2;

    //m_pri = 3;//父类私有成员,子类公有继承,无法访问

    }

    };

    class BossCat : public Cat {

    public:

    BossCat() {

    m_pub = 1;

    m_pro = 2;//爷爷类的保护成员   父类的保护成员,子类还是保护成员 再继承一次发现类内可以访问 所以不是 是保护保护

    //m_pri = 3;//父类私有成员,子类公有继承,无法访问

    }

    };

    void testCar() {

    Cat c;

    c.m_pub = 1;

    //c.m_pro = 2;//不行,保护成员在类外部不可以访问,在类内部才可以访问,类内可以访问,类外不可以访问,要么是私有,要么是保护

    }

    class Dog : protected Animal {

    public:

    Dog() {

    m_pub = 1;

    m_pro = 2;

    //m_pri = 3;//父类私有成员,子类保护继承,无法访问

    }

    };

    //现在证明两个变量都是保护的

    class PoliceDog :public Dog {

    public:

    PoliceDog() {

    m_pub = 1;//证明不是私有 因为私有变量无论怎么继承 子类都拿不到 这个变量在父类Dog中一定不是私有成员,

    m_pro = 2;//证明不是私有 因为私有变量无论怎么继承 子类都拿不到 这个变量在父类Dog中一定不是私有成员 

    //所以这两个变量在父类里面一定是保护成员

    }

    };

    void testDog() {

    Dog d;

    //d.m_pub = 1;  //类无法访问的话,要么是保护,要么是私有,我们还无法验证是保护还是私有

    //d.m_pro = 2;//类无法访问的话,要么是保护,要么是私有,我们还无法验证是保护还是私有

    }

    class Pig : private Animal {

    public:

    Pig() {

    m_pub = 1;

    m_pro = 2;

    //m_pri = 3;//父类私有成员,子类私有继承,无法访问

    }

    };

    class WildPig :public Pig {

    public:

    WildPig() {

    m_pub = 1;//证明该变量在父类 Pig中是私有的 因为如果是保护 子类公有继承一定能访问到

    m_pro = 2;//证明该变量在父类 Pig中是私有的 因为如果是保护 子类公有继承一定能访问到

    }

    };

    void testPig() {

    Pig p;

    //p.m_pub = 1;//类无法访问的话,要么是保护,要么是私有

    //p.m_pro = 2;//类无法访问的话,要么是保护,要么是私有

    }

    int main() {

    return 0;

    }

    //Ov Construction and Destruction Order

    //构造和析构顺序 

    #include <iostream>

    using namespace std;

    /*

    继承中,构造链里,先构造的后析构

    d->c->b->a  (d继承了c继承了b继承了a)

    构造顺序:a b c d

    析构顺序:d c b a

    */

    class Animal {

    public:

    Animal() {

    cout << “Animal 构造” << endl;

    }

    ~Animal() {

    cout << “Animal 析构” << endl;

    }

    };

    class Cat : public Animal{

    public:

    Cat() {

    cout << “Cat 构造” << endl;

    }

    ~Cat() {

    cout << “Cat 析构” << endl;

    }

    };

    class BossCat : public Cat {

    public:

    BossCat() {

    cout << “BossCat 构造” << endl;

    }

    ~BossCat() {

    cout << “BossCat 析构” << endl;

    }

    };

    void Test() {

    //Animal a;//调用函数时生成a对象 生成对象时调用构造函数 然后当函数销毁时 调用析构函数

    //Cat c;//先调用父类的构造函数,再调自己构造函数,再调自己的析构函数,最后调用父类的析构函数,有点像一个个括号

    BossCat b;//先调用父类的构造函数,再调自己构造函数,再调自己的析构函数,最后调用父类的析构函数,有点像栈 后进先出

    }

    int main() {

    Test();//调用函数时生成a对象 生成对象时调用构造函数 然后当函数销毁时 调用析构函数

    return 0;

    }

    //Object – Oriented 8 – 4 Access to Same – Named Attributes

    //同名属性访问

    #include <iostream>

    using namespace std;

    //同名属性:父类和子类都有一个名字相同的属性

    // 子类不会覆盖父类,因为存储的位置不在一个内存地址上

    //

    class Animal {//父类

    public:

    Animal() {

    m_Data = 17981;

    }

    int m_Data;

    };

    class Cat:public Animal {//子类

    public:

    Cat() {

    m_Data = 29812;

    }

    int m_Data;

    };

    void Test() {

    Cat c;

    cout << c.m_Data << endl; 

    cout << c.Animal::m_Data << endl;//这个数据还在,用一个作用域来进行访问就好了

    cout << &(c.m_Data) << endl;//不是同一个地址

    cout << &(c.Animal::m_Data) << endl;//不是同一个地址

    }

    int main() {

    Test(); //子类看起来会覆盖父类同名属性,其实并没有被覆盖掉

    return 0;

    }

    //Object – Oriented 8 – 5 Access to Same – Named Function

    //同名函数访问

    #include <iostream>

    using namespace std;

    //父类和子类有一个名称相同的函数

    class Animal{

    public:

    Animal() {

    }

    void eat() {

    cout << “动物吃东西” << endl;

    }

    };

    class Cat : public Animal {

    public:

    Cat() {

    }

    void eat() {

    Animal::eat();//用作用域 这样就可以先调 动物吃东西

    cout << “猫吃东西” << endl;

    }

    };

    int main() {

    Cat c;

    c.eat(); //输出猫吃东西, 

    //c.Animal::eat();//就像同名变量一样 直接点出来就是能拿到自己的 这样就能拿到父类的函数了

    return 0;

    }

    //Object – Oriented 8 – 6 Multiple Inheritance

    //多继承

    #include <iostream>

    using namespace std;

    //多继承:子类有一个,它可以继承多个父类

    class BaseA {

    public:

    int m_A;

    int m_Base;

    BaseA() :m_A(0),m_Base(520){}//构造函数

    };

    class BaseB {

    public:

    int m_B;

    int m_Base;

    BaseB() :m_B(0), m_Base(1314) {}//构造函数

    };

    class BaseC {

    public:

    int m_C;

    BaseC() :m_C(0) {}//构造函数

    };

    class Son : public BaseA, public BaseB, public BaseC {

    };

    int main() {

    Son s;

    s.m_A = 1;

    s.m_B = 2;

    s.m_C = 3;

    //s.m_Base = 8;//这样写会导致不明确,因为父类A和B有一个同名变量

    s.BaseA::m_Base = 8; //用定义域来拿到不同的m_Base值

    s.BaseB::m_Base = 9;

    cout << &s.BaseA::m_Base << endl;//不是同一个地址

    //如果存储在同一个地址 那么编译器就会识别到

    cout << &s.BaseB::m_Base << endl;//不是同一个地址

    cout << sizeof(s) << endl; //总共继承了来自三个父类的5个整型 一个整型 4字节

    return 0;

    }

  • #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;

    }

  • #C++ Day27 November 11 2025

    //Object – Oriented 7 – 1 Operator Overloading

    //运算符重载

    #include <iostream>

    #include <string>

    using namespace std;

    /*

    运算符重载就是对一个已有的运算符进行一个重新定义

    对不同的数据结构有不同处理

    */

    /*

    +

    4+5=9

    class A{

    };

    A a;

    A b;

    a + b;

    */

    int main() {

    //1.加法运算符

    int a = 520;

    int b = 1314;

    cout << a + b << endl; 

    //2.字符串拼接

    string c = “520”;

    string d = “1314”;

    cout << c + d << endl; //实际上就是运算符重载的技术

    string e = “我”;

    string f = “爱你”;

    cout << e + f << endl;

    return 0;

    }

    //Object – Oriented 7 – 2 Plus Operator Overloading

    //加号重载

    #include <iostream>

    using namespace std;

    /*

    +号

    */

    //复数类 实部和虚部 组成  实部和实部相加 虚部跟虚部相加

    class Complex {

    friend Complex operator+(Complex& a, Complex& b);

    //因为real和image是别的函数里面的私有成员,我想用全局函数去访问私有成员的话用友元就可以了

    friend Complex operator-(Complex& a, Complex& b);

    public:

    Complex() :real(0), image(0) {//初始化列表

    }

    Complex(int real, int image) {

    this->real = real;//用this指针避免变量名重复的冲突问题

    this->image = image;

    }

    /*

    Complex add(Complex& other) {//传进来的Complex我们叫它other

    Complex ret;//结不变

    ret.real = this->real + other.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = this->image + other.image;

    return ret;

    }

    */

    /*

    //这里采用成员函数的形式进行加法运算符重载

    Complex operator+(Complex& other) {//传进来的Complex我们叫它other

    Complex ret;//结不变

    ret.real = this->real + other.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = this->image + other.image;

    return ret;

    }

    */

    //a+bi

    void Print() {

    cout << real << ‘+’ << image << ‘i’ << endl;

    }

    private:

    int real;//实部

    int image;//虚部

    };

    //变成全局函数后 变成两个对象相加

    Complex operator+(Complex& a,Complex& b) {

    Complex ret;//结不变

    ret.real = a.real + b.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = a.image + b.image;

    //因为real和image是别的函数里面的私有成员,我想用全局函数去访问私有成员的话用友元就可以了

    return ret;

    }

    Complex operator-(Complex& a, Complex& b) {

    Complex ret;//结不变

    ret.real = a.real – b.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取

    变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = a.image – b.image;

    //因为real和image是别的函数里面的私有成员,我想用全局函数去访问私有成员的话用友元就可以了

    return ret;

    }

    int main() {

    Complex a(10,20);

    Complex b(5, 8); //a和b两个实例化对象

    //Complex c = a.add(b);//实例化一个c,a调用add接口后把b传进去的结果

    //Complex c = a.operator+(b);

    Complex c = a+b; //加法运算符重载 

    c.Print();

    Complex d = a – b;//复数的加减可以用 重载 乘除规格会变掉 所以不行

    d.Print();

    //需要把调用的函数变成一个加号最理想

    return 0;

    }

    //Object – Oriented 7 – 3 Left Shift Overloading

    //左移重载

    #include <iostream>

    using namespace std;

    /*

    Complex c;

    << 左移 目的 就是为了把这个对象输出出来 我要进行输出的话一定会调用cout.operator << (c),operator<<是cout的一个成员函数

    cout.operator << (c)

    //但是我们现在希望它必须是Complex的成员函数

    Complex的成员函数的话c必须得放在左边

    如果这样就会变成 c<<cout 这个达不到想要的效果

    //成员函数的重载  需要用全局函数的

    c.operator <<(cout)

    我们希望cout << c

    所以我们需要它从成员函数里面抽出来 变成一个全局函数

    所以需要用 全局函数作为友元声明

    */

    class Complex {

    friend Complex operator+(Complex& a, Complex& b);

    //因为real和image是别的函数里面的私有成员,我想用全局函数去访问私有成员的话用友元就可以了

    friend Complex operator-(Complex& a, Complex& b);

    //friend void operator<<(ostream& cout, Complex a);

    friend ostream& operator<<(ostream& cout, Complex a);//友元的函数声明也要改过来

    public:

    Complex() :real(0), image(0) {//初始化列表

    }

    Complex(int real, int image) {

    this->real = real;//用this指针避免变量名重复的冲突问题

    this->image = image;

    }

    /*

    Complex add(Complex& other) {//传进来的Complex我们叫它other

    Complex ret;//结不变

    ret.real = this->real + other.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = this->image + other.image;

    return ret;

    }

    */

    /*

    //这里采用成员函数的形式进行加法运算符重载

    Complex operator+(Complex& other) {//传进来的Complex我们叫它other

    Complex ret;//结不变

    ret.real = this->real + other.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = this->image + other.image;

    return ret;

    }

    */

    //a+bi

    void Print() {

    cout << real << ‘+’ << image << ‘i’ << endl;//cout的对象是ostream

    }

    void Printsub() {//减法需要配套的subprint函数

    //减法感觉需要配套的print函数,否则减法后虚部为负数会有类似5 + -8i的情况

    if (image > 0) {

    cout << real << ‘+’ << image << ‘i’ << endl;//复数是正数的情况

    }

    else if (image == 0) {//复数为0的情况

    cout << real << endl;

    }

    else {//复数为负数的情况

    cout << real << image << ‘i’ << endl;

    }

    }

    private:

    int real;//实部

    int image;//虚部

    };

    //变成全局函数后 变成两个对象相加

    Complex operator+(Complex& a, Complex& b) {

    Complex ret;//结不变

    ret.real = a.real + b.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = a.image + b.image;

    //因为real和image是别的函数里面的私有成员,我想用全局函数去访问私有成员的话用友元就可以了

    return ret;

    }

    Complex operator-(Complex& a, Complex& b) {

    Complex ret;//结不变

    ret.real = a.real – b.real; //自己的实数部分等于实数加实数 this 是指针 需用箭头来获取变量,other是引用 是一个对象 所以用.来获取变量

    ret.image = a.image – b.image;

    //因为real和image是别的函数里面的私有成员,我想用全局函数去访问私有成员的话用友元就可以了

    return ret;

    }

    /*

    void operator<<(ostream& cout, Complex a) {//std不用写直接写ostream& cout ,然后把Complex对象传进来

    cout << a.real << ‘+’ << a.image << ‘i’;//私有成员无法直接访问,需要变成一个友元

    }

    */

    //类型改成ostream& 来返回

    ostream& operator<<(ostream& cout, Complex a) {//std不用写直接写ostream& cout ,然后把Complex对象传进来

    cout << a.real << ‘+’ << a.image << ‘i’;//私有成员无法直接访问,需要变成一个友元

    return cout;

    }

    int main() {

    Complex a(10, 20);

    Complex b(5, 8); //a和b两个实例化对象

    //Complex c = a.add(b);//实例化一个c,a调用add接口后把b传进去的结果

    //Complex c = a.operator+(b);

    Complex c = a + b; //加法运算符重载 

    //c.Print();

    Complex d = a – b;//复数的加减可以用重载,复数乘除规则会变掉 所以不行

    //d.Printsub();

    //需要把调用的函数变成一个加号最理想

    //这里的左移c实际上调用了 operator<<(cout,c) 如果我再调左移的话会利用返回值来进行左移的 但是上面的返回值目前是void空类型

    cout << c << endl<<endl; //如果我们希望左移 列式 输出  也就是说能不断地往后面加上左移符号   所以函数再调用完毕后同样需要返回同一个值ostream

    return 0;

    }

    //Object – Oriented 7 – 4 Increment Overloading

    //递增运算符重载

    #include <iostream>

    using namespace std;

    /*

    递增运算符 ++ 的重载

    前置++

    后置++

    复数的++

    其实就是把实部和虚部++

    */

    class Complex {

    friend ostream& operator<<(ostream& c, Complex a);

    public:

    Complex() :real(0), image(0) {}

    Complex(int real, int image) {

    this->real = real;

    this->image = image;

    }

    /*

    void operator++() {//++后面只有一个操作符 不需要其它东西

    //要输出得把类型改成Complex

    this->real += 1;

    }

    */

    /*

    Complex operator++() {//++后面只有一个操作符 不需要其它东西

    //要输出得把类型改成Complex

    this->real += 1;

    return *this;//this指向自己 进行一次解引用就相当于返回了自己

    }

    */

    Complex& operator++() {//++后面只有一个操作符 不需要其它东西 加上引用确保还是原来的对象

    //要输出得把类型改成Complex

    this->real += 1;

    return *this;//this指向自己 进行一次解引用就相当于返回了自己

    }//前置++ 实现了 

    //前置++返回原对象

    Complex operator++(int) {//这里不能加引用 因为这是一个临时对象 这是一个拷贝

    //有参数就是后置++

    //需要先存一个对象

    Complex c = *this;

    this->real += 1;

    return c;

    }//后置++ 实现了

    //后置++返回新的拷贝对象

    private:

    int real;

    int image;

    };

    ostream& operator<<(ostream& c, Complex a) {

    c << a.real << ‘+’ << a.image << ‘i’;

    //cout其实就是我们传进来的c

    return c;

    }

    int main() {

    int x = 1;

    cout << ++(++x) << endl;

    cout << x << endl;//前两个输出的值是一样的

    Complex a(10, 10);

    cout << a << endl;

    //++a;//这里先实现前置++

    cout << ++(++a) << endl;//如果前面不加引用 ++a会生成新对象 跟原来的a对象不一样 没有关系了

    cout << a << endl;

    cout << a++ << endl;//跟a一样的( 输出的倒数第二第三行 一样)

    cout << a << endl;//这一行要多1

    //cout << ((a++)++)++  << endl;

    //cout << a << endl;//要差3

    //int b = 5;

    ////左值就是赋值语句左边出现的表达式 表示为一个有内存的对象 它可以被修改的

    //cout << ((b++)++)++ << endl; //++作为后置++的时候 就不能再调用++了

    ////但这个b++不能被修改 后置的递增运算符返回值不需要是原来的对象

    //cout << b << endl;

    /*

    x = 5;

    x++;//这个对象还是5

    */

    return 0;

    }

  • #C++ Day26 November 8 2025

     Day26 November 10 2025

    //Object – Oriented 6 – 1 Global Function as a friend element

    //全局函数作为友元

    #include <iostream>

    #include <string>

    using namespace std;

    //友元就是一个函数或一个类能够访问另外一个类的私有成员

    /*

    友元的目的

    让一个类或函数

    能够访问另一个类的私有成员

    友元的关键字:friend

    三种友元:

    1.全局函数作为友元

    2.类作为友元

    3.成员函数作为友元

    */

    class People {

    friend void friendVisit(People* p);//把函数拷到这里去 并且加上一个friend

    //友元 让一个函数能够访问另一个类的私有成员

    public:

    People(){

    m_House = “房子”;

    m_Car = “跑车”;

    }

    public://公有 房子

    string m_House;

    private://私有 车子

    string m_Car;

    };

    void friendVisit(People *p) {//好朋友   传一个People指针

    cout << “好朋友来访问你的” << p->m_House << endl;

    cout << “好朋友来访问你的” << p->m_Car << endl;//直接就会报错

    }

    int main() {

    People p;//实例化一个真正的人出来

    friendVisit(&p);//把对象指针传进来

    return 0;

    }

    //Object – Oriented 6 – 2 Class as a friend element

    //类作为友元

    #include <iostream>

    #include <string>

    using namespace std;

    //让一个类去访问另一个类的私有成员

    class People;//先声明下 让PeopleFriend 类能访问到

    class PeopleFriend {

    public:

    PeopleFriend() {//先把构造函数写出来

    }

    /*

    void visit(People *p){//传进来一个People指针    把这个函数的定义变成函数声明

    ////程序是从上往下执行的 所以可能有未定义的问题 看不到车和房子

    //cout << “好朋友来访问你的” << p->m_House << endl;//People类的定义在PeopleFriend的下面  在实现这个函数的时候 东西还没定义出来

    //cout << “好朋友来访问你的” << p->m_Car << endl;//私有成员一般访问不了

    }

    */

    void visit(People* p);

    };

    class People {

    friend class PeopleFriend;//把类作为友元让它的private元素能被朋友类访问 

    public:

    People() {//默认构造函数

    m_House = “房子”;

    m_Car = “跑车”;

    }

    public:

    string m_House;

    private:

    string m_Car;

    };

    void PeopleFriend::visit(People* p) {//把函数拷贝到这里  加上作用域    改成在这里定义函数

    cout << “好朋友来访问你的” << p->m_House << endl;

    cout << “好朋友来访问你的” << p->m_Car << endl;

    }

    int main() {

    People p;

    PeopleFriend pf;

    pf.visit(&p);

    return 0;

    }

    //Object – Oriented 6 – 3 Member function as a friend

    //成员函数作为友元

    #include <iostream>

    #include <string>

    using namespace std;

    //成员函数作为友元

    //PeopleFriend的某个函数能够访问People的私有成员变量

    class People;//先把它声明出来 

    class PeopleFriend {

    public:

    PeopleFriend() {} //只要声明 不要在这里定义了

    void VisitAll(People* p);

    void VisitPub(People* p);

    };

    class People {

    //friend class PeopleFriend; //把这个类作为友元 但是这里不能这么写 不然就两个都能访问了

    friend void PeopleFriend::VisitAll(People* p);//函数作为友元就保证了只有某些函数可以访问相应的成员变量

    //希望只有某些函数能够访问另外一个类的私有成员变量 希望能够访问的那些函数  我给它变成它的友元就好了

    public:

    People() {

    m_House = “别墅”;

    m_Car = “跑车”;

    }

    public:

    string m_House;

    private:

    string m_Car;

    };

    void PeopleFriend::VisitAll(People* p) {//加上作用域 这个要能访问两个 私有和公共

    cout << “好朋友访问了你的” << p->m_House << endl;//好朋友访问了你的公共属性

    cout << “好朋友访问了你的” << p->m_Car << endl;//类不是它的友元的话 访问不到它的私有成员变量的

    //既要访问你的别墅 又要访问你的跑车

    };

    void PeopleFriend::VisitPub(People* p) {//访问公共属性 这个只能访问公共的

    cout << “好朋友访问了你的” << p->m_House << endl;//好朋友访问了你的公共属性

    //cout << “好朋友访问了你的” << p->m_Car << endl;

    };

    int main() {

    People p;

    PeopleFriend pf;

    pf.VisitAll(&p);

    pf.VisitPub(&p);

    return 0;

    }

  • #C++ Day25 November 7 2025

    //Object – Oriented 5 – 5 static member variable

    //静态成员变量

    #include <iostream>

    #include <string>

    using namespace std;

    /*

    静态成员变量的特点:

    1.所有的对象共享同一份数据

    2.编译阶段分配内存

    3.需要在类中进行声明,在类外进行初始化

    */

    class Hero {

    public:

    Hero() {

    m_Name = “英雄”;

    m_Hp = 100;

    }

    ~Hero() {

    }

    //3.1声明的过程

    static int m_HeroCount;//静态成员变量 它是一个public变量 公共的 加了个static静态的

    //静态变量我们需要给它初始化 拷贝出来

    private:

    string m_Name;

    int m_Hp;

    };

    //3.2初始化的过程

    int Hero::m_HeroCount = 100;//加上作用域 就不是全局变量了

    int main() {

    Hero h;

    cout << h.m_HeroCount << endl;//error LNK2001: unresolved external symbol “public: static int Hero::m_HeroCount” (?m_HeroCount@Hero@@2HA)

    h.m_HeroCount = 101;

    cout << Hero::m_HeroCount << endl; //从类里面拿,输出101 说明对象上的变量和类本身的变量应该都是同一个变量

    //猜测的话打印地址就可以验证真伪了

    cout << &(h.m_HeroCount) << endl;

    cout << &(Hero::m_HeroCount) << endl;

    return 0;

    }

    //Object – Oriented 5 – 6 static member function

    //静态成员函数

    #include <iostream>

    #include <string>

    using namespace std;

    //静态成员函数内部只能用静态成员变量,不能用普通成员变量

    /*

    1.所有对象共享函数

    2.静态成员函数只能使用静态成员变量,无法使用普通成员变量

    */

    class Hero {

    public:

    Hero() {

    m_Name = “Vito”;

    m_Hp = 100;

    }

    ~Hero() {

    }

    //声明一个静态成员变量

    static int m_HeroCount;

    static int GetHeroCount() { //静态成员函数

    //静态的东西在内存里面只有一份 一份的时候去取这个成员的时候 它不知道要取哪个对象的成员 这里是没法访问的

    //m_Hp += 1;//不能在静态成员函数里操作非静态成员变量(普通成员变量)

    return m_HeroCount;

    }

    private:

    string m_Name;

    int m_Hp;

    static int GetHeroCount1() { //静态成员函数也可以有访问权限的设置

    return m_HeroCount;

    }

    };

    int Hero::m_HeroCount=100;  //初始化

    int main() {

    Hero h;

    cout << h.GetHeroCount() << endl;

    cout << Hero::GetHeroCount() << endl;//不从对象h上取

    //h.GetHeroCount1();//private的情况下 函数在类外访问不到

    return 0;

    }

    //Object – Oriented 5 – 7 this pointer

    //this指针

    #include <iostream>

    using namespace std;

    //类里面就有this这个变量

    /*

    this 指针

    1.解决命名冲突

    2.*this 就可以获取到这个对象本身

    this *this

    &h *(&h)==h

    */

    class Hero {

    public:

    Hero(int hp) { //形参 

    //当形参跟成员变量同名的时候   成员变量就被隐藏掉了 也就是被形参覆盖掉了

    //hp = hp;//这句话是形参赋值给形参自己 没有意义了 可以靠编译器,直接用鼠标识别是否同一变量

    this->hp = hp;//this代表指向对象的指针 通过箭头获取到它的成员变量

    cout << this << endl; //打印出十六进制的数据  指针

    cout << (*this).hp << endl;

    }

    int hp;//成员变量

    };

    int main() {

    Hero h(100);//实例化对象

    cout << h.hp << endl; //作用域问题

    cout << &h << endl;//跟this的地址一样  所以this实际上代表了这个对象的地址

    cout << (*(&h)).hp << endl;//跟this的地址一样  所以this实际上代表了这个对象的地址 

    //首先取地址 然后解引用 解引用后得到对象本身 然后通过.拿到hp

    return 0;

    }

    //Object – Oriented 5 – 8 const – qualified member function

    //const 修饰的成员函数

    #include <iostream>

    #include <vector> //右键或F12转到文档

    using namespace std;

    //常函数  成员函数的参数和函数体之间如果加上一个const的关键字 这个函数我们就叫它常函数

    //常函数的函数体中不能修改成员属性的值

    class Hero {

    public:

    Hero() :m_Hp(0) {}

    int getHp() const {

    //m_Hp = m_Hp + 1;//不小心改了一下 为了杜绝这种情况 成员函数加const

    //STL c++标准模板库 里面就有这项技术

    return m_Hp;

    }

    int setHp(int hp) {//这里也不能加const 因为加了const左值就改不了了

    m_Hp = hp;

    }

    private:

    int m_Hp;

    };

    int main() {

    const Hero h;

    //h.setHp(100);//常量调用一个非常量函数不可以 所以对于常量只能调用常函数

    h.getHp();

    return 0;

    }

    //Object – Oriented 5 – 9 mutable

    //可变

    //跟const 相对

    //mustable <-> const

    #include <iostream>

    using namespace std;

    class Hero {

    public:

    Hero():m_Hp(0), m_getHpCounter(0) {}

    int getHp() const {//常函数 

    //常函数成员变量一般不能改

    //里面希望有个变量能进行自增

    m_getHpCounter++;//加了mutable可以改,在常量函数里有一个变量统计次数就可以这么写

    return m_Hp;

    }

    void printCounter() const {

    cout << “Counter:” << m_getHpCounter << endl;

    }

    private :

    int m_Hp;

    mutable int m_getHpCounter;//成员变量加了mutable可以改

    };

    int main() {

    Hero h;

    h.getHp(), h.getHp(), h.getHp(), h.getHp(), h.getHp(), h.getHp();

    h.printCounter();

    return 0;

    }

  • #C++ Day24 November 6 2025

    //Objetct – Oriented 5 – 1 construct a function

    //构造函数

    #include <iostream>

    #include <string>

    using namespace std;

    /*

    构造函数需要注意的点

    1.函数名称和类名保持一致

    2.返回值类型 不需要写

    3.构造函数可以有参数

    */

    class Hero {

    public:

    //默认构造函数

    Hero() {

    m_Name = “”;

    m_SkillCount=4;

    m_Speed = 100;

    cout << “默认构造函数:Hero 构造完毕!” << endl;

    }

    //有参构造函数1

    //想要把它的名字构造进来

    Hero(string name) {

    m_Name = name;

    m_SkillCount = 4;

    m_Speed = 100;

    cout << “有参构造函数1:Hero 构造完毕!” << endl;

    }

    //有参构造函数2

    Hero(string name,int skillcount) {

    m_Name = name;

    m_SkillCount = skillcount;

    m_Speed = 100;

    cout << “有参构造函数2:Hero 构造完毕!” << endl;

    }

    private:

    string m_Name;

    int m_SkillCount;

    int m_Speed;

    };

    int main() {

    Hero h1;//一个类在实例化对象的时候 需要对对象进行初始化 这些构造函数需要在初始化里面进行

    Hero h2(“Vito”);//它能够识别括号里面每个参数的类型是否和上面的匹配

    Hero h3(); //这不是一个函数构造过程  这其实是一个函数声明  类似于int main();   int work();

    Hero h4{};//无参可以这样构造

    Hero h5 = Hero(“Vito”);//把string传进来 实际上这里生成了一个匿名对象 生成匿名对象后它赋值给h5

    Hero h6{ “Elon”,4 }; //这个也会调用有参构造函数

    return 0;

    }

    //Objetct – Oriented 5 – 2 destructor

    //析构函数

    #include <iostream>

    using namespace std;

    //析构函数是和构造函数相对的

    //构造函数是对对象进行初始化 析构函数就是对对象进行反初始化 就是做数据清理

    //析构函数是在对象被清理前由系统进行自动调用

    /*

    析构函数注意点

    1.函数名称和类名一致,并且在最前面加上一个~波浪号

    2.函数返回值不需要写

    3.不能有参数

    析构函数难点在面向对象的继承里面

    组合起来 封装 继承 多态 就会有非常多种情况

    */

    class Hero {

    public:

    //构造函数

    Hero() {//不做赋值

    cout << “Hero的默认构造函数调用完毕!” << endl;

    }

    //对象在创建时可能需要赋一些值 但是我在销毁的时候 其实不需要赋任何参数

    //析构函数

    ~Hero() {//析构函数就是在构造函数前加上一个波浪号 析构函数的特点就是里面没有参数

    cout << “Hero的析构函数调用完毕!” << endl;

    }

    };

    void test() {

    Hero h;

    }

    int main() {

    test();//这个对象的作用域结束时 系统会自动调用析构函数

    Hero h;

    int a;

    cin >> a; //程序没有结束的话不 return 0 就不会调用析构函数

    return 0;

    }

    这段代码展示了 C++ 中函数返回自定义类型(Hero类)对象的用法,核心涉及对象返回机制编译器优化(RVO/NRVO) 的概念,我们可以从以下几个角度理解:

    1. 代码逻辑解析

    • test3()函数:返回类型为Hero,函数内部创建一个Hero对象h(构造参数为 40),然后将该对象作为返回值返回。
    • func3()函数:调用test3(),并将返回的Hero对象赋值给新的Hero对象h。

    2. 关键概念:对象返回与复制构造

    正常情况下(不考虑编译器优化),当test3()返回局部对象h时,会发生:

    • test3()中创建h(调用Hero的构造函数,参数 40)。
    • 返回时,会生成一个临时对象(调用复制构造函数,用h复制初始化临时对象)。
    • func3()中,用这个临时对象初始化h(再次调用复制构造函数)。
    • 临时对象被销毁(调用析构函数)。

    但实际中,你可能观察不到这么多复制操作,这涉及编译器的优化。

    3. 编译器优化:RVO/NRVO

    • RVO(Return Value Optimization,返回值优化):编译器会直接在目标对象(func3()中的h)的内存位置构造test3()中的对象,避免临时对象和复制操作。
    • NRVO(Named Return Value Optimization,具名返回值优化):针对函数中有名的局部对象(如test3()中的h)的优化,效果和 RVO 类似。

    这就是代码注释中提到 “需要关掉 RVO 优化(/Zc:nrvo-)” 的原因 —— 如果不关闭优化,编译器会省略复制构造函数的调用(直接构造最终对象),导致无法观察到完整的对象复制过程(仅用于教学演示)。

    4. 总结

    这段代码的核心目的是演示 “函数返回自定义类型对象” 的机制,而关闭编译器优化是为了更清晰地观察对象的创建、复制和销毁过程。在实际开发中,通常不需要关闭这种优化,因为它能提升程序性能(减少不必要的对象复制)。

    //Objetct – Oriented 5 – 3 copy constructor

    //拷贝构造函数

    #include <iostream>

    using namespace std;

    //拷贝构造函数也是一种构造函数 它比较特殊 

    //用一个对象构造出另一个对象

    /*

    拷贝构造函数的定义

    类名(const 类型& 变量名){}

    引用的目的是避免在调用过程中 传参传进来的过程中多一次拷贝 所以传个别名进来 而const是为了传进来的参数不会被改变

    为了通过a对象来创建b对象 结果我把a对象改掉就不合理了 加const就变成一个常量就不能在内部改了

    */

    class Hero {

    public:

    //默认构造函数

    Hero() {

    m_Hp=100;//变量初始化

    cout << “Hero 默认构造函数调用完毕!” << endl;

    }

    //有参构造函数 为了给成员变量赋值

    Hero(int hp) {

    m_Hp = hp;

    cout << “Hero 有参构造函数调用完毕!” << endl;

    }

    //拷贝构造函数要用一个函数来构造另一个函数 它一定是一个有参构造函数 要传另外一个对象进来

    Hero(const Hero& h) {//拷贝构造函数 需要const Hero& h 这种写法

    //引用的目的是避免在调用过程中 传参传进来的过程中多一次拷贝 所以传个别名进来 而const是为了传进来的参数不会被改变

    //为了通过a对象来创建b对象 结果我把a对象改掉就不合理了

    //h.m_Hp = 4;//报错

    m_Hp = h.m_Hp;//不能改不代表不能拿 可以拿出来赋值给当前英雄的血量

    //把所有成员变量从const Hero& h对象上拷贝过来 就完成一次拷贝了

    cout << “Hero 拷贝构造函数调用完毕!” << endl;

    }

    //析构函数

    ~Hero() {

    cout << “Hero 析构函数调用完毕!” << endl;

    }

    private:

    int m_Hp;

    };

    /*

    拷贝构造函数调用时机

    1.用已经创建的对象来初始化对象

    2.函数的传参

    3.函数的返回值

    */

    //1.用已经创建的对象来初始化对象

    void func1() {

    cout << “————-func1————-” << endl;

    Hero h1(20);//实例化一个对象 h1

    Hero h2(h1);//用拷贝构造函数实例化一个对象 通过一个h1对象来生成h2对象

    //等这个函数执行完后 h1和h2都析构掉了

    }

    void test1(Hero h) {//h是形式参数  在函数定义时用来接收传入值的占位符

    }

    void test2(Hero* h) {//h是形式参数  在函数定义时用来接收传入值的占位符

    }

    //2.函数的传参

    void func2() {

    cout << “————-func2————-” << endl;

    Hero h1;

    //test1(h1);//h1作为是一个实参

    //当函数传参有这个对象类型的时候 进去就会调用一次拷贝构造函数

    test2(&h1);//这样不会调用拷贝构造函数 只有h1被析构掉 

    //因为指针代表了传进来对象的地址 并没有生成一个新的对象 所以并没有调用拷贝构造函数

    //拷贝构造函数的前提是 我要生成一个新的对象

    }

    //3.函数的返回值

    //再来实现一个接口

    Hero test3() {//返回值是一个Hero类型

    Hero h(40);

    return h;//把这个对象作为一个返回值返回出去了

    }

    void func3() {

    cout << “————-func3————-” << endl;

    Hero h=test3();//可以生成同一个返回值类型的对象

    //需要关掉 rvo优化  /Zc:nrvo-

    //不然编译器认为不调更加高效 逻辑也是对的

    }

    int main() {

    func1();

    func2();

    func3();

    return 0;

    }

    //Objetct – Oriented 5 – 4 initialization list

    //初始化列表

    //主要给构造函数进行初始化用

    /*

    初始话列表的语法

    构造函数(传参1,传参2): 成员变量1(传参1),成员变量2(传参2){}

    */

    #include <iostream>

    #include <string>

    using namespace std;

    class Hero {

    public:

    /*Hero(string name, int hp) {

    m_Name = name;

    m_Hp = hp;

    }*/

    //Hero(string name, int hp):m_Name(“猴子”),m_Hp(50) {//这里走初始化逻辑

    //

    //}

    Hero(string name, int hp,int speed) :m_Name(name), m_Hp(hp),m_Speed(speed) {

    }

    void Print() {

    cout << “英雄:” << m_Name << “的血量是” << m_Hp << “,速度是” <<m_Speed<< endl;

    }

    private:

    string m_Name;

    int m_Hp;

    int m_Speed;

    };

    int main() {

    Hero h(“Vito”, 100,10);

    h.Print();

    return 0;

    }