C++必修:类与对象(一)

news/2024/5/10 11:20:18

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
贝蒂的主页:Betty’s blog

1. 面向过程与面向对象

1.1. 面向过程

我们之前学习的C语言就是一种面向过程的语言,它强调事件的具体实现过程,一般以函数来具体实现。比如说我们用面向过程的思想炒菜就可以分为以下几个步骤:

img

img

1.2. 面向对象

C++就是一门典型的面向对象的语言,它将问题分成多个对象,更强调对象与对象之间的联系。我们仍以炒菜来举例,在这个问题中我们可以抽象出四个对象:人,菜,调料,锅

img

在面向对象中,我们更强调对象之间的连续,并不太在意其内在是如何完成的。

1.3. 对比

无论是面向过程还是面向对象的语言,都有其优缺点:

面向过程面向对象
优点流程化分工明确,效率高结构化更清晰,易维护
缺点可维护性差,扩展能力差开销大,性能低

2. 类的引入

在C++中,在原来C语言结构体的基础上引入了类的概念。与C语言最大的不同就是,C++可以在类中定义函数。

而由类声明定义的变量,我们称为对象

2.1. 类的两种定义

2.1.1. 第一种

因为C++兼容C语言,所以可以利用C语言的结构体关键字struct来定义类。但是由于struct在C++中升级为类,所以在定义对象时并不需要写struct关键字。

#include<iostream>
using namespace std;
struct Date
{int a;int b;
};
int main()
{Date d1;//okstruct Date d2;//okreturn 0;
}
2.1.2. 第二种

第二种是由C++的关键字class构成,其语法结构如下:

class className
{
// 类体:由成员函数和成员变量组成
};

#include<iostream>
using namespace std;
class Date
{int a;int b;
};

2.2. 单文件与多文件书写

在定义类时,我们可以将类的定义与声明在同一文件书写,或者在不同文件书写。

  1. 声明和定全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理
#include<iostream>
using namespace std;
class Date
{//定义与声明一起int Add(int x, int y){return x + y;}int year;int month;int day;
};
  1. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::。
#include<iostream>
using namespace std;
//test.h
class Date
{//定义与声明一起int Add(int x, int y);int year;int month;int day;
};
//test.cpp
int Date::Add(int x,int y)
{return x+y;
}

一般在我们平时练习时,更常用将定义与声明放在类中。但是在实际工程中,更倾向于奖定义与声明分类。

2.3. 成员变量的命名

虽然C++标准并没有规定成员变量的命名规则,但是大家约定俗成地在定义变量时会有一套特定的规则,目的就是解决可能存在的命名冲突的问题,比如说下面这段代码:

class Date
{void Init(int year, int month, int day){//命名冲突year = year;month = month;day = day;}int year;int month;int day;
};

这时为了解决问题这类问题,我们在定义成员变量时会对其进行特定地修饰。比如说_变量名或者是m_变量名或者变量名_。不同公司,不同的程序员可能都有一套自己的命名规则,但主流一般就是以上三种。

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

3. 类的访问限定符与封装

3.1. 访问限定符

在C++类中有三种访问限定符:**public,private,protected。**他们每一个都有自己独特的作用:

  • public修饰的成员在类外可以直接被访问
  • protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。如果后面没有访问限定符,作用域就到 } 即类结束。
  • class的默认访问权限为private,struct为public(因为struct要兼容C)。
class Date
{
//可以被直接访问
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
//不能被直接访问
private:int _year;int _month;int _day;
};

3.2. 封装

封装:用类将对象的属性(数据)与操作数据的方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

**封装本质上是一种管理,让用户更方便使用类。**比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

4. 类对象模型

4.1. 类对象的实例化

在类中的成员变量实际是一种声明,相当于一个设计图纸。而我们利用类名定义的对象就是类对象的实例化,相当于通过设计图纸实际创建出来。单独的类是并不占据实际空间的大小。

Date::_year = 1;//error

4.2. 类对象的存储

我们知道了类对象的创建,那么具体类中的成员变量与成员函数又是如何存储的呢?

4.2.1. 存储一

每次创建对象时,都开辟一个空间存储类成员变量与成员函数。

img

4.2.2. 存储二

每次创建对象时,都开辟一个空间存储类成员变量与成员函数的地址。

img

4.2.3. 存储三

每次创建对象时,都开辟一个空间存储类成员变量。而成员函数提前单独存储一个区域

img

首先如果是存储一的话,每次对象的实例化都会开辟函数的空间。而每个函数的功能都是一样的,这就造成了空间的浪费。而如何判断时存储二还是存储三,我们可以通过计算类大小来判断。

4.3. 类对象的大小

4.3.1. 一般类的计算

类型对象的大小我们可以借助运算符sizeof计算,并且类的大小也遵循结构体内存对齐规则:

  1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
  2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
  3. 对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。(VS 中默认的值为 8 ,Linux中gcc没有默认对齐数,对⻬数就是成员⾃⾝的⼤⼩)
  4. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
  5. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
#include<iostream>
using namespace std;
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 d;cout << sizeof(d) << endl;return 0;
}

img

根据内存对齐规则,我们知道成员变量的大小就是12,所以证明成员函数是存在于内存的其他位置。所以说存储三正确。一般这个存储成员函数的区域我们称之为公共代码段。

4.3.2. 空类的计算

当类中只有成员函数,或者什么都没有时。类的大小又为多少呢?

// 类中仅有成员函数
class A1
{
public:void func2() {}
};
// 类中什么都没有---空类
class A2
{
};
int main()
{A1 d1;A2 d2;cout << sizeof(d1) << endl;cout << sizeof(d2) << endl;return 0;
}

img

为什么空类的大小为1,而不是0呢?其实并不能难像,因为在我们进行空类的实例化时必须要有空间存储对象的大小。这是编译器就会默认给一个字节大小来标记这个类的对象,实际操作中实用性也很少。

5. this指针

5.1. 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, 11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;
}

对于上述类,有这样的一个问题:

Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

在C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数。通过不同的对象地址来分辨不同的对象,只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。实际代码如下:

void Init(Date* this, int year, int month, int day)
{this->_year = year;this->_month = month;this->_day = day;
}
d1.Init(&d1,2022, 1, 11);//实际传参

但是注意:我们并不能将隐式传参书写出来,因为这是编译器默认添加的

5.2. this指针的特点

  1. this指针的类型:类型为*** const**,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用。
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。

5.3. 两道问题

5.3.1. 问题一
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class Betty
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};
int main()
{Betty* p = nullptr;p->Print();(*p).Print();return 0;
}

img

为什么程序会正常运行,对空指针解引用不是会发生运行崩溃吗?首先我们得明白成员函数并不存放在类对象中,而是存放在公共代码段。虽然我们表面看上去解引用,但实际上编译器不需要通过解引用去找对应函数,只需要去公共代码区执行对应函数即可。

5.3.2. 问题二
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class Betty
{
public:void Print(){cout << _a << endl;}
private:int _a;
};
int main()
{Betty* p = nullptr;p->Print();(*p).Print();return 0;
}

img

这里就引起程序崩溃,因为我们知道访问对应的成员变量,会传递对应对象的地址。而这里的地址为nullptr,通过nullptr->_a引起程序崩溃。


http://www.mrgr.cn/p/48707811

相关文章

vscode侧边栏资源管理器重置

不知道点了哪里,资源管理器侧边栏只剩下一个npm脚本选项卡了,找解决方案找了好久,卸载了vscode都没用,还是因为我的搜索方式不对,最后终于找到了,记录下先 问题图:第一步:按curl + shilt + P 第二步:输入View: Reset View Locations

一文看懂什么是回调函数

文章目录一、什么是回调函数 二、回调示例 三、回调函数的应用场景 四、回调函数的优点 五、回调函数的注意事项一、什么是回调函数回调函数是一种在编程中常见的概念,它是指一个函数作为参数传递给另一个函数,并且在特定事件发生或条件满足时被调用执行的函数。 二、回调示例…

MySQL8.0.22安装

MySQL8.0.22安装及配置(超详细)大家好,今天我们来学习一下 MySQL8.0.22安装及配置,好好看,好好学,超详细的第一步进入MySQL官网下载,如下图所示:第二步进入下载完成后解压到除c盘以外的盘,如下图所示:第三步解压后的目录并没有的my.ini文件,没关系可以自行创建在安装根…

报错import build constraints exclude all Go files in

好久没用fyne突然报错 报错import ...go-gl.. build constraints exclude all Go files in go-gl .. 检查gcc --version正常输出 检查gcc版本正常&#xff0c;路径正常。 尝试解决的方法&#xff0c; 1.重新安装依赖&#xff0c;不行 2.重新配置下载地址&#xff0c;不…

Zephyr sensor子系统学习

一、背景 2023年7月份nRF Connect SDK 2.4.0最新版本&#xff0c;使用的Zephyr V3.3版本。从Zephyr 3.5版本在子系统中加入了sensing子系统。 现在最新的nRF Connect SDK 2.6.0 release支持v3.5.99-ncs1&#xff0c;已经支持sensing子系统 nRF52840现在官方支持两个传感器de…

数据库优化 索引(index)

介绍 索引是帮助数据库高效获取数据的数据结构优缺点: 优点:提高数据查询的效率,降低数据的IO成本。 通过索引列多数据进行排序,降低数据排序的成本,降低CPU消耗缺点:索引会占存储空间。 索引大大提高了查询效率,同时却也降低了insert、update、delete的效率结构 MySql数…

SpringMVC进阶(数据格式化以及数据校验)

文章目录 1.数据格式化1.基本介绍1.基本说明2.环境搭建 2.基本数据类型和字符串转换1.需求分析2.环境搭建1.data_valid.jsp首页面2.Monster.java封装请求信息3.MonsterHandler.java处理请求信息4.monster_addUI.jsp添加妖怪界面5.单元测试 3.保存妖怪信息1.MonsterHandler.java…

什么因素可以影响到代理IP稳定性?爬虫代理IP有哪些作用?

一、什么因素可以影响到代理IP稳定性 代理IP的稳定性受到多种因素的影响&#xff0c;以下是一些主要的因素&#xff1a; 代理IP的质量&#xff1a;不同的代理IP提供商提供的代理IP质量参差不齐&#xff0c;一些低质量的代理IP可能经常出现连接问题或速度慢的情况&#xff0c;…

5、Flink事件时间之Watermark详解

1&#xff09;生成 Watermark 1.Watermark 策略简介 为了使用事件时间语义&#xff0c;Flink 应用程序需要知道事件时间戳对应的字段&#xff0c;即数据流中的每个元素都需要拥有可分配的事件时间戳。 通过使用 TimestampAssigner API 从元素中的某个字段去访问/提取时间戳。…

linux2-ls

lsls , list directory contents, 在命令行中, 以平铺的形式, 展示当前工作目录(默认为/home/用户名 目录)下的内容(文件和文件夹)ls # 默认为ls /home/用户名 lsls -h -h, human-readable, 表示以易于阅读的形式, 列出文件的大小(如K,M,G) 不能单独使用, 要和-l混合使用 ls -h…

穿越物联网的迷雾:深入理解MQTT协议

目录标题 1、MQTT简介核心特性 2、MQTT的工作原理通信过程 3、MQTT的消息质量&#xff08;QoS&#xff09;4、安全机制5、实践应用环境准备示例项目发布者客户端订阅者客户端 6、最佳实践7、结论8、参考资料 在物联网&#xff08;IoT&#xff09;的海洋中&#xff0c;数据像水流…

架构师系列- 消息中间件(12)-kafka基础

1、应用场景 1.1 kafka场景 Kafka最初是由LinkedIn公司采用Scala语言开发&#xff0c;基于ZooKeeper&#xff0c;现在已经捐献给了Apache基金会。目前Kafka已经定位为一个分布式流式处理平台&#xff0c;它以 高吞吐、可持久化、可水平扩展、支持流处理等多种特性而被广泛应用…

ubutu ac68

1、直接去 https://github.com/morrownr/8814au 下载编译,运行 install-driver.sh,重启后 2、 3、 断开再连,然后usb拔掉再插,选择 虚拟机的系统 4 抓 https://blog.csdn.net/weixin_41856150/article/details/102327913

《痞子衡嵌入式半月刊》 第 98 期

痞子衡嵌入式半月刊: 第 98 期这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期。 本期刊是开源项目(GitHub: JayHeng/pzh-mcu-bi-weekly),欢迎提交 issue,投稿或推荐你知道的嵌入式那些事儿。 上期回顾 :《…

刷题训练之前缀和

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握前缀和算法。 > 毒鸡汤&#xff1a;学习&#xff0c;学习&#xff0c;再学习 ! 学&#xff0c;然后知不足。 > 专栏选自&#xff1a;刷题…

瑞士轮——结构体(快速排序 or 归并排序?)

瑞士轮——结构体&&(快速排序 or 归并排序?)题目链接:https://www.luogu.com.cn/problem/P1309 题意应该非常明确了(这里就不细讲了):有2*N个人,首先根据成绩进行排序,相邻的两个人进行比赛,强的人成绩+1,输的人成绩不变,最后又根据成绩进行排序,进行r次操作,…

管理集群工具之LVS

管理集群工具之LVS 集群概念 将很多机器组织在一起&#xff0c;作为一个整体对外提供服务集群在扩展性、性能方面都可以做到很灵活集群分类 负载均衡集群&#xff1a;Load Balance高可用集群&#xff1a;High Availability高性能计算&#xff1a;High Performance Computing …

pytest系列——allure之在测试用例添加标题(@allure.title())

前言 通过使用装饰器allure.title可以为测试用例自定义一个更具有阅读性的易读的标题。 allure.title的三种使用方式&#xff1a; 直接使用allure.title为测试用例自定义标题&#xff1b;allure.title支持通过占位符的方式传递参数&#xff0c;可以实现测试用例标题参数化&a…

C++|stack-queue-priority_queue(适配器+模拟实现+仿函数)

目录 一、容器适配器 1.1容器适配器概念的介绍 1.2stack和queue的底层结构 1.3deque容器的介绍 1.3.1deque的缺陷及为何选择他作为stack和queue的底层默认实现 二、stack的介绍和使用 2.1stack的介绍 2.2stack的使用 2.3stack的模拟实现 三、queue的介绍和使用 …