c风格结构体直接包进class却未加访问控制、未校验参数、未封装逻辑,仍是面向过程;必须用private封装数据,提供带校验的接口,补全构造函数,合理使用组合与策略模式。

把C风格的结构体+函数直接包进class里
最常见也最容易踩坑的做法,是把原C代码里的 struct 复制粘贴进 class,然后把所有操作函数挪成成员函数——但不改调用方式。比如原C代码有:
struct Hero {
char* name;
int life;
int money;
};
void fight(Hero* h, int lValue) { h->life -= lValue; }
改成C++后如果只写:
class Hero {
public:
char* name;
int life;
int money;
void fight(int lValue) { life -= lValue; }
};
看起来“面向对象”了,但问题没解决:数据仍是公开的,life 还能被外部随意修改;fight 也没做参数校验,传负数照样执行。这叫“披着class外衣的面向过程”。
必须加访问限定符,否则封装就失效了
把 public: 全部换成 private: 是第一步,但不能一刀切。真正该暴露的只有接口,不是所有字段都要藏起来。比如 name 可能需要读取,但不该允许直接赋值空指针。
-
name改成std::string,提供get_name()读取,不提供set_name()或仅提供带非空检查的版本 -
life和money必须私有,所有修改必须走fight()、treat()等方法,且方法内部要校验边界(如life不能低于 0) - 构造函数补全:避免出现未初始化的
name或负life,例如Hero(const std::string& n, int l = 100, int m = 0)
函数参数从“传结构体”变成“隐式this”
原C函数像 fight(Hero* h, int damage),迁移到成员函数后,h 消失了,damage 是唯一参数。但容易忽略的是:原函数可能依赖全局状态或额外上下文,比如日志开关、随机种子、配置表。
这些不能直接扔进成员函数里硬编码。建议:
- 把配置类(如
GameConfig)作为成员变量或通过构造函数注入 - 日志逻辑抽成独立服务,用依赖注入或单例,别让
fight()自己决定要不要打日志 - 避免在成员函数里调用
printf或std::cout—— 输出行为应该可替换、可测试
继承不是必须的,别为了“像OOP”强行加基类
看到“特种战士要加伪装属性”,第一反应不是立刻建 BaseHero 和 SpecialHero : public BaseHero。先问:这两个类型共用多少逻辑?是否真有“is-a”关系?还是只是“has-a”或纯数据差异?
更务实的做法:
- 用组合:给
Hero加一个std::optional<disguisestate></disguisestate>成员,需要时才启用 - 用策略模式:把战斗逻辑抽象成
FightStrategy接口,不同角色换不同策略,不改类结构 - 等出现 3 个以上相似角色、且公共逻辑稳定后,再提取基类 —— 过早抽象反而增加维护成本
真正的迁移难点不在语法,而在把“谁来改这个值”的控制权,从调用方手里收回到对象内部。只要 life 还能被外部直接写,哪怕套了 class,它就还是面向过程的代码。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











