WRITING · 文章

C++ 每日学习总结|2026 年 11 月 11 日

ZRonin

学习方向:C++ 面向对象编程(OOP) · DirectX 12 前置知识

今天完成了 C++ 面向对象的第 3~6 讲,涉及对象指针、构造与析构、继承、虚函数和多态。

这次总结保留你独立编写的代码、存在的问题、修正方式以及重要知识点,可以直接整理到博客或 GitHub 学习日志。

第 3 讲:对象指针与 .、->

1. 我的练习代码

#include <iostream>
using namespace std;

class GPU
{
private:
    int temperature = 40;

public:
    void SetTemperature(int t)
    {
        if (t >= 0 && t <= 120)
        {
            temperature = t;
        }
    }

    void ShowTemperature()
    {
        cout << "当前温度:" << temperature
             << "C" << endl;
    }
};

int main()
{
    GPU gpu1;
    gpu1.ShowTemperature();

    GPU* p = &gpu1;

    p->SetTemperature(85);

    gpu1.ShowTemperature();
    p->ShowTemperature();

    return 0;
}

2. 运行结果

当前温度:40C
当前温度:85C
当前温度:85C

3. 核心知识点

对象与对象指针的区别

GPU gpu1;
GPU* p = &gpu1;
  • GPU gpu1:创建一个 GPU 对象。
  • GPU* p:声明一个指向 GPU 对象的指针。
  • &gpu1:获取对象的地址。
  • p:保存对象的地址,而不是创建一个新对象。

. 和 -> 的区别

gpu1.ShowTemperature();  // 对象访问成员

p->ShowTemperature();     // 指针访问成员

对于普通对象指针:

p->ShowTemperature();

等价于:

(*p).ShowTemperature();

也就是先解引用指针,再访问对象成员。

4. 练习检查

本次代码没有发现需要纠正的错误。

特别是温度范围判断:

if (t >= 0 && t <= 120)

正确包含了两个边界值,也修正了上一讲容易遗漏边界的习惯。

本讲结论:指针能够通过 -> 访问它所指向对象的成员,修改的是原对象的数据。

第 4 讲:构造函数与析构函数

1. 我的原始代码

#include <iostream>
using namespace std;

class Renderer
{
private:
    int width;
    int height;

public:
    Renderer()
    {
        width = 800;
        height = 600;

        cout << "默认渲染器创建" << endl;
    }

    Renderer(int w, int h)
    {
        width = w;
        height = h;

        cout << "自定义渲染器创建" << endl;
        cout << "当前分辨率:"
             << width << "X" << height << endl;
    }

    ~Renderer()
    {
        cout << "渲染器销毁";
    }
};

int main()
{
    Renderer r1;

    {
        Renderer r2(1920, 1080);
        r2.Renderer();
    }

    r1.Renderer();

    return 0;
}

2. 发现的问题

这次主要有一个概念性错误:

r2.Renderer();
r1.Renderer();

构造函数不能通过对象像普通成员函数一样调用。

例如:

Renderer r1;

在这一句中,无参构造函数已经自动执行。

而:

Renderer r2(1920, 1080);

会自动选择带参构造函数:

Renderer(int w, int h)

所以不应该再写 r2.Renderer()。

另外,原代码缺少题目要求的 ShowResolution() 成员函数,析构输出也没有换行。

3. 修正后的代码


#include <iostream>
using namespace std;

class Renderer
{
private:
    int width;
    int height;

public:
    Renderer()
    {
        width = 800;
        height = 600;
        cout << "默认渲染器创建" << endl;
    }

    Renderer(int w, int h)
    {
        width = w;
        height = h;
        cout << "自定义渲染器创建" << endl;
    }

    ~Renderer()
    {
        cout << "渲染器销毁" << endl;
    }

    void ShowResolution()
    {
        cout << "当前分辨率:"
             << width << "x" << height << endl;
    }
};

int main()
{
    Renderer r1;

    {
        Renderer r2(1920, 1080);
        r2.ShowResolution();
    }

    r1.ShowResolution();

    return 0;
}

4. 核心知识点

构造函数析构函数Renderer()~Renderer()对象初始化时调用对象生命周期结束时调用可以带参数不能带参数可以重载不能重载主要负责初始化主要负责清理资源

构造与析构的顺序

Renderer r1;

{
    Renderer r2(1920, 1080);
}

执行过程:

  1. 调用 r1 的构造函数
  2. 调用 r2 的构造函数
  3. 离开内部作用域,调用 r2 的析构函数
  4. 离开 main() 作用域,调用 r1 的析构函数

5. 本讲总结

本次错误源于混淆了构造函数和普通成员函数。

需要重点记忆:

  • 构造函数负责对象初始化,不需要手动调用。
  • 普通成员函数可以通过对象主动调用。
  • 局部对象在生命周期结束时通常会自动析构。
  • 构造函数可以重载,编译器根据参数选择适合的版本。

这些知识是后续理解 C++ 的 RAII 资源管理机制的基础。

第 5 讲:继承(Inheritance)

1. 我的练习代码

#include <iostream>
using namespace std;

class GPU
{
private:
    int memory = 8;

public:
    void SetMemory(int m)
    {
        if (m > 0)
        {
            memory = m;
        }
    }

    void ShowMemory()
    {
        cout << "显存:" << memory
             << "GB" << endl;
    }
};

class NvidiaGPU : public GPU
{
public:
    void EnableRayTracing()
    {
        cout << "光线追踪已启用";
    }
};

int main()
{
    NvidiaGPU gpu1;

    gpu1.SetMemory(16);
    gpu1.ShowMemory();
    gpu1.EnableRayTracing();
}

2. 代码检查

本次代码逻辑正确,能够完成题目要求。

可以优化的是输出:

cout << "光线追踪已启用";

建议添加换行:

cout << "光线追踪已启用" << endl;

此外,main() 末尾省略 return 0; 在 C++ 中是合法的。

3. 核心知识点

继承的基本语法

class NvidiaGPU : public GPU

其中:

  • GPU:基类。
  • NvidiaGPU:派生类。
  • public:公有继承方式。

派生类可以使用基类中允许访问的成员,并增加自己的成员。

例如:

gpu1.SetMemory(16);        // 继承的成员函数
gpu1.ShowMemory();         // 继承的成员函数
gpu1.EnableRayTracing();   // 派生类自己的函数

继承与 private

基类中的私有成员:

private:
    int memory = 8;

不能被派生类的成员函数直接访问。

但派生类可以通过基类提供的公有函数间接修改它:

SetMemory(16);

4. 本讲总结

继承允许派生类复用基类的功能,并在此基础上添加自己的功能。

但需要特别注意:

继承不意味着派生类获得了访问基类所有成员的权限。

private 的访问控制依然有效。

第 6 讲:虚函数与多态

这是今天最重要的一节,也是理解 DX12 COM 接口的重要前置知识。

1. 我的练习代码

#include <iostream>
using namespace std;

class GPU
{
public:
    virtual void ShowInfo()
    {
        cout << "普通GPU" << endl;
    }
};

class NvidiaGPU : public GPU
{
public:
    void ShowInfo() override
    {
        cout << "NVIDIA RTX 4090" << endl;
    }
};

class AMDGPU : public GPU
{
public:
    void ShowInfo() override
    {
        cout << "AMD Radeon" << endl;
    }
};

int main()
{
    NvidiaGPU nvidia;
    AMDGPU amd;

    GPU* p1 = &nvidia;
    GPU* p2 = &amd;

    p1->ShowInfo();
    p2->ShowInfo();

    return 0;
}

2. 运行结果

NVIDIA RTX 4090
AMD Radeon

本次代码正确,成功实现了 C++ 运行时多态。

3. 核心知识点

virtual:虚函数

virtual void ShowInfo()

表示这个函数支持动态分派。

override:重写

void ShowInfo() override

表示派生类要重写基类中的虚函数,并让编译器检查是否正确重写。

多态:同一接口,不同实现

GPU* p1 = &nvidia;
GPU* p2 = &amd;

两个指针的静态类型都是 GPU*,但指向不同类型的派生类对象。

由于 ShowInfo() 是虚函数:

p1->ShowInfo();  // NVIDIA RTX 4090
p2->ShowInfo();  // AMD Radeon

程序会根据实际对象选择对应的重写版本。

4. 本次概念纠正

你在练习后提出:

删除 virtual 后,两句都会输出普通 GPU,因为基类和派生类存在同名函数时,会默认执行基类的函数。

这里的输出判断在通过 GPU* 调用且同时删除 override 的条件下正确,但原因需要修正。

如果删除 virtual 和派生类中的 override:

NvidiaGPU nvidia;

nvidia.ShowInfo();  // NVIDIA RTX 4090

GPU* p = &nvidia;
p->ShowInfo();       // 普通GPU

为什么两次输出不同?

  • nvidia.ShowInfo():表达式的静态类型是 NvidiaGPU,所以调用派生类函数。
  • p->ShowInfo():p 的静态类型是 GPU*,由于函数不是虚函数,所以调用基类版本。

如果基类的 ShowInfo() 是虚函数,则通过基类指针调用时会进行动态分派。

调用方式非虚函数虚函数nvidia.ShowInfo()NVIDIA RTX 4090NVIDIA RTX 4090p->ShowInfo()普通GPUNVIDIA RTX 4090

还有一个容易忽略的点:

如果删除基类的 virtual,却保留派生类的 override,代码会编译失败。

因为 override 要求函数确实重写了基类的虚函数。

5. 本讲总结

本节掌握了三个概念:

  • 静态绑定:根据表达式的静态类型决定非虚函数调用。
  • 动态绑定:虚函数调用可以根据实际对象的动态类型选择最终重写版本。
  • 多态:通过统一的基类指针或引用,调用不同派生类对象提供的实现。

这正是后面理解 COM 接口机制的重要基础。

今天暴露的问题及进步

学习内容表现后续重点指针与成员访问正确继续巩固对象生命周期构造函数初次使用时出现调用方式错误区分初始化和普通函数调用析构函数能正确定义加深作用域和生命周期的理解继承正确后续学习多级继承虚函数与重写正确强化静态绑定与动态绑定条件判断边界本次正确保持检查边界的习惯

相比前两讲,今天的代码中没有再出现循环或条件判断边界错误,函数定义、成员访问与继承结构也更加稳定。

目前主要需要强化的是构造函数的特殊性,以及多态调用时静态类型和动态类型的区别。

与 DirectX 12 的联系

今天学的四节课可以直接对应到你之前遇到的 DX12 代码:

ComPtr<IDXGIFactory6> factory;

HRESULT hr = CreateDXGIFactory2(
    0,
    IID_PPV_ARGS(factory.GetAddressOf())
);
已学 C++ 知识对应的 DX12 概念类与对象ComPtr 对象对象指针、->通过接口指针调用方法构造与析构ComPtr 的生命周期管理继承DXGI 接口的继承关系虚函数与多态COM 接口的方法调用机制

你现在回头看 factory->...、接口类型和成员函数,应该比刚开始接触 DX12 时更容易理解其 C++ 语法结构。

不过,COM 还涉及接口标识符、引用计数和 QueryInterface() 等知识,后续需要单独学习。

下一阶段学习安排

第 7 讲:纯虚函数与抽象类

理解 virtual void Function() = 0;

第 8 讲:智能指针与 COM 基础

模板、ComPtr、IUnknown、引用计数与 RAII

返回 DX12 系统课程

重新理解 DXGI Factory、Adapter 和 Device

今日结论

今天完成了四次独立 Coding 练习,已经能够使用对象指针、继承和虚函数编写简单的多态程序。第 4 讲存在的构造函数调用错误,也已经明确了修正方法。

下一步不必急着扩大学习范围。先通过纯虚函数与抽象类把当前学到的继承、多态知识连起来,再补充 COM 和智能指针,就可以恢复 DX12 学习主线。

今天最重要的收获:C++ 不仅能通过类组织数据和函数,还能利用继承与虚函数,通过统一的类型接口调用不同对象的实际实现。

评论