cpp自学 day2(—>运算符)

cpp 复制代码
#include <iostream> // 用于输入输出,如 std::cout, std::endl
#include <string>   // 图片中包含此头文件,尽管在此示例中未使用

// 定义 Entity 类,这是 ScopedPtr 将要管理的类型
class Entity {
public:
    int x; // Entity 类的一个公共成员变量

    // Entity 类的默认构造函数
    Entity() : x(0) { // 初始化 x 为 0
        std::cout << "Entity 构造" << std::endl;
    }

    // Entity 类的析构函数
    ~Entity() {
        std::cout << "Entity 析构" << std::endl;
    }

    // Entity 类的一个公共成员函数
    void Print() const { // const 表示此函数不会修改对象的状态
        std::cout << "Hello! x = " << x << std::endl;
    }
};

// 定义 ScopedPtr 类,一个简单的智能指针模拟
class ScopedPtr {
private:
    Entity* m_Obj; // 内部封装一个裸指针,指向 Entity 对象

public:
    // 构造函数:接受一个 Entity* 裸指针,接管其所有权
    ScopedPtr(Entity* entity)
        : m_Obj(entity) // 使用成员初始化列表初始化 m_Obj
    {
        // 构造函数体为空,因为初始化已在成员初始化列表中完成
        std::cout << "ScopedPtr 构造,管理地址: " << m_Obj << std::endl;
    }

    // 析构函数:当 ScopedPtr 对象销毁时,自动释放其管理的 Entity 对象
    ~ScopedPtr() {
        if (m_Obj) { // 确保 m_Obj 不是空指针,避免对空指针进行 delete
            std::cout << "ScopedPtr 析构,释放地址: " << m_Obj << std::endl;
            delete m_Obj; // 释放 m_Obj 指向的内存
            m_Obj = nullptr; // 将指针置空,防止悬空指针
        }
    }

    // 重载 operator->() (箭头运算符)
    // 作用:允许通过 ScopedPtr 对象像访问指针一样访问 Entity 对象的成员
    // 返回值:通常返回内部管理的裸指针
    Entity* operator->() {
        return m_Obj; // 返回内部存储的 Entity* 裸指针
    }

    // 重载 GetObject() 方法,返回内部裸指针
    Entity* GetObject() { return m_Obj; }

    // 禁用复制构造函数和复制赋值运算符,以实现独占所有权(类似 std::unique_ptr)
    // 防止多个 ScopedPtr 共同管理同一块内存,导致重复释放。
    ScopedPtr(const ScopedPtr&) = delete;
    ScopedPtr& operator=(const ScopedPtr&) = delete;
};

int main() {
    // 创建一个 ScopedPtr 对象 'entity',它将管理一个新创建的 Entity 对象。
    // 使用直接初始化语法,这是创建智能指针的推荐方式。
    ScopedPtr entity(new Entity());

    // 通过重载的 operator->() 访问 Entity 对象的成员函数 Print()
    // 编译时,entity->Print() 会被翻译为 (entity.operator->())->Print();
    entity->Print();

    // 访问 Entity 对象的成员变量 x (通过 operator->())
    entity->x = 20; // 修改 x 的值
    entity->Print(); // 再次打印,确认 x 已被修改

    // 暂停程序,等待用户输入,以便观察输出和析构过程
    std::cin.get();

    // main 函数结束时,'entity' 对象会离开作用域,
    // 其 ScopedPtr 类的析构函数会自动被调用,从而自动释放它所管理的 Entity 对象,避免内存泄漏。
    return 0;
}

看着代码学习

重载->运算符

解析 entityPtr->Print(); 这行代码的执行过程:

  1. 当编译器看到 entityPtr->Print() 时,它知道 entityPtr 是一个 ScopedPtr 类的对象,而不是一个裸指针。
  2. 于是,编译器会寻找 ScopedPtr 类中是否有 operator->() 的重载。
  3. 它找到了你定义的 Entity* operator->() 函数。
  4. 编译器会调用 entityPtr.operator->()
  5. entityPtr.operator->() 执行,并返回 entityPtr 内部存储的那个 Entity* 类型的裸指针(即 m_Obj 的值)。
  6. 然后,编译器会拿这个返回的 Entity* 裸指针,再对其应用 ->Print() 操作,最终调用到 Entity 类的 Print() 函数。

用箭头运算符获取内存中某值的偏移量笔记

  • 目的: 这种技巧主要用于在编译时计算结构体(或类)成员相对于该结构体(或类)起始地址的偏移量。

  • 核心思想: 利用 C/C++ 编译器在处理结构体成员访问时的特性,即编译器在编译阶段就已知结构体的内存布局和成员的相对位置。它不会真的去解引用一个空指针,而是计算成员的偏移量。

  • 语法示例:

    cpp 复制代码
    struct Vector3
    {
        float x, y, z;
    };
    
    int main()
    {
        // 计算 Vector3 结构体中成员 z 的偏移量
        int offset = (int)(&((Vector3*)nullptr)->z); // 1749396158901.png
        std::cout << offset << std::endl;
        // ...
    }
  • 解析步骤:

    1. (Vector3*)nullptr : 将空指针 nullptr 强制转换为指向 Vector3 类型的指针。
    2. ((Vector3*)nullptr)->z : 使用箭头运算符 -> 访问 Vector3 结构体中的成员 z。此时编译器会根据 Vector3 的定义,确定 z 相对于结构体起始地址的偏移量。
    3. &(...) : & 是取地址运算符,它获取的是 z 成员的地址。由于我们是基于 nullptr(地址 0)进行的操作,这个"地址"实际上就是 z 相对于结构体起始的偏移量。
    4. (int) : 将计算得到的偏移量(通常是 size_t 类型)强制转换为 int 类型以便打印。
相关推荐
孞㐑¥2 小时前
Linux之Socket 编程 UDP
linux·服务器·c++·经验分享·笔记·网络协议·udp
黄雪超5 小时前
JVM——函数式语法糖:如何使用Function、Stream来编写函数式程序?
java·开发语言·jvm
ThetaarSofVenice5 小时前
对象的finalization机制Test
java·开发语言·jvm
水木兰亭5 小时前
数据结构之——树及树的存储
数据结构·c++·学习·算法
思则变5 小时前
[Pytest] [Part 2]增加 log功能
开发语言·python·pytest
lijingguang6 小时前
在C#中根据URL下载文件并保存到本地,可以使用以下方法(推荐使用现代异步方式)
开发语言·c#
¥-oriented6 小时前
【C#中路径相关的概念】
开发语言·c#
CoderCodingNo6 小时前
【GESP】C++四级考试大纲知识点梳理, (7) 排序算法基本概念
开发语言·c++·排序算法
恋猫de小郭7 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
JosieBook7 小时前
【Java编程动手学】使用IDEA创建第一个HelloJava程序
java·开发语言·intellij-idea