当前位置: 首页 > news >正文

类和对象(上)

类和对象(上)

类定义格式

• class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的⽅法或者成员函数。

• 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_ 或者 m开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。

• C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。

• 定义在类⾯的成员函数默认为inline。

#include<iostream> using namespace std; class Stack { public: // 成员函数 void Init(int n = 4) { array = (int*)malloc(sizeof(int) * n); if (nullptr == array) { perror("malloc申请空间失败"); return; } capacity = n; top = 0; } void Push(int x) { // ...扩容 array[top++] = x; } 27 int Top() 28 { 29 assert(top > 0); 30 31 return array[top - 1]; 32 } 33 34 void Destroy() 35 { 36 free(array); 37 array = nullptr; 38 top = capacity = 0; 39 } 40 41 private: 42 // 成员变量 43 int* array; 44 size_t capacity; 45 size_t top; 46 }; // 分号不能省略 47 48 int main() 49 { 50 Stack st; 51 st.Init(); 52 st.Push(1); 53 st.Push(2); 54 55 cout << st.Top() << endl; 56 57 st.Destroy(); 58 59 return 0; 60 }

访问限定符

• C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接⼝提供给外部的⽤⼾使⽤。

• public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。

• 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 }即类结束。

• class定义成员没有被访问限定符修饰时默认为private,struct默认为public。

• ⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。

类域

• 类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤ :: 作⽤域操作符指明成员属于哪个类域。

• 类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。

#include<iostream> using namespace std; class Stack { public: // 成员函数 void Init(int n = 4); private: // 成员变量 int* array; size_t capacity; size_t top; }; // 声明和定义分离,需要指定类域 void Stack::Init(int n) { array = (int*)malloc(sizeof(int) * n); if (nullptr == array) { perror("malloc申请空间失败"); return; } capacity = n; top = 0; } int main() { Stack st; st.Init(); return 0; }

上⾯的程序运⾏后,我们看到没有成员变量的B和C类对象的⼤⼩是1,为什么没有成员变量还要给1个字节呢?因为如果⼀个字节都不给,怎么表⽰对象存在过呢!所以这⾥给1字节,纯粹是为了占位标识对象存在。

内存对⻬规则

• 第⼀个成员在与结构体偏移量为0的地址处。

• 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。

• 注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。

• VS中默认的对⻬数为8

• 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。

• 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。

内存对齐效率更高一点,因为如果要单独读取某个数据的时候只需要读取一遍

this指针

• Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了⼀个隐含的this指针解决这⾥的问题

• 编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。⽐如Date类的Init的真实原型为, void Init(Date* const this, int year,int month, int day)

• 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year;

• C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显⽰使⽤this指针。

#include<iostream> using namespace std; class Date { public: // void Init(Date* const this, int year, int month, int day) void Init(int year, int month, int day) { // 编译报错:error C2106: “=”: 左操作数必须为左值 // this = nullptr; // this->_year = year; _year = year; this->_month = month; this->_day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: // 这⾥只是声明,没有开空间 int _year; int _month; int _day; }; int main() { // Date类实例化出对象d1和d2 Date d1; Date d2; // d1.Init(&d1, 2024, 3, 31); d1.Init(2024, 3, 31); d1.Print(); d2.Init(2024, 7, 5); d2.Print(); return 0; }

这段代码看起来有问题,但让我先分析一下:

int main() { A* p = nullptr; // 创建一个指向A类的空指针 p->Print(); // 通过空指针调用成员函数 return 0; }

代码分析

问题所在:

  • p被初始化为nullptr(空指针)
  • p->Print()试图通过空指针调用成员函数

运行结果

理论上:这会导致未定义行为,通常会导致程序崩溃(段错误)

但实际上可能运行成功的原因:

  1. 成员函数的调用机制p->Print()在编译时被转换为A::Print(p)
  2. Print函数没有访问成员变量:函数内部没有使用this指针访问数据成员
  3. 编译器优化:由于不需要访问对象数据,函数可以正常执行

验证测试

class A { public: void Print() { cout << "A::Print()" << endl; // 如果加上这行,就会崩溃: // cout << _a << endl; // 访问成员变量,需要有效的this指针 } void PrintWithData() { cout << "Data: " << _a << endl; // 这里会崩溃! } private: int _a = 10; }; int main() { A* p = nullptr; p->Print(); // 可能正常运行 // p->PrintWithData(); // 一定会崩溃! return 0; }

重要结论

  • 这是未定义行为,不要在实际代码中这样写
  • 虽然可能"侥幸"运行成功,但依赖于编译器和具体实现
  • 安全的做法:确保指针不为空再调用成员函数

这是一种危险的编程实践,应该避免!

this指针存在内存哪个区域的 ()

A. 栈 B.堆 C.静态区 D.常量区 E.对象⾥⾯

正确答案是:A. 栈

详细解释

this指针的本质:

  • this是一个隐式的函数参数
  • 当调用成员函数时,编译器自动将对象的地址作为第一个参数传递

示例说明

class MyClass { public: void print() { // 编译器实际上将 this 作为第一个参数传递 // void print(MyClass* this); } }; int main() { MyClass obj; obj.print(); // 相当于 print(&obj); MyClass* ptr = new MyClass(); ptr->print(); // 相当于 print(ptr); return 0; }

内存区域分析

  1. this指针本身作为函数参数存储在栈中 ✓
  2. :如果对象是用new创建的,对象本身在堆中,但this指针仍在栈中
  3. 静态区:存储全局变量和静态变量
  4. 常量区:存储字符串常量等
  5. 对象里面:this指向对象,但本身不在对象内部

函数调用时的内存布局

栈帧(stack frame): | 返回地址 | | 参数n | | ... | | 参数1 | ← this指针在这里(作为第一个隐式参数) | 局部变量|

总结:this指针是成员函数的隐式参数,与其他函数参数一样存储在栈中。

http://www.jsqmd.com/news/89319/

相关文章:

  • 智能体开发系统学习实践
  • 马上2026年了,copilot还能用吗?
  • mysql中的索引页是什么?
  • 数据页和索引页有什么区别?
  • 《终极金钱心智》
  • 一文讲透XGBoost:从原理到实践的完整指南
  • 第13章:项目资源管理【章节重点】
  • EGSTalker踩坑日记第一弹
  • 图文详述:MySQL的下载、安装、部署、使用
  • 第14章:项目沟通管理【章节重点】
  • C#+VisionMaster联合开发(六)_控制器
  • Pelco KBD300A 模拟器:05.校验算法终极对比 + 完整 100+ 指令封装 + KBD300A 所有隐藏功能函数化
  • AI 如何从配置历史与变更日志中推理出“变更引发的故障”——自动化根因分析的因果推理引擎
  • 题目集4~5以及课堂测验的总结性Blog
  • 234回文链表
  • 练题100天——DAY26:汇总区间+丢失的数字+数组交集
  • 2025动漫剧本推荐,无需成本轻松创作
  • 22、正则表达式全解析:从基础到高级应用
  • 推荐一种并发线程中资源同步常用方法
  • 24、文本处理工具的使用与技巧
  • C#+VisionMaster联合开发(七)_通讯管理
  • Flutter 2025:从架构革命到商业落地,全面解析跨平台开发的“黄金时代”
  • 狮子老虎图像识别分类基于YOLO11-FasterNet实现含Python源码_268期
  • 2025 年 12 月胰岛素泵厂家最新推荐,如意泵,贴敷式与便携式二合一,全年龄段贴敷泵胰岛素泵公司选择指南 - 品牌鉴赏师
  • Claude vs ChatGPT vs Gemini: 기능 비교, 사용 경험, 적합 인군
  • 20、文件搜索、压缩与归档操作指南
  • 当AI芯片不再性感:博通的高增长,为何成了催命符?
  • 21、数据存档、备份与正则表达式应用全解析
  • JoyAgent-JDGenie项目业务逻辑梳理
  • 图论入门:从存储结构到DFS/BFS遍历,零基础也能看懂的实战教程