14-1 面向对象编程介绍

过程式编程

回到第 1.3 课——对象和变量简介,我们在 C++ 中将对象定义为“一块可用于存储值的内存”。具有名称的对象称为变量。我们的 C++ 程序由计算机指令的顺序列表组成,这些指令定义数据(通过对象)以及对该数据执行的操作(通过包含语句和表达式的函数)。

到目前为止,我们一直在进行一种称为过程式编程的编程。在过程式编程procedural programming中,重点是创建实现程序逻辑的“过程”(在 C++ 中称为函数)。我们将数据对象传递给这些函数,这些函数对数据执行操作,然后可能返回一个结果以供调用者使用。

在过程式编程中,函数和这些函数所操作的数据是独立的实体。程序员负责将函数和数据组合在一起以产生所需的结果。这导致代码如下所示:

eat(you, apple);

现在,环顾四周——你所见之处尽是物体:书籍、建筑、食物,甚至你自己。这类物体主要由两部分构成:1) 若干关联属性(如重量、颜色、尺寸、固态性、形状等);2) 若干可展现的行为(如被打开、使其他物体变热等)。属性与行为密不可分。

在编程中,属性由对象表示,行为由函数表示。因此,过程式编程对现实的描述相当拙劣,因为它将属性(对象)与行为(函数)割裂开来。


什么是面向对象编程?

面向对象编程object-oriented programming(通常简称为OOP)中,重点在于创建包含属性和一组明确定义的行为的程序定义数据类型。OOP中的“对象”一词指的是我们可以从这些类型中实例化的对象。

这使得代码看起来更像是这样:

you.eat(apple);

这使得主体(你)、被调用的行为(吃())以及该行为的附属对象(苹果)都更加清晰。

由于属性和行为不再分离,对象更易于模块化,这使得程序更易编写和理解,同时提供了更高的代码复用性。这些对象还通过允许我们定义与对象交互的方式以及对象间交互的方式,为数据操作提供了更直观的途径。

我们将在下一课中探讨如何创建此类对象。


过程式vs面向对象式的编程示例

以下是一个采用过程式编程风格编写的简短程序,用于输出动物的名称及其腿部数量:

#include <iostream>
#include <string_view>

enum AnimalType
{
    cat,
    dog,
    chicken,
};

constexpr std::string_view animalName(AnimalType type)
{
    switch (type)
    {
    case cat: return "cat";
    case dog: return "dog";
    case chicken: return "chicken";
    default:  return "";
    }
}

constexpr int numLegs(AnimalType type)
{
    switch (type)
    {
    case cat: return 4;
    case dog: return 4;
    case chicken: return 2;
    default:  return 0;
    }
}


int main()
{
    constexpr AnimalType animal{ cat };
    std::cout << "A " << animalName(animal) << " has " << numLegs(animal) << " legs\n";

    return 0;
}

image

在这个程序中,我们编写了若干函数,能够实现获取动物腿部数量、获取动物名称等功能。

虽然这些功能运行良好,但请考虑当我们需要更新程序使动物变成蛇时会发生什么。要将蛇添加到代码中,我们需要修改AnimalType、numLegs()和animalName()这三个函数。若代码库规模较大,还需更新所有调用AnimalType的函数——若该类型被广泛使用,则可能涉及大量代码修改(并存在破坏现有功能的风险)。

现在让我们以面向对象思维重写相同程序(保持输出结果不变):

#include <iostream>
#include <string_view>

struct Cat
{
    std::string_view name{ "cat" };
    int numLegs{ 4 };
};

struct Dog
{
    std::string_view name{ "dog" };
    int numLegs{ 4 };
};

struct Chicken
{
    std::string_view name{ "chicken" };
    int numLegs{ 2 };
};

int main()
{
    constexpr Cat animal;
    std::cout << "a " << animal.name << " has " << animal.numLegs << " legs\n";

    return 0;
}

image

在此示例中,每种动物都是程序定义的独立类型,该类型管理与该动物相关的所有内容(本例中仅需记录名称和腿部数量)。

现在考虑将动物更新为蛇的情况。我们只需创建Snake类型并替换原有的Cat类型即可。现有代码几乎无需修改,这意味着极大降低了破坏现有功能的风险。

如前所述,猫、狗、鸡的示例存在大量重复(因它们定义了完全相同的成员集)。这种情况下,创建通用动物结构体并为每种动物实例化可能更优。但若需为鸡添加其他动物无需的新成员(如每日食虫量),该如何处理?采用通用Animal结构体时,所有动物都会继承该成员。而基于面向对象的模型,我们可将该成员限定为仅鸡对象可用。


面向对象编程还带来了其他优势

在学校里,当你提交编程作业时,你的工作基本上就完成了。教授或助教运行你的代码,检查它是否产生正确结果。要么成功要么失败,你将据此获得评分。此时你的代码很可能被丢弃。

然而当代码提交至其他开发者使用的代码库,或投入真实用户使用的应用程序时,情况就截然不同了。新操作系统或软件版本可能破坏你的代码;用户会发现你编写的逻辑错误;商业伙伴会要求新增功能;其他开发者需要在不破坏现有代码的前提下进行扩展。你的代码必须具备进化能力——甚至可能需要重大演进——且需以最小的时间投入、最少的麻烦和最少的破坏实现。

应对这些挑战的最佳方式是让代码尽可能模块化(且无冗余)。为此,面向对象编程还提供了诸多实用概念:继承、封装、抽象和多态。

作者注:
语言设计师奉行这样的哲学:能用长词时,绝不用短词。
另外,为什么“缩写”这个词这么长?

我们将适时讲解这些概念的具体含义,以及它们如何帮助减少代码冗余,提升可修改性和可扩展性。当你真正掌握OOP并领悟其精髓后,很可能再也不愿回到纯过程式编程的时代.


术语“对象”

需注意术语“对象”存在一定程度的含义重载,这会引发部分混淆。在传统编程中,对象仅指存储值的内存单元。而在面向对象编程中,“对象”既包含传统编程意义上的内存单元,又同时具备属性与行为的复合特性。在本教程中,我们将优先采用对象的传统含义,当特指面向对象编程中的对象时,则使用“类对象”这一术语。


测验时间

问题 #1

更新上文中的动物过程化示例,将猫的实例化替换为蛇的实例化。

显示解决方案

#include <iostream>
#include <string_view>

enum AnimalType
{
    cat,
    dog,
    chicken,
    snake,
};

constexpr std::string_view animalName(AnimalType type)
{
    switch (type)
    {
    case cat: return "cat";
    case dog: return "dog";
    case chicken: return "chicken";
    case snake: return "snake";
    default:  return "";
    }
}

constexpr int numLegs(AnimalType type)
{
    switch (type)
    {
    case cat: return 4;
    case dog: return 4;
    case chicken: return 2;
    case snake: return 0;

    default:  return 0;
    }
}


int main()
{
    constexpr AnimalType animal{ snake };
    std::cout << "A " << animalName(animal) << " has " << numLegs(animal) << " legs\n";

    return 0;
}

image

问题 #2

更新上文中的动物面向对象式示例,将猫的实例化替换为蛇的实例化。

显示解决方案

#include <iostream>
#include <string_view>

struct Cat
{
    std::string_view name{ "cat" };
    int numLegs{ 4 };
};

struct Dog
{
    std::string_view name{ "dog" };
    int numLegs{ 4 };
};

struct Chicken
{
    std::string_view name{ "chicken" };
    int numLegs{ 2 };
};

struct Snake
{
    std::string_view name{ "snake" };
    int numLegs{ 0 };
};

int main()
{
    constexpr Snake animal;
    std::cout << "a " << animal.name << " has " << animal.numLegs << " legs\n";

    return 0;
}

image

posted @ 2025-12-26 17:33  游翔  阅读(9)  评论(0)    收藏  举报