C++ 类与对象:封装特性的实现与实战应用

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 需求分析

  1. 成员变量:半径 radius,设置为 private,避免外部直接修改。
  2. 成员函数:
    • setRadius(double r):设置半径,需校验半径是否大于 0。
    • getRadius():获取半径,对外提供只读接口。
    • getArea():计算圆的面积,公式:S=πr2S = \pi r^2S=πr2。
    • getPerimeter():计算圆的周长,公式:C=2πrC = 2\pi rC=2πr。
  3. 构造函数:提供空参和有参构造,初始化半径。

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 

五、封装的优势与开发规范

封装的核心优势

  1. 数据安全:通过 private 限制成员变量的直接访问,避免非法数据的输入。
  2. 代码复用:类的成员函数可以被多个对象复用,减少重复代码。
  3. 易于维护:类的内部实现细节被隐藏,修改内部逻辑时,只要对外接口不变,就不会影响外部代码。
  4. 接口清晰:对外只暴露必要的成员函数,让使用者无需关注内部实现,降低学习和使用成本。

5.1 C++ 类的开发规范

  • 成员变量尽量设置为 private,通过 publicget/set 函数访问和修改。
  • 构造函数要保证成员变量的初始化,避免出现未初始化的“脏数据”。
  • 涉及动态内存分配的类,必须手动编写析构函数释放资源,防止内存泄漏。
  • 类的命名采用大驼峰命名法,如 PersonCircle;成员函数和成员变量采用小驼峰命名法,如 setInforadius

六、本章总结

✅ 类是属性和行为的抽象,对象是类的具体实例,二者是面向对象编程的基础。
✅ 封装的核心是通过 public/protected/private 控制访问权限,隐藏实现细节,暴露统一接口。
✅ 构造函数负责对象初始化,支持重载;析构函数负责资源清理,不能重载。
✅ 合理使用封装特性,可以显著提升代码的安全性、复用性和可维护性。

Read more

《C++:从代码到机器》:vector 的坑只有 C++ 党懂?介绍使用 + 深度剖析 + 模拟实现,帮你全避开

《C++:从代码到机器》:vector 的坑只有 C++ 党懂?介绍使用 + 深度剖析 + 模拟实现,帮你全避开

✨ 孤廖:个人主页 🎯 个人专栏:《C++:从代码到机器》 🎯 个人专栏:《Linux系统探幽:从入门到内核》 🎯 个人专栏:《算法磨剑:用C++思考的艺术》 折而不挠,中不为下 文章目录 * 正文 * 1.vector的介绍及使用 * 1.1 vector的介绍 * 1.2 vector的使用 * 1.2.1 vector的定义 * 1.2.2 vector iterator 的使用 * 1.2.3 vector 空间增长问题 * 1.2.4 vector 增删查改 * 1.2.5 vector 迭代器失效问题。(重点)

By Ne0inhk
【Linux】别再用printf了!自己造个C++日志库

【Linux】别再用printf了!自己造个C++日志库

文章目录 * 什么是日志(Log)? * 1. 日志的定义 * 2. 常见的日志级别 * 实现日志类 * 思路 * 输出方式的实现 * 日志类 * 运行结果 * 总结 什么是日志(Log)? 1. 日志的定义 日志(Log)是程序运行时记录的重要信息,通常用于调试、监控和故障排查。它可以帮助开发者了解程序的运行状态,分析错误,甚至用于安全审计。 2. 常见的日志级别 级别描述DEBUG详细信息,仅在调试时使用INFO关键信息,如程序启动、结束等WARNING警告信息,不影响运行,但需要注意ERROR发生错误,可能影响功能FATAL严重错误,程序可能崩溃 实现日志类 思路 [2024-08-0412:27:03][DEBUG][202938][main.cc][16]- hello world 我们想实现一个日志类,是这样的,

By Ne0inhk
【第53节】Windows编程必学之使用C++写exe压缩加密壳

【第53节】Windows编程必学之使用C++写exe压缩加密壳

目录 一、实现背景 1.1 前言 1.2 前置知识 1.3 达到目标 二、壳的实现要点 2.1 写壳怎么做 2.2 写壳的困难点 2.3 如何写壳代码 2.4 API函数的调用问题 2.5 重定位问题 2.6 信息交互问题 2.7 调试问题 2.8 关于目标程序的随机基址 2.9 关于目标程序的导入表 2.10 关于动态加解密 2.11 关于TLS的处理 三、实现一个壳的步骤 四、

By Ne0inhk

C++未声明的标识符问题详解

C++未声明的标识符问题详解 1. 问题概述 未声明的标识符(undeclared identifier)是C++开发中最常见的编译错误之一。编译器在遇到标识符(变量、函数、类、类型等)时,需要在当前作用域或可见作用域中找到其声明。 2. 常见场景和原因 2.1 变量未声明 intmain(){ x =5;// 错误:'x'未声明return0;}// 正确做法intmain(){int x =5;// 先声明再使用return0;} 2.2 函数未声明 intmain(){myFunction();// 错误:'myFunction'未声明return0;}voidmyFunction(){// 定义在调用之后// ...} 2.3 类型未声明 intmain(

By Ne0inhk