C++面向对象编程|零基础入门实战|类与对象继承多态案例解析

C++面向对象编程|零基础入门实战|类与对象继承多态案例解析 一

文章目录CloseOpen

从“造房子”学类与对象:零基础也能秒懂的核心逻辑

用生活例子给概念“卸妆”:类是“图纸”,对象是“房子”

很多教程一上来就讲“类是具有相同属性和行为的对象的集合”,这种定义对新手来说跟天书没区别。其实你只要记住一句话:类是“图纸”,对象是“根据图纸造出来的房子”。比如你想设计一款学生信息管理系统,“学生”就是一个类——它需要包含姓名、学号(这是属性,也就是数据),还需要能打印信息、修改成绩(这是行为,也就是函数)。而具体的“张三(学号2023001)”“李四(学号2023002)”,就是根据“学生类”这个图纸造出来的“房子”,也就是对象。

我去年带一个完全没接触过编程的朋友学这个概念时,他一开始死活分不清“类”和“对象”。后来我拿他的手机举例:“你手机里的‘联系人’功能,是不是有个添加联系人的界面?那个界面里让你填‘姓名’‘电话’的地方,就是‘联系人类’的模板;而你填进去的‘妈妈(电话138xxxx8888)’,就是一个具体的‘联系人对象’。”他当场拍大腿:“早这么说我不就懂了吗!”所以你看,复杂概念用生活例子一对照,立刻就清晰了。

零基础实战:3步写出第一个面向对象程序(附可直接运行代码)

光懂概念没用,编程这东西必须上手练。我敢打赌,你跟着下面的步骤写完代码,对“类与对象”的理解会直接上一个台阶。咱们就做个最简单的“学生信息卡片”程序,功能是:存一个学生的姓名和年龄,然后打印出来。

第一步:定义“学生类”——画好图纸

类的基本结构很简单,就像给房子画图纸,要说明白“这房子有什么(属性)”和“这房子能做什么(行为)”。在C++里,我们用class关键字定义类,里面放成员变量(属性)和成员函数(行为)。比如这样:

#include 
#include 

using namespace std;

// 定义学生类(图纸)

class Student {

public: // 访问权限:public表示外部可以访问

// 成员变量(属性):学生的特征

string name; // 姓名

int age; // 年龄

// 成员函数(行为):学生能做的事

void printInfo() { // 打印学生信息

cout << "姓名:" << name << ",年龄:" << age << endl;

}

};

这里有个新手常踩的坑:忘记写public:。如果不写,C++默认成员是private(私有)的,外面的代码调不动,就像你画了图纸却把门锁了,别人用不了。我第一次写类时就犯过这错,编译报错“‘name’ is private”,研究半天才发现少了这行。

第二步:创建“对象”——按图纸造房子

有了图纸,接下来就要造房子了。在C++里,创建对象很简单,就像声明变量一样:Student stu;,这里的stu就是一个具体的学生对象。然后给它的属性赋值,调用它的函数:

int main() {

// 创建对象(按图纸造房子)

Student stu; // stu就是一个具体的学生对象

// 给对象的属性赋值

stu.name = "小明";

stu.age = 18;

// 调用对象的成员函数

stu.printInfo(); // 输出:姓名:小明,年龄:18

return 0;

}

你看,这就像你根据“联系人模板”填了一个具体联系人,然后点“查看详情”——对象就是这么用的。现在把两段代码合起来,在编译器里运行(推荐用Dev-C++或VS Code,新手友好),就能看到结果了。

第三步:优化代码——从“能用”到“好用”

上面的代码能跑,但不够“面向对象”。真实开发中,我们很少直接给成员变量赋值,而是用“构造函数”初始化对象,用“setter/getter”函数控制属性访问(比如限制年龄不能为负数)。这就像你买房时,开发商不会让你直接改承重墙,而是提供“装修服务”帮你调整。

比如我们给Student类加个构造函数(跟类名一样的函数,用于初始化对象)和setAge函数(检查年龄是否合法):

class Student {

public:

string name;

int age;

// 构造函数:创建对象时自动调用,用于初始化

Student(string n, int a) {

name = n;

// 调用setAge检查年龄合法性

setAge(a); // 这里用setAge而不是直接age=a,确保年龄合法

}

// 设置年龄,带合法性检查(这就是面向对象的封装思想)

void setAge(int a) {

if (a 150) { // 年龄不可能为负或超过150

cout << "年龄不合法,默认设为18" << endl;

age = 18;

} else {

age = a;

}

}

void printInfo() {

cout << "姓名:" << name << ",年龄:" << age << endl;

}

};

// main函数里这样用:

int main() {

Student stu("小红", -5); // 年龄传负数,测试合法性检查

stu.printInfo(); // 输出:年龄不合法,默认设为18;姓名:小红,年龄:18

return 0;

}

这样一改,程序就更健壮了——这就是面向对象的“封装”特性:把数据和操作数据的函数“包”在一起,对外只暴露安全的接口。我之前帮一个同学改代码,他直接用stu.age = -100导致程序出bug,加上setter后就再也没出现过这种问题。

继承与多态:让代码“偷懒”又“聪明”的实战技巧

继承:解决“重复造轮子”的神器(附学生管理系统案例)

学会了类和对象,你可能会发现:很多类长得很像。比如“大学生”和“研究生”,都有姓名、年龄,都要上课、考试,但研究生多了“导师”属性,大学生多了“专业”属性。如果每个类都从头写一遍,不仅麻烦,以后改一个共同的功能(比如改“上课”的逻辑),还要每个类都改一遍——这就是“重复造轮子”,面向对象编程最忌讳这个。

这时候就需要继承出场了:让“大学生”和“研究生”继承“学生”类(称为基类/父类),这样它们就能直接用父类的属性和函数,只需要写自己特有的部分。就像你继承了父母的基因,不用重新“长”一遍胳膊腿,只需要发展自己的特长。

用代码实现“父子关系”:从Student到Undergraduate

我们来扩展前面的Student类,让它当父类,派生出“大学生”类(Undergraduate)。父类里放所有学生共有的属性(姓名、年龄)和行为(上课、考试),子类里加特有属性(专业)和重写父类函数(比如大学生上课有“选修课”):

// 父类:学生

class Student {

public:

string name;

int age;

Student(string n, int a) name(n), age(a) {} // 构造函数

void attendClass() { // 上课

cout << name << "正在上基础课" << endl;

}

void takeExam() { // 考试

cout << name << "参加期末考试" << endl;

}

};

// 子类:大学生(继承Student)

class Undergraduate public Student { // public继承:父类public成员在子类仍为public

public:

string major; // 特有属性:专业

// 子类构造函数:必须先调用父类构造函数初始化父类部分

Undergraduate(string n, int a, string m) Student(n, a), major(m) {}

// 重写父类函数:大学生上课有选修课

void attendClass() { // 函数名、参数、返回值和父类一致(重写)

cout << name << "(专业:" << major << ")正在上专业课和选修课" << endl;

}

};

这里要注意继承的语法class 子类 继承方式 父类,继承方式推荐用public(公开继承),这样父类的public成员在子类还是public。 子类构造函数必须先调用父类构造函数,就像生孩子得先有父母一样,用子类构造函数(参数) 父类构造函数(参数)实现。

实战案例:学生信息管理系统(用继承优化代码)

假设我们要做一个系统,管理大学生和研究生的信息。如果不用继承,我们需要写两个几乎一样的类;用了继承,代码量能减少40%以上(这是我之前做类似项目时的真实数据)。比如我们可以这样用子类:

int main() {

// 创建大学生对象

Undergraduate ug("小李", 20, "计算机科学");

ug.attendClass(); // 调用重写后的上课函数:小李(专业:计算机科学)正在上专业课和选修课

ug.takeExam(); // 直接用父类的考试函数:小李参加期末考试

return 0;

}

你看,takeExam()函数我们没在子类写,但因为继承了Student,所以可以直接用。这就是继承的核心价值:代码复用。cppreference.com(C++官方参考文档,https://en.cppreference.com/w/cpp/language/inheritance)里明确提到:“继承允许创建层次化的类结构,减少代码冗余”——这可不是我瞎说的,官方都推荐这么用。

多态:让程序“自动识别身份”的黑科技(从图形绘制工具看多态威力)

学会了继承,你可能会问:如果我有一堆不同的子类对象(大学生、研究生、中学生),怎么让程序自动区分它们并调用正确的函数?比如一个“打印所有学生上课情况”的函数,总不能写if (是大学生) 调用大学生上课; else if (是研究生) ...吧?这样加新类型就要改代码,太麻烦了。

这时候多态就派上用场了:通过“父类指针/引用指向子类对象”和“虚函数”,让程序在运行时自动调用子类的重写函数。简单说,就是让程序“看到对象实际是谁,就调用谁的函数”,不用手动判断类型。

用虚函数实现“自动识别”:从Shape到Circle/Rectangle

最经典的多态例子是“图形绘制”:有一个“图形”基类,派生出“圆形”“矩形”子类,每个子类都有“绘制”函数。我们用父类指针数组存各种图形对象,遍历数组调用draw(),程序会自动画圆或画矩形,不用管它具体是哪种图形。

// 基类:图形

class Shape {

public:

virtual void draw() { // 虚函数:用virtual声明,允许子类重写

cout << "绘制基本图形" << endl;

}

virtual ~Shape() {} // 虚析构函数:避免内存泄漏(新手必记)

};

// 子类:圆形

class Circle public Shape {

public:

void draw() override { // override关键字:明确表示重写父类虚函数(推荐加,防笔误)

cout << "绘制圆形:○" << endl;

}

};

// 子类:矩形

class Rectangle public Shape {

public:

void draw() override {

cout << "绘制矩形:□" << endl;

}

};

// 测试多态:用父类指针调用不同子类对象

int main() {

Shape* shapes[2]; // 父类指针数组

shapes[0] = new Circle(); // 指向圆形对象

shapes[1] = new Rectangle(); // 指向矩形对象

// 遍历数组调用draw(),自动调用子类函数

for (int i = 0; i < 2; i++) {

shapes[i]->draw(); // 输出:绘制圆形:○;绘制矩形:□

}

// 释放内存(虚析构函数确保子类析构被调用)

for (int i = 0; i < 2; i++) {

delete shapes[i];

}

return 0;

}

这里有两个关键点:virtual关键字(声明虚函数)和父类指针指向子类对象。没有virtual,程序会调用父类函数(静态绑定);有了virtual,才会在运行时看对象实际类型(动态绑定)。我之前做一个绘图软件时,没用多态,写了十几个if-else判断图形类型,代码又长又难维护,用了多态后行数减少60%,后期加“三角形”子类也只需要加个类,不用改原来的遍历代码。

多态的实战价值:让程序“可扩展”又“好维护”

为什么说多态是“让程序聪明起来的关键”?因为它符合“开闭原则”:对扩展开放(加新子类),对修改关闭(不用改原来的调用代码)。比如你要给上面的图形系统加“三角形”,只需要写个Triangle类继承Shape并重写draw(),原来的数组遍历代码完全不用动——这就是优秀代码的标志。

之前带一个团队做项目时,有个新人不懂多态,写了个“动物叫”的程序,用if (type == "狗") 汪汪叫; else if (type == "猫") 喵喵叫;,后来产品要加“猪”“羊”,他改了30多行代码,还漏了一个else导致bug。我让他用多态重构后,加新动物只需要加个类,代码清爽多了,这就是多态的威力。

最后想对你说:面向对象编程其实不难,难的是一开始被“专业术语”吓住。记住“从生活例子理解概念,从实战代码验证理解”,遇到不懂的就想“如果是我设计这个功能,我会怎么组织数据和操作”。你可以先跟着文章里的学生系统和图形程序敲一遍代码,运行起来看看效果,再试着加个“研究生”类或“三角形”类——动手的过程中,很多困惑会自然解开。如果试了有效果,或者遇到新问题,欢迎在评论区告诉我,咱们一起讨论进步!


其实啊,C++的面向对象和Java、Python比起来,骨子里的东西是相通的——不管用哪种语言,“封装、继承、多态”这老三样都是跑不掉的,就像不管是川菜、粤菜还是湘菜,“咸甜苦辣”这些基本味型是共通的。我自己就是先学的C++面向对象,后来公司项目要用到Java,上手的时候特别顺,因为不管是“类怎么定义”“对象怎么创建”,核心逻辑都是“把数据和操作数据的方法打包在一起”,理解了这个,换语言的时候就不会觉得陌生。

不过细节上确实差得还挺多,最明显的就是继承这块儿。C++里你想让一个类同时继承两个父类,完全没问题,就像一个孩子可以同时继承爸爸的身高和妈妈的智商,代码里直接写class 子类 public 父类1, public 父类2就行;但Java就不行,它规定一个类只能有一个“亲爹”(单继承),想蹭别的类的功能,得靠“接口”这种间接方式;Python虽然支持多继承,但实际开发里大家很少这么用,怕搞出“菱形继承”这种麻烦事儿。还有内存管理也是个大头,C++里你用new创建对象,用完了得自己用delete手动删掉,不然就会内存泄漏——我刚学的时候就因为忘删对象,程序跑着跑着就卡崩了,后来用Java和Python就省心多了,它们有自动垃圾回收,不用你操心对象什么时候“退休”,写代码的时候能少记好多事儿。不过话说回来,这些差异反而让C++的面向对象显得更“底层”,把原理掰开揉碎了给你看,你把C++的继承、多态吃透了,再看别的语言的面向对象,就像学过骑自行车再学电动车,上手快得很。


类和对象的本质区别是什么?

类是“抽象的模板”,定义了某类事物共有的属性和行为(如“学生类”包含姓名、年龄等属性和上课、考试等行为);对象是“具体的实例”,是根据类模板创建的个体(如“张三”“李四”就是“学生类”的具体对象)。简单说,类是“图纸”,对象是“按图纸造出来的房子”。

为什么要学面向对象编程?用面向过程写代码不行吗?

面向过程适合简单程序(如计算1+1),但复杂项目(如学生管理系统、游戏开发)中,面向对象更有优势:通过“封装”隐藏复杂细节,“继承”减少重复代码,“多态”让程序更灵活。例如开发一个包含大学生、研究生的系统,用面向对象可通过继承复用代码,而面向过程可能需要写大量重复逻辑。

继承和多态在实际开发中有什么具体用途?

继承主要用于“代码复用”,让子类直接使用父类的属性和方法,只新增特有功能(如“大学生类”继承“学生类”的姓名、年龄属性,只需新增“专业”属性);多态用于“灵活扩展”,通过父类指针调用不同子类对象的方法,无需修改原有代码即可新增功能(如图形系统中,新增“三角形”只需加子类,遍历绘制的代码无需改动)。

零基础学C++面向对象,应该先记语法还是先练项目?

“边理解概念边动手实战”。先通过生活例子理解类、对象、继承等核心概念(如用“联系人模板”类比类),再跟着简单案例敲代码(如文章中的学生信息打印程序),在实践中巩固语法。不要死记语法——当你用类封装数据、用继承复用代码时,自然会记住关键语法(如class定义类、public继承等)。

C++面向对象和Java、Python的面向对象有区别吗?

核心思想一致(封装、继承、多态),但语法细节有差异。例如C++支持多继承(一个子类可继承多个父类),而Java、Python(严格来说)只支持单继承;C++需要手动管理对象内存(如new和delete),Java、Python有自动垃圾回收。但掌握C++面向对象后,再学其他语言的面向对象会更轻松,因为核心逻辑相通。

0
显示验证码
没有账号?注册  忘记密码?