从面向过程到面向对象:吃透 C++ 类与对象的核心逻辑
在 C++ 编程体系中,类(Class)和对象(Object)是面向对象编程(OOP)的基石。相比于 C 语言的 “面向过程”(以函数为核心),C++ 的 “面向对象” 通过封装、继承、多态三大特性,让代码更贴合现实世界的逻辑,也更易维护和扩展。本文将从 “类是什么、对象怎么用” 出发,拆解类的定义、对象的创建、成员访问、构造 / 析构函数等核心知识点,帮你彻底理解类与对象的本质。
一、先搞懂:为什么需要类和对象?
面向过程编程的痛点:比如要描述 “学生” 这个实体,用 C 语言需要定义独立的变量和函数,数据和行为分离,代码冗余且易出错:
c
运行
// C语言:数据与行为分离struct Student {
char name[20];
int age;
float score;};// 独立的函数操作结构体void setStudent(struct Student* s, const char* name, int age, float score) {
strcpy(s->name, name);
s->age = age;
s->score = score;}void printStudent(struct Student* s) {
printf("姓名:%s,年龄:%d,成绩:%.1f\n", s->name, s->age, s->score);}而 C++ 的类可以将 “学生的数据(属性)” 和 “操作数据的行为(方法)” 封装在一起,形成一个完整的 “实体”:
cpp
运行
// C++类:数据与行为封装class Student {public:
// 属性(成员变量)
string name;
int age;
float score;
// 方法(成员函数)
void setInfo(string n, int a, float s) {
name = n;
age = a;
score = s;
}
void printInfo() {
cout << "姓名:" << name << ",年龄:" << age << ",成绩:" << score << endl;
}};核心优势:数据和行为绑定,代码更内聚、更符合 “学生” 这个实体的自然逻辑。
二、类的本质:自定义数据类型的 “蓝图”
1. 类的定义:从语法到核心
类是 C++ 自定义的 “复合数据类型”,它定义了某一类实体的属性(成员变量) 和行为(成员函数),相当于创建对象的 “蓝图” 或 “模板”。
基本语法结构:
cpp
运行
class 类名 {
// 访问权限修饰符:public / private / protected(默认private)public:
// 成员变量(属性)
数据类型 变量名;
// 成员函数(行为)
返回值类型 函数名(参数列表) {
// 函数体
}private:
// 私有成员(仅类内部可访问)};关键概念:访问权限修饰符
| 修饰符 | 访问范围 | 核心用途 |
|---|---|---|
| public | 类内、类外、子类均可访问 | 对外提供的接口(如成员函数) |
| private | 仅类内部可访问(默认) | 封装内部数据,避免外部直接修改 |
| protected | 类内、子类可访问,类外不可访问 | 继承场景下的权限控制 |
封装的核心:将数据(成员变量)设为 private,通过 public 的成员函数(get/set)访问,避免外部直接修改数据导致的错误。
2. 类的定义示例:规范的封装写法
cpp
运行
#include <iostream>#include <string>using namespace std;// 定义Student类(规范封装:成员变量私有,通过接口访问)class Student {private:
// 私有成员变量:外部无法直接访问
string name;
int age;
float score;public:
// 公有成员函数:对外提供的接口
// 设置学生信息
void setInfo(string n, int a, float s) {
// 可以在内部做数据校验
if (a < 0 || a > 120) {
cout << "年龄非法!" << endl;
age = 0;
} else {
age = a;
}
name = n;
score = s;
}
// 获取姓名(只读)
string getName() {
return name;
}
// 获取成绩(只读)
float getScore() {
return score;
}
// 打印信息
void printInfo() {
cout << "姓名:" << name << ",年龄:" << age << ",成绩:" << score << endl;
}};三、对象的本质:类的 “实例化”
类是 “蓝图”,对象是根据蓝图创建的 “具体实例”—— 一个类可以创建多个对象,每个对象拥有独立的成员变量(属性),但共享类的成员函数(行为)。
1. 对象的创建与使用
方式 1:栈上创建对象(常用)
cpp
运行
int main() {
// 栈上创建Student对象:stu1、stu2是两个独立的实例
Student stu1;
Student stu2;
// 调用成员函数:对象名.成员函数()
stu1.setInfo("张三", 18, 90.5);
stu2.setInfo("李四", 19, 85.0);
// 调用打印函数
stu1.printInfo(); // 输出:姓名:张三,年龄:18,成绩:90.5
stu2.printInfo(); // 输出:姓名:李四,年龄:19,成绩:85.0
// 访问公有接口(获取私有成员)
cout << stu1.getName() << endl; // 输出:张三
// cout << stu1.age << endl; // 错误:private成员,类外无法访问
return 0;}方式 2:堆上创建对象(用 new/delete)
cpp
运行
int main() {
// 堆上创建对象:返回指向对象的指针
Student* pStu = new Student;
// 调用成员函数:指针->成员函数()
pStu->setInfo("王五", 20, 88.5);
pStu->printInfo(); // 输出:姓名:王五,年龄:20,成绩:88.5
// 释放堆内存(必须手动delete,否则内存泄漏)
delete pStu;
pStu = nullptr; // 避免野指针
return 0;}2. 类与对象的内存布局
每个对象的内存仅存储成员变量(成员函数存储在代码区,所有对象共享);
空类(无成员变量)的大小为 1 字节(编译器为了区分不同对象的地址)。
示例验证:
cpp
运行
class EmptyClass {}; // 空类cout << sizeof(EmptyClass) << endl; // 输出:1class Test {
int a; // 4字节
double b; // 8字节
void func() {} // 成员函数不占对象内存};Test t;cout << sizeof(t) << endl; // 输出:12(内存对齐后,4+8=12)四、类的核心函数:构造函数与析构函数
对象的生命周期:创建(初始化)→ 使用 → 销毁(清理)。构造函数负责初始化,析构函数负责清理,二者均由编译器自动调用,无需手动触发。
1. 构造函数:对象的 “初始化器”
核心特征:
函数名与类名完全相同,无返回值(连 void 都没有);
对象创建时自动调用(栈 / 堆创建均会调用);
支持重载(参数不同);
若未显式定义,编译器会生成 “默认构造函数”(无参、空实现)。
分类与示例:
cpp
运行
class Person {private:
string name;
int age;public:
// 1. 默认构造函数(无参)
Person() {
name = "未知";
age = 0;
cout << "默认构造函数调用" << endl;
}
// 2. 带参构造函数(重载)
Person(string n, int a) {
name = n;
age = a;
cout << "带参构造函数调用" << endl;
}
// 3. 拷贝构造函数(用已有对象初始化新对象)
Person(const Person& p) {
name = p.name;
age = p.age;
cout << "拷贝构造函数调用" << endl;
}
void print() {
cout << "姓名:" << name << ",年龄:" << age << endl;
}};int main() {
// 调用默认构造函数
Person p1;
p1.print(); // 输出:姓名:未知,年龄:0
// 调用带参构造函数
Person p2("赵六", 22);
p2.print(); // 输出:姓名:赵六,年龄:22
// 调用拷贝构造函数
Person p3 = p2;
p3.print(); // 输出:姓名:赵六,年龄:22
return 0;}2. 析构函数:对象的 “清理员”
核心特征:
函数名是
~类名,无返回值、无参数;对象销毁时自动调用(栈对象出作用域、堆对象 delete 时);
仅能有一个析构函数(不能重载);
若未显式定义,编译器生成 “默认析构函数”(空实现);
核心用途:释放对象占用的资源(如堆内存、文件句柄)。
示例:析构函数释放堆内存
cpp
运行
class MyArray {private:
int* arr; // 堆数组指针
int size;public:
// 构造函数:分配堆内存
MyArray(int s) {
size = s;
arr = new int[size]; // 动态分配数组
cout << "构造函数:分配" << size << "个int内存" << endl;
}
// 析构函数:释放堆内存
~MyArray() {
delete[] arr; // 释放数组
arr = nullptr;
cout << "析构函数:释放堆内存" << endl;
}};int main() {
{
MyArray arr(5); // 栈对象:出{}时调用析构函数
} // 此处输出:析构函数:释放堆内存
MyArray* pArr = new MyArray(10); // 堆对象
delete pArr; // 手动delete,调用析构函数 → 输出:析构函数:释放堆内存
return 0;}五、实战进阶:类的成员函数补充
1. 成员函数的分文件编写(工程化规范)
实际开发中,类的定义和实现需分离(头文件.h + 源文件.cpp),避免重复编译:
步骤 1:头文件(Student.h)
cpp
运行
#pragma once // 防止头文件重复包含#include <string>using namespace std;class Student {private:
string name;
int age;
float score;public:
// 仅声明成员函数
void setInfo(string n, int a, float s);
string getName();
void printInfo();};步骤 2:源文件(Student.cpp)
cpp
运行
#include "Student.h"#include <iostream>// 实现成员函数:类名::函数名void Student::setInfo(string n, int a, float s) {
name = n;
age = a;
score = s;}string Student::getName() {
return name;}void Student::printInfo() {
cout << "姓名:" << name << ",年龄:" << age << ",成绩:" << score << endl;}步骤 3:主函数(main.cpp)
cpp
运行
#include "Student.h"int main() {
Student stu;
stu.setInfo("孙七", 21, 92.0);
stu.printInfo();
return 0;}2. 静态成员:属于类,而非对象
静态成员(static)分为静态成员变量和静态成员函数,核心特征是 “归属于类,所有对象共享”。
示例:统计类的实例个数
cpp
运行
class Counter {private:
static int count; // 静态成员变量:统计对象个数public:
// 构造函数:创建对象时count+1
Counter() {
count++;
}
// 静态成员函数:访问静态成员变量(无this指针)
static int getCount() {
// cout << this->count << endl; // 错误:静态函数无this指针
return count;
}};// 静态成员变量必须在类外初始化int Counter::count = 0;int main() {
Counter c1;
Counter c2;
Counter c3;
// 静态成员函数:类名::函数名(无需创建对象)
cout << "对象个数:" << Counter::getCount() << endl; // 输出:3
return 0;}六、避坑指南:类与对象的常见错误
1. 访问权限错误
直接访问 private 成员:
stu.age = 18;→ 编译报错,需通过 public 接口修改;类外调用 protected 成员:仅继承场景下子类可访问,类外禁止。
2. 构造函数使用错误
调用默认构造函数时加括号:
Person p1();→ 这是函数声明,而非对象创建(正确:Person p1;);浅拷贝问题:类中有堆内存成员时,默认拷贝构造函数会导致 “重复释放内存”,需自定义拷贝构造函数(深拷贝)。
3. 析构函数遗漏
堆对象未 delete:导致内存泄漏;
类中有堆内存成员时,未定义析构函数释放资源。
4. 静态成员初始化错误
静态成员变量未在类外初始化:
int Counter::count = 0;不可省略。
七、总结:类与对象的核心逻辑
类:自定义数据类型,封装了属性(成员变量)和行为(成员函数),是创建对象的 “蓝图”;
对象:类的实例,拥有独立的成员变量,共享成员函数,有明确的生命周期;
构造函数:对象创建时初始化,支持重载,核心处理 “资源分配”;
析构函数:对象销毁时清理,核心处理 “资源释放”;
封装:通过访问权限控制,将数据隐藏,仅暴露必要接口,提升代码安全性。