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

news2024/12/23 18:44:19


✨✨ 欢迎大家来访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.coloradmin.cn/o/1655538.html

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

相关文章

鸿蒙开发之 if/else:条件渲染

ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态&#xff0c;使用if、else和else if渲染对应状态下的UI内容。 使用规则 支持if、else和else if语句。if、else if后跟随的条件语句可以使用状态变量。允许在容器组件内使用&#xff0c;通过条件渲染语句构建不同的子…

数据结构--图。

在前面&#xff0c;我们学习了线性表和树&#xff0c;而接下来我们要学习的图相较于他们就更加复杂。 目录 一.图的有关概念 一.图的有关概念 1.定义 图(graph)G由两个集合V和E组成&#xff0c;记为G&#xff08;VE)。V是顶点的有穷非空集合;E是边的集合,边是V中顶点的无序对…

02-单片机商业项目编程,从零搭建低功耗系统设计

一、本文内容 上一节《01-单片机商业项目编程&#xff0c;从零搭建低功耗系统设计-CSDN博客》已经对事件驱动原理有个基本了解&#xff0c;本节主要就是如何将事件写的更规范&#xff0c;而不是用t_flag这样的标记&#xff0c;写多了可读性也不强&#xff1b;本节结尾总结将提出…

【探索Java编程:从入门到入狱】Day5

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

《ESP8266通信指南》13-Lua 简单入门(打印数据)

往期 《ESP8266通信指南》12-Lua 固件烧录-CSDN博客 《ESP8266通信指南》11-Lua开发环境配置-CSDN博客 《ESP8266通信指南》10-MQTT通信&#xff08;Arduino开发&#xff09;-CSDN博客 《ESP8266通信指南》9-TCP通信&#xff08;Arudino开发&#xff09;-CSDN博客 《ESP82…

驱动比例线圈功率放大器

驱动比例线圈功率放大器是一种用于控制比例电磁铁的电流大小实现被控设备的位移&#xff0c;采用高性能的嵌入式32位微处理器作为运算核心&#xff0c;这些微处理器具有高速指令运行能力&#xff0c;电源24VDC驱动&#xff0c;输入指令兼容性强&#xff0c;输出电流大小可调&am…

云打印怎么保护用户的隐私?

随着互联网的发展&#xff0c;在当下的网络环境下&#xff0c;用户的隐私越来越难以保证安全。特别是对于打印业务来说&#xff0c;盗取用户文件、转卖客户信息的内容时有发生。那么我们作为出色的云打印服务商&#xff0c;该如何保证用户的隐私呢&#xff1f;今天就来给大家介…

正点原子Linux学习笔记(五)FrameBuffer 应用编程

FrameBuffer 应用编程 19.1 什么是 FrameBuffer19.2 LCD 的基础知识19.3 LCD 应用编程介绍使用 ioctl()获取屏幕参数信息使用 mmap()将显示缓冲区映射到用户空间 19.4 LCD 应用编程练习之 LCD 基本操作19.5 LCD 应用编程练习之显示 BMP 图片在 LCD 上显示 BMP 图像在开发板上测…

Java的BIO/NIO/AIO

1. Java中的BIO、NIO和AIO的基本概念及其主要区别 BIO (Blocking I/O): 传统的同步阻塞I/O模型。每个连接创建成功后都需要一个线程来处理&#xff0c;如果连接没有数据可读&#xff0c;则线程会阻塞在读操作上。这种模型简单易理解&#xff0c;但在高并发环境下会消耗大量系统…

【excel】数据非数值导致排序失效

场景 存在待排序列的数值列&#xff0c;但排序失效&#xff0c;提示类型有问题&#xff1a; 解决 选中该列&#xff0c;数据→分列 而后发现提示消失&#xff0c;识别为数字&#xff0c;可正常排序。

ERROR 1045 (28000) Access denied for user ‘root‘@‘IP‘(using password YES/NO)

查看权限 要查看MySQL用户的权限&#xff0c;您可以使用SHOW GRANTS语句。这将列出用户的权限&#xff0c;包括授予的权限和可以授予其他用户的权限。 以下是查看当前用户权限的SQL命令&#xff1a; SHOW GRANTS; 如果您想查看特定用户的权限&#xff0c;可以使用以下命令&…

【漏洞复现】金和OA FileDownLoad接口处存在任意文件读取漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

销售订单分析表-CX_SY_CONVERSION_NO_NUMBER异常

销售订单分析表-CX_SY_CONVERSION_NO_NUMBER异常 这里记录一次发生过的异常报错&#xff0c;和找到原因的过程&#xff1a;

Springboot+Vue项目-基于Java+MySQL的流浪动物管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

华为OD机试 - 计算三叉搜索树的高度 - 二叉树(Java 2024 C卷 100分)

华为OD机试 2024C卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷C卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测试…

每日Attention学习1——Parallel Aggregation Pyramid Pooling Module

模块出处 [CVPR 23] [link] [code] PIDNet: A Real-time Semantic Segmentation Network Inspired by PID Controllers 模块名称 Parallel Aggregation Pyramid Pooling Module (PAPPM) 模块作用 多尺度特征提取&#xff0c;更大感受野 模块结构 模块代码 import torch imp…

C++聊天服务器数据库创建

创建数据库chat show databases&#xff1a;展示所有的数据库 create database chat&#xff1a;创建一个数据库chat use chat&#xff1a;使用数据库 创建表User、Friend、AllGroup、GroupUser、OfflineMessage 表User包含&#xff1a;用户id、用户名、用户密码、当前登录…

jsp驾校管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 驾校管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用serlvetdaobean mvc 模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发…

二叉树习题汇总

片头 嗨&#xff01;大家好&#xff0c;今天我们来练习几道二叉树的题目来巩固知识点&#xff0c;准备好了吗&#xff1f;Ready Go ! ! ! 第一题&#xff1a;二叉树的最大深度 解答这道题&#xff0c;我们采用分治思想 1. 递归子问题&#xff1a;左子树的高度和右子树的高度 …

绿盟之旅——一段安全实习结束

去年&#xff0c;因为着急找实习&#xff0c;拿着简历就开始海投&#xff0c;当时想的是有人让我去就谢天谢地了&#xff0c;第一个约我面试的就是绿盟&#xff0c;也很顺利的通过了面试&#xff0c;当时让我选择在上海还是北京&#xff0c;我选择的是上海&#xff0c;因为学校…