C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)


✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭
~✨✨

🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。

我是Srlua小谢,在这里我会分享我的知识和经验。🎥

希望在这里,我们能一起探索IT世界的奥妙,提升我们的技能。🔮

记得先点赞👍后阅读哦~ 👏👏

📘📚 所属专栏:C/C++

欢迎访问我的主页:Srlua小谢 获取更多信息和资源。✨✨🌙🌙

​​

​​

目录

类的访问限定符及封装

访问限定符

public(公有):

protected(保护):

private(私有):

【访问限定符说明】

封装

案例:

类的作用域

类的实例化

类对象模型

如何计算类对象的大小

类对象的存储方式

结构体内存对齐规则

this指针

this指针的引出

this指针的特性

案例分析:

this指针存在哪里?

this指针可以为空吗?


类的访问限定符及封装

访问限定符

在面向对象的编程中,封装是一个核心概念,它隐藏了对象的内部实现细节,只对外提供必要的接口。封装通过访问限定符来控制类成员的访问权限,从而实现数据的隐藏和保

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

C++ 中有三种访问限定符:

public(公有)

成员在类的内部和外部都可以被访问。

protected(保护)

成员在类的内部和派生类(子类)中可以被访问,但不能在类的外部直接访问。

private(私有)

成员只能在类的内部被访问,不能在类的外部或派生类中直接访问。

【访问限定符说明】

1. public修饰的成员在类外可以直接被访问
2. protectedprivate修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

封装本质上是一种管理,让用户更方便使用类

案例:

计算机作为复杂设备,其设计体现了高度的封装性。

用户只需通过开关机键、键盘输入、显示器和USB插孔等外部接口与计算机交互,完成日常任务。计算机内部的核心部件如CPU、显卡、内存等,则隐藏在机壳内部,用户无需关心其详细设计或工作原理。这种设计使得计算机易于使用,同时保护了内部复杂结构的安全性和稳定性。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中

类体外定义成员时,需要使用 ::作用域操作符指明成员属于哪个类域。

class Person
{
public:void PrintPersonInfo();
private:char _name[20];char _gender[3];int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{cout << _name << " "<< _gender << " " << _age << endl;
}

类的作用域还涉及到类的成员在何处可以访问和使用。具体来说,某个类A中某个成员M在以下情况下具有类A的作用域:

  1. 该成员(M)出现在该类的某个成员函数中,并且该成员函数没有定义同名标识符。

  2. 该类(A)的某个对象的该成员(M)的表达式中。例如,a是A的对象,则在表达式a.M中,M具有类A的作用域。

  3. 在该类(A)的某个指向对象指针的该成员(M)的表达式中。例如,Pa是一个指向A类对象的指针,则在表达式Pa->M中,M具有类A的作用域。

  4. 在使用作用域运算符所限定的该成员中。例如,在表达式A::M中,M具有类A的作用域。

类的实例化

用类类型创建对象的过程,称为类的实例化

类是对象的模板或定义,它描述了对象的属性(成员变量)和方法(成员函数),但不分配实际内存来存储实例化的数据

通过类可以创建多个具有相同结构和行为的对象。这些对象会占用实际的物理空间来存储它们各自的属性值。

例如:

  • 学生信息表可以被视为一个类,定义了学生应具有的基本信息字段。而每个具体的学生记录就是该类的一个对象,它包含了这个学生的具体信息并占用内存空间。
  • 谜语和谜底的关系是一个很好的类比,谜语描述了谜底的特征,而谜底则是符合这些特征的具体实例。

在代码中,我们不能直接通过类名来访问或修改对象的成员变量,因为类本身并不存储具体的实例数据。

我们需要先创建类的实例(即对象),然后通过该对象来访问或修改其成员变量。

类与对象的关系可以比作建筑设计图与实际建筑的关系。

设计图(类)定义了建筑的结构和样式,但没有实际的建筑存在。只有当按照设计图进行建造(实例化)时,才会产生实际的建筑(对象),它占用物理空间并具有具体的形态和功能。

 类-->对象     ——     1-->多

类对象模型

如何计算类对象的大小

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?

运行结果: 

类对象的存储方式

类中既有成员变量,又有成员函数

class A1 {
public:void f1(){}
private:int _a;
};

对于类 A1,它有一个私有成员变量 _a(类型为 int)和一个公有成员函数 f1()。由于成员函数不占用类实例的内存空间(它们通常存储在代码段中,而不是数据段中),所以 A1 类实例的大小只与成员变量有关。在大多数系统上,一个 int 类型的成员变量通常占用 4 个字节(但这不是绝对的,取决于平台和编译器)。因此,sizeof(A1) 应该是 4(或可能是 4 的倍数,取决于内存对齐)。

类中仅有成员函数

class A2 {
public:void f2() {}
};

类中什么都没有---空类

class A3
{};

对于类 A2 和 A3,它们没有成员变量,只有成员函数。如前所述,成员函数不占用类实例的内存空间。然而,对于空类,编译器通常会为其分配至少一个字节的大小,以确保每个对象在内存中都有一个唯一的地址。因此,sizeof(A2) 和 sizeof(A3) 都应该是 1(或可能是 1 的倍数,取决于内存对齐和编译器的实现)。但在实践中,某些编译器可能会为空类分配更大的大小,以确保对象之间的内存地址有足够的间隔,这被称为“空基类优化”。

结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

结构体内存对齐规则

1. 第一个成员在与结构体偏移量为0的地址处。

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

注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8

3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

现在,关于结构体内存对齐的问题:

  1. 结构体怎么对齐?结构体对齐是为了满足处理器访问内存时的效率问题。当处理器从对齐的地址处读取数据时,通常比从非对齐的地址处读取数据要快。此外,某些硬件平台可能根本不支持非对齐的内存访问。​​​​​​​

  2. 为什么要进行内存对齐?如上所述,内存对齐可以提高处理器访问内存的效率,并避免在某些硬件平台上出现错误。

  3. 如何让结构体按照指定的对齐参数进行对齐?在 C++ 中,可以使用 alignas 关键字或特定的编译器指令(如 GCC 的 __attribute__((aligned(n))))来指定结构体的对齐参数。

  4. 能否按照3、4、5即任意字节对齐?是的,但需要注意的是,对齐参数应该是 2 的幂,并且小于或等于平台支持的最大对齐值。此外,过小的对齐值可能不会带来性能上的好处,而过大的对齐值可能会浪费内存。

  5. 什么是大小端?大小端是指多字节数据在内存中的存储顺序。大端模式(Big-Endian)是指数据的高位字节存储在内存的低地址处,而数据的低位字节存储在内存的高地址处。小端模式(Little-Endian)则相反,数据的低位字节存储在内存的低地址处,而数据的高位字节存储在内存的高地址处。

  6. 如何测试某台机器是大端还是小端?可以通过检查一个整数类型(如 int)的字节顺序来测试机器的大小端。一种常见的方法是创建一个整数,其高位字节设置为 1,其他字节设置为 0,然后检查该整数在内存中的地址处存储的值。

  7. 有没有遇到过要考虑大小端的场景?在处理跨平台的数据交换、网络通信或文件存储时,经常需要考虑大小端问题。因为不同的硬件平台可能使用不同的大小端模式,所以必须确保数据在发送和接收时的大小端一致性。

class A4
{
//private:char _ch;、int _i;
};
int main()
{cout<< sizeof(A4)<< endl;return 0;
}

不对齐就是五个字节,对齐就是八个字节

this指针

this指针的引出

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, d2;d1.Init(2022,1,11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;}

对于上述类,有这样的一个问题:

Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

在C++中,当你有一个类(比如Date类)并且这个类有成员函数(比如InitPrint),编译器确实为每个非静态成员函数增加了一个隐藏的指针参数this。这个this指针指向调用该成员函数的对象的地址。

当你创建Date类的两个对象d1d2,并分别调用它们的Init函数时,编译器会自动将this指针设置为指向当前对象(d1d2)的地址。这样,在Init函数的函数体内,所有对成员变量的操作都是通过这个this指针来完成的,从而确保了对正确对象的操作。

这个过程对用户(即程序员)来说是透明的,你不需要显式地传递this指针或进行任何特殊的操作。编译器会自动处理这一切。

在C++中,编译器为每个非静态成员函数隐式地传递一个名为this的指针,该指针指向调用该函数的对象。

这使得成员函数能够知道它们应该操作哪个对象的数据成员。这个过程对用户是透明的。

this指针的特性

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

2. 只能在“成员函数”的内部使用

3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针

4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

案例分析:

class A
{ 
public:void Print() {    cout<<"printf()"<<endl;   //正常运行}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}

cout<<"printf()"<<endl;        //正常运行 

在这段代码示例中,尽管 p 是一个指向 A 类对象的空指针(nullptr),但调用 p->Print(); 似乎可以成功执行,并且不会立即导致程序崩溃。这是因为 Print 函数是一个不依赖于 this 指针中存储的对象状态(即不访问任何成员变量)的成员函数。

在 C++ 中,成员函数通常通过 this 指针隐式地访问对象的成员。然而,如果成员函数不访问任何成员变量(也不调用其他访问成员变量的成员函数),那么实际上并不需要有效的 this 指针。在大多数现代编译器和硬件上,这样的调用可能不会立即导致崩溃,因为 this 指针通常只在函数内部需要访问成员变量时才会被使用。

但是,这并不意味着通过空指针调用成员函数是安全的或推荐的做法。尽管在的例子中 Print 函数能够执行,但这样做是未定义行为(Undefined Behavior, UB),并且可能导致不可预测的结果,包括(但不限于)程序崩溃、数据损坏或安全漏洞。

未定义行为意味着 C++ 标准没有规定在这种情况下程序应该如何表现。不同的编译器、不同的编译器设置、不同的操作系统或硬件架构都可能导致不同的结果。因此,我们应该始终避免通过空指针调用成员函数。

此外,一些编译器或编译器的优化设置可能会检测到这种潜在的未定义行为,并发出警告或错误。例如,使用某些静态分析工具或编译器的更严格的警告级别可能会帮助识别这种问题。

class A
{ 
public:void PrintA() {    cout<<_a<<endl;     //运行崩溃}
private:int _a;
};
int main()
{A* p = nullptr;p->PrintA();return 0;
}

而在这段代码中,程序崩溃的原因是解引用了一个空指针 p 来调用 PrintA 成员函数。即使 PrintA 函数不直接访问 _a 成员(实际上它是通过 this 指针隐式访问的),但调用成员函数本身就需要一个有效的对象实例。

在 C++ 中,当你有一个指向对象的指针,并试图通过该指针调用成员函数时,编译器会生成代码来隐式地传递一个指向该对象的 this 指针给成员函数。然而,如果指针是 nullptr(或称为空指针),那么 this 指针就会是无效的,尝试通过它访问成员会导致未定义行为,通常表现为程序崩溃

在这段代码中,p 被初始化为 nullptr,这意味着它并不指向任何有效的 A 类对象。

然后,尝试通过 p->PrintA(); 调用 PrintA 成员函数。

由于 p 是空的,this 指针也是无效的,因此程序崩溃。


this指针存在哪里?

this 指针是 C++ 编译器在调用成员函数时自动添加的一个隐式参数。它实际上是一个指向调用该成员函数的对象(或类的实例)的指针。这个指针并不是真正存储在对象本身的内存布局中,而是在成员函数被调用时,由编译器在函数调用栈帧(stack frame)中创建并管理的。

所以this指针是存在栈(stack)里的!

在成员函数内部,你可以通过 this 指针来访问或修改对象的成员变量。尽管在源代码中你并不会显式地看到 this 指针的传递和使用,但编译器会在编译时为你处理这些细节。

this指针可以为空吗?

this 指针本身在成员函数被调用时总是指向一个有效的对象(除非是通过某种非常规的方式调用成员函数,比如直接通过函数指针调用且没有正确的对象上下文)。然而,你不能显式地将 this 指针设置为 nullptr 或其他无效地址,因为 this 指针是由编译器管理的,而不是由程序员直接控制的。

但是,有一种情况需要注意:当你通过空指针(nullptr)来调用成员函数时,虽然技术上你并没有直接操作 this 指针,但这种行为是未定义的,并且很可能导致程序崩溃。这是因为即使函数体内不直接访问任何成员变量,成员函数被调用时仍然需要一个有效的 this 指针来作为上下文。当这个上下文不存在(即你试图通过一个空指针来调用成员函数)时,程序的行为就是未定义的。

所以,虽然不能直接设置 this 指针为空,但必须确保在调用成员函数时所使用的对象指针是有效的。

​​

希望对你有帮助!加油!

若您认为本文内容有益,请不吝赐予赞同并订阅,以便持续接收有价值的信息。衷心感谢您的关注和支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/8735.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据统计:词频统计、词表生成、排序及计数、词云图生成

文章目录 &#x1f4da;输入及输出&#x1f4da;代码实现 &#x1f4da;输入及输出 输入&#xff1a;读取一个input.txt&#xff0c;其中包含单词及其对应的TED打卡号。 输出 output.txt&#xff1a;包含按频率降序排列的每个单词及其计数&#xff08;这里直接用于后续的词云…

设计模式之单例模式详解

单例模式 描述&#xff1a;单例&#xff08;Singleton&#xff09;模式的定义&#xff1a;指一个类只有一个实例&#xff0c;且该类能自行创建这个实例的一种模式。 核心特点 单例类只有一个实例对象&#xff1b;该单例对象必须由单例类自行创建&#xff1b;单例类对外提供一…

OpenCV 入门(一) —— OpenCV 基础

OpenCV 入门系列&#xff1a; OpenCV 入门&#xff08;一&#xff09;—— OpenCV 基础 OpenCV 入门&#xff08;二&#xff09;—— 车牌定位 OpenCV 入门&#xff08;三&#xff09;—— 车牌筛选 OpenCV 入门&#xff08;四&#xff09;—— 车牌号识别 OpenCV 入门&#xf…

每日两题 / 23. 合并 K 个升序链表 94. 二叉树的中序遍历(LeetCode热题100)

23. 合并 K 个升序链表 - 力扣&#xff08;LeetCode&#xff09; 若lists有k个元素&#xff0c;调用k - 1次&#xff08;两个有序链表的合并&#xff09;即可 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNod…

探索鸿蒙开发:鸿蒙系统如何引领嵌入式技术革新

嵌入式技术已经成为现代社会不可或缺的一部分。而在这个领域&#xff0c;华为凭借其自主研发的鸿蒙操作系统&#xff0c;正悄然引领着一场技术革新的浪潮。本文将探讨鸿蒙开发的特点、优势以及其对嵌入式技术发展的深远影响。 鸿蒙操作系统的特点 鸿蒙&#xff0c;作为华为推…

一键接入电商API数据接口淘宝API通过商品ID、URL采集商品详情页实时数据API接入指南

一键接入电商API数据接口&#xff0c;尤其是淘宝API&#xff0c;通常需要遵循以下步骤&#xff1a; 注册账号&#xff1a;注册接入账号获取Api Key和Api Secret。 选择API&#xff1a;根据需要选择合适的API服务&#xff0c;如通过商品ID或URL采集商品详情页数据的API。 权限…

WPF鼠标拖拽的最佳实现

WPF鼠标拖拽的最佳实现 在很多项目中都会遇到鼠标拖拽控件移动的需求&#xff0c;常见的有从在列表中拖拽列表项移动&#xff0c;拖拽控件移动等。 本文将介绍2种拖拽的简单的实现 列表项的拖拽 本文将使用 gong-wpf-dragdrop 这个github上的库来实现列表的拖拽的效果&…

GitLab使用记录

GitLab 文章目录 1. 常用命令1.1 配置邮箱 用户名1.2 查看配置1.3 基本语法 2. 连接gitlab3. 直接拉去项目 1. 常用命令 1.1 配置邮箱 用户名 git config --global user.name ShangzheChen git config --global user.email 735511377qq.com1.2 查看配置 cat ~/.gitconfig这…

企业信使_登陆页

在当今数字化时代&#xff0c;企业与员工之间的沟通变得越来越重要。为了满足企业内部沟通的需求&#xff0c;一款功能强大而方便使用的企业信使_登陆页应运而生。企业信使_登陆页是一种专为企业内部使用而设计的通讯工具&#xff0c;可以帮助企业提高沟通效率&#xff0c;加强…

4.用python爬取保存在text中的格式为m3u8的视频

文章目录 一、爬取过程详解1.寻找视频的m3u8链接2.从网页源码中寻找视频的m3u8链接的第二部分内容3.从视频的m3u8链接获取视频 二、完整的代码 一、爬取过程详解 1.寻找视频的m3u8链接 这个文档承接了爬虫专栏的 第一节.python爬虫爬取视频网站的视频可下载的源url&#xff0…

车规级低功耗汽车用晶振SG-9101CGA

车规级晶振SG-9101CGA属于爱普生9101系列&#xff0c;是一款可编程晶振。SG-9101CGA车规级晶振采用2.5x2.0mm封装&#xff0c;利用PLL技术生产&#xff0c;此款振荡器的频率范围从0.67M~170MHZ任一频点可选&#xff0c;步进1ppm&#xff0c;采用标准CMOS输出&#xff0c;最大输…

为 Flutter 应用设置主题:ThemeData 和 ColorScheme 指南

在媒体和其他来源中有许多关于这个主题的文章&#xff0c;那么这篇文章的必要性是什么&#xff1f; 在本文中&#xff0c;我计划仅关注 ThemeData 小部件的关键点以及我的开发经验中最常用的参数&#xff0c;并且您将获得有关每个参数如何对您的应用程序执行操作的简要说明。 …

分类任务的基础学习

1.什么是分类&#xff1f; 2.局限性&#xff1a; 当样本量逐渐变大的时候&#xff0c;准确率会下降——>因为线性回归曲线距离我们的原点越远&#xff0c;预测就会开始不准确&#xff0c;因为 x前面的倍数就会越来越小&#xff0c;这就导致了样本量变大&#xff0c;但是那些…

Kafka 业务日志采集最佳实践

简介 Apache Kafka 是一个分布式流处理平台&#xff0c;主要用于构建实时数据流管道和应用程序。在收集业务日志的场景中&#xff0c;Kafka 可以作为一个消息中间件&#xff0c;用于接收、存储和转发大量的日志数据。将 Kafka 与其他系统&#xff08;如 Elasticsearch、Flume、…

docker-compose安装 人大金仓数据库

下载官网安装包 将安装包重命名为: kingbase.tar 再导入镜像仓库 docker load -i kingbase.tar目录创建data文件夹创建docker-compose文件 version: 3 services: kingbase: image: kingbase:v1 container_name: kingbaseports: - "54321:54321" volumes: -…

解决微信小程序电脑能正常使用,手机端无法正常访问的SSL证书问题

目录 前言1 问题描述与调试2 探索问题根源2.1 用户反馈收集2.2 尝试手机端访问2.3 PC端调试 3 确认问题与解决方案3.1 检查SSL证书3.2 重新部署SSL证书3.3 测试修复效果 4 SSL&#xff08;Secure Sockets Layer&#xff09;证书中间证书4.1 SSL证书链的构成4.2 中间证书的作用 …

【管理咨询宝藏97】智慧物流园区顶层设计方案

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏97】智慧物流园区顶层设计方案 【格式】PDF版本 【关键词】智慧园区、制造型企业转型、数字化转型 【核心观点】 - 中国物流业整体呈现集中度低…

springboot项目 字典/枚举翻译 终极解决方案 AOP+自定义注解+递归实体字段+实体动态三级缓存+责任链+多种转换方式

目录 前言实现思路技术确定 食用方式效果使用样例项目中使用第一步 复制包第二步 实现LoadDictDatabase并将其注入容器第三步 标识需要翻译的字段第四步 标识需要翻译的方法第五步 调用需要翻译的方法 实现细节TODO 前言 字典,即在存储介质中进行存储时,为了避免业务上对其名称…

数据结构复习指导之二叉树的概念

文章目录 二叉树 考纲内容 复习提示 1.二叉树的概念 1.1二叉树的定义及其主要特性 1.1.1二叉树的定义 1.1.2几种特殊的二叉树 1.1.3二叉树的性质 1.2二叉树的存储结构 1.2.1顺序存储结构 1.2.2链式存储结构 知识回顾 二叉树 考纲内容 &#xff08;一&#xff09;树…

苹果Mac用户下载VS Code(Universal、Intel Chip、Apple Silicon)哪个版本?

苹果macOS用户既可以下载通用版&#xff08;Universal&#xff09;&#xff0c;软件将自动检测用户的处理器并进行适配。 也可以根据型号下载对应CPU的版本&#xff1a; 使用Intel CPU的Mac电脑可下载Intel Chip版本&#xff1b; 使用苹果自研M系列CPU的Mac电脑下载Apple Si…