📖 学习信息
学习课程:黑马 C++ 零基础入门教程
本日学习:C++ 面向对象编程核心 —— 封装特性包含:面向对象 vs 面向过程、类与对象的基本概念、类的定义与对象创建、访问权限控制、成员属性与成员函数、struct 与 class 的区别、封装的意义与实践学习目标:理解面向对象编程思想,掌握类的定义和对象的使用方法,熟练运用封装特性设计安全、可复用的代码,为后续学习继承和多态打下坚实基础
一、前言
前面我们学习的都是面向过程编程:分析解决问题的步骤,用函数一步步实现这些步骤,调用函数解决问题。但当程序规模越来越大、逻辑越来越复杂时,面向过程的代码会变得臃肿、难以维护和扩展。面向对象编程(OOP) 是一种更符合人类思维的编程思想:将现实世界中的事物抽象为对象,每个对象包含属性(数据)和行为(函数),通过对象之间的交互解决问题。C++ 是一门面向对象的语言,封装、继承、多态 是面向对象的三大核心特性,而封装是三大特性之首,是学习面向对象的第一步。
二、面向过程 vs 面向对象
1. 面向过程
- 核心思想:步骤化,关注 "怎么做"
- 特点:以函数为单位,数据和函数分离
- 缺点:代码复用性差、扩展性差、维护困难
2. 面向对象
- 核心思想:对象化,关注 "谁来做"
- 特点:以对象为单位,将数据(属性)和操作数据的函数(行为)封装在一起
- 优点:代码复用性高、扩展性好、易于维护和理解
示例:设计一个 "圆" 的程序
- 面向过程:写一个函数计算圆的周长,再写一个函数计算圆的面积,传入半径作为参数
- 面向对象:设计一个 "圆" 类,包含 "半径" 属性和 "计算周长"、"计算面积" 两个行为,创建圆对象后直接调用对象的方法
三、类与对象的基本概念
1. 什么是类
类是对一类具有相同属性和行为的事物的抽象描述,是一个模板、一个蓝图。比如:所有的学生都有姓名、年龄、分数这些属性,都有学习、考试这些行为,我们就可以抽象出一个 "学生类"。
2. 什么是对象
对象是类的具体实例,是根据类这个模板创建出来的具体事物。比如:"张三" 是学生类的一个具体对象,"李四" 是学生类的另一个具体对象。
3. 类与对象的关系
- 类是模板,对象是根据模板创建的实例
- 一个类可以创建无数个对象
- 所有对象都拥有类中定义的属性和行为
四、类的定义与对象创建
1. 类的定义语法
class 类名 {
// 访问权限
// 成员属性:描述对象的特征(数据)
// 成员函数:描述对象的行为(函数)
}; // 注意:末尾必须有分号!
示例:定义一个圆类
// 圆类
class Circle {
public: // 公共访问权限
// 成员属性:半径
int m_r;
// 成员函数:计算周长
double calculatePerimeter() {
return 2 * 3.14 * m_r;
}
// 成员函数:计算面积
double calculateArea() {
return 3.14 * m_r * m_r;
}
};
2. 对象的创建与使用
类定义完成后,就可以像使用 int、double 一样,创建类的对象并使用。创建对象有两种方式:栈区创建和堆区创建。
(1)栈区创建对象(最常用)
#include <iostream>
using namespace std;
class Circle {
public:
int m_r;
double calculatePerimeter() {
return 2 * 3.14 * m_r;
}
double calculateArea() {
return 3.14 * m_r * m_r;
}
};
int main()
{
// 栈区创建圆对象c1
Circle c1;
// 给对象的属性赋值
c1.m_r = 10;
// 调用对象的成员函数
cout << "圆的半径:" << c1.m_r << endl;
cout << "圆的周长:" << c1.calculatePerimeter() << endl;
cout << "圆的面积:" << c1.calculateArea() << endl;
return 0;
}
(2)堆区创建对象
int main()
{
// 堆区创建圆对象,返回指针
Circle *c2 = new Circle;
// 通过指针访问成员
c2->m_r = 20;
cout << "圆的面积:" << c2->calculateArea() << endl;
// 手动释放堆内存
delete c2;
c2 = nullptr;
return 0;
}
3. 成员访问语法
- 栈区对象:
对象名.成员属性/对象名.成员函数() - 堆区对象:
对象指针->成员属性/对象指针->成员函数()
五、访问权限控制(封装核心)
C++ 提供了三种访问权限,用于控制类的成员(属性和函数)的访问范围,这是实现封装的核心机制。
1. 三种访问权限
表格
| 访问权限 | 含义 | 访问范围 |
|---|---|---|
public | 公共权限 | 类内部可以访问,类外部也可以访问 |
private | 私有权限 | 只有类内部可以访问,类外部不能访问 |
protected | 保护权限 | 类内部可以访问,子类可以访问,类外部不能访问(继承时讲解) |
2. 核心规则
- 类的成员如果不指定访问权限,默认是 private
- 封装的核心思想:将所有成员属性私有化,提供公共的成员函数来访问和修改属性
3. 示例:封装学生类
#include <iostream>
#include <string>
using namespace std;
class Student {
private: // 私有成员:类外部不能直接访问
// 成员属性私有化
string m_name;
int m_age;
double m_score;
public: // 公共成员:提供对外访问接口
// 设置姓名
void setName(string name) {
m_name = name;
}
// 获取姓名
string getName() {
return m_name;
}
// 设置年龄(添加合法性校验)
void setAge(int age) {
if (age >= 0 && age <= 120) { // 年龄必须在0-120之间
m_age = age;
} else {
cout << "年龄输入不合法!" << endl;
m_age = 0;
}
}
// 获取年龄
int getAge() {
return m_age;
}
// 设置分数
void setScore(double score) {
if (score >= 0 && score <= 100) {
m_score = score;
} else {
cout << "分数输入不合法!" << endl;
m_score = 0;
}
}
// 获取分数
double getScore() {
return m_score;
}
// 显示学生信息
void showInfo() {
cout << "姓名:" << m_name << ",年龄:" << m_age << ",分数:" << m_score << endl;
}
};
int main()
{
Student s1;
// 不能直接访问私有属性:s1.m_name = "张三"; // 错误!
// 只能通过公共接口访问和修改
s1.setName("张三");
s1.setAge(20);
s1.setScore(95.5);
s1.showInfo();
// 测试非法输入
s1.setAge(200); // 输出"年龄输入不合法!"
cout << "年龄:" << s1.getAge() << endl; // 输出0
return 0;
}
4. 封装的意义
- 提高代码安全性:通过公共接口访问私有属性,可以添加合法性校验,防止非法数据
- 提高代码复用性:将属性和行为封装在类中,多个对象可以复用同一个类的代码
- 降低代码耦合度:类外部不需要知道类内部的实现细节,只需要调用公共接口即可
- 便于维护和扩展:修改类内部的实现不会影响类外部的调用
六、struct 与 class 的区别
C++ 中 struct 和 class 都可以用来定义类,它们的唯一区别是默认访问权限不同:
struct:默认访问权限是publicclass:默认访问权限是private
示例:
// struct定义的类,默认public
struct Person1 {
string name; // 默认public
int age; // 默认public
};
// class定义的类,默认private
class Person2 {
string name; // 默认private
int age; // 默认private
};
int main()
{
Person1 p1;
p1.name = "张三"; // 正确:struct默认public
p1.age = 20; // 正确
Person2 p2;
// p2.name = "李四"; // 错误:class默认private,类外部不能访问
// p2.age = 20; // 错误
return 0;
}
使用建议:
- 当只需要封装简单的数据结构时,使用
struct - 当需要封装属性和行为,实现复杂逻辑时,使用
class
七、成员函数的分文件编写
和普通函数一样,类的成员函数也可以分文件编写,将类的声明放在头文件(.h)中,类的实现放在源文件(.cpp)中,这是工业级开发的标准规范。
1. 分文件编写步骤
- 创建头文件
Student.h:存放类的声明 - 创建源文件
Student.cpp:存放类的成员函数的实现 - 在主函数文件中包含头文件,使用类
2. 完整示例
第一步:头文件 Student.h
#pragma once // 防止头文件重复包含
#include <iostream>
#include <string>
using namespace std;
// 类的声明
class Student {
private:
string m_name;
int m_age;
double m_score;
public:
void setName(string name);
string getName();
void setAge(int age);
int getAge();
void setScore(double score);
double getScore();
void showInfo();
};
第二步:源文件 Student.cpp
#include "Student.h" // 包含对应的头文件
// 成员函数的实现:需要加上 类名:: 作用域限定符
void Student::setName(string name) {
m_name = name;
}
string Student::getName() {
return m_name;
}
void Student::setAge(int age) {
if (age >= 0 && age <= 120) {
m_age = age;
} else {
cout << "年龄输入不合法!" << endl;
m_age = 0;
}
}
int Student::getAge() {
return m_age;
}
void Student::setScore(double score) {
if (score >= 0 && score <= 100) {
m_score = score;
} else {
cout << "分数输入不合法!" << endl;
m_score = 0;
}
}
double Student::getScore() {
return m_score;
}
void Student::showInfo() {
cout << "姓名:" << m_name << ",年龄:" << m_age << ",分数:" << m_score << endl;
}
第三步:主函数文件 main.cpp
#include "Student.h"
int main()
{
Student s1;
s1.setName("张三");
s1.setAge(20);
s1.setScore(95.5);
s1.showInfo();
return 0;
}
八、常见错误与避坑点
-
类定义末尾忘记写分号
class Student { // 成员 } // 错误:缺少分号,编译器报错 -
类外部直接访问私有成员私有成员只能在类内部访问,类外部必须通过公共接口访问
-
成员函数实现时忘记加类名::
// 错误:没有加Student::,编译器会认为是全局函数 void setName(string name) { m_name = name; } // 正确 void Student::setName(string name) { m_name = name; } -
对象创建时加括号的误区
// 错误:这是声明一个函数,不是创建对象 Student s1(); // 正确:创建对象 Student s2; -
堆区对象忘记释放用 new 创建的堆区对象,必须用 delete 释放,否则会导致内存泄漏
九、综合实战案例:设计一个立方体类
设计一个立方体类,包含长、宽、高三个属性,提供设置和获取属性的接口,以及计算体积和表面积的成员函数。
#include <iostream>
using namespace std;
class Cube {
private:
int m_length; // 长
int m_width; // 宽
int m_height; // 高
public:
// 设置长
void setLength(int length) {
m_length = length;
}
// 获取长
int getLength() {
return m_length;
}
// 设置宽
void setWidth(int width) {
m_width = width;
}
// 获取宽
int getWidth() {
return m_width;
}
// 设置高
void setHeight(int height) {
m_height = height;
}
// 获取高
int getHeight() {
return m_height;
}
// 计算体积
int calculateVolume() {
return m_length * m_width * m_height;
}
// 计算表面积
int calculateArea() {
return 2 * (m_length * m_width + m_length * m_height + m_width * m_height);
}
};
int main()
{
Cube c1;
c1.setLength(10);
c1.setWidth(5);
c1.setHeight(3);
cout << "立方体的长:" << c1.getLength() << endl;
cout << "立方体的宽:" << c1.getWidth() << endl;
cout << "立方体的高:" << c1.getHeight() << endl;
cout << "立方体的体积:" << c1.calculateVolume() << endl;
cout << "立方体的表面积:" << c1.calculateArea() << endl;
return 0;
}
十、今日学习总结
-
面向对象思想:以对象为单位,将属性和行为封装在一起,更符合人类思维
-
类与对象:类是模板,对象是类的具体实例,一个类可以创建多个对象
-
类的定义:
class 类名 { 成员列表 };,末尾必须有分号 -
访问权限控制:
public:类内外都能访问private:只有类内部能访问- 封装核心:属性私有化,提供公共接口访问
-
struct 与 class 区别:默认访问权限不同,struct 默认 public,class 默认 private
-
成员函数分文件编写:声明放在.h 头文件,实现放在.cpp 源文件,加
类名::作用域限定符 -
封装的意义:提高代码安全性、复用性、可维护性
✍️ 下节预告
下一篇将学习 C++ 类和对象的核心进阶:构造函数与析构函数,包含:构造函数的定义与分类、拷贝构造函数、析构函数的作用、深拷贝与浅拷贝问题、初始化列表,掌握对象的创建与销毁机制,彻底搞懂对象的生命周期。