☰
C++类和对象编程题解析:用枚举与构造函数实现CPU类
2026/10/14 1:38:53 网站建设 项目流程

这类“C++类和对象函数题——6-3 创建CPU”的题目,很多教材和题库里都能见到,它不复杂,但特别有意思。很多人第一次做这道题时,卡住的往往不是类本身,而是题目里特意标注的“枚举基本知识”——枚举到底是什么、为什么要用它、枚举值能不能直接输入输出,这些问题搞不清楚,代码就写不利索。我见过不少同学,类、对象、构造函数都背得滚瓜烂熟,结果一遇到enum就懵了,最后只能硬凑一个int成员变量蒙混过关。

这道题其实是把“类的定义、对象的创建、构造函数、成员函数、枚举类型的使用”全部串在了一起,非常适合作为面向对象编程的入门训练。如果你正在学类和对象这一章,或者正在做类似的题库练习,这篇文章会从题目拆解、前置知识补全、完整代码实现到常见报错排查,一步步带你把它吃透。看完之后,你不仅能独立写出这道题,还能顺手把枚举这个知识点彻底搞明白。

1. 题目拆解:先看清题面再动手

1.1 “创建CPU”到底要创建什么

遇到一道编程题,第一件事不是开 IDE 写代码,而是先琢磨题面到底在说什么。“创建CPU”字面上是创建一个 CPU 对象,但实际考察的是:你能不能声明一个类、定义它的成员变量和成员函数,然后在主函数里实例化对象。

通常这道题的类会有这些成员:核心频率(整数)、价格(整数)、性能等级(枚举类型),外加一个输出函数output()。有的版本还会要求写无参构造、带参构造、获取成员值的get函数。看上去内容很多,但拆开来看就三类:数据成员、构造函数、成员函数。数据成员描述一个 CPU “长什么样”,构造函数负责“出厂初始化”,成员函数负责“这个对象能干什么”。

这类题目把细节都写在了题面的“说明”里。你可以把题面说明当成需求和接口文档,它说让你写哪些函数,你就写哪些函数,函数名和参数类型都必须和题面保持一致。很多人在线判题系统里丢分,不是因为逻辑错,而是函数名写错了或者返回值类型不对,这比逻辑错误更冤。

1.2 为什么性能等级要用枚举

题目特意强调“含说明和枚举基本知识”,等于是在暗示:性能等级这个属性别用int,要用枚举。为什么?因为性能等级的取值是固定而且有限的,比如 P1 到 P7,一共就 7 档。用int虽然也能存,但别人可以往里塞 100、-3 这种毫无意义的值,程序也不会拦你;用枚举,取值就被限制在这 7 个里面,代码更安全,可读性也更好。

举个生活例子。红绿灯的颜色只有红、黄、绿三种,如果用一个int表示,0 代表红、1 代表黄、2 代表绿,你看代码的时候还得记着这个对应关系;如果定义一个enum LightColor { RED, YELLOW, GREEN },代码里直接写RED,谁看谁懂。CPU 的性能等级也是一样的道理,P1 到 P7 本身就是一组有名字的常量,枚举天然就是为这种场景准备的。

网络上的题库版本里,有的把枚举定义在类外面,有的定义在类里面,有的要求输出P1到P7,有的只输出数字。这些都不影响核心逻辑,只是作用域和输出格式有区别。下面我会把最常见的类内枚举写法讲透,你再根据实际题目微调就行。

1.3 从“题面说明”里读出隐藏的设计意图

有些同学看题面只看到“代码要求”,看不到“设计意图”。其实像这种带“说明”的题目,每一句话都可能是考点。说“用枚举保存性能等级”,是想让你理解有限状态的建模方式;说“无参构造和带参构造都要有”,是想让你理解重载和对象初始化的不同方式;说“output 函数按指定格式输出”,是想让你理解输出格式的精确控制。

做这道题时,我建议你顺手把题面里每个要求翻译成对应的 C++ 语法点,比如:

题目要求对应的 C++ 知识点
创建一个 CPU 类类的声明与定义
包含频率、价格、性能等级成员变量的选择与类型设计
性能等级用枚举表示enum 或 enum class 的使用
提供无参和带参构造构造函数重载、缺省构造函数
能输出 CPU 信息成员函数、cout 格式控制
创建对象并调用函数对象实例化、成员访问

把题面翻译成这张表之后,你就会发现这题考察的知识点特别清晰,没有一处是多余的,也没有一处是不可实现的。翻译完这张表,代码怎么写就已经有一半思路了。

2. 前置知识补全:类和对象到底在做什么

2.1 类是一张图纸,对象是造出来的实物

在写代码之前,我想先把类和对象的关系用大白话捋一遍。你完全可以这样理解:类是一张图纸,对象是按照这张图纸造出来的实物。图纸上写了这台 CPU 有哪些属性(频率、价格、性能等级)和哪些动作(输出信息),但图纸本身不是 CPU;只有用图纸去“造”出一个具体的对象,它才占内存、才真正可以被使用。

CPU myCPU; // 按 CPU 这张图纸造出了一个叫 myCPU 的对象 CPU anotherCPU; // 又造了另一个对象,两个对象互不干扰

这就是“类”和“对象”最核心的区别:类是类型,对象是变量。你用同一个类可以创建无数个对象,每个对象都有自己的属性值,这些属性值互不影响。就像同型号的两块 CPU,一块频率高一点,一块价格便宜点,它们虽然用同一张图纸造的,但数据完全独立。

很多初学者会纠结一个问题:成员函数到底是属于类的还是属于对象的?答案是:从语法上讲,同一个类的所有对象共享同一份成员函数代码,但函数内部访问的成员变量是各个对象自己的。你调用cpu1.output()和cpu2.output(),输出的是各自的数据。理解这一点,你就不会写出那种“对象没初始化就调用函数”的代码了。

2.2 CPU 类需要哪些成员变量

设计一个类,第一步永远是找名词。题目说“CPU 有核心频率、价格、性能等级”,这些名词就是成员变量。我来逐个分析类型选择:

  • 核心频率:通常用整数,单位是 MHz 或者 GHz,频率不会出现小数(至少在这道题里不会),所以int够了。
  • 价格:同样是整数,用int存没问题。如果题目想考浮点数,会明确说“价格是 double 类型”,但大部分版本都用int。
  • 性能等级:这是最有讲究的。它只有 P1 到 P7 这 7 个取值,所以用枚举。你可以定义一个枚举类型CPU_Performance,枚举常量从 P1 到 P7。

成员变量的访问权限,我建议全部设为private。原因很简单:这是面向对象封装性的基本要求,外部不能随便改 CPU 的内部数据,只能通过构造函数或成员函数来操作。题目如果没特别说明,一般也是希望你写成private的,这是练习里的默认惯例。

有的同学把成员变量全写成public,觉得这样简单省事。短期内确实省事,但会养成坏习惯。一旦你进入后面的大作业或者真实项目,public成员变量会让类的内部结构完全暴露,任何人都能乱改数据,类就失去了自洽性。所以从这道题开始,就养成“成员变量私有、操作函数公有”的习惯。

2.3 构造函数:为什么对象一出生就必须被初始化

构造函数是一个特殊成员函数,名字和类名完全一样,没有返回值。它的作用是:在对象被创建的瞬间,自动给成员变量赋初值,让对象一出生就是合法状态。

这道题通常会要求两个构造函数:

  • 无参构造CPU():把频率、价格都设为 0,性能等级设为 P1。这样做是为了保证直接CPU c;创建出来的对象也是可用的,而不是一堆随机值。
  • 带参构造CPU(int f, int p, CPU_Performance pg):用调用者传入的值初始化成员变量,让你能创建出“定制”的 CPU。

如果你不写构造函数,编译器会生成一个默认构造函数,但这只是“形式上存在”,对内置类型的成员变量不做初始化。意味着int frequency的值是未定义的——可能是 0,可能是某个内存残留值,就是你常见的“随机数”。这个问题非常隐蔽,因为有些编译器在调试模式下会默认清零,程序跑起来没问题;到发布模式就随机出各种诡异结果。所以这道题要求显式写构造函数,不是形式主义,是必须的。

另外,构造函数是可以重载的,也就是同名函数参数不同可以有多个版本。CPU()和CPU(int, int, CPU_Performance)就是一组重载,编译器会根据你创建对象时写的参数列表决定调用哪一个。

2.4 枚举基础:从 enum 到 enum class,这道题到底用哪个

枚举(enumeration)是 C++ 里一种自定义类型,用来表示一组具名的整型常量。基础语法分两种,我先分别讲清楚,然后再说题目里怎么选。

第一种是传统枚举,也叫不限定作用域的枚举:

enum CPU_Performance { P1 = 1, P2, P3, P4, P5, P6, P7 };

这样写之后,P1、P2这些名字会被“注入”到当前作用域里。你在当前作用域直接写P3,编译器就知道这是枚举值。后面的P2不写初值,会自动取上一个值加 1,也就是 2,依此类推 P7 就是 7。这种“自动递增”是枚举最方便的特性,如果你想指定从 1 开始,显式写P1 = 1即可。

第二种是 C++11 引入的限定作用域枚举:

enum class CPU_Performance { P1 = 1, P2, P3, P4, P5, P6, P7 };

区别在于class关键字:枚举成员不会再注入到外围作用域,你必须写CPU_Performance::P1这种形式才能访问。好处是彻底避免了命名冲突,坏处是代码会稍微啰嗦,而且不能隐式转换成整数。

对于教材里的练习题,很多版本的官方参考代码用的是传统enum,因为语法更老、教材覆盖面更广。你在做题时可以优先跟随题目说明里的示例代码。如果题目没说,你就用传统枚举就行,因为后面要在类外写CPU::CPU_Performance来指定类型,用传统枚举也完全够用;如果你用的是支持 C++11 的编译器,用enum class加上作用域访问符也完全可以,只是风格更现代化。两种写法我都放在代码部分了,你自己按需挑一种。

关于枚举还有一个考点:它能不能直接cin输入?答案是不能。C++ 不允许直接从输入流读一个值到枚举变量里。所以通常的做法是先读一个int,再用static_cast转成枚举类型。同理,cout输出枚举变量时默认输出的是它的整数值,而不是名字。如果你想输出P6这个形式,就得自己拼字符串。这些细节我在第 3 部分的完整代码中会演示。

3. 完整实操:从类声明到代码跑通

3.1 先搭框架:类声明怎么写

写一个类,我建议先写类声明(在类里面只写函数声明),再到类外面写函数定义。这样代码结构更清晰,也方便你看清楚这个类到底有哪些接口。

class CPU { public: enum CPU_Performance { P1 = 1, P2, P3, P4, P5, P6, P7 }; CPU(); CPU(int f, int p, CPU_Performance pg); void output(); int getFrequency() const; int getPrice() const; CPU_Performance getPerformanceGrade() const; private: int frequency; int price; CPU_Performance performanceGrade; };

有几个细节值得说明。第一,为什么枚举定义在public区域但成员变量放在private区域?因为枚举类型是要暴露给外部使用的——调用者在创建对象时需要写CPU::P5或者通过CPU::CPU_Performance来声明变量。如果把它放在private里,外部根本没法使用这个类型,带参构造函数也就没法调用了。

第二,getFrequency这些读取函数我加了const后缀,表示这个函数不会修改成员变量。这对于“只读”函数来说是正确做法,虽然题目可能不要求,但加上是加分项。第三,你会发现我没有把输出函数写成void output()之外的其他形式,如果题目里要求你返回字符串或者做别的,再按题面调整。

3.2 成员函数的实现:把每个函数填实

类声明写好之后,类外面写实现。构造函数这块,无参构造要做到“对象一创建就有一个确定的初始状态”:

CPU::CPU() { frequency = 0; price = 0; performanceGrade = P1; }

带参构造就是把参数存进成员变量。这里为了让代码可读性更好,我用this指针明确区分参数和成员变量:

CPU::CPU(int f, int p, CPU_Performance pg) { frequency = f; price = p; performanceGrade = pg; }

如果你的编译器支持 C++11,还可以用初始化列表写得更简洁,效果完全一样:

CPU::CPU(int f, int p, CPU_Performance pg) : frequency(f), price(p), performanceGrade(pg) { }

output()函数要注意输出格式。最常见的题面要求是这样三行:

CPU的速度是357 价格是3600 性能等级是P6

对应代码如下:

void CPU::output() { cout << "CPU的速度是" << frequency << endl; cout << "价格是" << price << endl; cout << "性能等级是P" << performanceGrade << endl; }

这里有个很容易翻车的细节:直接cout << performanceGrade,输出的是整数 6,而不是“P6”。所以你要在枚举变量前面手动拼一个字符"P",后面的数字由枚举值自动转成整数输出。如果你想把枚举转成更标准的“字符串型 P6”,那就得用switch或者数组映射了,但这道题通常用不到,手动拼前缀是最简单可靠的做法。

返回函数就没什么好讲的了,直接返回值即可:

int CPU::getFrequency() const { return frequency; } int CPU::getPrice() const { return price; } CPU::CPU_Performance CPU::getPerformanceGrade() const { return performanceGrade; }

注意看第三个函数返回值那里写的是CPU::CPU_Performance。因为在类外面写定义,CPU_Performance这个枚举类型是定义在类里面的,所以必须用CPU::CPU_Performance来完整限定它。忘记加类名限定是这道题的高频编译错误之一,我后面会专门说。

3.3 主函数:创建一个对象并输出

主函数逻辑其实很简单:读三个值,创建对象,调用输出函数。

int main() { int f, p, pg; cin >> f >> p >> pg; CPU cpu(f, p, static_cast<CPU::CPU_Performance>(pg)); cpu.output(); return 0; }

这里pg是整数,输入 6 表示性能等级是 P6。但构造函数第三个参数要的是CPU_Performance类型的值,所以必须用static_cast<CPU::CPU_Performance>(pg)把整数转成枚举。这一步不能省,虽然有些编译器在传统枚举下可能只给警告不给报错,但规范写法就应该显式转换。

如果你的题面要求输入是P6这种带字母的字符串,那么读取逻辑要换成字符串,再解析出数字部分。但这种版本很少见,而且复杂度一下子提高不少,大多数练习题都是用整数输入。你可以根据自己手上的题面要求来调整。

3.4 完整可编译代码(含测试用例)

我把传统枚举版本和enum class版本的完整代码都放出来,方便你对比。

传统枚举版本:

#include <iostream> using namespace std; class CPU { public: enum CPU_Performance { P1 = 1, P2, P3, P4, P5, P6, P7 }; CPU(); CPU(int f, int p, CPU_Performance pg); void output(); int getFrequency() const; int getPrice() const; CPU_Performance getPerformanceGrade() const; private: int frequency; int price; CPU_Performance performanceGrade; }; CPU::CPU() { frequency = 0; price = 0; performanceGrade = P1; } CPU::CPU(int f, int p, CPU_Performance pg) : frequency(f), price(p), performanceGrade(pg) { } void CPU::output() { cout << "CPU的速度是" << frequency << endl; cout << "价格是" << price << endl; cout << "性能等级是P" << performanceGrade << endl; } int CPU::getFrequency() const { return frequency; } int CPU::getPrice() const { return price; } CPU::CPU_Performance CPU::getPerformanceGrade() const { return performanceGrade; } int main() { int f, p, pg; cin >> f >> p >> pg; CPU cpu(f, p, static_cast<CPU::CPU_Performance>(pg)); cpu.output(); return 0; }

enum class版本把枚举定义和所有引用方式改一下:

#include <iostream> using namespace std; class CPU { public: enum class CPU_Performance { P1 = 1, P2, P3, P4, P5, P6, P7 }; CPU(); CPU(int f, int p, CPU_Performance pg); void output(); int getFrequency() const; int getPrice() const; CPU_Performance getPerformanceGrade() const; private: int frequency; int price; CPU_Performance performanceGrade; }; CPU::CPU() { frequency = 0; price = 0; performanceGrade = CPU_Performance::P1; } CPU::CPU(int f, int p, CPU_Performance pg) : frequency(f), price(p), performanceGrade(pg) { } void CPU::output() { cout << "CPU的速度是" << frequency << endl; cout << "价格是" << price << endl; cout << "性能等级是P" << static_cast<int>(performanceGrade) << endl; } CPU::CPU_Performance CPU::getPerformanceGrade() const { return performanceGrade; } int main() { int f, p, pg; cin >> f >> p >> pg; CPU cpu(f, p, static_cast<CPU::CPU_Performance>(pg)); cpu.output(); return 0; }

注意enum class版本里,cout << performanceGrade是编译不通过的,因为强类型枚举不能隐式转换成整数,必须用static_cast<int>(performanceGrade)。这个区别特别容易踩坑,在你使用新版语法时一定要记住。

测试用例可以这样验证:输入

357 3600 6

期望输出

CPU的速度是357 价格是3600 性能等级是P6

如果输出和你题目给的样例不完全一致,把output()里的格式按题面要求的改一改就行,核心类设计不用动。

4. 常见问题与排查技巧实录

4.1 编译报错:枚举成员名冲突

写传统枚举时,最典型的报错是“P1重定义”或者“P1未声明”。原因通常是你把枚举放在了类外面,而另一个地方(比如某个全局变量或者其他枚举)已经用了P1这个名字。传统枚举的成员会注入到它所在的作用域,冲突概率很高。

解决办法有三种:第一种,把枚举定义到类里面,用CPU::P1这种形式访问,冲突面就小多了;第二种,给枚举值起更独特的别名,比如PERF_P1;第三种,直接用enum class,彻底隔离作用域。我建议做题时优先第一种或者第三种,又清晰又不容易出错。

4.2 链接错误:类外定义函数忘了限定类名

还有一种报错非常常见:在类外面写void output() { ... },然后编译报“无法解析的外部符号”或者“output函数未定义”。原因是你写了两个output,一个是类内声明,一个是类外定义,但类外的定义没有加CPU::前缀,编译器把它当成了另一个全局函数。

类外定义成员函数的语法是返回类型 类名::函数名(参数) { ... }。CPU::这个前缀是必须的,它的作用类似“归属标记”,告诉编译器这个函数属于哪个类。只要忘了,编译器就认为你定义了一个全新函数,而类里的output还是只有声明没有定义,链接时自然就失败了。

4.3 输出格式不对:不是逻辑错了,是排版错了

在线判题系统对输出格式的检查是逐字符匹配的,多一个空格、少一个换行都不能通过。常见的输出格式问题有:没按题面要求在“价格是”后面加空格;用了cout << endl却被要求输出\n;顺序和题面要求不一致。做这类题,拿到输出样例后先自己复制样例对照,尤其注意全角半角空格。我见过最冤的丢分就是中文字符串里混入了一个全角空格,判题系统直接给你判错。

如果题目只给了输出格式描述没给样例,你就按最常规的格式写,比如"CPU的速度是" << frequency << endl。拿不准时,在本地多跑几个用例,看看每个值替换后输出是否符合人类阅读习惯。

4.4 对象创建时参数类型不匹配

当你写了带参构造函数后,在主函数里创建对象时如果写了CPU cpu(357, 3600, 6);,在某些编译器下可能直接报错,因为 6 是int,无法隐式转换成枚举类型。传统枚举有些编译器会睁一只眼闭一只眼,但它会给你一个警告,告诉你类型转换不安全。正确做法是显式static_cast<CPU::CPU_Performance>(6)。

反过来的问题也容易出现:CPU_Performance类型的值不能直接用cin读取。你要先读int,再转成枚举,然后再传给构造函数。这个顺序不能乱。

4.5 避坑技巧:用 switch 或者映射表处理枚举输出

如果你想让output()输出更丰富的描述(比如“性能等级:P6,表示高性能”),那就需要处理枚举到字符串的映射。最笨但最清晰的方法是switch:

switch (performanceGrade) { case P1: cout << "入门级"; break; case P2: cout << "基础级"; break; // ... }

这里有一个容易忽略的坑:传统枚举的switch分支里,case后面直接写枚举名就能匹配,但如果你在类外部的成员函数里写,枚举名P1还能直接用,因为传统枚举把名字注入到了外围作用域。如果你用enum class,就必须写case CPU_Performance::P1:。两种语法下switch的写法是不同的,别搞混了。

另一个技巧是数组映射。你可以在类内放一个静态字符串数组,按枚举值索引:

const char* CPU_PerformanceName[] = { "", "入门级", "基础级", ... };

下标本来就和枚举值对应,效率高,代码也短。但这道题通常用不上,知道有这种方法就行。

5. 一点延伸:这道小题背后的工程思维

5.1 枚举是“有限状态”的最佳表达工具

从这道题你可以观察到一个通用经验:凡是值域固定的属性,都应该优先考虑枚举,而不是int或者字符串。程序里最常见的 bug 来源之一就是“魔法数字”。比如你要表示一周七天,有人用 1 到 7,有人用 0 到 6,还有人直接用字符串"Monday",不同的人在代码里混用这些表示方式,一天不出 bug 都是靠运气。

用枚举把这一组状态集中定义,编译器就能帮你做一层校验:你不能把一个任意的整数塞进去当星期几,至少要先通过一次显式转换。CPU 的性能等级、订单状态、用户角色、网络连接状态……这些全部是枚举的经典应用场景。这道题看起来只是在定义一个类,实际是在教你建模的时候如何表达离散状态。

5.2 类设计的本质是“找名词和动词”

如果你再往后学,会发现设计类的核心套路永远是:先找名词,它们是成员变量;再找动词,它们是成员函数;最后检查名词是否有限取值,有限取值就用枚举或布尔值。CPU这个例子就是活教材,“频率”“价格”“性能等级”是名词,“输出”“获取”是动词。先做这一步,再动手写代码,写出来的类天然就比拍脑袋想的要清晰。

我一直建议初学者在做这种小题时不要急着上网找答案。把答案遮住,自己从题面里把名词和动词圈出来,然后对照着写类声明。哪怕第一次写得很慢,这个“圈画–翻译–编码”的流程练熟了,后面做大作业的速度会快几倍。

5.3 怎么验证自己写对了:多做几个用例

很多同学写完这道题,输入一个样例通过后就算交差了。其实你还可以自己多试几个边界用例:输入最小性能等级 1,看输出是不是 P1;构造一个默认对象CPU c;,再调用c.output(),看是不是输出全 0;输入价格 0、频率 0 时程序会不会崩溃。这些用例不需要交上去,但它们能帮你排查成员变量初始化是否到位、枚举转换是否越界。

如果题目允许,你还可以在本地写一个简单的首尾对比:把创建的对象传进一个函数,调用它的get函数,检查返回值是否符合预期。这其实就是最小化的单元测试。能在做题阶段养成“多用例验证”的习惯,比多做十道题都有用。

我在实际带新人时最深的体会是:这种小题的价值从来不在功能本身,而在于它把面向对象的几个核心概念浓缩在几十行代码里。“枚举表达有限状态、构造函数保证合法初值、成员函数封装行为、私有变量保护内部数据”,这四件事你在“创建CPU”这题里全都练过一遍。以后再遇到订单系统、游戏角色、设备管理这些更复杂的设计,底层逻辑仍然是这一套。如果你正在做这道题,先别急着一遍跑通就换下一题,试着改一改枚举定义的位置、换成enum class、加一个set函数,每一次改动都会让你对类和对象的理解更扎实一点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询