博客

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

    }

  • #C++ Day23 November 5 2025

    //object – oriented 4 – 1 attribute & behavior

    //面向对象 属性和行为

    #include <iostream>

    using namespace std;

    //面向对象三大属性:封装、继承、多态

    /*

    封装指属性和行为的封装

    封装的语法:

    class 类名 {

    访问权限:

    属性(成员变量)

    行为(成员函数)

    };

    */

    class Hero {

    //访问权限 public private protected

    public:

    //属性

    int m_Id; //m->member 成员 m代表member

    int m_Hp; //血量

    //行为

    void addHp(int hp) {

    m_Hp += hp;//加血

    }

    void subHp(int hp) {

    m_Hp -= hp;//减血

    }

    };

    int main() {

    //通过类来生成对象的过程,叫实例化

    Hero h;

    //访问对象的属性

    h.m_Id = 5;

    h.m_Hp = 100;

    h.addHp(100);

    cout << “ID为”<<h.m_Id << “的英雄,血量是” << h.m_Hp << endl;

    h.subHp(100);//最简单的类的封装

    cout << “ID为” << h.m_Id << “的英雄,血量是” << h.m_Hp << endl;

    return 0;

    }

    //object – oriented 4 – 2 access permission

    //访问权限

    #include <iostream>

    using namespace std;

    /*

    访问权限:

    公共权限 public 类内可以访问,类外也可以访问

    保护权限 protected 类内可以访问,类外不可以访问  子类可以访问

    私有权限 private     类内可以访问,类外不可以访问  子类不可以访问

    B -> A  一个A类 一个B类 B类继承A类

    A 父类、基类 属性:名字、房子、支付密码

    B 子类、派生类   公有  保护  私有

    */

    class People {

    //公有权限

    public:

    int m_Id; //身份证号

    //保护权限

    protected:

    int m_HouseId; //房子合同号

    private:

    int m_PayPass;//支付密码

    public://在类里再写个函数

    void work() {

    //所有成员变量 类内均可以访问

    m_Id = 1;

    m_HouseId=2;

    m_PayPass = 123;

    }

    private://在类里再写个函数

    void work1() {

    //所有成员变量 类内均可以访问

    m_Id = 1;

    m_HouseId = 2;

    m_PayPass = 123;

    }

    };

    //简单介绍继承

    class Son : public People {//Son类继承了People类

    void func() {

    //在里面实现一个函数

    m_Id = 1;

    m_HouseId = 4; //保护成员,子类可以访问

    //m_PayPass = 123; //私有成员,子类无法访问

    }

    };

    int main() {

    People p;//instantiate a class 实例化一个类

    p.m_Id = 1;//公有成员 类外可以访问

    //p.m_HouseId=5;//保护成员 类外不可以访问

    //p.m_PayPass = 123;//私有成员 类外不可以访问

    p.work();

    //p.work1();//私有成员函数 类外不可以访问

    return 0;

    }

    //Object – Oriented 4 – 3 class & struct

    //class和struct区别

    //C++里面它们两个只有访问权限的区别

    #include <iostream>

    using namespace std;

    //struct && class

    //struct 权限 默认是公共的

    //class 权限 默认是私有的

    class C {

    int m_a;

    };

    struct S {

    int m_a;

    void func() {//C++的 struct 里面也可以定义成员函数  (C里面的struct 不可以定义成员函数)

    m_a = 666;

    }

    };

    int main() {

    C c;//实例化对象

    S s;

    //c.m_a;//点不出来 私有的

    s.m_a=4;//能点出来 公共的

    cout << s.m_a << endl;

    s.func();

    cout << s.m_a << endl;

    return 0;

    }

    AI:

    在 C++ 中,#include <string> 的核心作用是让编译器识别并使用标准库中的 std::string 类。这背后涉及 C++ 的类型系统、标准库设计和编译机制,具体原因如下:

    1. std::string 不是 C++ 的 “内置类型”

    C++ 的 “内置类型”(如 int、char、double 等)是语言本身直接支持的基础类型,编译器天生认识它们,无需额外声明。

    但 std::string 是标准库提供的 “类类型”(本质是对字符串的封装),它的定义、成员函数(如 size()、substr())、运算符重载(如 +、=)等逻辑并非由编译器内置,而是放在标准库的头文件中。

    2. <string> 头文件包含 std::string 的完整声明

    std::string 的 “设计蓝图”(类定义、成员函数声明等)被编写在 <string> 头文件中。当你在代码中使用 std::string 时(例如 std::string str = “hello”;),编译器需要知道:

    • 这个 std::string 是什么(类的结构)?
    • 它支持哪些操作(成员函数、运算符)?

    如果不通过 #include <string> 引入这些信息,编译器会把 std::string 视为 “未定义的类型”,直接报错(例如 error: ‘string’ is not a member of ‘std’)。

    3. 避免依赖 “间接包含” 的不可靠性

    有时你可能发现,不显式包含 <string> 也能编译通过(例如包含了 <iostream> 等其他头文件时)。这是因为某些头文件(如 <iostream>)内部可能间接包含了 <string> 的部分内容(例如为了支持 cout << string 这样的操作)。

    但这种 “间接包含” 是不可靠的

    • 标准并未规定其他头文件必须包含 <string>,不同编译器(如 GCC、Clang、MSVC)或标准库版本的实现可能不同;
    • 一旦依赖的间接包含被移除(例如编译器更新),代码会突然报错。

    因此,显式包含 <string> 是规范且安全的做法

    总结

    #include <string> 的目的是向编译器提供 std::string 类的完整声明,让编译器能够识别并正确处理字符串对象的定义、初始化和操作。由于 std::string 是标准库类而非内置类型,必须通过包含对应头文件才能使用。

    在Visual Studio中,打断点后有几种常用的下一步调试操作,核心快捷键如下:

    1. 逐过程(F10):执行当前行代码并跳到下一行,遇到函数时不进入函数内部,直接执行完整个函数。

    2. 逐语句(F11):和逐过程类似,但遇到函数调用时会进入函数体内,从函数第一行开始逐步调试。

    3. 跳到下一个断点(F5):若设置了多个断点,按下后程序会连续执行,直到停在紧接着的下一个断点处。

    4. 跳出函数(Shift+F11):若当前在函数内部调试,按下后会执行完函数剩余代码,直接跳回该函数的调用处并暂停。

    在 SetSpeed 函数中,return 的作用是在参数不合法时提前退出函数,避免执行后续的赋值操作。

    具体来说:当传入的 speed 小于 100 或大于 500 时,代码会先输出 “速度设置不合法”,然后通过 return 直接结束函数。这样一来,后续的 m_Speed = speed; 就不会被执行,从而阻止不合法的速度值被赋值给成员变量 m_Speed

    如果没有这个 return,即使 speed 不合法,程序也会继续执行到 m_Speed = speed;,导致非法值被保存,违背了 “限制速度在 100-500 之间” 的设计逻辑。

    简单说,这里的 return 是为了在参数无效时 “终止函数执行”,确保只有合法的参数才会生效

    //Object – Oriented 4 – 4 attribute privatization

    //面向对象 属性私有化

    #include <iostream>

    #include <string>

    //STL字符内容

    using namespace std;

    //企业级开发时基本都会把一些属性设置为私有的 private 这样可以自由地定义读写权限

    //接口、方法、函数 其实是同一个概念  都为了提供给外部调用

    //好处:

    //1.可以控制读写权限

    //2.可以检测数据的有效性

    class Hero{

    public:

    void SetName(string name) {

    m_Name = name; //传一个name

    }

    string GetName() {

    return m_Name;//不需要参数 直接返回成员变量就可以

    }

    int GetSkillCount() {

    return m_SkillCount;

    }

    void SetSpeed(int speed) {

    if (speed < 100 || speed>500) {

    cout << “速度设置不合法” << endl;//检测速度是否合法

    return;

    }

    m_Speed = speed; //传参传进去  拿不到值也可以在这按F9打个断点 开启Debug模式 然后程序跑到这一行中断 然后鼠标移上去标识符那就可看到666 按F10 往下执行一行 再去看m_Speed也变成666了

    }

    private:

    string m_Name;//名字 可读可写  名字随便改随便取

    int m_SkillCount=4;//技能数 只读 一个英雄技能数量固定 不能随便改

    int m_Speed;//速度 只写 速度可以去修改 但是不需要获取它的速度值

    };//如果直接开放公共的m_Speed 让外部去修改它 每次修改我都需要检测速度是否合法 但是我一旦抽象成函数后只需要检测一次就好了 这就是成员属性私有化的好处

    //属性变成私有以后 可以通过一些方式来控制它的读写权限 可以用visual studio的监视来观察相应的变量

    int main() {

    Hero h;

    /*

    h.m_Name = “Vito”;

    h.m_SkillCount = 4;

    m.m_Speed = 100;

    //都是私有成员 拿不到

    */

    h.SetName(“Vito”);

    cout << “英雄的名字叫:” << h.GetName() << endl;

    cout << “英雄的技能数为:” << h.GetSkillCount() << endl; //输出乱码的话说明没有初始化

    h.SetSpeed(666);

    return 0;

    }

  • #C++ Day22 November 4 2025

    //snake game

    #include <iostream>

    using namespace std;

    int main() {

    int cnt = 0;

    while (1) { //basic structure

    ++cnt;

    cout << “The game is running……” <<cnt <<endl;

    }

    return 0;

    }

    //07蛇体绘制

    #include <iostream>

    #include <Windows.h>

    using namespace std;

    #define H 27 //is mean 27 lines

    #define W 60

    //if we met the Height or width,we are using these substitutes.

    enum BlockType {//用枚举类型来定义每个格子是什么类型 自定义类型

    EMPTY = 0,//空

    FOOD = 1,//食物

    };

    struct Map {

    BlockType data[H][W];//H行 W列的矩阵 二维数组

    bool hasFood;//定义这张地图上有没有食物

    };

    struct Pos { //蛇的位置 结构体

    int x;

    int y;

    };

    struct Snake {

    Pos snake[H * W]; //位置 把蛇表示在一维数组里面

    int snakeDir;//蛇的方向

    int snakeLength;//蛇的长度

    };

    void initSnake(Snake* snk) {//为什么传指针 不传结构体本身 因为我要在函数内部修改值 并且在函数外部也改掉 必须用到指针

    snk->snakeLength = 3; //蛇的长度

    snk->snakeDir = 1; //蛇的方向

    snk->snake[0] = { W / 2,H / 2 };//蛇的位置放到屏幕中间 W是x H是y 结构体初始化

    snk->snake[1] = { W / 2 – 1,H / 2 };

    snk->snake[2] = { W / 2 – 2,H / 2 };

    }

    void hideCursor() {

    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //输出窗口句柄,句柄就是编号

    CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };//网上一搜索就知道这个结构体,代表隐藏掉它

    SetConsoleCursorInfo(hOutput, &curInfo);//传递cursor的地址,结构体指针

    }

    void initMap(Map* map) {//传递Map的指针 为了它这个结构体能够被修改

    for (int y = 0; y < H; ++y) {//遍历行

    for (int x = 0; x < W; ++x)//遍历列 先遍历哪个都行

    map->data[y][x] = BlockType::EMPTY;  //枚举 等价于 map->data[y][x] = 0;

    }

    map->hasFood = false;//第一层 没有食物

    }

    void drawUnit(Pos p, const char unit[]) {

    COORD coord;//结构体 代表坐标

    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//拿到窗口句柄

    coord.X = p.x + 1;//因为地图上下左右 都包了一圈 整体会有一的偏移量

    coord.Y = p.y + 1;

    SetConsoleCursorPosition(hOutput, coord); //设置光标位置的接口 传窗口句柄(哪个窗口) 和 位置变量结构体

    cout << unit;//打印传进来的字符串变量

    }

    void drawMap(Map* map) { //传参

    //cout << “123” << endl;//test the output

    system(“cls”); // calling the command prompt for clearing the terminal

    cout << “┏”; //⌈¯⌉ ⌊_⌋ upper border

    for (int i = 0; i < W; ++i) {

    cout << “━”;

    }

    cout << “┓”<<endl;

    for (int y = 0; y < H; ++y) {

    cout << “┃”;

    for (int x = 0; x < W; ++x) {

    if (map->data[y][x] == BlockType::EMPTY) {

    cout << ” “;

    }

    }

    cout << “┃” << endl;

    }

    cout << “┗”; // lowest border

    for (int i = 0; i < W; ++i) {

    cout << “━”;

    }

    cout << “┛” << endl;

    }

    void drawSnake(Snake* snk) {

    for (int i = 0; i < snk->snakeLength; ++i) {//遍历蛇身长度

    drawUnit(snk->snake[i], “■”); //蛇身长度 和 用什么来表示这条蛇

    }

    }

    int main() {

    Map map;//结构体

    Snake snk;

    hideCursor();//隐藏光标

    initMap(&map);//结构体传地址 变成结构体指针

    initSnake(&snk);

    drawMap(&map);//绘制地图 结构体传地址 变成结构体指针

    drawSnake(&snk);//绘制完地图后 把蛇传进来

    while (1) {//basic structure

    }

    return 0;

    }

    //08蛇体移动

    这段代码实现了贪吃蛇移动时身体部分的核心逻辑,我们来分析一下它的工作原理:

    这段代码的作用是让蛇的身体部分跟随前一段移动。假设snk->snake是一个存储蛇身体各段坐标的数组(通常snake[0]是蛇头,snake[1]是蛇头后面的第一节身体,以此类推),snk->snakeLength是蛇的总长度。

    循环从蛇的最后一段(i = snk->snakeLength – 1)开始,一直到第二节身体(i >= 1),将每一段的位置更新为前一段的位置(snk->snake[i] = snk->snake[i – 1])。

    举个例子:如果蛇有 3 段(长度为 3),索引为 0(头)、1(中间)、2(尾),那么循环会:

    • 先处理 i=2(尾部),让尾部移动到 i=1(中间)的位置
    • 再处理 i=1(中间),让中间移动到 i=0(头部)的位置

    这样就完成了整个身体的跟随移动。需要注意的是,这段代码只处理了身体部分,蛇头的移动需要在调用这个函数之后,根据当前方向(上 / 下 / 左 / 右)单独更新snake[0]的坐标。

    这种实现方式非常经典,通过 “后一段复制前一段位置” 的逻辑,高效地实现了蛇的整体移动效果。

    //08蛇体移动

    #include <iostream>

    #include <Windows.h>

    using namespace std;

    #define H 27 //is mean 27 lines

    #define W 60

    //if we met the Height or width,we are using these substitutes.

    const int dir[4][2] = { //让蛇动起来 上下左右来说 x,y方向 二维数组 

    {-1,0},//上 y方向-1 x方向不变

    {0,1},//右 →  y方向不变 x+1

    {1,0},//下

    {0,-1}//左

    };

    enum BlockType {//用枚举类型来定义每个格子是什么类型 自定义类型

    EMPTY = 0,//空

    FOOD = 1,//食物

    };

    struct Map {

    BlockType data[H][W];//H行 W列的矩阵 二维数组

    bool hasFood;//定义这张地图上有没有食物

    };

    struct Pos { //蛇的位置 结构体

    int x;

    int y;

    };

    struct Snake {

    Pos snake[H * W]; //位置 把蛇表示在一维数组里面

    int snakeDir;//蛇的方向

    int snakeLength;//蛇的长度

    };

    void initSnake(Snake* snk) {//为什么传指针 不传结构体本身 因为我要在函数内部修改值 并且在函数外部也改掉 必须用到指针

    snk->snakeLength = 3; //蛇的长度

    snk->snakeDir = 1; //蛇的方向

    snk->snake[0] = { W / 2,H / 2 };//蛇的位置放到屏幕中间 W是x H是y 结构体初始化

    snk->snake[1] = { W / 2 – 1,H / 2 };

    snk->snake[2] = { W / 2 – 2,H / 2 };

    }

    void hideCursor() {

    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //输出窗口句柄,句柄就是编号

    CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };//网上一搜索就知道这个结构体,代表隐藏掉它

    SetConsoleCursorInfo(hOutput, &curInfo);//传递cursor的地址,结构体指针

    }

    void initMap(Map* map) {//传递Map的指针 为了它这个结构体能够被修改

    for (int y = 0; y < H; ++y) {//遍历行

    for (int x = 0; x < W; ++x)//遍历列 先遍历哪个都行

    map->data[y][x] = BlockType::EMPTY;  //枚举 等价于 map->data[y][x] = 0;

    }

    map->hasFood = false;//第一层 没有食物

    }

    void drawUnit(Pos p, const char unit[]) {

    COORD coord;//结构体 代表坐标

    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//拿到窗口句柄

    coord.X = p.x + 1;//因为地图上下左右 都包了一圈 整体会有一的偏移量

    coord.Y = p.y + 1;

    SetConsoleCursorPosition(hOutput, coord); //设置光标位置的接口 传窗口句柄(哪个窗口) 和 位置变量结构体

    cout << unit;//打印传进来的字符串变量

    }

    void drawMap(Map* map) { //传参

    //cout << “123” << endl;//test the output

    system(“cls”); // calling the command prompt for clearing the terminal

    cout << “┏”; //⌈¯⌉ ⌊_⌋ upper border

    for (int i = 0; i < W; ++i) {

    cout << “━”;

    }

    cout << “┓”<<endl;

    for (int y = 0; y < H; ++y) {

    cout << “┃”;

    for (int x = 0; x < W; ++x) {

    if (map->data[y][x] == BlockType::EMPTY) {

    cout << ” “;

    }

    }

    cout << “┃” << endl;

    }

    cout << “┗”; // lowest border

    for (int i = 0; i < W; ++i) {

    cout << “━”;

    }

    cout << “┛” << endl;

    }

    void drawSnake(Snake* snk) {

    for (int i = 0; i < snk->snakeLength; ++i) {//遍历蛇身长度

    drawUnit(snk->snake[i], “■”); //蛇身长度 和 用什么来表示这条蛇

    }

    }

    // (0,0) , (0,1) , (0,2) 

    //  头        中       尾

    //往(1,0)方向走

    // (0+1,0+0) , (0,0) , (0,1) 

    //  头        中       尾

    void moveSnake(Snake* snk) {

    for (int i = snk->snakeLength – 1; i >= 1; –i) {

    snk->snake[i] = snk->snake[i – 1];

    }//整个贪吃蛇的核心

    //时间复杂度高 每次O(n)

    snk->snake[0].y += dir[snk->snakeDir][0];//蛇头加上当前的方向

    snk->snake[0].x += dir[snk->snakeDir][1];

    }

    void doMove(Snake* snk, Map* map) {

    Pos tail = snk->snake[snk->snakeLength – 1];//蛇的尾部位置,因为蛇的位置从0开始

    drawUnit(tail, ” “);//蛇尾部擦掉

    moveSnake(snk);

    drawUnit(snk->snake[0], “■”);//在蛇头的位置画一个新的方格出来

    }

    void checkSnakeMove(Snake* snk, Map* map) { //蛇和地图 结构体指针

    doMove(snk, map);

    }

    int main() {

    Map map;//结构体

    Snake snk;

    hideCursor();//隐藏光标

    initMap(&map);//结构体传地址 变成结构体指针

    initSnake(&snk);

    drawMap(&map);//绘制地图 结构体传地址 变成结构体指针

    drawSnake(&snk);//绘制完地图后 把蛇传进来

    while (1) {//basic structure

    checkSnakeMove(&snk, &map);

    }

    return 0;

    }

    //完成版

    #include <iostream>

    #include <Windows.h>

    #include <conio.h> //为了检测按键有没有被按下

    using namespace std;

    //每个函数代码不要太长 要简单能看懂

    #define H 27 //is mean 27 lines

    #define W 60

    //if we met the Height or width,we are using these substitutes.

    const int dir[4][2] = { //让蛇动起来 上下左右来说 x,y方向 二维数组 

    {-1,0},//上 y方向-1 x方向不变

    {0,1},//右 →  y方向不变 x+1

    {1,0},//下

    {0,-1}//左

    };

    enum BlockType {//用枚举类型来定义每个格子是什么类型 自定义类型

    EMPTY = 0,//空

    FOOD = 1,//食物

    };

    struct Map {

    BlockType data[H][W];//H行 W列的矩阵 二维数组

    bool hasFood;//定义这张地图上有没有食物

    };

    struct Pos { //蛇的位置 结构体

    int x;

    int y;

    };

    struct Snake {

    Pos snake[H * W]; //位置 把蛇表示在一维数组里面

    int snakeDir;//蛇的方向

    int snakeLength;//蛇的长度

    int lastMoveTime;//计算机的执行速度非常快 需要加点延时操作 上次移动的时间

    int moveFrequency;//频率

    };

    void initSnake(Snake* snk) {//为什么传指针 不传结构体本身 因为我要在函数内部修改值 并且在函数外部也改掉 必须用到指针

    snk->snakeLength = 3; //蛇的长度

    snk->snakeDir = 1; //蛇的方向

    snk->snake[0] = { W / 2,H / 2 };//蛇的位置放到屏幕中间 W是x H是y 结构体初始化

    snk->snake[1] = { W / 2 – 1,H / 2 };

    snk->snake[2] = { W / 2 – 2,H / 2 };

    snk->lastMoveTime = 0;

    snk->moveFrequency = 200;

    }

    void hideCursor() {

    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //输出窗口句柄,句柄就是编号

    CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };//网上一搜索就知道这个结构体,代表隐藏掉它

    SetConsoleCursorInfo(hOutput, &curInfo);//传递cursor的地址,结构体指针

    }

    void initMap(Map* map) {//传递Map的指针 为了它这个结构体能够被修改

    for (int y = 0; y < H; ++y) {//遍历行

    for (int x = 0; x < W; ++x)//遍历列 先遍历哪个都行

    map->data[y][x] = BlockType::EMPTY;  //枚举 等价于 map->data[y][x] = 0;

    }

    map->hasFood = false;//第一层 没有食物

    }

    void drawUnit(Pos p, const char unit[]) {

    COORD coord;//结构体 代表坐标

    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//拿到窗口句柄

    coord.X = p.x + 1;//因为地图上下左右 都包了一圈 整体会有一的偏移量

    coord.Y = p.y + 1;

    SetConsoleCursorPosition(hOutput, coord); //设置光标位置的接口 传窗口句柄(哪个窗口) 和 位置变量结构体

    cout << unit;//打印传进来的字符串变量

    }

    void drawMap(Map* map) { //传参

    //cout << “123” << endl;//test the output

    system(“cls”); // calling the command prompt for clearing the terminal

    cout << “┏”; //⌈¯⌉ ⌊_⌋ upper border

    for (int i = 0; i < W; ++i) {

    cout << “━”;

    }

    cout << “┓”<<endl;

    for (int y = 0; y < H; ++y) {

    cout << “┃”;

    for (int x = 0; x < W; ++x) {

    if (map->data[y][x] == BlockType::EMPTY) {

    cout << ” “;

    }

    }

    cout << “┃” << endl;

    }

    cout << “┗”; // lowest border

    for (int i = 0; i < W; ++i) {

    cout << “━”;

    }

    cout << “┛” << endl;

    }

    void drawSnake(Snake* snk) {

    for (int i = 0; i < snk->snakeLength; ++i) {//遍历蛇身长度

    drawUnit(snk->snake[i], “■”); //蛇身长度 和 用什么来表示这条蛇

    }

    }

    bool checkOutOfBound(Pos p) { //边界检测

    /*

    if (p.x == 0 || p.x == W + 1) { //逻辑或运算符

    return true;//说明超出了横向边界

    }

    if (p.y == 0 || p.y == H + 1) {

    return true;//说明超出了纵向边界

    }

    return false;//没有超出边界

    */

    //也可以写在一行上

    return (p.x <= 0 || p.x >= W + 1 || p.y <= 0 || p.y >= H + 1);

    }

    void checkEatFood(Snake* snk, Pos tail, Map* map) {//蛇和地图指针 蛇尾

    Pos head = snk->snake[0];//蛇头

    if (map->data[head.y][head.x] == BlockType::FOOD) {

    snk->snake[snk->snakeLength++] = tail;//变成尾巴的位置

    map->data[head.y][head.x] == BlockType::EMPTY;//吃到的食物的位置变成空

    map->hasFood = false;//当他变成false下次检测时就会生成新的出来

    drawUnit(tail, “■”);//在尾部放上一个方格

    }

    }

    // (0,0) , (0,1) , (0,2) 

    //  头        中       尾

    //往(1,0)方向走

    // (0+1,0+0) , (0,0) , (0,1) 

    //  头        中       尾

    void moveSnake(Snake* snk) {

    for (int i = snk->snakeLength – 1; i >= 1; –i) {

    snk->snake[i] = snk->snake[i – 1];

    }//整个贪吃蛇的核心

    //时间复杂度高 每次O(n)

    snk->snake[0].y += dir[snk->snakeDir][0];//蛇头加上当前的方向

    snk->snake[0].x += dir[snk->snakeDir][1];

    }

    bool doMove(Snake* snk, Map* map) {

    Pos tail = snk->snake[snk->snakeLength – 1];//蛇的尾部位置,因为蛇的位置从0开始

    drawUnit(tail, ” “);//蛇尾部擦掉

    moveSnake(snk);

    //在这条蛇移动之后检测,把蛇头的位置传进去

    if (checkOutOfBound(snk->snake[0])) {

    return false;//如果超出边界 就return false

    }

    checkEatFood(snk, tail, map);

    drawUnit(snk->snake[0], “■”);//在蛇头的位置画一个新的方格出来

    return true;//没有超出边界 就return true

    }

    bool checkSnakeMove(Snake* snk, Map* map) { //蛇和地图 结构体指针

    //如果上次刚移动过 我就不移动了

    int curTime = GetTickCount(); //当前时间

    if (curTime – snk->lastMoveTime > snk->moveFrequency) {

    if(false == doMove(snk, map))//边界检测

    return false;//当前时间-上次移动时间大于移动频率 就执行移动操作

    snk->lastMoveTime = curTime;//上次移动时间设置为当前时间

    }

    return true;

    }

    void checkChangeDir(Snake * snk) {//检测方向旋转

    if(_kbhit()){ //如果键盘被按下

    switch (_getch()) {

    case ‘w’:

    if(snk->snakeDir!=2)//不能同时按上下

    snk->snakeDir = 0;//把蛇的方向变成上方向 0

    break;

    case ‘d’:

    if (snk->snakeDir != 3)//不能同时按左右

    snk->snakeDir = 1;//把蛇的方向变成右方向 1

    break;

    case ‘s’:

    if (snk->snakeDir != 0)//不能同时按上下

    snk->snakeDir = 2;//把蛇的方向变成下方向 2

    break;

    case ‘a’:

    if (snk->snakeDir != 1)//不能同时按左右

    snk->snakeDir = 3;//把蛇的方向变成左方向 3

    break;

    default://其他按键不做任何处理

    break;

    }

    }

    }

    void checkFoodGenerate(Snake* snk, Map* map) {

    if (false == map->hasFood) {

    while (1) {

    int x = rand() % W;//生成的食物不能落在蛇身上

    int y = rand() % H; //0~H-1

    int i = 0;

    while (i < snk->snakeLength) {

    if (x == snk->snake[i].x && y == snk->snake[i].y) {//身体的第i个位置的x和y坐标

    break;

    }

    i++;//需要自增来遍历蛇身长度

    }

    if (i == snk->snakeLength) {

    map->data[y][x] = BlockType::FOOD;

    map->hasFood = true;

    drawUnit({ x,y }, “●”);

    return; //只要位置生成了就要return出去

    //break;//或者这样

    }

    }

    }

    }

    void initGame(Snake* snk, Map* map) {

    hideCursor();//隐藏光标

    initMap(map);//结构体传地址 变成结构体指针

    initSnake(snk);

    drawMap(map);//绘制地图 结构体传地址 变成结构体指针

    drawSnake(snk);//绘制完地图后 把蛇传进来

    }

    int main() {

    Map map;//结构体

    Snake snk;

    initGame(&snk, &map);

    while (1) {//basic structure

    checkChangeDir(&snk);

    if (false == checkSnakeMove(&snk, &map)) {

    break;//如果发现这条蛇移动到失败 就终止游戏

    }

    checkFoodGenerate(&snk, &map);

    }

    drawUnit({ W / 2 – 4,H / 2 }, “Game Over”);

    while(1){}//结束不让它结束

    return 0;

    }

  • #C++ Day21 October 31 2025

    //13 – 1 citation grammar

    //引用的语法

    #include <iostream>

    using namespace std;

    //quotation

    //引用比指针更好理解

    //指针:所以爱会消失吗。对嘛?

    //引用:给指针取一个别名 

    //& 

    //数据类型& 变量名 = 变量;

    //reference13-

    void test() {

    int a_very_very_very_very_very_very_very_very_long_array[8] = { 1,1 };

    for (int i = 2; i < 8; ++i) {

    a_very_very_very_very_very_very_very_very_long_array[i] = a_very_very_very_very_very_very_very_very_long_array[i – 1] * a_very_very_very_very_very_very_very_very_long_array[i – 1] + a_very_very_very_very_very_very_very_very_long_array[i – 2] * a_very_very_very_very_very_very_very_very_long_array[i – 2];

    }

    for (int i = 0; i < 8; ++i) {

    cout << a_very_very_very_very_very_very_very_very_long_array[i] << ” “;

    }

    //太长了代码

    cout << endl;

    for (int i = 2; i < 8; ++i) {

    a_very_very_very_very_very_very_very_very_long_array[i] = 0;

    }

    for (int i = 2; i < 8; ++i) {

    int& pre1 = a_very_very_very_very_very_very_very_very_long_array[i – 1];

    int& pre2 = a_very_very_very_very_very_very_very_very_long_array[i – 2];

    int& now = a_very_very_very_very_very_very_very_very_long_array[i];

    //referenced it

    now = pre1 * pre1 + pre2 * pre2;

    }

    for (int i = 0; i < 8; ++i) {

    cout << a_very_very_very_very_very_very_very_very_long_array[i] << ” “;

    }

    cout << endl;

    }

    int main() {

    /*

    int a = 1314;// 1. 定义int类型变量a,赋值为1314

    int& b = a;// 2. 定义int类型的引用b,将b绑定到变量a上

    b = 520;

    cout << “a = ” << a << endl;

    cout << “b = ” << b << endl;

    // 3. 查看a和b的内存地址:地址完全相同(证明是同一块内存)

    cout << “a的地址:” << &a << endl; 

    cout << “b的地址:” << &b << endl;

    */

    int a = 1314;// 1. 定义int类型变量a,赋值为1314

    int* b = &a;// 

    *b = 520;

    cout << “a = ” << a << endl;

    cout << “b = ” << *b << endl;//用指针写法需要多写三个*号 引用只需要写一个&

    test();

    return 0;

    }

    你写出的这两行代码涉及 C++ 中的引用(Reference) 特性,这是 C++ 区别于 C 语言的重要语法之一。下面我会逐行解析代码含义、引用的核心特性,以及实际使用中的注意事项,帮你彻底理解这段代码的本质。

    一、逐行解析代码含义

    cpp

    int a = 1314;  // 1. 定义int类型变量a,赋值为1314

    int& b = a;    // 2. 定义int类型的引用b,将b绑定到变量a上

    关键概念:什么是 “引用”?

    引用可以理解为变量的 “别名” —— 它不是一个新的变量(不占用额外内存),而是对已存在变量(这里的a)的 “另一个名字”。

    就像生活中 “张三” 的别名是 “小张”,喊 “张三” 或 “小张” 都是指同一个人;代码中a和b也指向同一块内存空间,操作a或b会产生完全相同的效果。

    二、代码的实际效果(通过示例验证)

    我们可以通过一段完整代码,观察a和b的关联关系:

    cpp

    #include <iostream>

    using namespace std;

    int main() {

        int a = 1314;

        int& b = a;  // b是a的引用(别名)

        // 1. 输出a和b的值:结果相同(都指向同一块内存)

        cout << “初始值:a = ” << a << “, b = ” << b << endl;  // 输出:a = 1314, b = 1314

        // 2. 修改b的值:a的值也会同步变化

        b = 520;

        cout << “修改b后:a = ” << a << “, b = ” << b << endl;  // 输出:a = 520, b = 520

        // 3. 查看a和b的内存地址:地址完全相同(证明是同一块内存)

        cout << “a的地址:” << &a << endl;  // 例如输出:0x61fe1c

        cout << “b的地址:” << &b << endl;  // 输出与a完全相同:0x61fe1c

        return 0;

    }

    从运行结果能明确看到:

    • a和b的值始终同步(改一个,另一个也变);
    • a和b的内存地址完全一致(证明引用不占用额外内存,只是 “别名”)。

    三、引用的核心语法规则(必须牢记)

    这段代码看似简单,但背后隐藏着引用的不可违背的规则,一旦违反会直接编译报错:

    • 引用必须 “初始化”,且初始化后不能改绑
      • 错误写法 1:只定义引用不绑定变量(编译报错)
        int& b; // 错误!引用必须在定义时绑定一个已存在的变量
      • 错误写法 2:初始化后尝试绑定其他变量(编译报错)
        int c = 666;
        b = c; // 错误!不是“改绑c”,而是“把c的值赋给b(即a)”
    • 引用的类型必须与绑定的变量类型完全一致
      不能用int&绑定double变量,类型不匹配会编译报错:
      double d = 3.14;
      int& b = d; // 错误!引用类型(int&)与变量类型(double)不匹配
    • 不存在 “空引用”
      引用必须绑定到一个 “真实存在的变量” 上,不能像指针那样指向NULL(空地址):
      int& b = NULL; // 错误!引用无法绑定空地址

    四、引用与指针的区别(为什么用引用?)

    很多初学者会把引用和指针混淆,因为二者都能 “间接操作变量”,但本质差异很大。通过对比能更清晰理解引用的设计目的:

    特性引用(Reference指针(Pointer
    初始化必须在定义时绑定变量,不可改绑可定义后赋值,可指向不同变量
    空值无空引用(必须绑定有效变量)有空指针(NULL/nullptr)
    内存占用不占用额外内存(仅别名)占用内存(存储变量地址,如 4/8 字节)
    语法复杂度直接使用(如b = 520),更简洁需解引用(如*p = 520),易出错
    适用场景函数参数、返回值(避免拷贝)动态内存分配、数组遍历等

    你的代码中,int& b = a的核心价值是:用更简洁的语法间接操作a,且避免指针的空值风险和语法复杂度

    五、常见使用场景(代码示例)

    除了直接绑定变量,引用更常用在函数参数和返回值中,核心目的是避免变量拷贝(提升效率) 和实现 “修改实参” 的效果

    场景 1:函数参数用引用(修改实参)

    cpp

    // 用引用参数修改外部变量的值(无需指针,语法更简洁)

    void addOne(int& x) {  // x是外部变量的引用

        x += 1;  // 修改x等同于修改外部变量

    }

    int main() {

        int a = 1314;

        addOne(a);  // 调用后,a的值变为1315

        cout << a;  // 输出1315

        return 0;

    }

    场景 2:函数返回引用(避免返回值拷贝)

    cpp

    // 返回数组的引用(避免拷贝整个数组,提升效率)

    int& getArrayElement(int arr[], int index) {

        return arr[index];  // 返回arr[index]的引用

    }

    int main() {

        int arr[3] = {10, 20, 30};

        getArrayElement(arr, 1) = 200;  // 直接修改返回的引用(即arr[1])

        cout << arr[1];  // 输出200

        return 0;

    }

    总结

    你写的int a = 1314; int& b = a;是 C++ 引用的基础用法,核心是:

    • b是a的别名,二者指向同一块内存;
    • 操作b等同于操作a,且引用不占用额外内存;
    • 引用必须初始化、不可改绑、类型匹配,这些规则是避免错误的关键。

    理解引用后,你会发现它在函数设计、高效编程中非常实用,是 C++ 中简化代码、提升性能的重要工具。

    你写出的 int* b = &a; 是 C/C++ 中指针(Pointer) 的基础用法,与上一轮提到的 “引用”(int& b = a)虽都能间接操作变量,但本质是完全不同的语法特性。下面将逐行解析代码含义、指针的核心逻辑、与引用的关键差异,以及实际使用中的注意事项,帮你彻底掌握指针的基础用法。

    一、逐行解析代码含义(结合上一轮的 int a = 1314

    先补充完整代码上下文,方便理解:

    cpp

    运行

    int a = 1314;    // 1. 定义int类型变量a,赋值为1314(内存中会分配一块4字节空间存储1314)

    int* b = &a;     // 2. 定义int类型的指针变量b,将a的内存地址赋值给b

    关键概念:什么是 “指针”?

    指针是一种专门存储 “内存地址” 的变量—— 它本身占用独立的内存空间,存储的内容不是具体的值(如 1314),而是另一个变量(如a)在内存中的 “位置编号”(即地址)。

    可以类比生活中的 “快递柜”:

    • a 是快递柜里的 “包裹”(存储具体内容 1314);
    • a 的内存地址(比如0x61fe1c)是快递柜的 “柜门编号”;
    • b 是一张写着 “柜门编号” 的 “纸条”(本身是独立的载体,存储的是地址,而非包裹内容)。

    二、代码的实际效果(通过示例验证指针的操作逻辑)

    通过一段完整代码,观察指针如何通过 “地址” 间接操作变量a:

    cpp

    运行

    #include <iostream>

    using namespace std;

    int main() {

        int a = 1314;    // 变量a:值=1314,地址=0x61fe1c(示例地址,实际由系统分配)

        int* b = &a;     // 指针b:存储a的地址(即b的值=0x61fe1c)

        // 1. 输出关键信息:区分“指针本身的值”“指针指向的值”“变量a的地址”

        cout << “a的值:” << a << endl;          // 输出a的具体值:1314

        cout << “a的地址(&a):” << &a << endl; // 输出a的内存地址:0x61fe1c

        cout << “指针b的值(存储的地址):” << b << endl; // 输出b存储的地址:0x61fe1c(与&a相同)

        cout << “指针b指向的值(*b):” << *b << endl; // 输出b指向的变量的值:1314(即a的值)

        // 2. 通过指针修改a的值:用“*b”(解引用)操作指向的变量

        *b = 520; // 含义:找到b存储的地址对应的变量(即a),将其值改为520

        cout << “\n修改*b后:” << endl;

        cout << “a的值:” << a << endl;          // 输出:520(a被间接修改)

        cout << “指针b指向的值(*b):” << *b << endl; // 输出:520

        // 3. 指针本身的地址(证明指针是独立变量,占用内存)

        cout << “指针b自身的地址(&b):” << &b << endl; // 输出:0x61fe18(与a的地址不同,证明b是独立变量)

        return 0;

    }

    运行结果会清晰体现指针的核心逻辑:

    • 指针b存储的是a的地址(b = &a);
    • 需通过解引用运算符* 才能访问指针指向的变量(*b等价于a);
    • 指针b本身是独立变量,有自己的内存地址(&b与&a不同),占用 4 字节(32 位系统)或 8 字节(64 位系统)内存。

    三、指针的核心语法规则(必须牢记,避免编译错误)

    int* b = &a; 看似简单,但指针的语法规则比引用更严格,违反会导致编译报错或运行时崩溃:

    1. 指针的 “类型匹配” 规则

    指针的类型必须与 “指向的变量类型” 完全一致(除非用void*万能指针,但需谨慎):

    • 正确:int* b = &a;(int*指针指向int变量a);
    • 错误:double* b = &a;(double*指针不能指向int变量,类型不匹配,编译报错)。

    2. 指针的 “初始化” 与 “空指针”

    • 指针可以先定义后赋值(区别于引用必须初始化):cpp

      运行




      int* b;    // 允许先定义指针(未初始化时,值是随机的“野指针”,危险!)
    • int a = 1314;
    • b = &a;    // 后续赋值为a的地址(此时b是有效指针)




    • 避免 “野指针”:未指向有效变量时,建议赋值为空指针nullptr(C++11 后)或NULL(本质是 0):cpp

      运行




      int* b = nullptr; // 空指针:明确表示“暂时未指向任何有效变量”





    • ⚠️ 注意:不能对空指针使用*b(解引用空指针会导致程序崩溃)。

    3. 指针的 “解引用” 与 “取地址”

    • 取地址运算符&:获取变量的内存地址(如&a即 “a 的地址”);
    • 解引用运算符*:通过指针存储的地址,访问对应的变量(如*b即 “b 指向的变量”);
    • 二者是 “逆操作”:*(&a) == a(先取 a 的地址,再解引用,结果还是 a)。

    四、指针与引用的核心差异(避免混淆)

    你之前写的 “引用”(int& b = a)和现在的 “指针”(int* b = &a)都能间接操作a,但本质是两种不同的语法,差异如下表(关键区分点):

    对比维度指针(int* b = &a引用(int& b = a
    本质独立变量,存储 “地址”变量的 “别名”,不存储地址(无独立内存)
    初始化要求可先定义后赋值(允许int* b;)必须在定义时绑定变量(禁止int& b;)
    指向变更可修改指向(如b = &c;,改为指向 c)一旦绑定,不可改绑其他变量
    空值支持支持空指针(int* b = nullptr)无空引用(必须绑定有效变量)
    操作语法需解引用(*b = 520)直接使用(b = 520),更简洁
    内存占用占用内存(4/8 字节,取决于系统位数)不占用额外内存
    安全性风险较高(野指针、空指针解引用会崩溃)安全性高(无野引用、空引用问题)

    五、指针的常见使用场景(代码示例)

    指针是 C/C++ 的核心特性,适用场景比引用更广泛,尤其是需要 “动态内存”“数组操作”“多态” 等场景:

    场景 1:操作数组(数组名本质是 “指向首元素的指针”)

    数组的内存是连续的,指针可以通过 “指针移动” 遍历数组:

    cpp

    运行

    #include <iostream>

    using namespace std;

    int main() {

        int arr[3] = {10, 20, 30};

        int* p = arr; // 数组名arr等价于&arr[0](首元素地址),p指向arr[0]

        // 遍历数组:指针移动(p++即“指向当前元素的下一个元素”)

        for (int i = 0; i < 3; i++) {

            cout << “arr[” << i << “] = ” << *p << endl; // 解引用p,获取当前元素

            p++; // 指针移动(地址增加4字节,因int占4字节)

        }

        return 0;

    }

    // 输出:

    // arr[0] = 10

    // arr[1] = 20

    // arr[2] = 30

    场景 2:动态内存分配(用new/delete管理内存)

    指针可以指向 “运行时才分配的内存”(动态内存),这是引用无法实现的:

    cpp

    运行

    #include <iostream>

    using namespace std;

    int main() {

        // 动态分配1个int大小的内存,指针p指向这块内存

        int* p = new int; 

        *p = 1314; // 给动态内存赋值

        cout << “动态内存的值:” << *p << endl; // 输出:1314

        // 释放动态内存(必须手动释放,否则内存泄漏)

        delete p; 

        p = nullptr; // 释放后赋值为空指针,避免野指针

        return 0;

    }

    场景 3:函数参数传递(修改实参,或传递大型数据)

    指针作为函数参数,可实现 “修改外部变量”(类似引用),且支持传递数组、动态内存等:

    cpp

    运行

    #include <iostream>

    using namespace std;

    // 用指针参数修改外部变量

    void multiplyBy2(int* x) {

        if (x != nullptr) { // 先判断指针非空,避免崩溃

            *x *= 2; // 解引用指针,修改外部变量

        }

    }

    int main() {

        int a = 1314;

        multiplyBy2(&a); // 传递a的地址

        cout << a << endl; // 输出:2628(a被修改)

        return 0;

    }

    总结

    你写的 int* b = &a; 是指针的基础用法,核心要点如下:

    • b 是指针变量,存储的是a的内存地址(而非a的值);
    • 必须通过解引用*b 才能访问或修改a的值;
    • 指针是独立变量,有自己的内存地址,支持后续修改指向(如b = &c);
    • 与引用相比,指针更灵活(支持动态内存、数组操作),但安全性更低(需避免野指针、空指针解引用)。

    指针是 C/C++ 的难点,但也是核心能力 —— 掌握指针后,才能理解动态内存、数据结构(如链表、树)等更复杂的编程概念。

    Day22 November 3 2025

    //13 – 2 reference characteristics

    #include <iostream>

    using namespace std;

    //two charaters of reference:

    //1.must initialization(必须初始化)

    //2.Can’t be modified after initialization(初始化以后无法修改)

    //跟指针定义的区别,指针不一定要初始化,定义后只要不是指针常量后续都可以修改

    //因为引用一定要初始化,所以根本不会有空指针问题

    //引用为后续STL源码打基础

    int main() {

    //int& a;//error:must have an equal symbol

    int a = 3, c = 6;

    int& b = a; 

    b = c;//b still a reference to a,b=6

    cout << a << b << c << endl; // 666

    return 0;

    }

    //13 – 3 the essence of reference

    //引用的本质

    #include <iostream>

    using namespace std;

    //引用 解引用

    //引用的底层特性 其实就是指针常量

    int main() {

    int a = 520;

    //int& b = a;

    /*

    00007FF7ACEE1865  lea         rax,[a]  

    00007FF7ACEE1869  mov         qword ptr [b],rax 

    */

    //b = 1314;//引用后面赋值不需要写星号

    /*

    00007FF7ACEE186D  mov         rax,qword ptr [b]  

    00007FF7ACEE1871  mov         dword ptr [rax],522h 

    */

    //汇编代码一模一样,说明引用就是指针常量

    //打断点可看窗口 反汇编 deassembly

    int* const b = &a;//pointer constant  (指针常量) 的初始化

    /*

    00007FF64AF11865  lea         rax,[a]  

    00007FF64AF11869  mov         qword ptr [b],rax  

    */

    *b = 1314;//指针常量后面赋值需要写星号

    //这里是解引用 dereference

    /*

    00007FF64AF1186D  mov         rax,qword ptr [b]  

    00007FF64AF11871  mov         dword ptr [rax],522h  

    */

    return 0;

    }

    //13 – 4 function passing a reference as a parameter

    //引用作为函数传参

    #include <iostream>

    using namespace std;

    int countAndSum(int arr[], int size, int target, int& count) {

    int sum = 0;

    cout << &count << endl;//打印count的地址

    for (int i = 0; i < size; ++i) {

    if (arr[i] == target) {

    count++; //初始化时用引用 传进来的地址也一模一样

    sum += arr[i];

    }

    }

    return sum; //计算所有等于target的数的和

    }

    int countAndSum2(int arr[], int size, int target, int* count) {//使用指针

    int sum = 0;

    cout << count << endl;//打印count的地址

    for (int i = 0; i < size; ++i) {

    if (arr[i] == target) {

    *count++; 

    sum += arr[i];

    }

    }

    return sum; //计算所有等于target的数的和

    }

    struct S {

    int a, b, c, d, e, f, g;

    };

    //void printS(S s) {//传参是个结构体

    // cout << &s << endl;//如果结构体不用引用,传进来前的地址输出出来,不是同一个地址

    // //C++ 和其他语言区别 结构体 作为参数时 会拷贝一份新的数据出来

    // //当结构体非常大 如有个非常大的数组a[1000000]时 拷贝就相当耗时了

    //

    // cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << endl;

    //}

    void printS2(S &s) {//传参是个结构体

    cout << &s << endl;//传进来前的地址输出出来,不是同一个地址

    //C++ 和其他语言区别 结构体 作为参数时 会拷贝一份新的数据出来

    //当结构体非常大 如有个非常大的数组a[1000000]时 拷贝就相当耗时了

    //所以加上引用就能避免拷贝 让传参和实际的参数同一个地址,这就是引用在函数传参时的作用

    cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << endl;

    }

    int main() {

    int arr[]{ 1,2,3,2,4,5,6,4,3,2 };//10 numbers

    //返回统计数组中值为2的元素个数以及返回和

    int c = 0;

    cout << &c << endl;//打印c的地址

    int sum = countAndSum(arr, 10, 2, c);

    cout << sum << ” ” << c << endl;

    S s = { 1,2,3,4,5,6,7 };

    cout << &s << endl;

    //如果结构体不用引用 传进来后的地址输出出来,不是同一个地址

    printS2(s);

    return 0;

    }

    //13 – 5 reference as a function return value

    //引用作为函数返回值

    #include <iostream>

    using namespace std;

    int getArrayValue(int arr[], int index) {

    return arr[index];//上面不加引用这里返回的是一个值

    }

    int &getArrayValue2(int arr2[], int index) {

    return arr2[index];//上面加了引用这里返回一个别名

    }

    //STL底层源码里面有很多 面向对象 中括号运算符重载也有这种

    int main() {

    int a[] = { 8,7,6,5,4,3 };

    //我想打印出第四个元素5

    cout << getArrayValue(a, 3) << endl;

    //想把下标3这个值改掉 但是不想访问数组 需要加上引用

    getArrayValue2(a, 3) = 999; //直接对函数赋值也没有报错,这里相当于a[3]=999,然后函数就可以作为左值来赋值了

    cout << getArrayValue2(a, 3) << endl;

    return 0;

    }

    //13 – 6 constant reference

    //常量引用

    //常量引用非常广泛 尤其是在STL实现上

    #include <iostream>

    #include <vector>

    using namespace std;

    struct S {

    int a, b, c, d, e, f;

    };

    //加引用少一次拷贝 让它更加高效

    void printS(S& s) {

    s.b = 520; //加const 在引用前变 常量引用 防止有人不小心把这个值改了

    cout << s.a << s.b << s.c << s.d << s.e << s.f << endl;

    }

    void printS2(const S& s) {

    //同一个对象很难避免写函数的人不去修改它 为了避免别人修改 用const关键词修饰

    // 在很多STL底层源码有

    //s.b = 520; //加const 在引用前变 常量引用 防止有人不小心把这个值改了

    cout << s.a << s.b << s.c << s.d << s.e << s.f << endl;

    }

    int main() {

    int a;

    const int& b = a;

    //引用 =指针常量

    //常量引用 = 常量指针常量

    S s = { 1,2,3,4,5,6 };

    printS2(s);

    vector <int>a;//F12查看源代码

    return 0;

    }

    //13 – 7 pointer reference

    //指针引用 

    //*&

    #include <iostream>

    using namespace std;

    void allocMemory1(char * ptr,int bytes) {//传进去一个char类型的指针变量 和一个 字节数bytes

    //这个函数作用 传入一个指针 和 字节数,然后从堆heap上 申请对应字节数的内存 并且把地址赋值给ptr

    ptr = new char[bytes];

    cout << “ptr 的 地址:” << &ptr << endl;//ptr虽然本身也是一个地址,但我们想要ptr它的地址

    }

    void test1() {

    //test1这个函数 首先定义了一个初始化的指针 并且把它初始化为NULL

    char* p = NULL;

    allocMemory1(p, 5); //然后调用allocMemory1这个函数 期望是这个函数申请的那块内存的首地址

    cout << (void*)p << endl;//然后把首地址转换为void*以后把它打印出来(转换成一个通用的指针类型)

    //为什么要转换  原因是不转换的话 C++会认为它是一个C风格的字符串 字符串如果为空 直接输出程序会导致崩溃

    cout << “p 的 地址:” << &p << endl;

    //这里的p是实参 ptr是形参

    }

    void allocMemory2(char*& ptr, int bytes) {//在*后加上引用&  引用的是一个指针变量

    //上面加上引用后 ptr就变成p的别名了

    //传进去一个char类型的指针变量 和一个 字节数bytes

    //这个函数作用 传入一个指针 和 字节数,然后从堆heap上 申请对应字节数的内存 并且把地址赋值给ptr

    ptr = new char[bytes];

    cout << “ptr 的 地址:” << &ptr << endl;//ptr虽然本身也是一个地址,但我们想要ptr它的地址

    }

    void test2() {

    //test1这个函数 首先定义了一个初始化的指针 并且把它初始化为NULL

    char* p = NULL;

    allocMemory2(p, 5); //然后调用allocMemory1这个函数 期望是这个函数申请的那块内存的首地址

    //上面加上引用后 ptr就变成p的别名了 所以两个是同一个变量

    //实参 p  和形参 ptr是同一块地址了 打印的地址就不再是空了 这就是指针引用的作用

    cout << (void*)p << endl;//然后把首地址转换为void*以后把它打印出来(转换成一个通用的指针类型)

    //为什么要转换  原因是不转换的话 C++会认为它是一个C风格的字符串 字符串如果为空 直接输出程序会导致崩溃

    cout << “p 的 地址:” << &p << endl;

    //这里的p是实参 ptr是形参

    }

    int main() {

    //test1();

    test2();

    return 0;

    }