C++ 类与对象:封装、实例化与 this 指针详解
C++ 类与对象是面向对象编程的核心基础。内容涵盖类的定义格式、访问限定符规则及类域作用域处理,阐明成员函数声明与定义的分离方式。解析类实例化过程,说明对象内存分配与成员变量关系,分析对象大小计算中的内存对齐原则及成员函数不占用对象空间的特性。深入讲解 this 指针的隐含机制,展示其在成员函数中区分对象实例的作用,并通过代码示例演示空指针解引用风险。最后对比 C 与 C++ 实现栈结构的差异,体现封装优势。

C++ 类与对象是面向对象编程的核心基础。内容涵盖类的定义格式、访问限定符规则及类域作用域处理,阐明成员函数声明与定义的分离方式。解析类实例化过程,说明对象内存分配与成员变量关系,分析对象大小计算中的内存对齐原则及成员函数不占用对象空间的特性。深入讲解 this 指针的隐含机制,展示其在成员函数中区分对象实例的作用,并通过代码示例演示空指针解引用风险。最后对比 C 与 C++ 实现栈结构的差异,体现封装优势。

类是 C++ 面向对象编程的核心,实现了数据与方法的封装,其定义、访问限定与类域规则构成了基础框架。实例化让抽象的类转化为实际内存中的对象,对象大小计算与成员函数存储特性,体现了 C++ 的内存设计逻辑。this 指针解决了成员函数区分对象的关键问题,而 C 与 C++ 实现 Stack 的对比,直观展现了封装带来的优势。
class Stack {
// ...
};
class 为定义类的关键字,Stack 为类的名字,{}中为类的主体,最后面加上分号";"。类主体中的内容成为类的成员:类中的变量称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数。_ 或者 m—>_capacity 等。但不是 C++ 强制的,只是为了方便操作,不同公司有着自己的书写规范。struct 也可以定义类,因为 C++ 兼容 C 语言的 struct 用法,而且 C++ 将其升级成了类。一个明显的变化是 C++ 在 struct 可以定义函数,注意还是推荐 class 定义类。inline。(当然,最终还是取决于编译器)class Stack {
// 成员函数
void Init() {
// ...
}
void Push() {
// ...
}
// 成员变量
int a;
int top;
int capacity;
};
// 与结构体类似,对一种事物的描述--属性
int main() {
Stack s1;
Stack s2;
return 0;
}
在 C++,类与结构体高度相似。其中明显的一个差异是:结构体名称不代表类型,只有当加上 struct 关键字后才是(或者 typedef 简化操作);而类可以直接使用名称,不需要加 class 关键字。
解释第 2 条: 对于命名规范,一般有以下几种:
- 驼峰法:
StackInit(自定义类型、函数) -> 开头单词的首字母大写 + 后续每个单词的首字母都大写;initCapacity(变量) -> 开头单词的首字母小写 + 后面的每个单词的首字母都大写。- Google C++ 风格:
stack_init(函数)、init_capacity(变量) -> 单词之间都用_隔开。
public 修饰的成员在类外面可以直接访问;protected、private 修饰的成员只能在类内访问,对于二者具体的区别,等到继承部分就会体现,现在默认作用相同,但一般使用 private。} 即类结束。class 定义成员在没有被访问限定符修饰时默认为 private,但 struct 定义成员默认为 public。private / protected 访问权限,而需要提供给外部使用的成员函数会设置为 public 访问权限。// C++ 将数据和方法封装在一起,放在类中;
// 封装的本质体现了更严格的规范管理;
class Stack {
public: // 公有
// 成员函数
void Init(int capacity = 4) {
_a = nullptr; // 这里要 malloc
_top = 0;
_capacity = 0;
}
void Push(int x) {
// ...
}
private: // 私有:不希望别人修改我定义的的成员变量
// 成员变量
int* _a;
int _top;
int _capacity;
};
int main() {
Stack s1;
Stack s2;
s1.Init();
s2.Push(1);
// s1.top++; // 无法访问
return 0;
}
// 对于结构体,C++ 兼容其用法
typedef struct A {
void func() {}
int a1;
int a2;
} A;
// 当然 C++ 也将其升级为类
struct B {
public: // 公有
// 成员函数
void Init() {
_a = nullptr;
_top = 0;
_capacity = 0;
}
private: // 私有
// 成员变量
int* _a;
int _top;
int _capacity;
};
int main() {
// C 语言写法
struct A a1; // 加关键字才是类型
A a2; // 或者重命名
// 类写法
B b1; // 名称就是类型
return 0;
}
在这里体现了类和结构体第 2 个不同的地方:
小贴士: C++ 中类访问权限默认为私有。 C++ 中结构体访问权限默认为公有
对于这两者,一般还是用类。当然,如果一开始就希望类的权限是公开,比如定义链表节点继续用 struct。
:: 域操作符说明该成员属于哪个类域;Stack.h 文件
#pragma once
#include <iostream>
class Stack {
public:
// 成员函数声明
void Init(int capacity = 4);
void Push(int x);
private:
// 成员变量
int* _a;
int _top;
int _capacity;
};
Stack.cpp 文件
#include "stack.h"
// 类域成员函数定义
// 指明类域
void Stack::Init(int capacity) {
_a = nullptr;
_top = 0;
_capacity = capacity;
}
void Stack::Push(int x) {
// ...
}
进行函数的声明定义分离:定义类的成员函数需要指明类域。 (搜索变量顺序:现在局部局,后全局域以及类域)
test.cpp 文件
#include "stack.h"
int main() {
Stack s1;
s1.Init();
return 0;
}
// 类的实例化
class Stack {
public:
void Init(int capacity = 4) {
_a = nullptr;
_top = 0;
_capacity = capacity; // 修正赋值逻辑
}
void Push(int x) {
// ...
}
private:
// 成员变量在这里只是声明,不开辟空间
int* _a;
int _top;
int _capacity;
};
int main() {
// Stack 类实例化出对象 s1、s2
Stack s1;
s1.Init();
Stack s2;
s2.Push(2);
// 这时,成员变量才会占空间
return 0;
}
类实例化出的对象,都有独立的数据空间,所以肯定包含成员变量,但是对于成员函数却不包含。
首先函数被编译后是段指令,被存储在代码段(单独区域)中,这样对象也只能包含函数的指针,但是没有这个必要。
实例化出的对象调用的都是同一个函数,如果对象包含成员函数,空间就浪费了。在实际中,函数指针是一个地址,调用函数被编译成指令 [call 地址],编译器在编译链接只需要找到函数地址,不用再运行时找。(动态多态是在运行时找,这就需要存储函数地址,以后会学到)
知道了类包含着谁,就要开始计算对象的大小,这里,C++ 规定类实例化的对象符合内存对齐规则。
class A {
public:
// 成员函数
void Init(int n = 4) {}
private:
// 成员变量,声明
char a;
int y;
};
int main() {
// 定义,类实例化对象 A
A s1;
s1.Init();
A s2;
s2.Init(100);
cout << sizeof(s1) << endl;
cout << sizeof(A) << endl;
return 0;
}
可以看到,根据对齐规则,内存大小为 8,没有包含成员函数。
【那么对于下面两个类的大小就很有意思了!】
class B {
void Init() {
// ...
}
};
class C {
// ...
};
int main() {
cout << sizeof(B) << '\n';
cout << sizeof(C) << endl;
return 0;
}
疑问?既然类不包含成员函数,为什么大小还是 1 呢?
这里就纯粹是为了占位标识对象存在,要不然谁知道对象存在过呢?!
Stack 类中由成员函数 Init、Push,当类实例化两个对象时,二者调用同一个函数,是怎么区分的?这就是 C++ 隐含的 this 指针。this 指针。比如:void Init(Stack* const this,...)。this 指针访问,比如:this->_capacity = capacity;。#include <iostream>
using namespace std;
class Date {
public:
// void Init(Date* const this, int year, int month, int day)--但不能显式的写出来
// const 保护 this 不能修改
void Init(int year, int month, int day) {
// 这里的 this 是可以写的,或者混着 this->_year = year;
this->_month = month;
_day = day;
}
void Print() {
cout << _year << "/" << _month << "/" << _day << '\n';
}
private:
// 成员变量声明,没有开空间
int _year;
int _month;
int _day;
};
int main() {
Date d1;
Date d2;
// this 指针,在函数实参和形参的位置不可以显式出现
// 但在函数体内可以出现
// d1.Init(&d1, 2025, 7, 31); 不能这样写出来
d1.Init(2025, 7, 31);
// d2.Init(&d2, 2025, 9, 1); 不能写出来
d2.Init(2025, 7, 9);
d1.Print();
d2.Print();
;
}
(对于使用 this 指针的场景后面会见到)
A、编译报错;B、运行崩溃;C、正常运行
#include <iostream>
using namespace std;
class A {
public:
void Print() // 实际上为 void Print(A* this)
{
cout << this << endl; // 输出 this 指针的值
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main() {
A* p = nullptr;
// p 是空指针
p->Print(); // 等价于 A::Print(p),把 p 作为 this 参数传入
// 修改后 (*p).Print();
// 同样等价于:A::Print(p)
return 0;
}
在这里可能很多人会选 B!首先看到是对空指针指针 p 进行解引用(不会报错),但是编译器执行的就是注释中所示,而且对象不包含成员函数。
A、编译报错;B、运行崩溃;C、正常运行
#include <iostream>
using namespace std;
class A {
public:
void Print() {
cout << this << endl;
cout << "A::Print()" << endl;
cout << _a << endl; // _a 是成员变量,存在对象里的,this->_a 所以会出现问题。
}
private:
int _a;
};
int main() {
A* p = nullptr;
p->Print();
return 0;
}
这里,对成员变量进行了访问,在前面已经说了编译器通过
this指针访问成员函数的成员变量,所以发生空指针解引用!
A. 栈 B. 堆 C. 静态区 D. 常量区 E. 对象里面
this是存在栈里面的,像形参一样,类似局部变量。但是this可能会被高频地访问,就会放在寄存器中。
面向对象的三大特性:封装、继承、多态,通过对比 C++ 与 C 语言的代码风格进一步了解封装的概念。
Init 给的缺省参数会很方便,并且因为隐式的 this 指针,不再需要传递对象的地址,使用类型不用再 typedef。Stack 相较于 C 语言变化很多,但实际不大,等学到适配器实现 Stack 就会有新的体验。类、实例化与 this 指针,共同构成 C++ 面向对象编程的核心基础。这些机制实现了数据与方法的封装,让代码更具规范性与可读性。通过与 C 语言的对比,C++ 面向对象的设计优势得以直观体现。掌握这些知识,也为后续学习继承、多态等高级特性,打下了坚实基础。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online