【C++】初识类和对象

引言

在C语言中,我们用结构体来描述一个复杂的对象,这个对象可能包括许多的成员,如用结构体描述一个学生的成绩,或者描述一个日期等。

struct Date
{int _year;int _month;int _day;
};

如上是一个描述日期的结构体定义,里面可以有年、月、日这些成员,但是不能在里面有函数的声明或定义,这就使得和这个日期对象有关的函数需写在外部,在命名时就需要防止冲突。而且C语言的结构体对成员变量的保护不到位,可以随意访问对象的成员变量,非常不安全。因此,C++在兼容C语言 struct 的用法的同时将它升级为了类,并且C++喜欢用 class 关键字来定义类


类的定义  

class 类名
{// 类体:由成员函数和成员变量组成}; // 末尾分号不能落下

类体中的内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者 成员函数

struct ListNode
{//struct ListNode* next; // C语言写法ListNode* next;          // C++可以这样用int val;
};

类名就是类型,所以不需要再像C语言一样加上 struct 来表示类型。 


接下来我们来定义一个简单的日期类:

class Date
{void Init(int year, int month, int day){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};

我们在类中写了一个 Init 函数用来初始化我们的对象,我们试着用这个类来实例化对象。

int main()
{Date d1;d1.Init(2024, 1, 22);return 0;
}

用类的类型创建对象的过程,称为类的实例化。可以把类理解为设计图纸,用类实例化对象就是用图纸创建出具体的实物。但是目前我们的程序是有问题的,这个函数运行不了,因为目前这个函数是私有的,在类外面无法访问,这涉及到访问限定符的问题。


访问限定符 

访问限定符包括:public(公有)、private(私有)、protected(保护)public 修饰的成员在类外可以直接被访问,而 private 和 protected 修饰的成员在类外不能直接被访问,现阶段暂时认为private 和 protected 没有什么区别。由于 struct 需要兼容C语言的用法,因此类中没使用访问限定符时默认是共有的,而 class 默认是私有的,因此我们上面那段代码无法运行。

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};

加上访问限定符让成员函数的访问权限为公有就能运行了,成员变量一般都定义为私有。值得注意的是,如果成员函数在类中定义,编译器可能会将其当成内联函数处理,因此可以将较短的函数定义在类中,而较长的函数可以将声明与定义分离。


如果要将成员函数的声明和定义分离,在类外定义函数时需要在函数名字前使用域作用限定符指定类名,因为类定义了一个新的作用域。如下所示:

class Date
{
public:void Init(int year, int month, int day);private:int _year;int _month;int _day;
};void Date::Init(int year, int month, int day)
{_year = year;_month = month;_day = day;
}

指定类名相当于告诉编译器这个函数是哪个类的,不然在涉及成员变量时编译器找不到这个变量的定义。 


类的实例化

类是对对象进行描述的,就像一张设计图纸,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。在类中定义的成员变量都只是声明,没有内存空间。只有当使用这个类去实例化对象的时候,它们才会作为该对象的专属成员变量而定义,也就是有了空间。

一个类可以实例化出多个对象,那它们是不是都有专属于自己的成员变量呢?是的,来看这个例子

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(2024, 1, 22);d2.Init(2023, 1, 22);d1.print();d2.print();return 0;
}

这里我们用 Date 类实例化了两个对象比对它们进行初始化,然后借助 print 函数打印了它们各自的成员变量,很明显它们都有各自的成员变量。可是在调用函数时明明没有传参,它们的成员变量是怎么被准确找到的呢?这就是 this 指针的功劳了。


this 指针

其实每个“非静态的成员函数“的第一个参数都是一个隐藏的 this 指针,该指针指向的是当前对象(函数运行时调用该函数的对象)。在函数体中所有涉及“成员”的操作,都是通过该指针去访问。只不过这个过程我们看不到,因为用户不需要传递该指针编译器会自动帮我们传递与使用该指针。

class Date
{
public:// 第一个形参是帮助理解,用户不能自己写// 为了篇幅着想,此处 Init 函数只给出声明,定义上面有void Init(Date* const this, int year, int month, int day);void print(Date* const this) { // 在函数中显示使用 this 是可以的,大部分情况不需要,交给编译器cout << this->_year << " " << this->_month << " " << this->_day << endl; }private:int _year;int _month;int _day;
};int main()
{Date d1, d2;// 同样第一个实参用于帮助理解,用户不能自己传d1.Init(&d1, 2024, 1, 22);d1.print(&d1);return 0;
}

通过这段代码你应该知道对象的成员变量如何被访问了,实际上是编译器在负重前行,默默地帮我们传递对象的地址,默默地使用 this 指针访问成员变量

注意我给出的 this 指针的类型,它被 const 所修饰,因此不能改变指向,也非常合理,要是哪位大牛在函数内把 this 指针改了可就不好玩了。 

总结一下: this 指针的类型为:类类型* const,它是“成员函数”第一个隐含的指针形参,当对象调用成员函数时,自动将对象地址作为实参传递给this形参,并且只能在“成员函数”的内部使用,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递。


类(对象)的大小

int main()
{Date d1;d1.Init(2024, 1, 22);cout << sizeof(d1) << endl;return 0;
}

依旧是上面的日期类,你认为对象 d1 的大小是多少呢?通过前面的学习,成员变量存储在对象中应该已经是大家的共识了,那么成员函数会不会存在对象里呢?我们运行下程序看看。

 

由结果可见,成员函数是不存在对象中的。这也很符合实际,一个类可以实例化出那么多对象,要是每个对象都存一次函数,那岂不是太浪费空间了。事实上,成员函数存放在公共的代码段,大家都能调用。


好了现在你已经知道一个类的大小就是该类中”成员变量”之和,并且要注意内存对齐。那么请问 A类的大小又是多少?

class A
{};int main()
{cout << sizeof(A) << endl;return 0;
}

简单,没有成员变量,所以大小是0,是这样吗?那么如果我用这个类实例化一个对象,你怎么表示这个对象存在过呢?简单来说就是你怎么表示这个对象。因此,对于没有成员变量的类(就算它有成员函数也一样,对象中不存储成员函数),其实例化对象大小为 1 个字节,这个字节不存储有效数据,只是来唯一标识这个类的对象


现在问题又来了,this 指针为什么不存在对象中呢?首先 this 指针是形参,形参一般都是存在栈上的,也就是函数栈帧中,函数结束它就销毁了。但在 vs 下,this 指针通常存储在 ecx 寄存器中,因为它需要频繁使用,而且本身大小较小,将其存放在寄存器中可以提高访问速度。


现在补充一下内存对齐的知识:

1. 第一个成员变量存储在结构体偏移量为0的地址,即从结构体的起始位置开始。
2. 其他成员变量要
对齐到对齐数的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数与 该成员大小较小值。 (VS中默认的对齐数为8)
3. 结构体总大小为:
最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


有趣的程序

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

该程序可以正常运行,虽然 p 是空指针,但是并没有涉及到访问空指针的问题,因为成员函数不存放在对象中,通过 p 能调用函数只是因为 p 指向的对象是 A 类的,而 Print 函数也是 A 类的。随后在函数内也没有访问成员变量,因此程序没问题。如图所示:

这里 p 的作用主要有两个:第一,让编译器知道调用的是 A 类里的 Print 函数。第二,调用函数时顺便把对象的地址,也就是 p 本身传给了形参 this 指针。

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

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

相关文章

week2day1 列表操作()

一. 列表相关操作 1.列表支持的数学运算符&#xff1a; a . 、* 列表1 列表2 将两个列表合并成一个新的列表 list [10,20,30] list2 [340,50,60] print(list list2) [10, 20, 30, 340, 50, 60]b. 列表 *N / N* &#xff08;N是正整数&#xff09; 将列表中的元素重复…

vue常用指令有哪些

Vue常用的指令包括&#xff1a; v-model&#xff1a;实现双向数据绑定&#xff0c;用于表单元素的值与Vue实例的数据属性进行关联。v-if、v-else-if、v-else&#xff1a;用于条件性地渲染DOM元素&#xff0c;根据表达式的真假来决定是否渲染元素。v-show&#xff1a;通过改变C…

超融合基础架构理解

1 超融合基础架构 1.1 定义 超融合基础架构&#xff08;Hyper-converged infrastructure&#xff0c;缩写为HCI&#xff09;&#xff0c;是一种集成了存储设备及虚拟运算的信息基础架构框架。在这样的架构环境中&#xff0c;同一厂商的服务器与存储等硬件单元&#xff0c;搭配…

【网站项目】基于SSM的263货物进销管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

探索Docker-Compose:从基础到高级命令全解析

探索Docker-Compose&#xff1a;从基础到高级命令全解析 引言Docker-Compose基础1. Docker-Compose简介2. 安装Docker-Compose3. 编写第一个Compose文件4. 基本命令 Docker-Compose文件结构解析1. 理解docker-compose.yml2. 基本组件3. 文件示例4. 配置项解析 常用Docker-Compo…

洛谷P1319 压缩技术(C语言)

这样一道入门题目&#xff0c;本来可以用for循环直接操作&#xff0c;但作者异想天开(xian de dan teng)地把所有数据登记在一个数组里面&#xff0c;然后再统一按格式输出。也就是定义一个数组Map&#xff0c;大小为n成n&#xff0c;然后按照输入数据&#xff0c;把Map中每一个…

【50.2K⭐】Tabby:一款强大、灵活且跨平台的免费终端应用程序

【50.2K⭐】Tabby&#xff1a;一款强大、灵活且跨平台的免费终端应用程序 在快节奏的现代生活中&#xff0c;我们总是在寻找提高工作效率的方法。如果你是一位开发人员&#xff0c;或者是一个对技术充满好奇心的电脑爱好者&#xff0c;我们经常需要在 Windows 上进行远程操作与…

加密机授权报错如何排查?进入加密机后台的方式介绍

我们在此前的文章中介绍过不少TSINGSEE青犀视频安防监控视频平台关于加密机授权操作及相关疑问解答&#xff0c;感兴趣的用户可以翻阅往期的文章进行了解。由于新用户咨询该方面的问题较多&#xff0c;今天我们再来介绍一下用户在使用过程中遇到的问题。 1、如何进入加密机后台…

Vue-33、Vue中为什么使用render函数

1、main.js //该文件是整个项目的入口文件 //引入Vue import Vue from vue //引入APP组件&#xff0c;他是所有组件的父组件 import App from ./App.vue //关闭Vue是生产提示 Vue.config.productionTip false; //创建Vue实例对象---vm new Vue({render: h > h(App), }).$m…

2024中国(杭州)国际冷链物流与冷库建设技术展览会

2024中国&#xff08;杭州&#xff09;国际冷链物流与冷库建设技术展览会 地点&#xff1a;杭州国际博览中心 时间&#xff1a;2024年7月8日-10日 ■组展背景&#xff1a; 冷链物流行业作为现代物流行业的重要组成部分&#xff0c;近年来发展迅速。导及相关部委高度重视国内…

笔试面试题——二叉树进阶(二)

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、二叉搜索树与双向链表1、题目讲解2、思路讲解递归展开图3、代码实现 二、从前序遍历和中序…

安装向量数据库milvus可视化工具attu

使用docker安装的命令和简单就一个命令&#xff1a; docker run -p 8000:3000 -e MILVUS_URL{milvus server IP}:19530 zilliz/attu:v2.3.5sunyuhuasunyuhua-HKF-WXX:~/dockercom/milvus$ docker run -p 8000:3000 -e MILVUS_URL127.0.0.1:19530 zilliz/attu:latest yarn run…

pulsed-phase thermography (PPT)汉语意思是什么,用来干什么的?

问题描述&#xff1a;pulsed-phase thermography (PPT)汉语意思是什么&#xff0c;用来干什么的&#xff1f; 问题解答&#xff1a; "Pulsed-Phase Thermography"&#xff08;PPT&#xff09;翻译为中文是脉冲相位热成像。这是一种热成像技术&#xff0c;常用于材料…

Xcode查看APP文件目录

一、连接真机到MAC电脑上 二、打开Devices 点击window -> Devices and Simulatores 三、选中设备、选择app 四、选择下载内容 五、查看文件内容 得到的文件 右键显示包内容&#xff0c;获得APP内数据 六、分发证书无法下载 使用分发的证书无法下载文件内容&#xf…

(delphi11最新学习资料) Object Pascal 学习笔记---第2章第五节(日期和时间)

Object Pascal 学习笔记&#xff0c;Delphi 11 编程语言的完整介绍 作者: Marco Cantu 笔记&#xff1a;豆豆爸 2.5 日期和时间 ​ 早期版本的Pascal 语言中&#xff0c;没有原生的日期和时间类型&#xff0c;但是 Object Pascal 引入了一种原生的日期和时间类型&#xff0c;…

k8s的包管理工具helm

Helm是什么? 之前的这篇文章介绍了一开始接触k8s的时候接触到的几个命令工具 kubectl&kubelet&rancher&helm&kubeadm这几个命令行工具是什么关系&#xff1f;-CSDN博客 Helm 是一个用于管理和部署 Kubernetes 应用程序的包管理工具。它允许用户定义、安装和…

联邦推荐系统相关论文创新点总结

FD-GATDR: A Federated-Decentralized-Learning Graph Attention Network for Doctor Recommendation Using EHR 本文的主要内容是基于电子健康记录&#xff08;EHR&#xff09;构建了一个医生推荐系统。该系统通过分析患者的EHR历史&#xff0c;提供个性化的医生推荐&#xf…

美丽田园三大类服务相互协同促进协同增长 美丽田园美容已ipo上市

美丽田园三大类服务相互协同促进协同增长 美丽田园美容已ipo上市 从事健康与美丽管理服务的美丽田园医疗健康产业有限公司以多层次、环环紧扣的良性循环&#xff0c;将带动美与健康行业回归稳健的高速发展态势。美丽田园覆盖的业务领域横跨生活美容、医疗美容及医疗健康三大业…

说一下事件代理

事件代理&#xff08;Event Delegation&#xff09;是一种在开发中优化事件处理的技术&#xff0c;它利用事件冒泡的原理&#xff0c;将事件处理程序绑定在父元素上&#xff0c;通过判断事件的目标来执行相应的操作。这种方式可以减少事件处理程序的数量&#xff0c;提高性能&a…

ansible的file模块详解

四、ansible常用模块 1.file模块 file模块主要用于远程主机上的文件的操作,file模块包含下面选项: path : 必选项,定义目录/目录的路径 state : file : 查看文件状态,默认选项,若文件不存在,也不会被创建,会报错文件不存在, …