2026/2/9 20:01:53
网站建设
项目流程
上海建站市场,优秀门户网站欣赏,wordpress每段不同图片,重庆电脑网站建设大家好#xff01;今天咱们正式踏入 C 的核心 ——类和对象的世界。如果说 C 语言是 “面向过程” 的工具箱#xff0c;那 C 的 “类和对象” 就是把工具打包成 “智能设备”#xff0c;让代码更贴近现实逻辑。这篇文章先从最基础的 3 个问题入手#xff1a;对象占多大内存…大家好今天咱们正式踏入 C 的核心 ——类和对象的世界。如果说 C 语言是 “面向过程” 的工具箱那 C 的 “类和对象” 就是把工具打包成 “智能设备”让代码更贴近现实逻辑。这篇文章先从最基础的 3 个问题入手对象占多大内存为什么函数能区分不同对象对象的 “出生” 和 “死亡” 谁来管全程带代码和图解新手也能轻松看懂一、对象大小只装 “数据”不装 “功能”刚学类的时候我总疑惑类里又有成员变量比如日期的年 / 月 / 日又有成员函数比如打印日期那实例化一个对象后它占多大内存呢难道把函数也一起装进去了1.1 关键结论对象只存储成员变量其实答案很简单类对象的大小 所有成员变量的大小之和遵循 C 语言结构体的内存对齐规则成员函数压根不占对象的空间为什么因为函数编译后是一段 “指令代码”这些代码会统一存放在内存的代码段所有对象共用同一份。如果每个对象都存一份函数100 个对象就会存 100 份相同的指令纯属浪费内存举个例子就懂了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; // 4字节 int _month; // 4字节 int _day; // 4字节 }; int main() { Date d1, d2; // 实例化两个对象 d1.Init(2024, 5, 20); d2.Init(2024, 5, 21); // 打印对象大小44412字节无内存对齐时 cout Date对象大小 sizeof(d1) endl; return 0; }运行结果Date对象大小12不同编译器对齐规则可能微调但肯定不包含函数。1.2 内存分布图解为了更直观画一张内存分布图建议保存、Print () 函数指令标注 “所有对象共用”箭头d1 调用 Print () 时指向代码段的 Print () 指令)二、this 指针对象的 “专属身份证”接着上面的例子d1 和 d2 都调用 Print () 函数函数怎么知道该打印 d1 的日期还是 d2 的日期总不能 “脸盲” 吧这就需要 C 的隐藏机制 ——this 指针。2.1 this 指针是什么编译器会给每个成员函数 “偷偷加一个参数”当前类类型的指针名叫 this。它指向当前调用该函数的对象函数里访问的所有成员变量本质都是通过 this 指针访问的。比如我们写的Init函数编译器会偷偷改成这样我们看不到但实际运行是这样cpp// 我们写的代码无this void Init(int year, int month, int day) { _year year; _month month; _day day; } // 编译器实际处理的代码加了this void Init(Date* const this, int year, int month, int day) { this-_year year; // 显式通过this访问成员 this-_month month; this-_day day; }而我们调用d1.Init(2024,5,20)时编译器也会偷偷传参cpp// 我们写的调用代码 d1.Init(2024,5,20); // 编译器实际执行的代码传d1的地址给this Init(d1, 2024, 5, 20);2.2 this 指针的 3 个关键性质不能显式写不能在函数的形参或实参里写 this编译器会自己处理但可以在函数体内显式用比如this-_year存储位置通常存在栈区作为函数参数压栈部分编译器会优化到寄存器比如 VS 用 ECX 寄存器不在对象里也不在堆 / 静态区指向不能改this 是const指针比如Date* const this只能指向当前对象不能指向其他对象。2.3 经典易错题空指针调用成员函数会崩溃吗这是面试常考的坑咱们用两个例子对比瞬间明白例子 1空指针调用不访问成员的函数cpp#include iostream using namespace std; class A { public: void Print() { // 只打印字符串不访问成员变量 cout A::Print() endl; } private: int _a; // 成员变量 }; int main() { A* p nullptr; // 空指针 p-Print(); // 调用Print() return 0; }运行结果正常打印A::Print()不崩溃。原因Print () 不访问成员变量不需要解引用 this 指针虽然 this 是 nullptr但没用到直接执行代码段的指令即可。例子 2空指针调用访问成员的函数cpp#include iostream using namespace std; class A { public: void Print() { // 访问成员变量_a本质是this-_a cout _a endl; } private: int _a; }; int main() { A* p nullptr; // 空指针 p-Print(); // 调用Print() return 0; }运行结果程序崩溃。原因Print () 要访问_a即this-_a但 this 是 nullptr空指针解引用空指针会触发内存访问错误。三、默认成员函数对象的 “自动服务”当我们定义一个类时即使什么成员函数都不写编译器也会自动生成 6 个 “默认成员函数”这篇先讲最常用的 2 个构造、析构。它们就像对象的 “自动服务”负责对象的 “出生初始化” 和 “死亡清理”。3.1 构造函数对象的 “出生向导”为什么需要构造函数C 语言里我们定义结构体后要手动调用Init函数初始化比如InitDate(date, 2024,5,20)万一忘了调用成员变量就是随机值。C 的构造函数解决了这个问题对象实例化时编译器会自动调用构造函数完成成员变量的初始化不用我们手动调。构造函数的 5 个核心特点函数名 类名比如 Date 类的构造函数就叫 Date无返回值不用写 void也不用 return 任何值自动调用创建对象时自动执行不能手动调用除非搞特殊操作不推荐支持重载可以写多个构造函数满足不同初始化需求默认生成如果我们没写构造函数编译器会自动生成一个 “无参默认构造函数”一旦我们写了编译器就不生成了。3 种 “默认构造函数”重点“默认构造函数” 指的是不用传实参就能调用的构造函数有 3 种编译器自动生成的无参构造我们写的无参构造函数我们写的全缺省构造函数所有参数都有默认值。⚠️ 注意这 3 种只能存在一个否则调用时会有歧义编译器不知道选哪个。代码示例构造函数的用法cpp#include iostream using namespace std; class Date { public: // 1. 无参构造函数默认构造之一 Date() { _year 2000; _month 1; _day 1; } // 2. 全缺省构造函数默认构造之一 // 注意如果同时写无参和全缺省编译报错歧义 // Date(int year 2000, int month 1, int day 1) { // _year year; // _month month; // _day day; // } // 3. 带参构造函数非默认需要传参 Date(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; // 调用无参构造默认构造 Date d2(2024,5,20);// 调用带参构造 // Date d3(); // 错误编译器会认为这是函数声明不是创建对象 d1.Print(); // 输出 2000/1/1 d2.Print(); // 输出 2024/5/20 return 0; }编译器默认构造的 “小脾气”如果我们没写构造函数编译器自动生成的默认构造有个特点对内置类型int、char、指针等不初始化成员变量是随机值对自定义类型比如类、结构体会调用该自定义类型的默认构造函数。比如cppclass Time { public: // Time的无参构造 Time() { _hour 0; _minute 0; _second 0; cout Time默认构造调用 endl; } private: int _hour; int _minute; int _second; }; class Date { private: // 内置类型默认构造不初始化随机值 int _year; // 自定义类型默认构造会调用Time的无参构造 Time _t; }; int main() { Date d; // 创建Date对象会打印Time默认构造调用 return 0; }3.2 析构函数对象的 “资源清理工”为什么需要析构函数如果对象里申请了资源比如堆内存、文件句柄对象销毁时不清理就会造成 “内存泄漏”。析构函数的作用就是对象生命周期结束时自动调用清理对象申请的资源不是销毁对象本身对象本身在栈 / 堆里由系统回收。析构函数的 5 个核心特点函数名 ~ 类名比如 Date 类的析构函数叫Date无参数、无返回值不能重载一个类只能有一个析构自动调用对象出作用域比如 main 函数结束、delete 对象时自动执行默认生成没写析构时编译器自动生成默认析构清理规则和默认构造类似 —— 内置类型不处理自定义类型调用其析构。什么时候需要自己写析构只有当类申请了资源比如 new、malloc 分配内存时才需要手动写析构函数释放资源。如果没有资源申请比如 Date 类用编译器默认的就够了。代码示例手动写析构函数以栈为例cpp#include iostream using namespace std; class Stack { public: // 构造函数申请堆内存资源 Stack(int capacity 4) { _arr new int[capacity]; // 申请堆内存 _top 0; _capacity capacity; cout Stack构造调用 endl; } // 析构函数释放堆内存清理资源 ~Stack() { delete[] _arr; // 释放堆内存 _arr nullptr; _top _capacity 0; cout Stack析构调用 endl; } private: int* _arr; // 堆内存指针需要清理 int _top; // 内置类型 int _capacity; // 内置类型 }; int main() { Stack s; // 创建Stack对象调用构造 // main结束时s出作用域自动调用析构释放_arr return 0; }运行结果plaintextStack构造调用 Stack析构调用四、总结这篇我们搞懂了类和对象的 3 个核心基础对象大小只存成员变量成员函数在代码段共用this 指针隐藏的 “对象身份证”区分不同对象的调用构造 / 析构对象的 “自动初始化” 和 “自动清理”有资源申请才手动写析构。下一篇我们会深入讲剩下的默认成员函数拷贝构造、赋值重载 大家可以先动手敲一敲今天的代码感受一下对象的 “自动服务” 有多方便如果有疑问欢迎在评论区留言