C++ 类和对象入门:从 class、访问限定符到 this 指针

🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《指针合集》 | 《C语言基础》 | 《数据结构》 | 《机器学习导论》 | 《前端基础》 | 《python基础》 ✨ 数据即知识,压缩即智能
目录
- [C++ 类和对象入门:从 class、访问限定符到 this 指针](#C++ 类和对象入门:从 class、访问限定符到 this 指针)
-
- 前言
- 一、类的基本认识
-
- [1.1 为什么需要类?](#1.1 为什么需要类?)
- [1.2 什么是类?](#1.2 什么是类?)
- [1.3 类的基本定义格式](#1.3 类的基本定义格式)
- [1.4 成员变量为什么常常加下划线?](#1.4 成员变量为什么常常加下划线?)
- 二、访问限定符与封装
-
- [2.1 什么是访问限定符?](#2.1 什么是访问限定符?)
- [2.2 为什么成员变量通常放 private?](#2.2 为什么成员变量通常放 private?)
- [2.3 封装的本质是什么?](#2.3 封装的本质是什么?)
- [三、class 和 struct 的区别](#三、class 和 struct 的区别)
-
- [3.1 C++ 中 struct 也可以定义成员函数](#3.1 C++ 中 struct 也可以定义成员函数)
- [3.2 class 和 struct 的主要区别](#3.2 class 和 struct 的主要区别)
- [3.3 什么时候用 class,什么时候用 struct?](#3.3 什么时候用 class,什么时候用 struct?)
- 四、类域与成员函数定义
-
- [4.1 什么是类域?](#4.1 什么是类域?)
- [4.2 类外定义成员函数为什么要加类名?](#4.2 类外定义成员函数为什么要加类名?)
- [4.3 类域的作用](#4.3 类域的作用)
- 五、对象实例化
-
- [5.1 类只是设计图,对象才真正占空间](#5.1 类只是设计图,对象才真正占空间)
- [5.2 一个类可以实例化多个对象](#5.2 一个类可以实例化多个对象)
- [5.3 类和对象的关系](#5.3 类和对象的关系)
- 六、对象大小与内存布局
-
- [6.1 对象里面到底存了什么?](#6.1 对象里面到底存了什么?)
- [6.2 sizeof 对象时算什么?](#6.2 sizeof 对象时算什么?)
- [6.3 内存对齐是什么?](#6.3 内存对齐是什么?)
- [6.4 内存对齐的基本规则](#6.4 内存对齐的基本规则)
- [6.5 空类对象为什么大小是 1?](#6.5 空类对象为什么大小是 1?)
- [七、this 指针](#七、this 指针)
-
- [7.1 问题:成员函数怎么知道当前对象是谁?](#7.1 问题:成员函数怎么知道当前对象是谁?)
- [7.2 this 指针是什么?](#7.2 this 指针是什么?)
- [7.3 this 指针能不能显式使用?](#7.3 this 指针能不能显式使用?)
- [7.4 this 指针能不能被修改?](#7.4 this 指针能不能被修改?)
- [7.5 this 指针存在哪里?](#7.5 this 指针存在哪里?)
- 八、空指针调用成员函数的问题
-
- [8.1 为什么有些空指针调用成员函数不崩?](#8.1 为什么有些空指针调用成员函数不崩?)
- [8.2 为什么访问成员变量就可能崩?](#8.2 为什么访问成员变量就可能崩?)
- [8.3 这类写法能不能在项目中使用?](#8.3 这类写法能不能在项目中使用?)
- [九、C 和 C++ 实现 Stack 的对比](#九、C 和 C++ 实现 Stack 的对比)
-
- [9.1 C 语言实现 Stack 的特点](#9.1 C 语言实现 Stack 的特点)
- [9.2 C++ 实现 Stack 的方式](#9.2 C++ 实现 Stack 的方式)
- [9.3 C++ 版本看起来改变了什么?](#9.3 C++ 版本看起来改变了什么?)
- [9.4 C 和 C++ 实现 Stack 的核心区别](#9.4 C 和 C++ 实现 Stack 的核心区别)
- 十、重新理解封装
-
- [10.1 封装不是把代码写复杂](#10.1 封装不是把代码写复杂)
- [10.2 封装让接口和实现分离](#10.2 封装让接口和实现分离)
- 十一、常见错误总结
-
- [11.1 类定义结尾忘记分号](#11.1 类定义结尾忘记分号)
- [11.2 类外访问 private 成员](#11.2 类外访问 private 成员)
- [11.3 类外定义成员函数忘记加类域](#11.3 类外定义成员函数忘记加类域)
- [11.4 以为类定义时就分配了对象空间](#11.4 以为类定义时就分配了对象空间)
- [11.5 以为成员函数存储在每个对象中](#11.5 以为成员函数存储在每个对象中)
- [11.6 误以为 this 指针存在对象里](#11.6 误以为 this 指针存在对象里)
- 十二、全文总结
-
- [13.1 本文核心内容](#13.1 本文核心内容)
前言
学完 C 语言以后,再开始学习 C++,第一个真正有"C++味道"的内容通常就是:类和对象。
很多同学刚看到类时,会觉得它像是"升级版结构体":
- C 语言结构体只能放数据;
- C++ 的类既可以放数据,也可以放函数;
- 数据和函数放在一起之后,代码看起来更像一个整体。
这个理解没错,但还不够完整。
C++ 类和对象真正重要的地方在于:
它把数据和操作数据的函数封装到一起,并通过访问权限控制外部如何使用对象。
比如我们实现一个栈。
在 C 语言里,通常是结构体保存数据,函数单独放在外面:
cpp
ST s;
STInit(&s);
STPush(&s, 1);
STPop(&s);
而在 C++ 中,我们可以写成:
cpp
Stack s;
s.Init();
s.Push(1);
s.Pop();
这两种写法底层逻辑差别没有想象中那么大,但代码组织方式已经变了。
C++ 更强调:
对象自己管理自己的数据,对外只暴露必要的接口。
一、类的基本认识
1.1 为什么需要类?
在 C 语言中,我们可以用结构体描述一个对象的数据。
比如日期:
cpp
struct Date
{
int year;
int month;
int day;
};
这个结构体能说明一个日期应该有三个数据:
- 年
- 月
- 日
但是如果我们想初始化日期、打印日期,就需要额外写函数:
cpp
void DateInit(struct Date* d, int year, int month, int day)
{
d->year = year;
d->month = month;
d->day = day;
}
void DatePrint(struct Date* d)
{
printf("%d/%d/%d\n", d->year, d->month, d->day);
}
调用时:
cpp
struct Date d;
DateInit(&d, 2024, 3, 31);
DatePrint(&d);
这种写法能用,但数据和函数是分开的。
当代码规模变大以后,就会出现一些问题:
- 谁都可以直接修改结构体成员;
- 数据和操作数据的函数分散在不同地方;
- 调用函数时总要手动传结构体地址;
- 接口和内部细节边界不清楚。
C++ 的类就是为了解决这类问题而引入的重要机制。
1.2 什么是类?
类可以理解成一种自定义类型。
它不仅能描述对象有哪些数据,还能描述对象能做哪些操作。
比如定义一个日期类:
cpp
#include <iostream>
using namespace std;
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;
};
这里的 Date 就是一个类。
它里面有两类成员:
| 成员类型 | 含义 |
|---|---|
| 成员变量 | 保存对象的数据,也叫属性 |
| 成员函数 | 操作对象数据的函数,也叫方法 |
在这个例子中:
cpp
int _year;
int _month;
int _day;
是成员变量。
cpp
void Init(...)
void Print()
是成员函数。

1.3 类的基本定义格式
C++ 使用 class 关键字定义类。
基本格式如下:
cpp
class 类名
{
public:
// 公有成员
private:
// 私有成员
protected:
// 保护成员
};
注意:类定义结束时,右大括号后面的分号不能省略。
cpp
};
例如:
cpp
class Stack
{
public:
void Init()
{
// 初始化
}
private:
int* _a;
size_t _capacity;
size_t _top;
};
其中:
class是定义类的关键字;Stack是类名;{}中是类体;- 类体中可以定义成员变量和成员函数;
- 最后的
;必须写。
1.4 成员变量为什么常常加下划线?
在很多 C++ 代码中,成员变量经常写成:
cpp
int _year;
int _month;
int _day;
也有一些代码会写成:
cpp
int year_;
int month_;
int day_;
或者:
cpp
int m_year;
int m_month;
int m_day;
这些写法不是 C++ 强制规定,而是命名习惯。
目的很简单:
区分成员变量、普通局部变量和函数参数。
例如:
cpp
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
这里:
_year是成员变量;year是函数参数。
这样写代码时,不容易混淆。
二、访问限定符与封装
2.1 什么是访问限定符?
C++ 中有三个常见访问限定符:
| 访问限定符 | 含义 |
|---|---|
public |
公有成员,类外可以直接访问 |
private |
私有成员,类外不能直接访问 |
protected |
保护成员,类外不能直接访问,继承中会体现作用 |
入门阶段可以先这样理解:
public:对外开放的接口;private:类内部维护的实现细节;protected:暂时可以先理解为类似private,后面学继承时再深入。
例如:
cpp
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;
};
类外可以访问 public 成员:
cpp
Date d;
d.Init(2024, 3, 31);
d.Print();
但是不能直接访问 private 成员:
cpp
d._year = 2025; // 错误,_year 是 private

2.2 为什么成员变量通常放 private?
如果成员变量全部放到 public,外部代码就可以随便修改对象内部状态。
比如一个栈:
cpp
class Stack
{
public:
int* _a;
size_t _capacity;
size_t _top;
};
这样写以后,外部可以直接修改:
cpp
Stack s;
s._top = 100000;
s._capacity = 1;
这会破坏栈的内部逻辑。
正常情况下:
_top应该由Push()和Pop()维护;_capacity应该由扩容逻辑维护;_a不应该被外部随便改。
更合理的写法是:
cpp
class Stack
{
public:
void Push(int x);
void Pop();
int Top();
bool Empty();
private:
int* _a;
size_t _capacity;
size_t _top;
};
这样,外部只能通过公开接口使用栈:
cpp
s.Push(1);
s.Pop();
s.Top();
不能直接破坏内部数据。
2.3 封装的本质是什么?
封装不是简单地"把东西藏起来"。
更准确地说,封装做了两件事:
- 把相关的数据和函数组织到一个类里面;
- 通过访问权限限制外部对内部数据的直接访问。
封装的意义在于:
外部只需要知道怎么用,不需要知道内部怎么实现。
比如使用栈时,外部只需要知道:
cpp
Push()
Pop()
Top()
Empty()
不需要关心:
cpp
_a
_top
_capacity
这样代码会更安全,也更容易维护。
三、class 和 struct 的区别
3.1 C++ 中 struct 也可以定义成员函数
在 C 语言中,struct 主要用于组织数据。
但在 C++ 中,struct 被升级了,它也可以定义成员函数。
例如:
cpp
struct ListNode
{
void Init(int x)
{
val = x;
next = nullptr;
}
int val;
ListNode* next;
};
这在 C++ 中是合法的。
也就是说:
C++ 中的 struct 也可以看作一种类。
3.2 class 和 struct 的主要区别
class 和 struct 最主要的区别是默认访问权限不同。

例如:
cpp
class A
{
int _a;
};
这里 _a 默认是 private。
而:
cpp
struct B
{
int _b;
};
这里 _b 默认是 public。
3.3 什么时候用 class,什么时候用 struct?
实际写代码时,通常有一个习惯:
- 如果只是简单保存数据,常用
struct; - 如果需要封装数据和操作,常用
class。
例如链表节点这种简单数据结构,可以写成 struct:
cpp
struct ListNode
{
int val;
ListNode* next;
};
而栈、队列、日期类这种需要维护内部逻辑的类型,更适合写成 class:
cpp
class Stack
{
public:
void Push(int x);
void Pop();
private:
int* _a;
size_t _top;
size_t _capacity;
};
这不是硬性规定,而是工程代码中的常见风格。
四、类域与成员函数定义
4.1 什么是类域?
类本身会形成一个新的作用域。
类里面定义的成员变量和成员函数,都属于这个类的作用域。
例如:
cpp
class Stack
{
public:
void Init(int n = 4);
private:
int* _a;
size_t _capacity;
size_t _top;
};
这里的 Init、_a、_capacity、_top 都属于 Stack 这个类域。
4.2 类外定义成员函数为什么要加类名?
如果成员函数只在类里面声明,在类外定义,就必须加上类域。
例如:
cpp
class Stack
{
public:
void Init(int n = 4);
private:
int* _a;
size_t _capacity;
size_t _top;
};
类外定义时要写:
cpp
void Stack::Init(int n)
{
_a = (int*)malloc(sizeof(int) * n);
if (_a == nullptr)
{
perror("malloc fail");
return;
}
_capacity = n;
_top = 0;
}
这里的:
cpp
Stack::Init
表示:
这个 Init 函数属于 Stack 类。
这里的使用方法可以参考之前我们名字空间使用的内容。
如果写成:
cpp
void Init(int n)
{
_a = ...
}
编译器会把它当成普通全局函数。
此时 _a、_capacity、_top 都找不到。
4.3 类域的作用
类域影响的是编译器查找名字的规则。
当编译器看到:
cpp
void Stack::Init(int n)
它知道当前函数是 Stack 的成员函数。
所以函数体里访问:
cpp
_a
_capacity
_top
时,编译器会到 Stack 类域中查找这些成员。
这就是为什么类外定义成员函数必须加:
cpp
类名::函数名
五、对象实例化
5.1 类只是设计图,对象才真正占空间
类本身只是一个类型,是一种抽象描述。
比如:
cpp
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;
};
这只是定义了 Date 类型。
此时还没有真正的日期对象。
只有当我们写:
cpp
Date d1;
Date d2;
才真正创建了两个对象。
这个过程叫:
类实例化出对象。
可以用理解为:
类像建筑设计图,对象像根据图纸建出来的房子。
图纸说明房子应该怎么建,但图纸本身不能住人;
真正建出来的房子才占空间,才能使用。

5.2 一个类可以实例化多个对象
例如:
cpp
Date d1;
Date d2;
d1.Init(2024, 3, 31);
d2.Init(2024, 7, 5);
d1.Print();
d2.Print();
输出:
cpp
2024/3/31
2024/7/5
d1 和 d2 都是 Date 类型。
它们结构相同,但数据不同。
也就是说:
d1有自己的_year、_month、_day;d2也有自己的_year、_month、_day;- 两个对象的数据互不影响。
5.3 类和对象的关系
可以这样总结:
| 概念 | 含义 |
|---|---|
| 类 | 类型、模板、设计图 |
| 对象 | 根据类创建出的具体实体 |
| 成员变量 | 每个对象各自保存一份 |
| 成员函数 | 所有对象共享同一份函数代码 |
一句话:
类定义共同结构,对象保存具体数据。
六、对象大小与内存布局
6.1 对象里面到底存了什么?
当我们初学的时候会以为:
类里既有成员变量,也有成员函数,所以对象里应该也存成员变量和成员函数。
实际上,对象中主要存储的是成员变量。
成员函数代码不会在每个对象中保存一份。
原因很简单。
比如:
cpp
Date d1;
Date d2;
d1 和 d2 的日期数据不同,所以它们必须各自保存成员变量。
但 Init() 和 Print() 的函数代码是一样的。
如果每个对象都存一份函数代码,就太浪费空间了。
所以:
对象保存成员变量,成员函数放在公共代码区,所有对象共享。

6.2 sizeof 对象时算什么?
看下面这个类:
cpp
class Date
{
private:
int _year;
int _month;
int _day;
};
如果一个 int 是 4 字节,那么:
cpp
sizeof(Date)
通常是:
cpp
12
因为对象中有三个 int 成员变量。
但是对象大小不一定永远等于成员变量大小简单相加。
原因是:
对象大小还要遵守内存对齐规则。
6.3 内存对齐是什么?
看下面这个类:
cpp
class A
{
private:
char _ch;
int _i;
};
很多环境下:
cpp
sizeof(A)
不是 5,而是 8。
原因是内存对齐。
可以粗略理解成:
cpp
_ch 占 1 字节
中间填充 3 字节
_i 占 4 字节
所以总大小是 8 字节。
内存对齐的目的主要是让 CPU 更高效地访问数据。
6.4 内存对齐的基本规则
入门阶段可以先记住这几条:
- 第一个成员从偏移量 0 开始;
- 后面的成员要放到合适的对齐位置;
- 对象整体大小通常要是最大对齐数的整数倍;
- 成员顺序可能影响对象大小。
比如:
cpp
class A
{
private:
char _ch;
int _i;
};
通常布局大概是:
| 偏移量 | 内容 |
|---|---|
| 0 | _ch |
| 1~3 | 填充字节 |
| 4~7 | _i |
所以最终大小是 8 字节。

6.5 空类对象为什么大小是 1?
看下面两个类:
cpp
class B
{
public:
void Print()
{
cout << "B::Print()" << endl;
}
};
class C
{
};
它们都没有成员变量。
但是:
cpp
sizeof(B)
sizeof(C)
通常都是:
cpp
1
为什么没有成员变量还要占 1 个字节?
原因是:
对象必须在内存中有一个可区分的地址。
如果空对象大小是 0,那么多个空对象就不好区分。
所以 C++ 会给空类对象分配 1 个字节作为占位。
注意,这 1 个字节不是用来存成员函数的,只是为了表示对象存在。
七、this 指针
7.1 问题:成员函数怎么知道当前对象是谁?
看下面代码:
cpp
Date d1;
Date d2;
d1.Init(2024, 3, 31);
d2.Init(2024, 7, 5);
Init() 函数只有一份代码。
那么问题来了:
d1.Init(...)时,函数怎么知道要修改d1?d2.Init(...)时,函数怎么知道要修改d2?
答案是:
C++ 会给非静态成员函数隐含传递一个 this 指针。
7.2 this 指针是什么?
this 指针指向当前调用成员函数的对象。
例如:
cpp
d1.Init(2024, 3, 31);
可以粗略理解成编译器背后传了一个对象地址:
cpp
this = &d1;
而:
cpp
d2.Init(2024, 7, 5);
可以理解成:
cpp
this = &d2;
所以成员函数内部访问成员变量时,本质上是通过 this 指针访问的。
比如:
cpp
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
可以理解成:
cpp
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
7.3 this 指针能不能显式使用?
可以。
在成员函数内部,可以显式写 this->:
cpp
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
一般情况下,不写也可以:
cpp
_year = year;
编译器会自动识别 _year 是成员变量。
但是在讲解代码、区分成员来源时,显式写 this-> 会更直观。
7.4 this 指针能不能被修改?
不能。
this 指针可以理解成一个指针常量。
也就是说,它指向当前对象,不能被重新赋值。
下面写法是错误的:
cpp
this = nullptr; // 错误
但是可以通过 this 访问成员:
cpp
this->_year = year;
7.5 this 指针存在哪里?
this 是成员函数的隐含参数。
既然是函数参数,它通常和普通参数一样,在函数调用过程中存在。
它不存放在对象里面。
所以不要以为每个对象内部都有一个 this 指针。
正确理解是:
对象里主要保存成员变量;
调用成员函数时,编译器隐含传入当前对象地址,这个地址就是 this。
八、空指针调用成员函数的问题
8.1 为什么有些空指针调用成员函数不崩?
看下面代码:
cpp
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
有些环境下,这段代码可能会正常输出:
cpp
A::Print()
原因是:
Print() 函数内部没有访问任何成员变量。
虽然 p 是空指针,但函数体中没有真正通过 this 去访问对象内部数据。
所以它可能暂时看起来"没事"。
8.2 为什么访问成员变量就可能崩?
再看这段代码:
cpp
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
这次 Print() 内部访问了 _a。
而访问 _a 本质上就是:
cpp
this->_a
此时 this 是空指针。
所以程序很可能崩溃。
8.3 这类写法能不能在项目中使用?
不能。
空指针调用成员函数属于非常危险的行为。
即使某些情况下不崩,也不代表它是正确写法。
这类例子只适合理解:
- 成员函数代码不在对象内部;
- 成员变量访问依赖
this; - 空指针调用成员函数存在风险。
实际写代码时,一定要保证指针有效后再调用成员函数。

九、C 和 C++ 实现 Stack 的对比
9.1 C 语言实现 Stack 的特点
在 C 语言中,通常先定义结构体:
cpp
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
} ST;
然后再定义一组函数:
cpp
void STInit(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
STDataType STTop(ST* ps);
bool STEmpty(ST* ps);
void STDestroy(ST* ps);
使用时:
cpp
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
while (!STEmpty(&s))
{
printf("%d\n", STTop(&s));
STPop(&s);
}
STDestroy(&s);
这种写法的特点是:
- 数据放在结构体中;
- 操作数据的函数在结构体外;
- 每次调用函数都要手动传
&s; - 如果结构体成员暴露,外部可以直接修改内部数据。
9.2 C++ 实现 Stack 的方式
C++ 可以把数据和函数都放到类里面:
cpp
#include <iostream>
#include <cassert>
#include <cstdlib>
using namespace std;
typedef int STDataType;
class Stack
{
public:
void Init(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (_a == nullptr)
{
perror("malloc fail");
return;
}
_capacity = n;
_top = 0;
}
void Push(STDataType x)
{
if (_top == _capacity)
{
int newcapacity = _capacity * 2;
STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newcapacity);
if (tmp == nullptr)
{
perror("realloc fail");
return;
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top++] = x;
}
void Pop()
{
assert(_top > 0);
--_top;
}
STDataType Top()
{
assert(_top > 0);
return _a[_top - 1];
}
bool Empty()
{
return _top == 0;
}
void Destroy()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
使用时:
cpp
int main()
{
Stack s;
s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
while (!s.Empty())
{
cout << s.Top() << endl;
s.Pop();
}
s.Destroy();
return 0;
}
9.3 C++ 版本看起来改变了什么?
从使用方式上看,C++ 版本更自然。
C 语言风格:
cpp
STPush(&s, 1);
STPop(&s);
STTop(&s);
C++ 风格:
cpp
s.Push(1);
s.Pop();
s.Top();
C++ 版本不需要手动传 &s。
原因是:
成员函数调用时,
this指针会隐含传递当前对象地址。
例如:
cpp
s.Push(1);
可以粗略理解成:
cpp
Push(&s, 1);
只是这个过程由编译器自动完成。
9.4 C 和 C++ 实现 Stack 的核心区别
| 对比项 | C 语言版本 | C++ 类版本 |
|---|---|---|
| 数据保存 | struct Stack |
class Stack |
| 函数位置 | 全局函数 | 成员函数 |
| 调用方式 | STPush(&s, x) |
s.Push(x) |
| 对象地址传递 | 手动传结构体指针 | this 隐含传递 |
| 数据保护 | 容易被外部直接修改 | 成员变量可以设为 private |
| 接口表达 | 函数操作结构体 | 对象调用自己的方法 |

十、重新理解封装
10.1 封装不是把代码写复杂
有些初学者刚接触类时,会觉得:
以前 C 语言写结构体加函数挺清楚的,为什么 C++ 非要包成类?
其实类不是为了把代码写复杂。
它是为了在代码规模变大后,让代码更可控。
比如对于栈,外部真正需要使用的是:
cpp
Push()
Pop()
Top()
Empty()
而不是直接修改:
cpp
_a
_top
_capacity
这些内部成员应该由类自己维护。
10.2 封装让接口和实现分离
一个设计良好的类,通常会把成员分成两部分:
cpp
public:
// 对外接口
private:
// 内部实现细节
外部通过 public 接口使用对象。
内部通过 private 成员维护对象状态。
这样做的好处是:
- 外部使用更简单;
- 内部数据更安全;
- 后续修改实现时,对外影响更小。
比如以后 Stack 不想用动态数组实现,改成链表实现。
只要接口不变:
cpp
Push()
Pop()
Top()
Empty()
外部使用代码就可以尽量不用改。

十一、常见错误总结
11.1 类定义结尾忘记分号
错误:
cpp
class Date
{
private:
int _year;
}
正确:
cpp
class Date
{
private:
int _year;
};
11.2 类外访问 private 成员
错误:
cpp
Date d;
d._year = 2024;
如果 _year 是 private,类外不能直接访问。
正确做法是通过公开接口:
cpp
d.Init(2024, 3, 31);
11.3 类外定义成员函数忘记加类域
错误:
cpp
void Init(int year, int month, int day)
{
_year = year;
}
正确:
cpp
void Date::Init(int year, int month, int day)
{
_year = year;
}
11.4 以为类定义时就分配了对象空间
类只是类型定义,不是对象。
cpp
class Date
{
private:
int _year;
int _month;
int _day;
};
这只是说明 Date 对象应该长什么样。
只有写:
cpp
Date d;
才真正创建对象。
11.5 以为成员函数存储在每个对象中
成员函数不会在每个对象里保存一份。
对象主要保存成员变量。
成员函数代码存放在公共代码区,所有对象共享。
11.6 误以为 this 指针存在对象里
this 是成员函数调用时隐含传递的参数。
对象里面不会额外存一个 this 指针。
十二、全文总结
13.1 本文核心内容
本文主要讲了 C++ 类和对象入门中的几个核心知识点:
- 类的定义;
- 成员变量和成员函数;
- 访问限定符;
class和struct的区别;- 类域;
- 对象实例化;
- 对象大小;
- 内存对齐;
this指针;- C 和 C++ 实现 Stack 的区别;
- 封装思想。