C++ | 类和对象(上)

目录

什么是类

类的介绍

struct在两种语言中的有何区别

私有变量命名注意点

类的作用域

类的声明定义分离

类的访问限定符

封装

类的实例化

类对象的存储

this指针

一道this指针相关的王炸题:

结语


什么是类

类的介绍

我们举一个日常生活中的例子:

手机,是一类产品,这姑且算是一个类,而手机里面又分了很多具体的品牌:华为,小米,iphone等等,这些就算是手机这个类面向的对象

而我们C++类的学习,需要用到C语言中的一个知识点:结构体

我们试想一下:假设struct是定义的一本书,那么这就是一个类,而我们在main函数中创建了多个关于书的变量,这些变量就是书这个结构体创建出来的对象,如下代码:

#include<iostream>
using namespace std;struct Book//类
{int _a;int _b;int _c;
};int main()
{//类创建出的两个对象struct Book s1;struct Book s2;return 0;
}

struct在两种语言中的有何区别

我们之前用C语言代码实现数据结构的种种的时候,总会发现,我们的类里面只有数据,比如int,double,char等等,我们的各种待实现的函数都是在头文件中的全局定义的

这会有一个很麻烦的点:命名

我们在写栈的时候,可能会在同一个头文件中还会写队列相关的类和声明,这时我们栈的名字只能带点特色:

Stackinit,因为除了栈之外还有一个QueueInit,如果单写一个Init的话,编译器会不知道这是谁的初始化

但是在C++中的类对此进行了升级

1. 我们的类中不仅可以声明变量,还能直接写函数!

2. 我们在main函数中创建对象的时候无需再写如struct Book作为变量名,只写类名即可

#include<iostream>
using namespace std;struct Stack
{void Init(int n = 4){_a = (int*)malloc(sizeof(int) * n);if (_a == nullptr){perror("malloc fail");return;}_capacity = 0;_top = 0;}int* _a;int _capacity;int _top;
};int main()
{Stack s1;s1.Init(10);return 0;
}

我们会看到,如上代码,我们直接使用了Stack作为变量类型的名字而非struct Stack

私有变量命名注意点

如上我们写的变量前面都加上了_,比如_capacity,_top......

至于为什么要这样子写,我们看一段代码就能明白了:

#include<iostream>
using namespace std;struct Date
{void Init(int year = 2024, int month = 3, int day = 31){year = year;month = month;day = day;}int year;int month;int day;
};int main()
{Date s1;s1.Init();return 0;
}

看上述代码,你会发现里面出现了year = year这样子的写法,那这里面哪个是形参,那个是实参,我们并不知道

而且,我们的代码不是只写给我们自己看的,写完了之后说不定未来还会被某个人维护,但是这样子的代码可读性极差,会被骂的

所以我们就将类里面变量的名字做一点修改,这样就不会出现上述的情况了

但是每个公司,每个地方或许会有不同的命名风格:_day,day_......

类的作用域

类的声明定义分离

如果我们将类定义在头文件里面了(类中的函数只是声明),而我们在.cpp文件中要实现类中的函数的话

我们就需要在.cpp文件中使用的函数的前面加上      类名::

如下:

//.h文件内
struct Date
{void Init();int _year;int _month;int _day;
};
//.cpp文件内
void Date::Init()
{;
}

我们在.h文件中定义了类之后,在.cpp文件上实现,但是.cpp文件上找不到这个函数的出处啊

如上,.cpp文件里面找不到.h文件里的类里面的函数,是因为类自成一个类域,在这个类域里面的内容都是给包装起来的,我们是没法使用的

我们目前一共学习了4种域:局部域、全局域,命名空间域、类域

我们可以用理解命名空间域的方式来理解类域

如果我们想访问类里面的内容的话,就需要告诉编译器我是在这个类域里面的,编译器认识了,代码就能跑得了了

类的访问限定符

在C++里,我们并不会像C语言一样一直用struct,更多的是使用class,如下:

class Date
{void Init();int _year;int _month;int _day;
};

除了名称的改变,其他什么都不变

那有人就会疑惑了,既然什么都不变,那又何必多此一举搞一个class呢?

这就涉及到了访问限定符的相关概念

我们再来看一组代码:

class Date
{void Init();int _year;int _month;int _day;
};int main()
{Date s1;s1.Init();return 0;
}

看着好像没什么不对的,但是:

报错了

这是因为我们使用的是class,而在C++里面,有公有和私有的概念

C++中有三个单词代表公私有:

  • public(公有)
  • private(私有)
  • protected(私有)

由于C++兼容C语言,所以C语言中的struct依然能使用,也能拿来定义类

但是与class不同的是,struct定义的类默认是public,也就是公有,意味着里面的变量都是可以访问的

但是class默认是私有的,所以我们上面的代码跑不了,就是因为class默认私有,而我们将Init定义在私有里面,不能使用

如果变量为私有的话,那么我们在类外面就不能访问,这样子设计,是为了更加的安全

我们试想一下:中国的高铁和火车,如果要乘坐就需要买票、排队、刷脸,之后有序入座,这样子仅仅有条的,也同样有助于管理

但是我们再看看印度阿三的火车:

两相比较之下,相信你会明白为什么会出现访问限定符这个东西的

那如果我们想在同一个类里面既有公有又有私有的话,那我们就需要使用访问限定符:

class Date
{
public:/公有void Init();
private:私有int _year;int _month;int _day;
};int main()
{Date s1;s1.Init();return 0;
}

通过访问限定符,我们就实现了公有和私有的分离

封装

无论是C语言,还是C++,抑或是Java等,都是面向对象的语言

而所有面向对象的语言都有三个特征:

  • 封装
  • 继承
  • 多态

后面两个继承和多态我们暂时无需理会,这些是我们在很后面才会学到的内容

我们今天要讲的就一个封装:

封装的本质是便于管理,我将我类里面的内容分开进行管理,公有和私有,我想让你用的你才能用,我不想让你用的我就隐藏起来

就好比我们坐的火车,买了坐票的人才有座位,买了卧铺的人才有床睡,不然没有票买谁想坐哪里就坐哪里那可太乱了

类的实例化

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

如上,这是我们声明出来的一个类,这个类里面有三个变量:year、month、day

但是仔细想一下,这三个变量是声明还是定义?开空间了吗?

答案是否定的,这里只是声明,并没有开空间

那这些变量在哪里开的空间?

class Date
{int _year;int _month;int _day;
};int main()
{Date s1;Date s2;return 0;
}

看这个main函数,我现在用这个Date类创建出了一个对象,开辟了空间,而开辟出来的空间,就是留给如上这三个变量的

也就是说:这些变量的空间是跟类一块儿定义出来的

举一个形象的例子:我们建房子之前都需要有一张设计图

而我们的设计图就可以理解为是类

我们通过这张设计图就能建出一栋又一栋的房子,这就是我们通过设计图这个类创建出来的对象

而我们的设计图是不占空间的,但是建出来的房子是多少平在图纸上是有规定的,房子是占空间的

我们通过设计图建出房子是实例化

我们通过类创建出变量是类的实例化

类对象的存储

我们可以对类进行sizeof操作看一下结果:

class Date
{
public:void Init(int year){_year = year;}
private:int _year;int _month;int _day;
};int main()
{Date s1;cout << sizeof(s1) << endl;return 0;
}

我们可以看到,结果是12

我们按照C语言中学到的内存对齐的规则来看一看的话,我们会发现:

三个int,大小是12,总大小是最大对齐数的整数倍,也就是int的整数倍,刚好是12

另外:类的大小计算规则就是C语言中内存对齐的规则

但是也许你会疑惑:难道类中的函数不用计算大小吗?

我们再加一个函数试试:

class Date
{
public:void Init(int year){_year = year;}int Add(int a, int b){return a + b;}
private:int _year;int _month;int _day;
};int main()
{Date s1;cout << sizeof(s1) << endl;return 0;
}

我们会发现,结果还是12,这就意味着函数的大小是不被包含在类里面的

或者我们换一个思路,再来看点有意思的:

int main()
{Date s1;cout << sizeof(s1) << endl;Date s2;cout << sizeof(s2) << endl;return 0;
}

我们现在创建出了两个对象,但是这两个的大小都是12

试想一下,这两个对象出自同一个类,如果这两个对象都要使用类里面的函数,那函数在类里面又没有开空间存进去,那我该怎么用呢?

两个对象里面都有空间存着变量

就像一个小区里面一栋一栋的房子,当然你也可以说是居民楼

那假如我们现在要建一个篮球场,建一个高尔夫球场,建一个体育馆

那我们如果在每家每户里面都建一个,是不是有点太浪费了呀

我们只需要在公共场地建一个,如果想要打篮球,打高尔夫什么的,直接到公共建好的场地里就可以了

我们再来看两段代码,看一下这两段代码的结果:

class Date
{};class Book
{
public:void func(){}
};int main()
{Date s1;Book s2;cout << sizeof(s1) << endl;cout << sizeof(s2) << endl;
}

可能有人会觉得:输出的结果应该是 0 0,因为没有变量,只有函数或者连函数都没有,就是一个空类

但其实:

我们试想一下:如果我说我创建出来了一个对象,但是没有开空间,那我这个对象到底创建了出来没有,地址是什么?空间都没有,哪来的地址?

所以,即使是空类,我们创建对象的时候也会开空间,最小为1

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 s1, s2;s1.Init(2024, 1, 13);s2.Init(2023, 11, 18);s1.Print();s2.Print();return 0;
}

我们有了一个类,创建了两个对象

但是这两个对象都是使用的都是同一个类,我们在调用Print函数的时候,我们是这样调用的:

s1.Print();
s2.Print();

不知各位有没有发现什么猫腻

我们用的是同一个函数,我们也没有传参,甚至函数都是无参的

但是当我们调用的时候,却能打印出不同的各自的日期,这是为什么?

这是因为编译器会有一个隐含的this指针

这就相当于,你看似没有传参,但是编译器已经帮你把对象的地址传过去了,并且在函数那里用了一个隐含的this指针来接收对象的地址

我们将this指针显示写出来给大家对照这看一看:

//类内部
/*void Print(int* this)
{cout << this->_year << " " << this->_month << " " << this->_day << endl;
}*/
void Print()
{cout << _year << " " << _month << " " << _day << endl;
}//main函数内部
//s1.Print(&s1);
s1.Print();//s2.Print(&s2);
s2.Print();

这下子我们就明白了,为什么我们明明没有传参,用的同一个函数,但是却能调用,因为隐含的this指针已经把对象的地址传过去了

其实Java也有一个this指针,但是python不是,python的那个叫做self,但性质也八九不离十

那我们的this指针是存在哪里的呢?

首先肯定不在类里,因为我们类的大小就是由类中变量决定的

静态区是存储static,全局变量的,不是

堆区的使用甚至要我们自己开辟空间,也不是

所以,this指针大概率是存在栈上的

为什么说是大概率呢?因为这是看编译器的,有些编译器会将this指针存进寄存器之中,因为我们老是要使用this指针,所以编译器干脆直接将其存进寄存器里面了,相当于是一个优化

class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main()
{Date s1;s1.Init(2024, 1, 13);return 0;
}

我们看到这段代码:

我们在main函数中对s1进行初始化时,只传了三个参数,我们来看看反汇编代码

注:此处使用的是VS2022

我们可以看到,前三个是分别将按个参数传了过去

但是我用红色框框圈起来的哪个部分,这个的意思是将s1的地址传给rcx这个寄存器,而s1的地址就是由this指针维护的,也就是相当于把this指针的值存进rcx这个寄存器里面了

一道this指针相关的王炸题:

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

请问,这道题会报错还是崩溃还是正常运行?

答案是正常运行

这是因为,虽然指针p是空指针,但是我们将nullptr作为this指针的值传过去时,我们并没有要通过this指针找类A中的相关变量,并没有,所以即使我传了一个nullptr过去,也对程序毫无影响,因为根本就没有用到this指针

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

那如果是这种情况呢?

我们会看到,我们将p的值置为nullptr之后,又将其作为this指针的值传过去,但是不比上一题没用到this指针,这题需要使用this指针去寻找变量_a

但是找不到啊!拿一个nullptr怎么找得到呢?

综上,这题我们的程序会报错

结语

类和对象上篇算是C++的一个开端

这一章准确来说是为了类和对象(中)那六个默认构造函数做铺垫

如果觉得这篇文章对你有帮助的话,希望能够多多支持!!

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

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

相关文章

罗宾斯《管理学》第15版笔记/课后习题/考研真题答案

第Ⅰ篇 管理导论 第1章 工作场所中的管理者和你 1.1 知识结构导图 1.2 考点难点归纳 1.3 课后习题详解 1.4 考研真题详解 附加模块一 管理史 知识结构导图 考点难点归纳 课后习题详解 考研真题详解 第2章 决 策 2.1 知识结构导图 2.2 考点难点归纳 2.3 课后习题详解…

C之·标准库<string.h>

系列文章目录 文章目录 前言一、字符串分割函数1.strtok()2. 总结 前言 <stdlib.h> 是C语言中的一个头文件&#xff0c;提供了一系列用于操作字符串的函数。例如查找子字符串、拼接字符串、比较字符串等等。为了方便开发者进行字符串操作&#xff0c;C语言提供了一个标准…

python爬虫学习-------scrapy的第一部分(二十九天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

做App小程序h5的软件 校园小程序有哪些小程序源码平台 微信小程序里发表的展示圈子 怎么将小程序分享到朋友圈小程序社区 小程序在大学校

最近几年&#xff0c;校园外卖跑腿服务市场迅速兴起。由于学生每天课程繁忙&#xff0c;很多人没有时间去食堂或外面的餐厅用餐&#xff0c;校园外卖跑腿平台提供了便捷和快速的解决方案&#xff0c;满足了学生的饮食跑腿需求&#xff0c;并受到越来越多学生的喜爱。 那么&…

C语言程序设计(二)

1、算法、数据结构、程序 为解决一个问题而采取的方法和步骤&#xff0c;就称为“算法”。 2、算法的5大特征 3、判断n是否为素数&#xff1a;只需要从2循环到根号n。 优化原理&#xff1a;素数是因子为1和本身&#xff0c; 如果num不是素数&#xff0c;则还有其他因子&…

stm32单片机开发三、DMA

DMA其实就是一种将ADC的数据寄存器、串口的数据寄存器等等一些数据放到sram中特定位置&#xff0c;方便CPU去读取 比如ADC转换&#xff0c;DMA直接转换的ADC的值放在内存中的特定位置&#xff0c;CPU可以直接去读取 uint16_t AD_Value[4]; //定义用于存放AD转换结果的全局…

上市企业数字赋能指数数据集-2001到2022年(TF-IDF)

01、数据简介 上市公司数字赋能指数是一个用来衡量上市公司利用数字技术提高业务能力和效率的指标。这个指数反映了上市公司利用大数据、云计算和人工智能等数字技术&#xff0c;高效地利用商业资源和信息&#xff0c;并扩展供应关系的能力。市公司数字赋能指数是一种综合性的…

怎么给字符串字段加索引?

怎么给字符串字段加索引&#xff1f; 现在&#xff0c;几乎所有的系统都支持邮箱登录&#xff0c;如何在邮箱这样的字段上建立合理的索引&#xff0c;是我们今天要讨论的问题。 假设&#xff0c;你现在维护一个支持邮箱登录的系统&#xff0c;用户表是这么定义的&#xff1a; …

美富特 | 邀您参加2024全国水科技大会暨技术装备成果展览会

王涛 四川美源环能科技有限公司 技术总监 报告题目&#xff1a;绿色智慧水岛如何助力工业园区污水及再生水资源化利用降碳增效 拥有十余年的环保行业从业经验&#xff0c;对各类前沿物化、生化及膜技术均有丰富的研发、设计及应用经验&#xff0c;先后参与多项重点核心技术…

日本宇宙航空研究“Int-Ball2”自由飞行相机机器人采用的Epson IMU

IMU有助于飞行的稳定控制和电池充电的自动对接- 精工爱普生公司&#xff08;TSE:6724&#xff0c;“Epson”&#xff09;很高兴地宣布&#xff0c;日本宇宙航空研究开发机构&#xff08;JAXA&#xff09;选择了爱普生M-G370系列的惯性测量单元&#xff08;IMU&#xff09;&…

开源相机管理库Aravis例程学习(五)——camera-api

开源相机管理库Aravis例程学习&#xff08;五&#xff09;——camera-api 简介例程代码函数说明arv_camera_get_regionarv_camera_get_pixel_format_as_stringarv_camera_get_pixel_formatARV_PIXEL_FORMAT_BIT_PER_PIXEL 简介 本文针对官方例程中的&#xff1a;03-camera-api…

Swift - 可选项(Optional)

文章目录 Swift - 可选项&#xff08;Optional&#xff09;1. 可选项&#xff08;Optional&#xff09;2. 强制解包&#xff08;Forced Unwrapping&#xff09;3. 判断可选项是否包含值4. 可选项绑定&#xff08;Optional Binding&#xff09;5. 等价写法6. while循环中使用可选…

【论文阅读】互连网络的负载平衡路由算法 (CQR, Channel Queue Routing 通道队列路由)

Channel Queue Routing (CQR) 通道队列路由 1. Channel Queue Routing (CQR) 的动机 (1) 排队论(queueing theory)模型(2) GAL’s latency on tornado traffic(3) Routing tornado traffic with CQR 2. Channel Queue Routing 通道队列路由3. CQR 的性能4. 总结 Channel Queu…

白话机器学习1:分类问题中的评价指标

机器学习中的评价指标非常多&#xff0c;它们用来衡量模型的性能和预测能力。不同类型的机器学习任务可能需要不同的评价指标。以下是一些常见的评价指标&#xff0c;按照不同类型的机器学习任务分类&#xff1a; 对于分类问题&#xff1a; 准确率&#xff08;Accuracy&#…

[NeurIPS-23] GOHA: Generalizable One-shot 3D Neural Head Avatar

[pdf | proj | code] 本文提出一种基于单图的可驱动虚拟人像重建框架。基于3DMM给粗重建、驱动结果&#xff0c;基于神经辐射场给细粒度平滑结果。 方法 给定源图片I_s和目标图片I_t&#xff0c;希望生成图片I_o具有源图片ID和目标图片表情位姿。本文提出三个分支&#xff1a;…

pytorch中创建maskrcnn模型

0.模型输入/输出参数参见 链接: pytorch的mask-rcnn的模型参数解释 核心代码 GeneralizedRCNN(这里以mask-rcnn来解释说明) # 通过输入图像获取fpn特征图,注意这里的backbone不是直接的resnet,而是fpn化后的 features self.backbone(images.tensors) # 由于是mask-rcnn,故而…

SpringCloud系列(10)--Eureka集群原理及搭建

前言&#xff1a;当注册中心只有一个&#xff0c;而且当这个注册中心宕机了&#xff0c;就会导致整个服务环境不可用&#xff0c;所以我们需要搭建Eureka注册中心集群来实现负载均衡故障容错 Eureka架构原理图 1、Eureka集群原理 2、创建Eureka Server端服务注册中心模块 (1)在…

R语言使用sjPlot包优雅绘制回归模型的交互效应图

交互作用效应(p for Interaction)在SCI文章中可以算是一个必杀技&#xff0c;几乎在高分的SCI中必出现&#xff0c;因为把人群分为亚组后再进行统计可以增强文章结果的可靠性&#xff0c;进行可视化后可以清晰的表明变量之间的关系。不仅如此&#xff0c;交互作用还可以使用来进…

Dockerfile实战(SSH、Systemctl、Nginx、Tomcat)

目录 一、构建SSH镜像 1.1 dockerfile文件内容 1.2 生成镜像 1.3 启动容器并修改root密码 二、构建Systemctl镜像 2.1 编辑dockerfile文件 ​编辑2.2 生成镜像 2.3 启动容器&#xff0c;并挂载宿主机目录挂载到容器中&#xff0c;然后进行初始化 2.4 进入容器验证 三、…

照片误删怎么办?华为手机删除的照片如何恢复?

我们在使用华为手机时&#xff0c;可能会因为各种原因不小心删除一些照片。如果这些照片对我们来说很重要&#xff0c;那么恢复它们是非常必要且急迫的。那么华为手机删除的照片如何恢复呢&#xff1f;本文将为您介绍3种恢复华为手机中误删照片的方法。 如何恢复华为手机中被删…