1. 从“过程”到“对象”为什么我们需要面向对象编程如果你已经跟着这个系列走过了前三篇从变量、循环、函数一路打怪升级过来那么恭喜你你已经掌握了C作为一门“高级C语言”的大部分核心玩法。但如果你觉得写代码还是像在写一份冗长的“操作说明书”——先做A再做B检查C然后处理D——那么是时候推开一扇新世界的大门了面向对象编程。我刚开始学C时对“面向对象”这个词充满了敬畏和困惑。听起来很高大上但到底有什么用直到我接手维护一个几千行的、用纯C风格或者说面向过程风格写的学生管理系统。那个程序里学生的“数据”比如姓名、学号、成绩和“操作”比如计算平均分、打印信息是割裂的。数据用一堆全局数组存着操作它们的函数散落在各个角落。想加一个“班级”的概念几乎意味着要把整个程序重写一遍。那感觉就像在管理一个没有文件夹、所有文件都堆在桌面上的电脑找什么都得靠搜索改一点东西就可能引发连锁崩溃。面向对象编程就是为了解决这种混乱而生的。它的核心思想很简单把数据和操作这些数据的方法打包成一个整体这个整体就叫“对象”。比如一个“学生”对象它内部既有姓名、学号这些数据也有“计算绩点”、“打印信息”这些方法。外界想了解这个学生不用去翻一堆全局变量和函数直接跟这个“学生对象”打交道就行。这就像现实世界你想用手机不需要知道它内部CPU怎么运算、屏幕怎么发光你只需要知道按哪个键能打电话、划哪里能看视频。手机这个“对象”把复杂的内部细节“封装”起来只给你提供简单易用的接口。这种思维方式带来了三大核心武器封装、继承和多态。封装让你代码更安全、更模块化继承让你能复用已有的代码像搭积木一样构建新功能多态则让你的程序更灵活能应对未来的变化。接下来我们就从最基础的“类与对象”开始一步步拆解这三大利器。别担心我会用最“人话”的例子带你绕过我当年踩过的所有坑。2. 蓝图与实物理解类与对象面向对象编程里最基础、也最重要的两个概念就是“类”和“对象”。你可以这样理解类就像一张设计蓝图。它定义了某一类事物应该长什么样有什么属性能做什么事。比如“汽车”的设计蓝图规定了它有轮子、发动机、方向盘等属性以及启动、加速、刹车等方法。但蓝图本身不能开它只是一个抽象的描述。对象就是根据蓝图制造出来的具体实物。比如根据“汽车”蓝图生产出来的你家那台车车牌是“京A·12345”颜色是红色。这个具体的车就是一个“对象”。程序运行时我们操作的都是一个个具体的对象。2.1 定义一个类从设计学生开始让我们动手定义一个最简单的Student类。假设我们只需要记录学生的姓名和年龄。class Student { public: // 访问权限后面会细说 // 成员变量属性 string name; int age; // 成员函数方法 void introduce() { cout 大家好我叫 name 今年 age 岁。 endl; } };看这就是一个最简单的类定义。class是关键字Student是类名。大括号{}里面定义了类的“成员”name和age是成员变量也叫属性introduce是成员函数也叫方法。public:表示这些成员是公开的外部可以直接访问。注意类定义末尾的分号;千万不能省略这是很多新手容易忘记的地方编译器会报一堆你看不懂的错误。2.2 创建并使用对象让蓝图变成现实定义了蓝图现在我们来“造”几个学生int main() { // 创建对象实例化 Student stu1; // stu1 是一个Student类的对象 Student stu2; // 访问对象的成员使用点运算符 . stu1.name 张三; stu1.age 18; stu1.introduce(); // 输出大家好我叫张三今年18岁。 stu2.name 李四; stu2.age 20; stu2.introduce(); // 输出大家好我叫李四今年20岁。 return 0; }Student stu1;这行代码就叫做“实例化”它根据Student类这个蓝图在内存中创建了一个实实在在的stu1对象。stu1和stu2虽然都是Student类型但它们各自拥有独立的内存空间修改stu1的name不会影响stu2。这个“点运算符.”就是你和对象“对话”的方式用来访问它的内部成员。实操心得刚开始你可能会觉得“这不就是struct结构体加了个函数吗”没错在C中class和struct在底层非常相似。关键区别在于默认的访问权限class默认成员是private私有的而struct默认是public公共的。我们通常用class来强调数据封装用struct来组织纯粹的数据集合。但理解了这个你就抓住了类与对象的物理本质——它就是一块包含了数据和函数指针的自定义内存区域。3. 封装把秘密藏起来只留一扇门上面例子中我们把name和age都设为public这意味着在main函数里我可以随意写stu1.age -5;。这显然不合理年龄怎么能是负数呢这就是不封装的坏处数据完全暴露毫无安全性可言。封装的理念就是将对象的属性和实现细节隐藏起来仅对外公开有限的接口函数进行交互。就像你的银行账户余额属性是隐藏的你不能直接改但可以通过“取款”、“查询”这些公开接口方法来安全地操作它。3.1 访问权限控制public, private, protectedC用三个关键字来控制类成员的访问权限public公有类内、类外都可以自由访问。就像客厅谁都能进。private私有只有类自己的成员函数可以访问。就像你的卧室外人不能进。这是封装的基石类的数据成员通常设为private。protected受保护介于两者之间类内和派生类子类可以访问。这个我们讲到继承时再说。我们来改造一下Student类实现封装class Student { private: // 将数据隐藏起来 string name; int age; public: // 对外提供操作接口 // 设置年龄可以加入合法性检查 void setAge(int newAge) { if (newAge 0 newAge 150) { // 简单的数据验证 age newAge; } else { cout 年龄输入不合法 endl; } } // 获取年龄 int getAge() { return age; } // 设置姓名 void setName(string newName) { name newName; } // 获取姓名 string getName() { return name; } void introduce() { cout 大家好我叫 name 今年 age 岁。 endl; } }; int main() { Student stu; // stu.age 18; // 错误age是private无法直接访问 stu.setName(王五); stu.setAge(18); // 通过公共接口安全地设置 // stu.setAge(-5); // 这里会被接口内的检查拦截年龄不会被修改 cout stu.getName() 的年龄是 stu.getAge() endl; // 通过接口获取 stu.introduce(); return 0; }看到了吗现在main函数无法直接触碰name和age。所有操作都必须通过setAge、getAge这些公开的成员函数。在setAge函数里我们加入了简单的数据验证确保了数据的有效性。这就是封装的力量数据安全了代码也更清晰了。你想修改学生信息去找对应的set函数。你想获取信息去找对应的get函数。这种函数就是我们常说的“Getter”和“Setter”。3.2 构造函数与析构函数对象的生与死对象被创建时需要初始化对象生命结束时可能需要清理资源比如关闭文件、释放内存。这两个特殊时刻由两个特殊的成员函数管理构造函数在对象创建时自动调用用于初始化对象。函数名与类名相同没有返回类型。析构函数在对象销毁时自动调用用于清理工作。函数名是类名前加一个波浪线~没有参数和返回类型。class Student { private: string name; int age; int* scores; // 假设我们用一个动态数组来存储多门课成绩 public: // 1. 默认构造函数无参 Student() { name 未知; age 0; scores nullptr; // 初始化为空指针 cout 调用默认构造函数 endl; } // 2. 带参数的构造函数重载 Student(string stuName, int stuAge) { name stuName; age stuAge; scores new int[5]; // 为5门课成绩分配堆内存 for(int i 0; i 5; i) scores[i] 0; // 初始化为0 cout 调用带参构造函数姓名为 name endl; } // 3. 析构函数 ~Student() { if (scores ! nullptr) { delete[] scores; // 释放动态分配的内存防止内存泄漏 scores nullptr; } cout 析构函数被调用清理了 name 的资源 endl; } // ... 其他的Getter、Setter和introduce函数 }; int main() { Student stu1; // 调用默认构造函数 Student stu2(赵六, 19); // 调用带参构造函数 { Student stu3(临时学生, 20); // 在作用域内创建 } // 作用域结束stu3被销毁自动调用析构函数 cout main函数即将结束 endl; return 0; } // main结束stu1和stu2被销毁自动调用析构函数运行这段代码你会清晰地看到构造函数和析构函数被调用的顺序。这是C管理资源生命周期的核心机制。特别是当类中有指针成员并指向动态分配的内存堆内存时必须在析构函数中释放否则会造成“内存泄漏”。这是新手常踩的大坑注意事项如果你没写任何构造函数编译器会生成一个什么都不做的默认构造函数。但只要你定义了一个带参数的构造函数编译器就不再提供默认构造函数。此时如果你写Student stu;就会编译错误。一个好的习惯是总是显式地定义一个无参的默认构造函数。构造函数可以重载根据参数不同执行不同的初始化逻辑。析构函数只有一个不能重载。对于管理资源的类编写析构函数是必须的。4. 继承站在巨人的肩膀上写代码想象一下你要开发一个游戏里面有Enemy敌人这个类它有health血量、attack()攻击等方法。现在你需要两种具体的敌人Goblin哥布林和Dragon龙。龙会飞会喷火哥布林会偷东西。如果分别从头写这两个类你会写很多重复的代码比如health和基础的attack()。继承就是为了解决代码复用的问题。它允许我们定义一个类派生类/子类基于另一个类基类/父类。子类会自动获得父类的所有属性和方法除了构造函数、析构函数和私有成员并且可以添加自己特有的属性和方法或者修改父类的方法。4.1 基础的继承语法// 基类父类 class Enemy { protected: // 让子类可以访问 int health; int attackPower; public: Enemy(int h, int ap) : health(h), attackPower(ap) {} // 构造函数初始化列表 void takeDamage(int damage) { health - damage; if (health 0) health 0; cout 敌人受到 damage 点伤害剩余血量 health endl; } virtual void attack() { // virtual关键字为多态做准备 cout 敌人发动了普通攻击造成 attackPower 点伤害 endl; } }; // 派生类子类Goblin 公有继承自Enemy class Goblin : public Enemy { private: int stealPower; // 哥布林特有的属性偷窃能力 public: // 子类构造函数需要调用父类构造函数初始化继承来的成员 Goblin(int h, int ap, int sp) : Enemy(h, ap), stealPower(sp) { cout 一只哥布林出现了 endl; } // 重写父类的attack方法 void attack() override { // override关键字C11明确表示重写提高可读性 cout 哥布林用短刀砍击造成 attackPower 点伤害 endl; } // 子类特有的方法 void steal() { cout 哥布林偷走了你的 stealPower 枚金币 endl; } }; // 派生类子类Dragon class Dragon : public Enemy { public: Dragon(int h, int ap) : Enemy(h, ap) { cout 一条巨龙降临了 endl; } void attack() override { cout 巨龙喷出炽热的火焰造成 attackPower * 2 点范围伤害 endl; // 伤害加倍 } void fly() { cout 巨龙腾空而起 endl; } }; int main() { Goblin g(50, 10, 5); // 血量50攻击力10偷窃力5 Dragon d(200, 25); // 血量200攻击力25 g.attack(); // 调用Goblin自己的attack g.steal(); // 调用Goblin特有的steal g.takeDamage(30); // 调用继承自Enemy的takeDamage d.attack(); // 调用Dragon自己的attack d.fly(); // 调用Dragon特有的fly d.takeDamage(100); // 调用继承自Enemy的takeDamage return 0; }语法class Goblin : public Enemy表示Goblin类以公有继承的方式继承Enemy类。这意味着Enemy中的public成员在Goblin中仍然是public。Enemy中的protected成员在Goblin中仍然是protected。Enemy中的private成员在Goblin中不可直接访问但可以通过父类的public或protected方法间接访问。初始化列表 Enemy(h, ap)在子类构造函数中至关重要它用来显式调用父类的构造函数初始化从父类继承来的成员。子类无法直接初始化父类的私有成员。4.2 继承中的构造与析构顺序当创建一个子类对象时调用父类的构造函数。调用子类成员对象的构造函数如果有的话。调用子类的构造函数体。 析构的顺序则完全相反子类析构 - 子类成员析构 - 父类析构。这个顺序是自动的理解它有助于你调试和理解对象的生命周期。常见问题“我该用公有继承、保护继承还是私有继承”公有继承最常用表示“是一个”的关系。Goblin是一个Enemy。这是逻辑上的继承。保护/私有继承很少用表示“以...实现”的关系。更多是技术上的复用而非逻辑上的派生。对于新手99%的情况使用公有继承就够了。如果你不确定那就用公有继承。5. 多态同一操作不同行为多态是面向对象最精妙、也最强大的特性。字面意思是“多种形态”。在C中多态通常指通过基类的指针或引用调用一个虚函数时实际执行的是派生类中重写的那个函数版本。这有什么用回到游戏的例子。假设我们有一个Enemy指针数组里面装着各种实际是Goblin或Dragon的敌人。在游戏主循环中我们想让他们全部攻击玩家。如果没有多态我们需要判断每个指针具体指向哪种敌人然后分别调用不同的attack函数代码会非常冗长。有了多态我们可以这样写int main() { // 创建一个基类指针数组指向不同的派生类对象 Enemy* enemies[3]; enemies[0] new Goblin(50, 10, 5); enemies[1] new Dragon(200, 25); enemies[2] new Goblin(40, 8, 3); cout \n--- 敌人全体攻击--- endl; for (int i 0; i 3; i) { enemies[i]-attack(); // 关键在这里 // 虽然enemies[i]是Enemy*类型但它实际指向Goblin或Dragon对象 // 因此这里调用的是各自派生类重写的attack版本 } cout \n--- 战斗结束清理战场 --- endl; for (int i 0; i 3; i) { delete enemies[i]; // 记得释放内存 } return 0; }输出会是一只哥布林出现了 一条巨龙降临了 一只哥布林出现了 --- 敌人全体攻击--- 哥布林用短刀砍击造成10点伤害 巨龙喷出炽热的火焰造成50点范围伤害 哥布林用短刀砍击造成8点伤害 --- 战斗结束清理战场 ---神奇吗enemies[i]在编译时是Enemy*类型编译器只知道它指向一个Enemy。但在运行时它才知道这个指针实际指向的是Goblin还是Dragon并调用对应类的attack方法。这就是“运行时多态”或“动态绑定”。5.1 实现多态的关键虚函数要实现上述效果有两个必要条件基类中的函数必须声明为virtual虚函数。就像我们之前在Enemy::attack()前加的那样。派生类必须重写这个虚函数。最好加上override关键字C11这样如果你不小心写错了函数签名参数或返回类型编译器会报错这是一个很好的安全措施。虚函数表这是多态的实现原理。每个有虚函数的类或从有虚函数的类派生而来都有一个隐藏的成员——虚函数表指针vptr它指向一个虚函数表vtable。表中存放了该类所有虚函数的实际地址。当通过基类指针调用虚函数时程序会通过vptr找到vtable再找到正确的函数地址进行调用。这个过程发生在运行时因此有少量性能开销但对于现代计算机这点开销在大多数场景下完全可以接受。5.2 纯虚函数与抽象类有时候基类中的某个函数在逻辑上无法给出一个有意义的实现。比如Shape形状基类有一个draw()绘制方法。但“形状”本身太抽象了不知道怎么画。这时我们可以将它声明为纯虚函数。class Shape { public: virtual void draw() 0; // 0 表示这是一个纯虚函数 virtual double area() 0; // 计算面积也是纯虚函数 // 可以包含非虚函数和成员变量 void printInfo() { cout 这是一个形状。 endl; } };包含纯虚函数的类叫做抽象类。抽象类不能实例化对象即你不能写Shape s;。它的作用就是作为一个接口强制要求它的所有派生类除非也是抽象类必须实现这些纯虚函数。class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} // 必须实现基类的所有纯虚函数否则Circle也会变成抽象类 void draw() override { cout 绘制一个半径为 radius 的圆形。 endl; } double area() override { return 3.14159 * radius * radius; } }; class Rectangle : public Shape { private: double width, height; public: Rectangle(double w, double h) : width(w), height(h) {} void draw() override { cout 绘制一个 width x height 的矩形。 endl; } double area() override { return width * height; } }; int main() { // Shape s; // 错误Shape是抽象类不能创建对象 Shape* shapes[2]; shapes[0] new Circle(5.0); shapes[1] new Rectangle(4.0, 6.0); for (int i 0; i 2; i) { shapes[i]-draw(); cout 面积是 shapes[i]-area() endl; shapes[i]-printInfo(); // 调用继承来的非虚函数 delete shapes[i]; } return 0; }抽象类和纯虚函数定义了接口规范是实现多态和大型项目架构设计的强大工具。它确保了所有派生类都遵循同一套操作标准。6. 面向对象实战设计一个简单的银行账户系统让我们把封装、继承、多态综合运用起来设计一个迷你银行账户系统。6.1 基类 Account账户首先定义一个抽象的Account基类规定所有账户都必须有存款、取款、查询余额的功能。#include iostream #include string using namespace std; // 抽象基类账户 class Account { protected: string accountNumber; // 账号 string ownerName; // 户主名 double balance; // 余额 public: Account(string accNum, string name, double initBalance) : accountNumber(accNum), ownerName(name), balance(initBalance) {} virtual ~Account() {} // 虚析构函数确保派生类对象被基类指针删除时能正确调用派生类析构函数 // 纯虚函数定义接口 virtual void deposit(double amount) 0; // 存款 virtual bool withdraw(double amount) 0; // 取款返回是否成功 virtual void display() const 0; // 显示账户信息const表示不修改对象 // 非虚函数通用功能 double getBalance() const { return balance; } string getAccountNumber() const { return accountNumber; } };6.2 派生类 SavingsAccount储蓄账户储蓄账户可能有最低余额限制取款不能低于这个限制。class SavingsAccount : public Account { private: double minBalance; // 最低余额要求 public: SavingsAccount(string accNum, string name, double initBalance, double minBal) : Account(accNum, name, initBalance), minBalance(minBal) { if (initBalance minBal) { cout 警告初始余额低于最低要求 endl; } } void deposit(double amount) override { if (amount 0) { balance amount; cout ownerName 的储蓄账户存入 amount 元。当前余额 balance endl; } else { cout 存款金额必须为正数 endl; } } bool withdraw(double amount) override { if (amount 0 (balance - amount) minBalance) { balance - amount; cout ownerName 的储蓄账户取款 amount 元。当前余额 balance endl; return true; } else { cout 取款失败金额无效或余额将低于最低要求( minBalance 元)。 endl; return false; } } void display() const override { cout --- 储蓄账户信息 --- endl; cout 账号 accountNumber endl; cout 户主 ownerName endl; cout 余额 balance 元 endl; cout 最低余额要求 minBalance 元 endl; } };6.3 派生类 CheckingAccount支票账户支票账户可能没有最低余额限制但每次取款可能会收取小额手续费。class CheckingAccount : public Account { private: double transactionFee; // 每笔取款手续费 public: CheckingAccount(string accNum, string name, double initBalance, double fee) : Account(accNum, name, initBalance), transactionFee(fee) {} void deposit(double amount) override { if (amount 0) { balance amount; cout ownerName 的支票账户存入 amount 元。当前余额 balance endl; } else { cout 存款金额必须为正数 endl; } } bool withdraw(double amount) override { double totalDeduction amount transactionFee; if (amount 0 balance totalDeduction) { balance - totalDeduction; cout ownerName 的支票账户取款 amount 元手续费 transactionFee; cout 元。当前余额 balance endl; return true; } else { cout 取款失败金额无效或余额不足含手续费。 endl; return false; } } void display() const override { cout --- 支票账户信息 --- endl; cout 账号 accountNumber endl; cout 户主 ownerName endl; cout 余额 balance 元 endl; cout 取款手续费 transactionFee 元/笔 endl; } };6.4 使用多态管理多种账户现在我们可以用基类指针来统一管理不同类型的账户。int main() { // 创建不同类型的账户对象用基类指针指向它们 Account* myAccounts[3]; myAccounts[0] new SavingsAccount(SA001, 张三, 1000.0, 100.0); // 最低余额100 myAccounts[1] new CheckingAccount(CA001, 李四, 500.0, 2.0); // 手续费2元 myAccounts[2] new SavingsAccount(SA002, 王五, 2000.0, 500.0); cout \n 初始账户状态 endl; for (int i 0; i 3; i) { myAccounts[i]-display(); cout endl; } cout \n 进行一系列交易 endl; // 多态的威力无需知道具体是哪种账户统一调用接口 myAccounts[0]-deposit(500); // 张三存钱 myAccounts[0]-withdraw(2000); // 张三取钱会失败低于最低余额 myAccounts[0]-withdraw(800); // 张三取钱成功 myAccounts[1]-withdraw(100); // 李四取钱含手续费 myAccounts[1]-deposit(300); // 李四存钱 myAccounts[2]-withdraw(1600); // 王五取钱 cout \n 最终账户状态 endl; for (int i 0; i 3; i) { myAccounts[i]-display(); cout endl; } // 清理内存 for (int i 0; i 3; i) { delete myAccounts[i]; } return 0; }这个例子展示了面向对象的核心优势封装每个账户的内部数据余额、最低限额、手续费被保护起来只能通过规定的deposit、withdraw方法操作保证了数据安全。继承SavingsAccount和CheckingAccount复用Account的代码账号、户主名、余额等只需关注自己特有的逻辑最低余额、手续费。多态main函数中的数组和循环代码完全不用关心具体是哪种账户。无论是储蓄账户还是支票账户都通过统一的Account指针接口来操作。如果要新增一种“信用卡账户”只需要从Account派生一个新类并实现那几个纯虚函数main函数里的管理代码一行都不用改这就是“对扩展开放对修改关闭”的开闭原则的体现。7. 进阶话题与避坑指南掌握了三大特性你已经入门了面向对象。但在实际项目中还有一些细节和陷阱需要特别注意。7.1 对象的拷贝深拷贝与浅拷贝这是C面试的经典问题也是实际开发中Bug的高发区。当你用一个对象初始化另一个对象或者将对象作为参数传值给函数时会发生“拷贝”。class SimpleClass { public: int* data; SimpleClass(int value) { data new int(value); // 在堆上分配内存 } ~SimpleClass() { delete data; // 释放内存 } }; int main() { SimpleClass obj1(10); SimpleClass obj2 obj1; // 默认拷贝构造浅拷贝 // 问题来了obj1.data 和 obj2.data 指向同一块堆内存 // main函数结束时obj2先析构delete了那块内存 // 然后obj1再析构试图delete同一块已经释放的内存 - 程序崩溃 }默认的拷贝行为是“浅拷贝”即只复制指针的值地址而不复制指针指向的内容。这会导致多个对象共享同一资源析构时重复释放引发未定义行为通常是崩溃。解决方案自定义拷贝构造函数和拷贝赋值运算符实现“深拷贝”。class SafeClass { public: int* data; int size; SafeClass(int value) : size(1) { data new int(value); } // 1. 拷贝构造函数 SafeClass(const SafeClass other) : size(other.size) { data new int[size]; for(int i 0; i size; i) { data[i] other.data[i]; // 复制内容而不是地址 } cout 调用了拷贝构造函数 endl; } // 2. 拷贝赋值运算符 SafeClass operator(const SafeClass other) { if (this ! other) { // 防止自我赋值 delete[] data; // 释放原有资源 size other.size; data new int[size]; for(int i 0; i size; i) { data[i] other.data[i]; } } cout 调用了拷贝赋值运算符 endl; return *this; } ~SafeClass() { delete[] data; } };经验法则如果你的类管理了动态分配的内存、文件句柄、网络连接等资源那么你很可能需要自己定义拷贝构造函数、拷贝赋值运算符和析构函数。这被称为“三法则”。在C11之后还有“五法则”加上移动构造函数和移动赋值运算符。对于新手一个简单的规避策略是禁用拷贝使用unique_ptr等智能指针管理资源。class NonCopyable { public: NonCopyable() default; // 删除拷贝构造和拷贝赋值 NonCopyable(const NonCopyable) delete; NonCopyable operator(const NonCopyable) delete; };7.2 友元打破封装的特权封装是把数据藏起来但有时候某些特定的外部函数或类需要直接访问这些私有数据。比如一个打印函数需要直接读取对象的私有成员来格式化输出。这时可以使用friend友元关键字。class MyClass { private: int secret; public: MyClass(int s) : secret(s) {} // 声明一个全局函数为友元 friend void printSecret(const MyClass obj); // 声明另一个类为友元 friend class FriendClass; }; void printSecret(const MyClass obj) { // 这个函数不是MyClass的成员但可以访问其私有成员 cout The secret is: obj.secret endl; } class FriendClass { public: void peek(const MyClass obj) { cout FriendClass sees: obj.secret endl; } };慎用友元友元破坏了封装性降低了代码的可维护性。它应该被视为一种例外手段而不是常规工具。在大多数情况下通过提供良好的公有接口Getter/Setter来访问数据是更好的选择。7.3 静态成员属于类本身的成员有时候我们需要一些数据或函数属于整个类而不是类的某个对象。比如统计一共创建了多少个某个类的对象。这就需要static静态成员。class Counter { private: static int count; // 静态成员变量声明记录对象总数 int id; public: Counter() { id count; // 每创建一个对象count加1并赋予id cout 创建了第 id 个Counter对象。 endl; } ~Counter() { cout 销毁了第 id 个Counter对象。 endl; --count; } static int getTotalCount() { // 静态成员函数 return count; } }; // 静态成员变量必须在类外定义和初始化 int Counter::count 0; int main() { cout 当前对象数 Counter::getTotalCount() endl; // 通过类名直接访问静态函数 Counter c1; Counter c2; { Counter c3; cout 当前对象数 Counter::getTotalCount() endl; } // c3离开作用域被销毁 cout 当前对象数 Counter::getTotalCount() endl; return 0; }关键点静态成员变量不属于任何对象它在所有对象间共享只有一份内存。静态成员变量必须在类外通常是.cpp文件单独定义和初始化格式为类型 类名::变量名 值;。静态成员函数没有this指针因此它不能访问类的非静态成员因为非静态成员需要通过this指针来访问。静态成员函数通常用于操作静态成员变量。面向对象编程是一个庞大而精深的体系这一篇我们只是打下了最核心的基础。从把数据和函数打包成“对象”到用“封装”保护内部状态用“继承”复用已有代码再用“多态”写出灵活而强大的程序这条路径是理解现代软件设计的基石。我建议你多动手把文中的例子敲一遍然后尝试设计一些自己的小类比如Book、Library、Vehicle、Car。遇到问题就去查、去调试这才是学习编程最快的方式。当你开始习惯用对象的视角去思考问题时你会发现很多复杂的系统设计突然变得清晰起来。