一、从现实世界到程序世界
我们熟悉的现实世界中,一切事物都是对象。对象可以是有形的,比如房屋、汽车、飞机、动物、植物;也可以是无形的,比如一项计划、一个订单、一次交易。对象可以是一个简单的个体(比如一个人),也可以是诸多其他对象组合而成的复杂体(比如一个公司由多个部门组成,每个部门又由许多人组成)。
对相似的对象进行抽象,找出其共同的属性和行为,便构成了一种类型。例如,张三、李四、王五都是具体的人(对象),对他们抽象后可以得到”人”这个类型——每个人都有姓名、年龄、身高等属性,也都有吃饭、走路、说话等行为。
编写程序的目的是描述与解决现实世界中的问题。面向对象程序设计的第一步,就是将现实世界中的对象和类如实反映到程序中。作为一种面向对象的程序设计语言,C++ 支持这种抽象:将抽象后的数据(属性)和函数(行为)封装在一起,便构成了 C++ 的”类”(class)。
二、面向对象程序设计的四个主要特点
| 特点 | 核心思想 | 通俗理解 |
|---|---|---|
| 抽象 | 提取同类对象的共同特征,忽略非本质细节 | 从具体的人中提炼出”人”这个概念 |
| 封装 | 将数据和对数据的操作捆绑在一起,隐藏内部细节,对外提供接口 | 把属性和方法装进一个”盒子”,只留几个按钮在外面 |
| 继承 | 在已有类的基础上扩展出新类,复用已有代码 | 学生是人的一种,自动拥有人的所有特征 |
| 多态 | 同一操作作用于不同对象,可以产生不同的执行结果 | 同样说”叫”,狗叫是”汪汪”,猫叫是”喵喵” |
这四个特点将在后续章节中逐一展开讲解。
三、抽象
抽象(Abstraction)是指从具体事物中提取出我们关心的特征,忽略不相关的细节,形成对该事物的抽象描述。
现实世界中的”学生”:有学号、姓名、年龄、专业、成绩等属性,也有选课、考试、打印成绩单等行为。在程序中,我们关心哪些特征,就提取哪些,不关心的(比如鞋码、发色)可以忽略。
抽象的结果就是”类”——类是对一组具有相同属性和行为的对象的抽象描述。
现实世界: 张三(具体学生) → 抽象 → "学生"类
程序世界: Student 类 → 实例化 → Student 对象 s1Code language: JavaScript (javascript)
四、封装
封装(Encapsulation)是将抽象得到的数据成员(属性)和成员函数(行为)捆绑在一起,形成一个有机的整体(类)。同时,封装还隐藏了对象的内部实现细节,对外只暴露有限的接口(公有成员函数),外部只能通过接口来访问对象。
封装的好处:
- 数据保护:外部不能随意修改对象内部的数据,只能通过规定的方法修改,避免数据被破坏。
- 降低耦合:外部只需要知道”能做什么”,不需要知道”怎么做”,类内部实现改变不影响外部代码。
- 提高可维护性:类的修改局限在类内部,不影响程序其他部分。
五、代码示例
以下代码演示了如何用 C++ 定义一个简单的类,体现抽象和封装的思想:
#include <iostream>
#include <string>
using namespace std;
// ===== 定义一个"学生"类 =====
class Student
{
private: // 私有成员:外部不能直接访问
string name; // 姓名
int age; // 年龄
double score; // 成绩
public: // 公有接口:外部通过它们访问对象
// 设置学生信息
void setName(string n)
{
name = n;
}
void setAge(int a)
{
if (a > 0 && a < 150) // 封装了数据校验逻辑
age = a;
else
age = 0;
}
void setScore(double s)
{
if (s >= 0 && s <= 100)
score = s;
else
score = 0;
}
// 获取学生信息
string getName()
{
return name;
}
int getAge()
{
return age;
}
double getScore()
{
return score;
}
// 行为:显示信息
void display()
{
cout << "姓名:" << name << ",年龄:" << age
<< ",成绩:" << score << endl;
}
// 行为:判断是否及格
bool isPass()
{
return score >= 60;
}
};
int main()
{
// 创建 Student 类的对象(实例化)
Student s1;
Student s2;
// 通过公有接口设置对象数据
s1.setName("张三");
s1.setAge(20);
s1.setScore(85.5);
s2.setName("李四");
s2.setAge(19);
s2.setScore(58.0);
// 通过公有接口访问对象
s1.display();
s2.display();
cout << s1.getName() << (s1.isPass() ? " 及格" : " 不及格") << endl;
cout << s2.getName() << (s2.isPass() ? " 及格" : " 不及格") << endl;
return 0;
}Code language: PHP (php)
分析:
Student类是对”学生”这一现实事物的抽象:提取了姓名、年龄、成绩三个属性,以及设置和获取信息、显示、判断是否及格等行为。- 数据成员
name、age、score被声明为private,外部不能直接访问,只能通过public的成员函数间接访问,这就是封装。 setAge和setScore中加入了数据校验逻辑,防止非法数据进入对象内部,体现了封装对数据的保护作用。main函数中通过Student s1;创建了Student类的对象(实例),通过对象调用成员函数完成操作。
六、类与对象的关系
| 概念 | 说明 | 类比 |
|---|---|---|
| 类(class) | 抽象的模板,描述一类对象的共同特征 | 建筑图纸 |
| 对象(object) | 类的具体实例,占有实际内存空间 | 按图纸建好的房子 |
| 实例化 | 从类创建对象的过程 | 按图纸施工 |
| 成员函数 | 类中定义的函数,描述对象的行为 | 房子的功能(开门、开灯) |
| 数据成员 | 类中定义的变量,描述对象的属性 | 房子的属性(面积、朝向) |
一个类可以创建多个对象,每个对象拥有独立的数据成员副本,但共享同一份成员函数的代码。
七、注意事项
- 类是用户自定义的数据类型,定义后不占用内存(类似于
int这个类型本身不占内存),只有创建对象时才分配内存。 - 类的定义以分号
;结尾,不能省略。 private和public是访问限定符。private成员只能在类内部访问,public成员在类内外均可访问。默认情况下(不写访问限定符时),class的成员是private的。- 成员函数可以在类体内定义(隐式内联),也可以在类体内声明、类体外定义(需加
类名::前缀)。 - 封装的核心不是”把东西藏起来”,而是”合理地暴露接口、保护数据”。不要为了封装而把所有成员都设为
private,要根据设计需求决定访问级别。
八、常见错误
1. 忘记类定义末尾的分号
class Student
{
// ...
} // 错误:缺少分号Code language: JavaScript (javascript)
2. 直接访问私有成员
Student s;
s.name = "张三"; // 错误:name 是 private 成员Code language: JavaScript (javascript)
解决:通过公有成员函数 s.setName("张三");
3. 类体外定义成员函数忘记加类名限定符
void display() // 错误:不知道是哪个类的 display
{
cout << name << endl;
}Code language: JavaScript (javascript)
正确写法:
void Student::display()
{
cout << name << endl;
}Code language: JavaScript (javascript)
4. 混淆类和对象
Student.age = 20; // 错误:age 是对象的成员,不是类的成员Code language: JavaScript (javascript)
正确写法:
Student s;
s.setAge(20);Code language: CSS (css)