小狗不用重学吃喝——白拿"动物套餐";喊一声"说话",狗汪汪、猫喵喵。
所有动物都会吃、会睡、有名字。如果给狗、猫、鹦鹉各写一个类,这些共同能力就要抄三遍——太浪费!继承:先造一个"动物"基类把公共部分写好,狗和猫用一句 class Dog : public Animal 白拿这些能力,只补自己特有的(狗会汪、猫会喵)。多态:饲养员对任何宠物都只喊一句"说话!"——狗回"汪汪"、猫回"喵",同一句指令,不同行为,由对象的"真实身份"决定。
class Dog : public Animal);方法重写 override(子类同名方法覆盖父类版本);重载(同类中同名不同参数,另一回事);virtual(虚函数:运行时按真实对象选版本);多态(同一接口,不同表现)。class Dog : public Animal,知道子类白拿了什么、要自己补什么。// 基类:所有动物共有的能力,只写一遍 class Animal { public: string name; Animal(string n) { name = n; } // 基类构造器 void eat() { cout << name << " 在吃" << endl; } void sleep() { cout << name << " 在睡" << endl; } }; // 子类:: public Animal → 白拿 name / eat() / sleep() class Dog : public Animal { public: Dog(string n) : Animal(n) {} // 必须调用基类构造器填 name void bark() { cout << name << ": 汪汪!" << endl; } // 特有 };
三条要点:
class 子类 : public 基类——冒号后写"谁是我爹"。Dog 时先执行 Animal(n)(父先出生),再执行 Dog 自己的部分——Dog(string n) : Animal(n) {} 里的 : Animal(n) 就是"先叫爸爸初始化"。: Animal(n),name 会是什么状态?③ 想再加一个 Parrot(鹦鹉)类,eat/sleep 要重新写吗?class Parrot : public Animal 直接白拿,只写特有部分(比如 squawk())——继承就是"不重复劳动"。基类给所有动物一个默认的"说话":发出"???"。但狗、猫都有自己的叫声——子类同名写一个方法,就把父类的版本"换掉"了,这叫方法重写(override,覆盖):
// 基类:默认行为 class Animal { public: string name; Animal(string n) { name = n; } string speak() { return "???"; } // 默认:说不清 }; class Dog : public Animal { public: Dog(string n) : Animal(n) {} string speak() { return "汪汪"; } // 重写:同名,狗换掉父类版本 }; class Cat : public Animal { public: Cat(string n) : Animal(n) {} string speak() { return "喵"; } // 重写:猫换掉父类版本 };
注意:重写不是新增功能,而是换版本——子类对象调 speak() 时,执行的是子类自己的。
顺便分清它的"同名不同姓"表亲——重载(overload):
// 这是"重载",不是重写:同一个类里,参数不同 void show() {} // 无参数版本 void show(int x) {} // 带参数版本——两个共存,按实参选择
Dog d("小黄"); cout << d.speak(); 输出什么?② 若 Dog 里再写一个 string speak(int n)(返回"汪 n 次"),它和父类的 speak() 是"重写"还是"重载"关系?③ 一句话:重写看什么、重载看什么?speak(int n) 与父类 speak() 参数不同、不是覆盖关系——这是 Dog 里多出的一个重载版本;而 Dog 的无参 speak() 对父类才是重写。
③ 重写看"父子 + 同名同参"(换版本);重载看"同类 + 同名不同参"(加版本)。先看一个坑。假设 pets 数组存的是 Animal*(基类指针):
// 没加 virtual 时: Dog d("小黄"); Animal* p = &d; // 基类指针指向狗 cout << p->speak() << endl; // 输出 ??? —— 执行的是 Animal 的版本!
为什么?没加 virtual 时,编译器按"指针的类型"(Animal)在编译期就选好了版本——它只看指针穿的是"Animal 工装",不管里面装的其实是狗。这叫静态绑定。
给基类方法加上 virtual 后,改成运行时看对象的真实身份再选版本——这叫动态绑定,也就是多态:
// 加了 virtual: class Animal { public: virtual string speak() { return "???"; } // virtual:听真实身份 }; class Dog : public Animal { public: string speak() override { return "汪汪"; } // override 是"我确实想重写"的声明 }; Dog d("小黄"); Animal* p = &d; cout << p->speak() << endl; // 输出 汪汪 —— 运行时发现是 Dog,执行 Dog 版
比喻:指针是工装。没 virtual = "看工装办事"(你穿 Animal 工装就按 Animal 对待);有 virtual = "看人下菜"(不管你穿什么工装,你是狗就汪汪)。
Animal* q = new Cat("小美"); cout << q->speak() << endl; delete q;——有 virtual 时输出什么?没 virtual 呢?delete 为什么安全?(提示:基类析构函数也应 virtual,本课记结论即可)delete q 安全是因为 Animal 的析构函数也标了 virtual——通过基类指针删子类对象时,会"拆到根"(先调子类析构再调基类析构),不会"拆一半"泄漏。考试记结论:用基类指针 delete 子类对象,基类析构必须 virtual。override 关键字提醒自己"我确实在重写",写错签名编译器会报错。场景:宠物店来了 3 只宠物(狗、猫、狗)。饲养员只会喊一句 show()("报上名来 + 叫一声"),不想分别写"如果是狗就……如果是猫就……"。用多态,一个循环搞定:
show(),输出应该是什么?如果明天来了只鹦鹉(新加 Parrot 类,重写 speak 返回"嘎!"),for 循环那几行要改吗?new Parrot("小绿") 即可,for 循环 0 行不用改——它只会喊 show(),鹦鹉的"嘎!"由 Parrot 自己的 speak() 负责。这就是多态的美:新类型自动接入旧系统(专业话:对扩展开放、对修改关闭)。// 输出:小黄: 汪汪 / 小美: 喵 / 小黑: 汪汪 #include <iostream> #include <string> using namespace std; class Animal { public: string name; Animal(string n) { name = n; } virtual string speak() { return "???"; } void show() { cout << name << ": " << speak() << endl; } virtual ~Animal() {} // 虚析构:基类指针 delete 才安全 }; class Dog : public Animal { public: Dog(string n) : Animal(n) {} string speak() override { return "汪汪"; } }; class Cat : public Animal { public: Cat(string n) : Animal(n) {} string speak() override { return "喵"; } }; int main() { Animal* pets[3] = { new Dog("小黄"), new Cat("小美"), new Dog("小黑") }; for (int i = 0; i < 3; i++) { pets[i]->show(); // 统一指令,各叫各的 delete pets[i]; // 逐个释放 } return 0; }
class Dog : public Animal 里的 public 是什么意思?Dog "白拿"了 Animal 的哪些东西?public 表示公有继承:基类 public 的成员在子类里保持 public(还有 protected/private 两种继承方式,本课记 public 即可)。Dog 白拿了 name 属性、eat()、sleep()、speak() 的默认版本,只需补自己特有的 bark() 或重写 speak()。Animal* p = new Dog("小黄"); cout << p->speak() << endl; delete p;——speak 有 virtual 时输出什么?没有 virtual 呢?delete p 为什么安全?: public 基类 白拿公共能力(构造器要先调基类);重写 = 同名换版本;virtual = 看真实身份办事——基类指针指向谁就执行谁的版本,这就是多态。