类于对象(上)--- 类的定义、访问限定符、计算类和对象的大小、this指针

2024-03-23 02:36

本文主要是介绍类于对象(上)--- 类的定义、访问限定符、计算类和对象的大小、this指针,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        在本篇中将会介绍一个很重要和很基础的Cpp知识——类和对象。对于类和对象的篇目将会有三篇,本篇是基础篇,将会介绍类的定义、类的访问限定符符和封装、计算类和对象的大小、以及类的 this 指针。目录如下:

目录

1. 关于类

1.1 类的定义

2 类的访问限定符及封装

 1.3 类的实例化——对象

3 类与对象的空间大小

3.1 结构体内存对齐规则

4. this 指针

4.1 this 指针的特性 

1. 关于类

        首先需要先区分两个名词:面向对象面向过程。面向对象的思想主要发挥在Cpp中,而面向过程的思想主要发挥在C语言中。

        面向过程:关注过程,分析问题的每一个步骤,然后提高函数对每一个步骤都逐一解决。就好像点外卖,首先我需要打开外卖app,然后决定要吃什么,然后找到对应的店铺,接着下单,等待到餐以及去取餐。

        面向对象:关注的是对象,将一件事情拆分为不同的对象,依靠对象之间的交互完成。比如点外卖分为了三个对象:我、外卖员、商家。我负责下单,外卖员负责送外卖,商家负责做餐。将一件事情先分给不同的对象,然后让对象之间相互配合完成这件事情。

1.1 类的定义

        在C++的类,与C语言中的结构体很相似,都用来同一定义我们的自定义类型变量。但是在Cpp中的类不仅仅可以定义成员变量,还可以定义成员函数

        对于类的定义如下:

class ClassNmae {//成员函数 and 成员变量
};struct ClassName {//成员函数 and 成员变量
};

        其中 class、struct定义类的关键字,ClassName为类的名字,{} 中为类的主体注意类定义结束时后面分号不能省略。(对于 class 和 struct 都可以用于定义类,比较常用的是 class,将会在下文中介绍这两者的区别)。
        对于类体中内容称为类的成员:类中的变量称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数

        对于成员函数的声明定义方法:1.声明和定义放在类体中;2.声明与定义分离。

        1.声明与定义放在类体中:

class Stack {
public:void Init() {_a = (int*)malloc(sizeof(int) * 4);_capacity = 4;_top = 0;}void Push(int x) {if (_top == _capacity) {int newCapacity = 2 * _capacity;int* tmp = (int*)realloc(_a, sizeof(int) * newCapacity);if (tmp == nullptr) {perror("realloc failed:");exit(1);}_a = tmp;_capacity = newCapacity;}_a[_top++] = x;}void Pop() {assert(_top != 0);_top--;}//...
private:int* _a;int _top;int _capacity;
};

        2.声明与定义分离:

// Stack.h
class Stack {
public:void Init();void Push(int x);void Pop();//...
private:int* _a;int _top;int _capacity;
};// Stack.c
void Stack::Push(int x) {if (_top == _capacity) {int newCapacity = 2 * _capacity;int* tmp = (int*)realloc(_a, sizeof(int) * newCapacity);if (tmp == nullptr) {perror("realloc failed:");exit(1);}_a = tmp;_capacity = newCapacity;}_a[_top++] = x;
}void Stack::Init() {_a = (int*)malloc(sizeof(int) * 4);_capacity = 4;_top = 0;
}void Stack::Pop() {assert(_top != 0);_top--;
}

        如上就是成员函数的声明定义方法,通常更推荐使用第二种,将声明与定义分离,不过需要定义与声明分离之后的定义形式。

2 类的访问限定符及封装

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

        访问限定符:public(公有)、private(私有)、protected(保护)。

        访问限定符说明:

        1.public修饰的成员在类外可以直接被访问。

        2.protected、private修饰的成员在类外不能直接被访问。

        3.访问权限的作用域从该访问限定符出现的位置开始到下一个访问限定符出现为止,若后面没有访问限定符,则到 } 结束。

        4.class 的默认访问权限为 private,struct 为public

        面向对象的三大特性:封装、继承、多态。本篇将主要介绍封装。

        封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互。(简单来说,就是将类中的部分变量进行隐藏(使用private修饰),仅仅放出一些变量(public修饰)用于和类外的变量进行交互)。

        如下:

        图中显示,并不能直接访问由 private 修饰的变量,只能通过由 public 修饰的成员函数来进行对对象进行操作。

        类的作用域,我们既然将类进行了封装,那么对于类就存在一个新的作用域。类的所有成员都在类的作用域中。类外定义成员时,需要使用 “ : : ” 作用域操作符指明成员属于哪一个类域。

 1.3 类的实例化——对象

        用类类型创建对象的过程叫做类的实例化

        类是对对象进行描述的,类似于一个模板一样的东西,我们可以看见一个用类创建出来的对象有哪些成员变量和成员函数。对于类来说,并不占有空间。

        所以一个类可以实例化多个对象,实例化出来的对象占据实际的物理空间,存储类的成员。如下图:

        使用 Stack 类定义出两个对象。

3 类与对象的空间大小

        在C语言中,我们计算时间结构体的大小使用的是内存对齐规则,那么我们在Cpp中该使用什么样的办法计算一个类或对象所占空间大小呢?我们在Cpp中同样使用的是内存对齐规则,不过Cpp中的内存对齐规则和C语言中的内存对齐规则存在些许不同,比如在Cpp中不仅仅存在成员变量,还存在成员函数,成员函数的大小该如何计算

        我们先计算一下对于以上 Stack 的一个对象的大小:

        如上图所示,计算出来的结果显示为16,计算的大小也刚好是 Stack 中成员变量的大小,并没有计算成员函数的大小。说明在Cpp中的内存对齐规则中,并不会计算成员函数的大小

        对于Cpp中的成员函数来说,并不是每个实例化的对象都存在独立空间的成员函数成员函数是所有同样类的对象的共享成员函数成员函数的存放在一个公共代码区

        但是若一个类只存在成员函数而没有成员变量,或则成员函数和成员变量都没有呢,那这个类的大小是0吗?如下:

        如上图所示,当类只存在成员函数而没有成员变量时,计算出来的空间为1。那是因为在Cpp的标准中,编译器给空类一个字节来唯一标识这个类的对象。

3.1 结构体内存对齐规则

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

        2. 其他成员变量要对齐到某个数字的整数倍的地址处。(注:对齐数 = 编译器默认的一个对齐数 与 该成员变量大小的较小值)

        3. 结构体的总大小:最大对齐数的整数倍(所有成员的类型的最大者和编译器默认对齐数的较小值)

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

4. this 指针

        如下图,我们在成员函数中的变量名前增加了一个 this 指针:

        为什么我们加入了一个 this 指针,我们的程序还能正确的运行呢?

        这和我们调用成员函数相关,当我们在调用成员函数时,我们并不需要将成员变量传入成员函数中,而是直接调用(如图中的 Print 函数),成员函数就可以根据对应的成员变量而调用。这是因为:

        Cpp编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数 this ,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

4.1 this 指针的特性 

        成员函数中的 this 指针存在一些特性以及使用时的细节,将在以下给出。       

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

        2. this 指针只能存在于成员函数之中,并不能在成员函数外使用。

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

        4. this 指针是“成员函数”第一个隐含的指针形参,一般情况下由exc寄存器自动传递,不需要由用户来操作。

        可以理解这两个函数是相同的,但是并不能写成第二种,第二种会报错。

        this 指针的使用细节如下:

        如上图所示,当我们在一个类中定义了两个 Print 函数,但是真正运行起来时,只有 Print1 可以正常的运行,而 Print2 并不能正常的运行。这是因为:

        当我们执行 Print1 时,成员函数中的 this 指针直接拷贝了 nullptr,然后执行 Print1 中的语句。但是当执行 Print2 时,this 指针拷贝了 nullptr,然后在接下来的语句中直接调用了 nullptr 处的 _a 变量,此处的变量并没有被开发,所以运行起来会报错。

这篇关于类于对象(上)--- 类的定义、访问限定符、计算类和对象的大小、this指针的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/836867

相关文章

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化

go 指针接收者和值接收者的区别小结

《go指针接收者和值接收者的区别小结》在Go语言中,值接收者和指针接收者是方法定义中的两种接收者类型,本文主要介绍了go指针接收者和值接收者的区别小结,文中通过示例代码介绍的非常详细,需要的朋友们下... 目录go 指针接收者和值接收者的区别易错点辨析go 指针接收者和值接收者的区别指针接收者和值接收者的

Pyserial设置缓冲区大小失败的问题解决

《Pyserial设置缓冲区大小失败的问题解决》本文主要介绍了Pyserial设置缓冲区大小失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录问题描述原因分析解决方案问题描述使用set_buffer_size()设置缓冲区大小后,buf

Java Optional避免空指针异常的实现

《JavaOptional避免空指针异常的实现》空指针异常一直是困扰开发者的常见问题之一,本文主要介绍了JavaOptional避免空指针异常的实现,帮助开发者编写更健壮、可读性更高的代码,减少因... 目录一、Optional 概述二、Optional 的创建三、Optional 的常用方法四、Optio

详解C++中类的大小决定因数

《详解C++中类的大小决定因数》类的大小受多个因素影响,主要包括成员变量、对齐方式、继承关系、虚函数表等,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 非静态数据成员示例:2. 数据对齐(Padding)示例:3. 虚函数(vtable 指针)示例:4. 继承普通继承虚继承5.

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

使用Dify访问mysql数据库详细代码示例

《使用Dify访问mysql数据库详细代码示例》:本文主要介绍使用Dify访问mysql数据库的相关资料,并详细讲解了如何在本地搭建数据库访问服务,使用ngrok暴露到公网,并创建知识库、数据库访... 1、在本地搭建数据库访问的服务,并使用ngrok暴露到公网。#sql_tools.pyfrom

Java实现将byte[]转换为File对象

《Java实现将byte[]转换为File对象》这篇文章将通过一个简单的例子为大家演示Java如何实现byte[]转换为File对象,并将其上传到外部服务器,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言1. 问题背景2. 环境准备3. 实现步骤3.1 从 URL 获取图片字节数据3.2 将字节数组

Javascript访问Promise对象返回值的操作方法

《Javascript访问Promise对象返回值的操作方法》这篇文章介绍了如何在JavaScript中使用Promise对象来处理异步操作,通过使用fetch()方法和Promise对象,我们可以从... 目录在Javascript中,什么是Promise1- then() 链式操作2- 在之后的代码中使