这部分是 C++ 类和对象的入门基础,讲解了类的定义、访问限定符、类域、对象实例化、对象大小计算、this 指针六大核心内容,同时对比了 C 和 C++ 实现栈的差异,帮你理解 C++封装的核心思想。大白话 + 代码案例 + 核心结论。
一、类的定义:C++ 面向对象的基础载体
类是 C++ 实现面向对象的核心,是对 数据(属性)和操作数据的方法(函数)的封装,相当于自定义了一个新的类型,后续可以用这个类型创建对象。
1. 类的基本定义格式
用class关键字定义,语法固定,结尾的分号不能省略:
class 类名
{
// 访问限定符:public/protected/private
访问限定符:
成员变量; // 类的属性,描述对象的特征
成员函数; // 类的方法,描述对象的行为
}; // 分号必须加!
示例(栈类):
class Stack
{
public:
// 成员函数:操作栈的方法
void Init(int n = 4); // 初始化
void Push(int x); // 入栈
int Top(); // 取栈顶
void Destroy(); // 销毁
private:
// 成员变量:栈的属性
int* array; // 存储栈元素的数组
size_t capacity; // 栈的容量
size_t top; // 栈顶指针
};
2. 成员变量的命名惯例
为了区分成员变量和函数形参 / 局部变量,C++ 没有强制规定,但是为了区分,选一种即可:
- 前缀加
_:_year、_a - 后缀加
_:year_、a_ - 前缀加
m_:m_year、m_a
3. class 和 struct 的区别(C++ 中)
C++ 兼容 C 的struct,同时将struct升级为类,和class的核心区别只有默认访问权限:
表格
| 关键字 | 默认访问权限 | 适用场景 |
|---|---|---|
| class | private | 定义普通类,强调封装,成员默认隐藏 |
| struct | public | 偏数据结构(如链表节点),成员默认开放 |
其他共性:
- 都可以定义成员变量和成员函数;
- 都可以用访问限定符修改权限;
- 都可以实例化对象,作为自定义类型使用。
示例(struct 升级为类):
// C++的struct:可定义函数,无需typedef,自身就是类型
struct ListNodeCPP
{
void Init(int x) { val = x; next = nullptr; } // 成员函数
ListNodeCPP* next; // 成员变量
int val;
};
// 直接使用,无需typedef
ListNodeCPP node;
node.Init(10);
4. 成员函数的特性
定义在类内的成员函数,编译器会默认当作 inline 内联函数(减少函数调用开销,适合短小函数);如果函数体较长,建议类内声明,类外定义(后续结合类域讲解)。
二、访问限定符:实现 C++ 的封装特性
封装是面向对象三大特性之一,访问限定符是 C++ 实现封装的核心手段,通过限制成员的访问权限,选择性地将接口暴露给外部,隐藏内部实现细节,避免数据被随意修改。
1. 三种访问限定符
C++ 提供public、protected、private三种访问限定符,核心规则:
表格
| 限定符 | 类外能否直接访问 | 类内能否直接访问 | 继承中作用(后续讲) |
|---|---|---|---|
| public | ✅ 可以 | ✅ 可以 | 子类可直接访问 |
| protected | ❌ 不可以 | ✅ 可以 | 子类可直接访问 |
| private | ❌ 不可以 | ✅ 可以 | 子类不可直接访问 |
关键注意:
protected和private在类外访问权限完全一致,区别仅在继承阶段体现;- 访问权限的作用域:从当前限定符出现的位置,到下一个限定符出现为止,无后续限定符则到类结束。
2. 通用使用原则
- 成员变量:默认设为
private/protected,隐藏数据,避免外部随意修改; - 成员函数:需要外部调用的设为
public(对外接口),内部辅助函数设为private。
示例:
class Date
{
public:
// public:对外接口,外部可调用
void Init(int year, int month, int day) { _year = year; _month = month; _day = day; }
void Print() { cout << _year << "/" << _month << "/" << _day << endl; }
private:
// private:隐藏数据,外部不可直接访问
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.Init(2024, 3, 31); // 调用public函数
d.Print(); // 调用public函数
// d._year = 2025; // 错误:private成员,类外不可访问
return 0;
}
三、类域:类的专属作用域
类定义了一个新的独立作用域,类的所有成员(变量 + 函数)都属于这个类域,外部无法直接访问,必须通过作用域操作符::指明所属类域,这是类内声明、类外定义成员函数的核心。
1. 类域的核心规则
- 类内的成员,在类外访问 / 定义时,必须加
类名::; - 编译器编译时,会先在当前作用域查找标识符,找不到则到类域 / 全局域查找。
2. 成员函数:类内声明,类外定义
适合函数体较长的场景,语法:返回值 类名::函数名(形参列表) { 函数体 }示例:
class Stack
{
public:
// 类内声明:仅写函数原型
void Init(int n = 4);
private:
int* array;
size_t capacity;
size_t top;
};
// 类外定义:必须加Stack::,指明属于Stack类域
void Stack::Init(int n)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
注意:类外定义时,缺省参数只能写在类内声明处,类外定义不能重复写!
四、实例化:用类创建对象的过程
类只是一个抽象的模板 / 设计图,里面的成员变量只是声明,没有分配实际的物理内存,无法存储数据;实例化就是用这个模板在内存中创建具体的对象,分配内存,此时才能真正使用类的功能。
1. 实例化的核心理解
类 ≈ 建筑设计图,对象 ≈ 实际建造的房子:
- 设计图(类):规划了有哪些成员(房间),但没有实体,不能住人(存储数据);
- 房子(对象):根据设计图建造,分配实际空间,能住人(存储数据);
- 一个设计图(类)可以建造多套房子(多个对象),每个对象独立占用内存,存储自己的成员变量。
2. 实例化的语法
和创建内置类型变量一致:类名 对象名;(无参)/类名 对象名(实参)示例:
class Date
{
public:
void Init(int year, int month, int day) { _year = year; _month = month; _day = day; }
void Print() { cout << _year << "/" << _month << "/" << _day << endl; }
private:
int _year; // 仅声明,无内存
int _month;
int _day;
};
int main()
{
Date d1; // 实例化:创建对象d1,分配内存,存储成员变量
Date d2; // 实例化:创建对象d2,独立内存,和d1互不影响
d1.Init(2024, 3, 31);
d2.Init(2024, 7, 5);
d1.Print(); // 2024/3/31
d2.Print(); // 2024/7/5
return 0;
}
五、对象的大小计算:只存成员变量,不存成员函数
对象的内存中只存储成员变量,成员函数不存储在对象中,所有对象共享一份成员函数(存储在代码段),大幅节省内存。
1. 为什么对象不存成员函数?
- 成员函数编译后是一段指令,存储在代码段,无需在每个对象中重复存储;
- 多个对象的成员函数逻辑完全一致,只是操作的成员变量不同,重复存储会造成巨大内存浪费;
- 编译器调用成员函数时,会通过this 指针(后续讲)找到对应的对象,确定操作哪个对象的成员变量。
2. 对象大小的计算规则
- 仅计算成员变量的大小,成员函数不占对象内存;
- 成员变量的大小计算遵循C++ 内存对齐规则(和结构体对齐规则完全一致);
- 空类 / 无成员变量的类:对象大小为1 字节(纯粹为了占位标识,表示对象存在,否则无法区分多个空对象)。
3. 内存对齐规则(必记)
和 C 语言结构体对齐一致,VS 编译器默认对齐数为 8,GCC 默认对齐数为成员的自身大小:
- 第一个成员对齐到偏移量为 0的地址处;
- 其他成员对齐到对齐数的整数倍地址处(对齐数 = 编译器默认对齐数 和 成员自身大小的较小值);
- 整个对象的总大小,为所有成员最大对齐数的整数倍;
- 嵌套类 / 结构体时,嵌套的成员对齐到自己的最大对齐数的整数倍,整体大小为所有最大对齐数(含嵌套)的整数倍。
4. 经典案例计算
class A // 有成员变量:char(1) + int(4)
{
public:
void Print() { cout << _ch << endl; }
private:
char _ch;
int _i;
};
class B // 无成员变量,仅成员函数
{
public:
void Print() {}
};
class C {}; // 空类
int main()
{
cout << sizeof(A) << endl; // 8:char占1,对齐3字节,int占4,总8(最大对齐数4)
cout << sizeof(B) << endl; // 1:无成员变量,占位1字节
cout << sizeof(C) << endl; // 1:空类,占位1字节
return 0;
}
六、this 指针:解决对象与成员函数的关联问题
多个对象共享一份成员函数,那么成员函数如何知道操作的是哪个对象的成员变量?C++ 通过隐含的 this 指针解决这个问题,这是类的成员函数的核心底层机制。
1. this 指针的本质
- 编译器会为每个非静态成员函数,在形参第一个位置自动增加一个当前类类型的 const 指针:
类名* const this; - 调用成员函数时,编译器会自动将对象的地址作为实参,传递给 this 指针;
- 成员函数中访问的所有成员变量,本质都是通过 this 指针访问:
_year≡this->_year。
2. this 指针的核心特点
- 编译器自动添加:不能在函数的形参 / 实参位置显式写 this 指针,编译器会自动处理;
- 函数体内可显式使用:可以在成员函数中直接写 this 指针,访问对象或成员;
- this 指针是 const 指针:
类名* const this,指针本身的地址不能修改(this = nullptr;编译报错),但可以修改指向的对象的成员; - this 指针存储在栈区:属于函数的形参,和普通局部变量一样,存在栈中,不占对象的内存;
- 空指针问题:this 指针可以为空,但解引用空的 this 指针会导致程序崩溃(后续案例)。
3. 底层实现示例
以 Date 类的 Init 函数为例,编译器的底层处理:
// 我们写的代码
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
};
// 编译器实际编译后的代码(隐含this指针)
class Date
{
public:
void Init(Date* const this, int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
};
// 我们调用的代码:d1.Init(2024,3,31);
// 编译器实际调用的代码:Date::Init(&d1, 2024,3,31);
4. 经典 this 指针题
题目 1:空指针调用无成员变量访问的成员函数
class A
{
public:
void Print() { cout << "A::Print()" << endl; }
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print(); // 结果:正常运行(输出A::Print())
return 0;
}
原因:Print 函数没有访问任何成员变量,不需要解引用 this 指针,仅调用代码段的函数,不会触发空指针解引用,因此正常运行。
题目 2:空指针调用有成员变量访问的成员函数
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl; // 本质:this->_a
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print(); // 结果:运行崩溃
return 0;
}
原因:Print 函数中访问_a,本质是this->_a,而 this 指针是nullptr,解引用空指针导致程序崩溃(编译不会报错,因为语法无问题,运行时才会出错)。
七、C 和 C++ 实现 Stack 的对比:理解 C++ 的封装优势
C++ 的栈实现和 C 的底层逻辑一致,但通过类的封装让代码更安全、更简洁,核心差异体现在封装性和语法便捷性,对比后能更直观理解 C++ 面向对象的优势。
1. 核心差异对比
| 特性 | C 语言实现 Stack | C++ 语言实现 Stack |
|---|---|---|
| 数据与方法 | 数据(struct)和方法(函数)分离,函数需显式传对象地址 | 数据和方法封装在类中,通过 this 指针隐式传地址,无需手动传 |
| 访问控制 | 结构体成员默认开放,可随意修改,易出 bug | 成员变量设为 private,仅通过 public 接口访问,数据更安全 |
| 类型使用 | 需 typedef 重命名,否则写 struct Stack | 类名直接作为类型,无需 typedef,简洁 |
| 函数参数 | 所有操作函数都需传 Stack*,冗余 | 隐含 this 指针,函数参数更简洁,支持缺省参数 |
| 封装性 | 无封装,数据和方法松散关联 | 封装性强,隐藏内部实现,仅暴露接口 |
2. 核心优势总结
C++ 的封装不是简单的代码整合,而是更严格的管理:
- 隐藏内部数据(成员变量 private),避免外部随意修改导致的 bug;
- 对外暴露统一接口(成员函数 public),让使用者无需关心内部实现,降低使用成本;
- 语法上通过 this 指针、缺省参数等,让代码更简洁,调用更方便。
:基础核心知识点超详细入门讲解&spm=1001.2101.3001.5002&articleId=159130833&d=1&t=3&u=1bbf40f70c574654a797a73645f4967d)
6289

被折叠的 条评论
为什么被折叠?



