C++的类与对象(二):this指针

news2024/11/17 10:52:46

目录

结构体内存对其规则

相关面试题

this指针

相关面试题

右箭头选择运算符->

 C语言和C++实现Stack的对比


结构体内存对其规则

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

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

对齐数 = 编译器默认对齐数与该成员大小的较小值(vs中默认为8)

3、结构体总大小为:最大对齐数的整数倍

4、如果是嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有结构体的最大对齐数的整数倍

相关面试题

1、内存对齐会导致空间闲置,那为什么要进行内存对齐?

①提高读取效率

②硬件(地址总线)规定内存一次读取4(32根线)或8(64根线)个字节

③如果不内存对齐可能会对同一块内存需要多次读取

采用内存对齐:一次读取4个字节,先读取一个字节的i后将其它三个没用的字节丢掉,然后继续读取四个字节的a

不采用内存对齐:一次读取4个字节,先读取i的一个字节和a的前三个字节后将a的前三个字节丢掉,然后继续读取a剩余的一个字节,最后将那三个字节和最后一个字节组合才能得到a,麻烦

2、如何让结构体按照指定的对齐参数进行对齐?

答:#pragma pack(指定对齐数) 

this指针

#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;
	int _month;
	int _day;
};

int main()
{
	Date d1, d2;
	d1.Init(2022, 1, 27);
	d2.Init(2022, 2, 28);
	d1.Print();
	d2.Print();
	return 0;
}

        Data类中有Init和Print两个成员函数,不论是d1还是d2它们调用的Init函数和Print函数在实现上是一样的,为什么编译器可以做到d1调用Print函数就会打印d1对象的成员变量组成的日期,d2调用Print函数就会打印d2对象的成员变量组成的日期?

        这是因为每一个成员函数都会多一个叫this的指针,它是成员函数的第一个参数,那么对于Print函数其实我们可以将其理解成:

void Print(Data* this)
{
	cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
void Print(Data* this,int year,int month,int day)
{
	_year = year;    //实际上就是this->_year = year;
    _month = month;  //this->_month = month;
    _day = day;      //this->_day = day;
}    

在调用的时候,编译器也会将其处理成这样的形式:

d1.Print(&d1);
d2.Print(&d2);

将调用对象的地址当作实参传递给函数(重点!!!)

~传入谁的地址就访问谁,但是这个传递是隐式的~

注意事项:

1、this指针实际上的格式是:类名* const this,即指向的内容可以更改,但本身不能修改

2、形参和实参位置不能显式的写this指针

3、函数内部可以使用this指针

相关面试题

1、this指针存在哪里?堆?栈?静态区?常量区?对象里面?

对象里面(×):计算类的大小时,有三个整型成员变量的类的大小是12字节,如果this指针存放在对象中类的大小应该是大于12的

常量区×被const修饰的变量不会存放在常量区,const char*p = "xzxxxxxx"中,p作为指针是存放在栈上的,"xzxxxxxx"是被存放在常量区的,这段代码的意思是将字符串存储的地址给了p,打印p显示出来的就是常量区的地址,被const修饰的变量i的地址和变量j的地址相近,由于变量j是存放在栈上的,可以推出被const修饰的变量i也是存放在栈上的

不加void*强转,p打印出来的就是字符串"xzxxxxxx" ,强转后打印的是字符串的地址

静态区(×):被static修饰的变量和全局变量才存放在静态区

堆(×):calloc、malloc、realloc开辟的才在堆上

栈(): this指针是成员函数的形参,而形参跟指针一样是存放在栈上的

有些编译器(比如vs)会将this指针存放在寄存器上,频繁访问this指针时效率更高

2、下面程序编译运行结果是: A、编译报错 B、运行崩溃 C、正常运行

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

        指向A类的指针p被初始化为了空指针,由于成员函数Print不在A类的对象中,故当p访问Print函数时,并不会解引用p获得p中存放的地址(获得了也没用,Print函数根本不在p所指向的那片空间上,对此编译器表示不做无用功)通过汇编指令我们也可以发现这里执行的是将p指针中存放的值存入rcx寄存器中,然后将寄存器 RCX 的值写入到栈上偏移为 8 字节处,对于Print函数汇编指令就直接是call Print的地址并没有说在p指向的空间中寻找该函数,从侧面印证如果指针解引用后要访问的空间不在指针指向的空间上指针不会解引用的结论:

        实际上这一步就是向this指针传值(nullptr,因为p是空指针),因为在之前说过d1.Print(),其实括号内还有一个隐藏传递的内容&d1,只有这样编译器才能分辨到底要打印哪个对象的内容,d1.Print() => d1.Print(&d1)、(*p).Print() => (*p).Print(*p)

有空指针,传递了空指针,但是没有对该空指针进行解引用(因为没必要)所以没有问题

我们是好朋友(可解引用),(一定吗)我们是好朋友吗?有钱(非空)的时候才是

结论:有->编译器在编译时不一定会让它成功的解引用,只有当指针试图访问的内容的确在指针所指向的那片空间上,才会进行解引用

 3、下面程序编译运行结果是: A、编译报错 B、运行崩溃 C、正常运行

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

①p仍然是空指针,仍然不会进行解引用,向this指针传递的仍然是空指针

②但是,由于_a(A类的成员变量)是在p所指向的空间的,所以会发生解引用

③在打印_a的时候,实际上的代码应该是this->_a,即(*this)._a,而this指针是空指针,对空指针的解引用会报错

右箭头选择运算符->

基本概念:利用指针访问结构中的成员是很普遍的,因此C语言专门提供了一种运算符:->

注意事项:

1、运算符->是运算符*和运算符.的组合

new_node->value = 10; => (*new_node).value = 10;

        先对new_node解引用(获取该指针所指向内存地址上存储的值)从而找到它所指向的结构,然后再选择结构或对象中的成员value

2、由于运算符->产生左值,所以可以在任何允许普通变量的地方使用它

 C语言和C++实现Stack的对比

C语言实现:

typedef int DataType;
typedef struct Stack
{
     DataType* array;
     int capacity;
     int size;
}Stack;

void StackInit(Stack* ps)
{
     assert(ps);
     ps->array = (DataType*)malloc(sizeof(DataType) * 3);
     if (NULL == ps->array)
     {
         assert(0);
         return;
     }

     ps->capacity = 3;
     ps->size = 0;
}

void StackDestroy(Stack* ps)
{
     assert(ps);
     if (ps->array)
     {
         free(ps->array);
         ps->array = NULL;
         ps->capacity = 0;
         ps->size = 0;
     }
}

void CheckCapacity(Stack* ps)
{
     if (ps->size == ps->capacity)
     {
         int newcapacity = ps->capacity * 2;
         DataType* temp = (DataType*)realloc(ps->array, 
         newcapacity*sizeof(DataType));
             if (temp == NULL)
             {
                 perror("realloc申请空间失败!!!");
                 return;
             }
         ps->array = temp;
         ps->capacity = newcapacity;
    }
}

void StackPush(Stack* ps, DataType data)
{
     assert(ps);
     CheckCapacity(ps);
     ps->array[ps->size] = data;
     ps->size++;
}

int StackEmpty(Stack* ps)
{
     assert(ps);
     return 0 == ps->size;
}

void StackPop(Stack* ps)
{
     if (StackEmpty(ps))
     return;
     ps->size--;
}

DataType StackTop(Stack* ps)
{
     assert(!StackEmpty(ps));
     return ps->array[ps->size - 1];
}

int StackSize(Stack* ps)
{
     assert(ps);
     return ps->size;
}

int main()
{
 Stack s;
 StackInit(&s);
 StackPush(&s, 1);
 StackPush(&s, 2);
 StackPush(&s, 3);
 StackPush(&s, 4);
 printf("%d\n", StackTop(&s));
 printf("%d\n", StackSize(&s));
 StackPop(&s);
 StackPop(&s);
 printf("%d\n", StackTop(&s));
 printf("%d\n", StackSize(&s));
 StackDestroy(&s);
 return 0;
}

在使用C语言实现时,Stack相关操作函数有以下特性:

  •  每个函数的第一个参数都是Stack*
  • 函数中必须要对第一个参数检测,因为该参数可能为NULL
  • 函数中都是通过Stack*参数操作栈的
  • 调用时必须传递Stack结构体变量的地址

结论:结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据的方式分离,而且实现上较为复杂,涉及到大量指针操作,稍不注意就会出错 

C++实现:

typedef int DataType;
class Stack
{
public:
     void Init()
     {
         _array = (DataType*)malloc(sizeof(DataType) * 3);
         if (NULL == _array)
         {
             perror("malloc申请空间失败!!!");
             return;
         }
         _capacity = 3;
         _size = 0;
     }

     void Push(DataType data)
     {
         CheckCapacity();
         _array[_size] = data;
         _size++;
     }
 
    void Pop()
     {
         if (Empty())
         return;
         _size--;
     }

 DataType Top(){ return _array[_size - 1];}

 int Empty() { return 0 == _size;}

 int Size(){ return _size;}

 void Destroy()
 {
     if (_array)
     {
         free(_array);
         _array = NULL;
         _capacity = 0;
         _size = 0;
     }
 }

private:
     void CheckCapacity()
     {
         if (_size == _capacity)
         {
            int newcapacity = _capacity * 2;
            DataType* temp = (DataType*)realloc(_array, newcapacity *
            sizeof(DataType));
             if (temp == NULL)
             {
                 perror("realloc申请空间失败!!!");
                 return;
             }
             _array = temp;
             _capacity = newcapacity;
         }
     }

private:
     DataType* _array;
     int _capacity;
     int _size;
};

int main()
{
 Stack s;
 s.Init();
 s.Push(1);
 s.Push(2);
 s.Push(3);
 s.Push(4);
 
 printf("%d\n", s.Top());
 printf("%d\n", s.Size());
 s.Pop();
 s.Pop();
 printf("%d\n", s.Top());
 printf("%d\n", s.Size());
 s.Destroy();
 return 0;
}

        C++中可以通过类将数据以及操作数据的方法进行完美结合,通过访问权限可以控制哪些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,而且每个方法都不需要传递Stack*的参数了,编译器编译后该参数会自动还原,即C++中的Stack*参数是编译器维护的,C语言中需要用户自己维护(看起来是s.Init或者s.Push(1),实际上却是s.Init(&s)和s.Push(&s,1))

~over~

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

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

相关文章

Jumpserver 使用

目录 1、邮箱设置 2、用户管理 创建用户组 创建用户 3、资产管理 创建被管理机器的账号 添加资产 测试资产联通 推送账号 4、权限管理 资产授权 建命令过滤器 测试 使用 Xshell 登录 5、查看命令记录 查看回放 1、邮箱设置 修改当前站点的URL为本机IP或者jumps…

3.8题目练习

1.数的计算 题目其他部分&#xff1a; 这道题目可以先试着自己推一下找一下规律&#xff0c;题目给出的数字是一个小于等于一千的数字&#xff0c;这里我们不妨考虑一下从小往大推&#xff0c;我们从1开始&#xff0c;用f[i]来表示对应i共有几个合法的序列&#xff1a; 当n为1…

设计模式大题做题记录

设计模式大题 09年 上半年&#xff1a; 09年下半年 10年上半年 10年下半年 11年上半年 11年下半年 12年上半年 12年下半年 13年上半年 13年下半年

MindOpt优化器: 浅谈版本0.x和1.x之间API的差异

Mindopt 是一个优化求解器&#xff0c;如果它有两个主要版本——0.xx和1.x.x&#xff08;最新版本1.1.1&#xff09;&#xff0c;它们代表着软件开发的两个不同阶段。版本1.0.0表示软件的一个大的里程碑&#xff0c;代表着软件第一个正式的“成熟”发布版本&#xff0c;而0.25是…

用开发CesiumJS模拟飞机飞行应用(一,基本功能)

本部分向您展示如何构建您的第一个 Cesium 应用程序&#xff0c;以可视化模拟从旧金山到哥本哈根的真实航班&#xff0c;并使用 FlightRadar24收集的雷达数据。您将学习如何&#xff1a; 在网络上设置并部署您的 Cesium 应用程序。 添加全球 3D 建筑物、地形和图像的基础图层。…

高校水电预付费系统有什么作用?

Hey小伙伴们&#xff0c;今天来聊聊咱们大学里的一个超级实用的设施——水电预付费系统&#xff01;你是不是还在为每个月的水电账单烦恼呢&#xff1f;别担心&#xff0c;这个系统就是你的生活小帮手&#xff01; 首先&#xff0c;想象一下这 样的场景&#xff1a;开学初&…

Python通过SFTP实现网络设备配置备份

一、背景 为了防止网络设备意外损坏&#xff0c;导致配置文件无法恢复&#xff0c;可以通过将网络设备的配置文件备份到本地电脑上。 一般情况下&#xff0c;设备支持通过FTP、TFTP、FTPS、SFTP和SCP备份配置文件。其中使用FTP和TFTP备份配置文件比较简单&#xff0c;但是存在…

JAVA实现图像取模

JAVA对图像取模 就是图片变成点阵 原图 取模效果图 代码如下&#xff1a; public static void main(String[] args) throws IOException {try {// 读取图像文件BufferedImage image ImageIO.read(new File("C:/Users/xiaol/Desktop/img/0.jpg"));// 定义阈值&am…

模拟实现strlen函数

一、逐个计数法 #include<assert.h> #include<stdio.h>size_t my_strlen(const char* p) {int count 0;assert(p);//断言while (*p ! \0){p;count;}return count; }int main() {char str[] "hello world";size_t len my_strlen(str);printf("%d…

听 GPT 讲 client-go 源代码 (23)

分享更多精彩内容&#xff0c;欢迎关注&#xff01; File: client-go/kubernetes/scheme/register.go 在client-go项目中&#xff0c;client-go/kubernetes/scheme/register.go文件的作用是进行Kubernetes API对象的Scheme注册。 Scheme是一个用于序列化和反序列化Kubernetes A…

抖音视频评论采集工具|短视频批量下载软件

《抖音视频评论采集工具——解放双手的智能助手》 在数字化时代&#xff0c;抖音视频已成为人们获取信息、娱乐放松的重要来源之一。针对抖音视频评论的采集需求&#xff0c;我们推出了一款功能强大的软件&#xff0c;让您轻松实现评论批量提取&#xff0c;QQ:290615413提高工作…

stm32学习笔记:SPI通信协议原理(未完)

一、SPI简介(serial Peripheral Interface&#xff08;串行 外设 接口&#xff09;) 1、电路模式&#xff08;采用一主多从的模式&#xff09;、同步&#xff0c;全双工 1 所有SPI设备的SCK、MOSI、MISO分别连在一起 2 主机另外引出多条SS控制线&#xff0c;分别接到各从机的S…

二维码门楼牌管理系统应用场景:商业与零售业发展的助推器

文章目录 前言一、二维码门楼牌管理系统的基本功能二、商业和零售业中的应用场景三、二维码门楼牌管理系统的优势分析四、结论 前言 在数字化时代的浪潮中&#xff0c;二维码门楼牌管理系统凭借其独特的优势&#xff0c;正在逐步成为商业和零售业发展的新宠。它不仅能够为商家…

LLMs在BI中的运用

现有的BI分析存在以下一些问题&#xff1a; 原始数据入库规整要求比较高。业务过程产生的数据需要经过一些清洗等前置处理后才能够进行后续的BI分析使用。业务部门的数据分析过度依赖于技术部门。而业务与技术之间由于对分析需求理解上的差异&#xff0c;往往需要繁琐的沟通与…

六、长短时记忆网络语言模型(LSTM)

为了解决深度神经网络中的梯度消失问题&#xff0c;提出了一种特殊的RNN模型——长短期记忆网络&#xff08;Long Short-Term Memory networks, LSTM&#xff09;&#xff0c;能够有效的传递和表达长时间序列中的信息并且不会导致长时间前的有用信息被忽略。 长短时记忆网络原理…

springboot244基于SpringBoot和VUE技术的智慧生活商城系统设计与实现

智慧生活商城系统的设计与实现 摘 要 计算机网络发展到现在已经好几十年了&#xff0c;在理论上面已经有了很丰富的基础&#xff0c;并且在现实生活中也到处都在使用&#xff0c;可以说&#xff0c;经过几十年的发展&#xff0c;互联网技术已经把地域信息的隔阂给消除了&…

【wine】winetricks部署一个windows xp 应用程序的基础运行环境

AI 的资料 我想基于wintricks的“安装windows dll 或组件”功能&#xff0c;安装一个基础的windows xp运行环境&#xff0c;应当安装那些项目&#xff1f; 为了基于winetricks创建一个基础的Windows XP运行环境&#xff0c;您应该考虑安装以下项目以提高兼容性&#xff1a; 核…

非常好用的Java诊断工具Arthas(阿尔萨斯)

小伙伴们好&#xff0c;欢迎关注&#xff0c;一起学习&#xff0c;无限进步 文章目录 Window 安装Linux 安装SpringBoot 整合 arthas其他使用场景 Arthas是阿里巴巴开源的一款Java诊断工具&#xff0c;可以用于线上诊断问题、监控应用性能等。支持 Linux/Mac/Windows&#xff0…

3D-Genome | Hi-C互作矩阵归一化指南

Hi-C 是一种基于测序的方法&#xff0c;用于分析全基因组染色质互作。它已广泛应用于研究各种生物学问题&#xff0c;如基因调控、染色质结构、基因组组装等。Hi-C 实验涉及一系列生物化学反应&#xff0c;可能会在输出中引入噪声。随后的数据分析也会产生影响最终输出噪声&…

【C++】浅谈 vector 迭代器失效 深拷贝问题

目录 前言 一、底层空间改变 【错误版本1】 &#x1f31f;【解答】正确版本 ​ 【错误版本2】 &#x1f31f;【解答】正确版本 二、指定位置元素的删除操作--erase 【错误版本1】 &#x1f31f;【解答】 【错误版本2】 &#x1f31f;【解答】 三、深拷贝问题 前言 迭…