【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,一经查实,立即删除!

相关文章

超融合基础架构理解

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…

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

&#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…

Xcode查看APP文件目录

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

k8s的包管理工具helm

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

阿里云优惠券领取入口、使用方法和限制条件,2024最新

阿里云优惠代金券领取入口&#xff0c;阿里云服务器优惠代金券、域名代金券&#xff0c;在领券中心可以领取当前最新可用的满减代金券&#xff0c;阿里云百科aliyunbaike.com分享阿里云服务器代金券、领券中心、域名代金券领取、代金券查询及使用方法&#xff1a; 阿里云优惠券…

如何在Mac上安装PHP环境

前置环境&#xff1a;HomeBrew # Homebrew 是 Mac 上最好的包管理器之一&#xff0c;可以用于安装各种开源软件。从 Terminal&#xff08;终端&#xff09;执行以下命令安装 Homebrew&#xff1a; /usr/bin/ruby -e $(curl -fsSL https://raw.githubusercontent.com/Homebrew/i…

全流程机器视觉工程开发(一)环境准备,paddledetection和labelme

前言 我现在在准备做一个全流程的机器视觉的工程&#xff0c;之前做了很多理论相关的工作。大概理解了机器视觉的原理&#xff0c;然后大概了解了一下&#xff0c;我发现现在的库其实已经很发展了&#xff0c;完全不需要用到非常多的理论&#xff0c;只需要知道开发过程就可以…

Flutter 滚动布局:sliver模型

一、滚动布局 Flutter中可滚动布局基本都来自Sliver模型&#xff0c;原理和安卓传统UI的ListView、RecyclerView类似&#xff0c;滚动布局里面的每个子组件的样式往往是相同的&#xff0c;由于组件占用内存较大&#xff0c;所以在内存上我们可以缓存有限个组件&#xff0c;滚动…

软考系分之计算机网络规划设计、综合布线、RAID和网络存储等

文章目录 1、概要2、网络的三层模型3、综合布线系统4、廉价磁盘冗余阵列&#xff08;RAID&#xff09;5、网络存储6、总结 1、概要 本篇重点介绍计算机网络中的网络规划设计、综合布线、RAID和网络存储。 2、网络的三层模型 三层模型分为核心层、汇聚层和接入层&#xff0c;接…

【C++修行之道】竞赛常用库函数(sort,min和max函数,min_element和max_element、nth_element)

目录 一、sort 1.1sort简介 语法 参数 功能 适用容器 1.2sort的用法 1.3自定义比较函数 示例 1265蓝桥题 —— 排序 二、min和max函数 三、min_element和max_element 497蓝桥题 —— 成绩分析 四、nth_element 一、sort 1.1sort简介 sort函数包含在头文件<a…

Vue3组件库开发 之Button(2) 未完待续

Vue3组件库开发 之Button(1) 中新建项目&#xff0c;但未安装成功ESLINT 安装ESLINT npm install eslint vite-plugin-eslint --save-dev 安装eslint后&#xff0c;组件文件出现错误提示 添加第三方macros &#xff0c;虽然不是官网但很多开发者都是vue3开发人员 安装macros…

C++大学教程(第九版)6.29素数

题目 (素数)素数是只能被1和自已整除的整数。例如,235和7是素数而468和9不是素数 a)编写一个函数&#xff0c;确定一个数是否是素数。 b)在程序中使用这个函数&#xff0c;该程序确定和打印2 ~10000之间的所有素数。在确信已找到所有的素数之前&#xff0c;实际需测试这些数中…

基于PSO-BP神经网络的风电功率预测(MATLAB)

作品简介 &#xff1a;关注公众号“电击小子程高兴的MATLAB小屋”获取优惠 主要内容 该模型将粒子群算法与BP神经网络结合用于BP神经网络的训练&#xff0c;即优化BP网络中的连接权值和各项阈值&#xff0c;然后利用神经网络分布式并行处理优势、自适应学习能力以及较好的…