C++ 类与对象:封装特性的实现与实战应用
C++ 类与对象:封装特性的实现与实战应用
💡 学习目标:掌握类与对象的核心概念,理解封装的本质与价值,能够独立设计并实现具有封装特性的 C++ 类。
💡 学习重点:类的定义与对象实例化、访问权限控制、构造函数与析构函数的使用、封装的实战场景应用。
一、类与对象的核心概念
✅ 结论:类是 C++ 面向对象编程的核心载体,是对一类事物属性和行为的抽象描述;对象是类的具体实例,是内存中实际存在的实体。
1.1 类的组成
一个完整的 C++ 类通常包含两部分:
- 成员变量:描述类的属性,如人的姓名、年龄,圆的半径等。
- 成员函数:描述类的行为,如人的吃饭、跑步,圆的面积计算等。
1.2 类的定义格式
#include<iostream>#include<string>usingnamespace std;// 类的定义:以 class 关键字开头classPerson{// 访问权限修饰符,后续详细讲解private:// 成员变量:属性 string name;int age;public:// 成员函数:行为voidsetInfo(string n,int a);// 设置属性voidshowInfo();// 展示属性};1.3 对象的实例化与使用
对象是类的具体实例,实例化对象的语法与定义普通变量类似,有两种常见方式:
// 方式1:栈上实例化对象 Person p1; p1.setInfo("张三",20); p1.showInfo();// 方式2:堆上实例化对象(需要用 new 关键字) Person *p2 =newPerson(); p2->setInfo("李四",25); p2->showInfo();// 堆上对象使用后需手动释放内存delete p2; p2 =nullptr;二、封装的本质:访问权限控制
💡 封装是面向对象三大特性之一,核心是通过访问权限修饰符隐藏类的内部实现细节,只对外暴露必要的接口,提高代码的安全性和可维护性。
C++ 提供了三种访问权限修饰符,作用域从修饰符出现位置开始,到下一个修饰符或类结束为止。
2.1 三种访问权限
| 修饰符 | 访问范围 | 核心特点 |
|---|---|---|
public | 类内、类外、子类 | 对外公开的接口,允许外部直接访问 |
protected | 类内、子类 | 仅允许类自身和子类访问,类外不可见 |
private | 类内 | 仅允许类自身访问,子类和类外均不可见 |
⚠️ 注意事项:类的默认访问权限是 private,结构体 struct 的默认访问权限是 public。
2.2 访问权限的代码演示
#include<iostream>#include<string>usingnamespace std;classPerson{// 默认 private string addr ="北京市";private: string name;int age;public:voidsetInfo(string n,int a){// 类内可以访问 private 成员变量 name = n; age = a;}voidshowInfo(){ cout <<"姓名:"<< name <<" 年龄:"<< age <<" 地址:"<< addr << endl;}};intmain(){ Person p; p.setInfo("王五",30); p.showInfo();// 错误:name 是 private 成员,类外无法访问// p.name = "赵六";// 错误:addr 是默认 private 成员,类外无法访问// p.addr = "上海市";return0;}三、构造函数与析构函数:对象的生命周期管理
💡 构造函数负责对象的初始化,析构函数负责对象的清理工作,二者均由编译器自动调用,无需手动触发。
3.1 构造函数
3.1.1 构造函数的特性
- 函数名与类名完全相同,没有返回值类型,连
void都不能写。 - 对象实例化时自动调用,且只调用一次。
- 可以重载,支持不同方式的初始化。
- 如果没有手动定义构造函数,编译器会自动生成一个空参构造函数。
3.1.2 构造函数的分类与实现
#include<iostream>#include<string>usingnamespace std;classPerson{private: string name;int age;public:// 1. 空参构造函数(默认构造函数)Person(){ cout <<"空参构造函数被调用"<< endl; name ="未知"; age =0;}// 2. 有参构造函数Person(string n,int a){ cout <<"有参构造函数被调用"<< endl; name = n; age = a;}// 3. 拷贝构造函数(用已有对象初始化新对象)Person(const Person &p){ cout <<"拷贝构造函数被调用"<< endl; name = p.name; age = p.age;}voidshowInfo(){ cout <<"姓名:"<< name <<" 年龄:"<< age << endl;}};// 构造函数的调用方式intmain(){// 方式1:括号法 Person p1;// 调用空参构造 Person p2("张三",20);// 调用有参构造 Person p3(p2);// 调用拷贝构造// 方式2:显示法 Person p4 =Person(); Person p5 =Person("李四",25);// 方式3:隐式转换法(不推荐) Person p6 ={"王五",30}; p1.showInfo(); p2.showInfo();return0;}⚠️ 注意事项:使用括号法调用空参构造时,不能写 Person p1(),这会被编译器识别为函数声明,而非对象实例化。
3.2 析构函数
3.2.1 析构函数的特性
- 函数名以
~开头,后跟类名,没有返回值类型,也没有参数。 - 对象销毁时自动调用,且只调用一次。
- 不能重载,一个类只能有一个析构函数。
- 如果没有手动定义析构函数,编译器会自动生成一个空析构函数。
3.2.2 析构函数的实战价值
析构函数常用于释放对象占用的动态资源,如堆内存、文件句柄等,避免内存泄漏。
#include<iostream>#include<string>usingnamespace std;classStudent{private: string *name;// 堆内存指针int age;public:// 有参构造:分配堆内存Student(string n,int a){ cout <<"有参构造函数被调用"<< endl; name =newstring(n);// 堆上分配字符串 age = a;}// 析构函数:释放堆内存~Student(){ cout <<"析构函数被调用"<< endl;if(name !=nullptr){delete name;// 释放堆内存 name =nullptr;// 防止野指针}}voidshowInfo(){ cout <<"姓名:"<<*name <<" 年龄:"<< age << endl;}};intmain(){// 栈对象:函数结束时自动销毁,触发析构函数 Student s1("小明",18); s1.showInfo();// 堆对象:手动 delete 时触发析构函数 Student *s2 =newStudent("小红",17); s2->showInfo();delete s2; s2 =nullptr;return0;}四、封装的实战案例:设计一个圆形类
💡 需求:设计一个圆形类 Circle,包含半径属性,提供设置半径、计算面积、计算周长的功能,要求通过封装隐藏半径的直接访问,确保半径的合法性。
4.1 需求分析
- 成员变量:半径
radius,设置为private,避免外部直接修改。 - 成员函数:
setRadius(double r):设置半径,需校验半径是否大于 0。getRadius():获取半径,对外提供只读接口。getArea():计算圆的面积,公式:S=πr2S = \pi r^2S=πr2。getPerimeter():计算圆的周长,公式:C=2πrC = 2\pi rC=2πr。
- 构造函数:提供空参和有参构造,初始化半径。
4.2 完整代码实现
#include<iostream>#include<cmath>usingnamespace std;// 定义圆周率常量constdouble PI =acos(-1.0);classCircle{private:// 私有成员变量:半径double radius;public:// 空参构造:默认半径为 0Circle(){ radius =0.0; cout <<"空参构造函数被调用,默认半径 0"<< endl;}// 有参构造:初始化半径Circle(double r){setRadius(r);// 调用 setRadius 做合法性校验 cout <<"有参构造函数被调用,半径设置为 "<< radius << endl;}// 设置半径:校验合法性voidsetRadius(double r){if(r >=0){ radius = r;}else{ cout <<"⚠️ 半径不能为负数,默认设置为 0"<< endl; radius =0.0;}}// 获取半径doublegetRadius(){return radius;}// 计算面积doublegetArea(){return PI * radius * radius;}// 计算周长doublegetPerimeter(){return2* PI * radius;}};intmain(){// 测试空参构造 Circle c1; cout <<"圆1 半径:"<< c1.getRadius()<< endl; cout <<"圆1 面积:"<< c1.getArea()<< endl; cout <<"圆1 周长:"<< c1.getPerimeter()<< endl; cout <<"-------------------------"<< endl;// 测试有参构造:合法半径 Circle c2(5.0); cout <<"圆2 半径:"<< c2.getRadius()<< endl; cout <<"圆2 面积:"<< c2.getArea()<< endl; cout <<"圆2 周长:"<< c2.getPerimeter()<< endl; cout <<"-------------------------"<< endl;// 测试非法半径设置 Circle c3(-3.0); cout <<"圆3 半径:"<< c3.getRadius()<< endl;// 动态修改半径 c3.setRadius(4.0); cout <<"修改后圆3 半径:"<< c3.getRadius()<< endl; cout <<"修改后圆3 面积:"<< c3.getArea()<< endl;return0;}4.3 运行结果
空参构造函数被调用,默认半径 0 圆1 半径:0 圆1 面积:0 圆1 周长:0 ------------------------- 有参构造函数被调用,半径设置为 5 圆2 半径:5 圆2 面积:78.5398 圆2 周长:31.4159 ------------------------- ⚠️ 半径不能为负数,默认设置为 0 有参构造函数被调用,半径设置为 0 圆3 半径:0 修改后圆3 半径:4 修改后圆3 面积:50.2655 五、封装的优势与开发规范
✅ 封装的核心优势
- 数据安全:通过
private限制成员变量的直接访问,避免非法数据的输入。 - 代码复用:类的成员函数可以被多个对象复用,减少重复代码。
- 易于维护:类的内部实现细节被隐藏,修改内部逻辑时,只要对外接口不变,就不会影响外部代码。
- 接口清晰:对外只暴露必要的成员函数,让使用者无需关注内部实现,降低学习和使用成本。
5.1 C++ 类的开发规范
- 成员变量尽量设置为
private,通过public的get/set函数访问和修改。 - 构造函数要保证成员变量的初始化,避免出现未初始化的“脏数据”。
- 涉及动态内存分配的类,必须手动编写析构函数释放资源,防止内存泄漏。
- 类的命名采用大驼峰命名法,如
Person、Circle;成员函数和成员变量采用小驼峰命名法,如setInfo、radius。
六、本章总结
✅ 类是属性和行为的抽象,对象是类的具体实例,二者是面向对象编程的基础。
✅ 封装的核心是通过 public/protected/private 控制访问权限,隐藏实现细节,暴露统一接口。
✅ 构造函数负责对象初始化,支持重载;析构函数负责资源清理,不能重载。
✅ 合理使用封装特性,可以显著提升代码的安全性、复用性和可维护性。