【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.hqwc.cn/news/422997.html

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

相关文章

不同业务模式的跨境电商如何借助云桥通SDWAN组网做Tiktok生意?

TikTok for Business的出海电商行业运营策略负责人在演讲中强调&#xff0c;商家在TikTok实现销售的两种主要方法包括开设TikTokShop和利用各类广告产品引流到独立站。在TikTokShop开店的过程中&#xff0c;商家需完成入驻、学习运营指南、通过新店铺考察期等步骤。而利用广告引…

HYBBS 表白墙网站PHP程序源码 可封装成APP

源码介绍 PHP表白墙网站源码&#xff0c;可以做校园内的&#xff0c;也可以做校区间的&#xff0c;可封装成APP。告别QQ空间的表白墙吧。 安装PHP5.6以上随意 上传程序安装&#xff0c;然后设置账号密码&#xff0c;登陆后台切换模板手机PC都要换开启插件访问前台。 安装完…

MySQL---多表分组查询综合练习

创建dept表 CREATE TABLE dept ( deptno INT(2) NOT NULL COMMENT 部门编号, dname VARCHAR (15) COMMENT 部门名称, loc VARCHAR (20) COMMENT 地理位置 ); 添加dept表主键 mysql> alter table dept add primary key(deptno); Query OK, 0 rows affected (0.02 s…

最小生成树(java版)

&#x1f4d1;前言 本文主要是【最小生成树】——最小生成树使用的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一…

什么是VUE 创建第一个VUE实例

一、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 构建用户界面 的 渐进式 框架 Vue2官网&#xff1a;Vue.js 1.什么是构建用户界面 基于数据渲染出用户可以看到的界面 2.什么是渐进式 所谓渐进式就是循序渐进&#xff0c;不一定非得把Vu…

从丘成桐、张益唐的超国民待遇,看孙卫东风光回国的可能性

复旦博士孙卫东在美国流浪16年&#xff0c;这事儿最近在国内媒体沸沸扬扬&#xff0c;围绕着是否让孙卫东回国&#xff0c;国民形成了截然对立的两派&#xff1a;一派呼吁帮其回国&#xff0c;认为他身上流着中国人的血、是我们的同胞&#xff1b;另一派则是反对阻止&#xff0…

SpringMVC环境搭配

概述 Spring MVC是Spring Framework提供的Web组件&#xff0c;全称是Spring Web MVC,是目前主流的实现MVC设计模式的框架&#xff0c;提供前端路由映射、视图解析等功能 mvc是什么 MVC是一种软件架构思想&#xff0c;把软件按照模型&#xff0c;视图&#xff0c;控制器来划分…

Python3+Selenium框架搭建

Webdriver概述 Webdriver (Selenium2&#xff09;是一种用于Web应用程序的自动测试工具&#xff0c; Thoughtworks公司一个强大的基于浏览器的开源自动化测试工具&#xff0c;通常用来编写web应用的自动化测试。 Selenium 是一个用于Web应用程序测试的工具。 Selenium测试直…

【大数据】流处理基础概念(二):时间语义(处理时间、事件时间、水位线)

流处理基础概念&#xff08;二&#xff09;&#xff1a;时间语义 1.流处理场景下一分钟的含义2.处理时间3.事件时间4.水位线5.处理时间与事件时间 本篇博客&#xff0c;我们将介绍流式场景中时间语义和不同的时间概念。我们将讨论流处理引擎如何基于乱序事件产生精确结果&#…

(二十四)Kubernetes系列之Helm3

Helm为kubernetes的包管理工具&#xff0c;就像Linux下的包管理器&#xff08;yum/apt等&#xff09;&#xff0c;可以很方便的将之前打包好的yaml文件部署到kubernetes上。 1.安装访问地址&#xff1a;https://github.com/helm/helm/releases 点击查看最新的版本&#xff0c…

MAXWELL

MAXWELL 一、maxwell是什么 maxwell 官网地址&#xff1a;http://maxwells-daemon.io/ 因为官网是纯英文的&#xff0c;倒是不难懂&#xff0c;但总觉得写的略粗糙&#xff08;也可能笔者英文水平确实拉胯&#xff0c;有待提高&#xff09;。所以还是自己百度了一下。 当my…

一篇搞定大论文参考文献,从找文献到交叉引用全流程

我们在写论文过程中&#xff0c;肯定会引用他人的文献&#xff0c;根据自己的写作经历&#xff0c;总结了一套很实用且不会出错的引用方法。1、记录文献顺序 你在论文中每引用一篇文献&#xff0c;你就在文献后加上[1][2]…[n]&#xff0c;然后新建一个word文档&#xff0c;在…