为什么是指针而不是对象?C++ 底层原理入门指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:初学者的永恒问题

\n\n

如果你曾经浏览过 Stack Overflow 的 C++ 板块,你很可能见过一个获得数千次浏览的问题:“为什么我应该使用指针,而不是对象本身?”。这并非无聊的好奇心。每个从 Python 或 JavaScript 开始学习 C++ 的人都会遇到这个两难选择。对象似乎很方便:创建、调用方法、然后忘记。那为什么还需要这些星号、箭头和 new 呢?

\n\n

实际上,答案隐藏在 C++ 底层内存管理 的方式中。在本文中,我们不会只给出干巴巴的定义,而是会分析程序员实际生活中遇到的场景,在这些场景中,指针不仅方便,而且是唯一正确的选择。准备好深入代码的引擎室了吗?出发!

\n\n

对象 vs 指针:编译器怎么说?

\n\n

让我们直接进入正题。假设你正在为你的 RPG 游戏编写一个 Monster 类。

\n\n
class Monster {\npublic:\n    Monster() { std::cout << "怪物已创建!\\n"; }\n    ~Monster() { std::cout << "怪物已被销毁!\\n"; }\n    void roar() { std::cout << "RAAAWR!\\n"; }\n};
\n\n

现在有两种使用这个类的方式:

\n\n

1. 栈上的对象(自动管理)

\n
void spawnOnStack() {\n    Monster m;  // 对象在此处存在\n    m.roar();\n}  // 此处 m 被自动销毁
\n\n

2. 指向堆的指针(手动管理)

\n
void spawnOnHeap() {\n    Monster* m = new Monster(); // 对象在堆中\n    m->roar();\n    delete m;  // 必须手动删除!\n}
\n\n

看起来第一种方式更简单、更安全。那为什么还需要第二种呢?底层隐藏着关键区别:栈(stack)和堆(heap)。

\n\n
    \n
  • 栈 — 小、快,但对象只存在于 {} 代码块内。
  • \n
  • 堆 — 大、慢,但对象会一直存在,直到你删除它。
  • \n
\n\n

当对象体积巨大时(例如 4K 纹理),将其存储在栈上意味着有栈溢出的风险。这时指针就登场了。

\n\n

使用指针(而非对象)的五大理由

\n\n

现在我们已经理解了基本区别,让我们分析一下指针不是一种任性,而是一种必要性的具体场景。

\n\n

1. 多态:当类型不明确时

\n

这可能是最常见的原因。假设你有一个基类 Character 和它的派生类 Warrior、Mage。如果你在栈上创建对象,你必须明确指定类型:

\n\n
Warrior w;  // 没问题,但这并非多态
\n\n

但如果角色类型是在游戏运行时选择的呢?只有指针才能提供灵活性:

\n\n
Character* hero;\nif (choice == "warrior")\n    hero = new Warrior();\nelse\n    hero = new Mage();\n\nhero->attack(); // 将调用派生类的方法!\ndelete hero;
\n\n

底层原理: 编译器使用虚函数表(vtable)。指针存储对象的地址,调用方法时会查找 vtable。对于栈上的对象,这种技巧行不通——类型是固定的。

\n\n

2. 函数传参避免复制

\n

看看这段代码:

\n\n
void process(Monster m) { // 按值传递!\n    m.roar();\n}\n\nMonster bigBoss;\nprocess(bigBoss); // 复制!
\n\n

这里 process 接收的是对象的副本。如果 Monster 包含一个 100 MB 的数组——你刚刚白白复制了 100 MB。指针解决了这个问题:

\n\n
void process(Monster* m)                

博客

书籍推荐