菜谱只是张纸,厨师才真会做菜——把"数据 + 操作"打包成一盒。
以前写程序像按菜谱炒菜:数据(食材)散在厨房各处,函数(步骤)一个个调用——这叫面向过程。现在想象一位厨师:他有属性(名字、工龄),会做事(切菜、炒菜、上菜)——数据和操作被打包在一个人身上,这叫面向对象。"类"(class)是设计图纸,"对象"(object)是按图纸造出的真房子:同一张图纸能盖很多栋房子,每栋都有自己独立的房间(数据)。
管理 100 个游戏角色,两种写法对比:
// 面向过程:数据是"表",函数靠参数 i 知道在操作谁 string name[100]; int hp[100]; void attack(int i, int j) { hp[j] -= 10; } // i 打 j,靠参数 i 暗示"是谁" // 面向对象:每个角色是一个对象,数据和能力打包在一起 class Player { string name; // 属性:我叫什么 int hp; // 属性:我有多少血 void attack(Player &other) { other.hp -= 10; } // 方法:我打他 void heal() { hp += 20; } // 方法:我回血 };
面向对象里,"谁"不用传:a.attack(b) 读起来就是"小明打小强",数据和动作天然绑在一起。
a.hp 属于谁?③ 同一张"图纸"(类)能造出几个对象?a.hp 是 a 自己的血量,b 的血量是 b.hp,各住各的房。
③ 任意多个:类是图纸,对象是房子;Player a, b, c; 就是按同一张图纸盖了 3 栋,每栋的 name/hp 独立。C++ 里 struct 是"纯数据袋子"(只有变量),class 是"数据 + 操作的完整包装"。进化过程:
// 老式:struct 只有数据,操作散在外面 struct Student { string name; int score; }; void study(Student &s) { s.score += 1; } // 操作在外面,靠参数找 s // 新式:class 把数据和方法都装进去 class Student { string name; // 属性 int score; // 属性 void study() { score += 1; } // 方法:不用传"是谁" string who() { return name; } // 方法 // 构造器:和类同名、无返回值,对象"出生"时自动调用 Student(string n, int s) { name = n; score = s; } };
用构造器造对象,就像"出生时填好档案":
Student a("小明", 90); // a 出生:name=小明, score=90(构造器自动执行)
Student b("小红", 85); // b 出生:name=小红, score=85
b.study(); // b 的学习能力直接调用,不用告诉它"b 是谁"
Student b("小红", 85); 之后连续调用两次 b.study();,此时 b.score 等于多少?构造器在这过程中被调用了几次?房子装修时,水管电线都砌进墙里,外面只留一个开关。你按开关就能开灯,完全不用知道电线怎么走——这就是封装:内部零件藏起来(private),只留接口给外人用(public)。
// 银行账户:余额藏在"墙里" class BankAccount { int balance; // private(class 默认):藏在墙里的水管 public: BankAccount(int x) { balance = x; } // 出生时设初始余额 void deposit(int x) { balance += x; } // 接口:存钱 void withdraw(int x) { if (x <= balance) balance -= x; // 规则在接口里把关 } int query() { return balance; } // 接口:查余额 };
为什么不让 balance 直接 public?因为谁都能写 acc.balance = -100——余额变成负数,银行直接破产!把 balance 藏起来后,外人只能走 deposit / withdraw 这两个"开关",而规则(不能取超过余额)就写在开关里面。
acc.balance = -10000; 会怎样?② balance=50 时调用 withdraw(200),之后 query() 返回几?③ 一句话:封装把"规则"放在哪里把关?-> 访问,知道何时 delete。造对象有两个"地方":
Student a("小明", 90);——函数结束时 a 自动消失,省心。Student* p = new Student("小红", 85);——对象在"仓库"(堆)里,函数结束不自动消失,必须自己 delete p; 释放,否则"漏房"(内存泄漏)。p 本身只是地址(一张写着房子位置的纸条)。想通过 p 用对象里的东西,就用 ->:"顺着地址进去"——p->study() 等价于"(*p).study()"。
生命周期口诀:new 出生 → 使用 → delete 归还 → 别再用。delete 之后还碰 p,p 就成了野指针(纸条还在,房子已拆)。
Student* p = new Student("小红", 85);
p->study(); // 顺着地址进去,调方法
cout << p->who() << endl; // 小红
delete p; // 拆房归还
p->study(); // ← 危险:野指针!房子拆了还进去住
delete p; 删掉,程序会出什么问题?p->study(); 是野指针使用(delete 之后)。若删掉 delete p;,对象永远不会被释放——每造一个都漏一间房,程序越跑占的内存越多(内存泄漏),但当下不报错,更难发现。// 输出:balance: 120 / balance: 100 #include <iostream> #include <string> using namespace std; class BankAccount { int balance; // private:藏起来 public: BankAccount(int x) { balance = x; } // 构造器 void deposit(int x) { balance += x; } void withdraw(int x) { if (x <= balance) balance -= x; } int query() { return balance; } string report() { return "balance: " + to_string(balance); } }; int main() { BankAccount acc(100); // 栈对象:出生 balance=100 acc.deposit(50); // 150 acc.withdraw(30); // 120 acc.withdraw(200); // 200 > 120,规则挡住,不变 cout << acc.report() << endl; // balance: 120 BankAccount* p = new BankAccount(10); // 堆对象 p->deposit(90); // 100 cout << p->report() << endl; // balance: 100 delete p; // 释放,之后别再碰 p return 0; }
Player,对象 Player a("小明", 100);——a 是按 Player 图纸造的一个具体角色。BankAccount acc(50); 里的构造器起什么作用?如果这个类没写构造器,balance 会是什么状态?acc.balance = -10000;——绕过所有检查,余额直接负一万,银行破产;private 时这行连编译都过不了。Student* p = new Student("小红", 85); delete p; p->study();——最后一行为什么危险?"野指针"像什么?p->study() 是"房子拆了还进去住"(use-after-free),可能读到垃圾数据甚至崩溃。野指针 = 一张地址还写着、但房子已拆的纸条。