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

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

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

所属专栏: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.mzph.cn/diannao/4128.shtml

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

相关文章

【网络通信】初探网络层次结构(OSI七层网络模型)

随着信息技术的飞速发展&#xff0c;网络通信已经成为现代社会不可或缺的一部分。网络通信的实现离不开网络协议栈的支持&#xff0c;而网络协议栈则是由多个层次组成的。这些层次各自承担着不同的任务&#xff0c;共同构成了网络通信的基石。本文将对网络通信中的各类层进行详…

Swift - 函数

文章目录 Swift - 函数1. 函数的定义2. 隐式返回(Implicit Return)3. 返回元组&#xff1a;实现多返回值4. 函数的文档注释5. 参数标签&#xff08;Argument Label&#xff09;6. 默认参数值&#xff08;Default Parameter Value&#xff09;7. 可变参数&#xff08;Variadic P…

LM2576D2TR4-5G 3.0安15伏降压开关稳压器 PDF中文资料_参数_引脚图

LM2576D2TR4-5G 规格信息&#xff1a; 制造商:ON Semiconductor 产品种类:开关稳压器 RoHS:是 装置风格:SMD/SMT 封装 / 箱体:TO-263-5 输出电压:5 V 输出电流:3 A 输出端数量:1 Output 最大输入电压:45 V 拓扑结构:Buck 最小输入电压:7 V 开关频率:52 kHz 最小工作…

seq2seq架构略解

用于序列翻译任务&#xff08;下图来自d2l&#xff09; 训练时输入输出格式&#xff1a; 若数据集为{ <(a1,a2,a3,a4,a5),(b1,b2,b3,b4,b5)> }&#xff08;AB语言对应的句子组&#xff09; 输入 A语言的单词序列结束符&#xff08;a1,a2,a3,a4,a5,<eos>&#xf…

Unity类银河恶魔城学习记录14-5 p152 Lost currency save and enemy‘s currency drop

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili LostCurrencyController.cs using System.Collections; using System.Colle…

【STM32+HAL】三轴按键PS2摇杆

一、准备工作&#xff1a; 有关CUBEMX的初始化配置&#xff0c;参见我的另一篇blog&#xff1a;【STM32HAL】CUBEMX初始化配置 有关定时器触发ADC模式配置&#xff0c;详见【STM32HAL】ADC采集波形实现 二、所用工具&#xff1a; 1、芯片&#xff1a; STM32F407VET6 2、CUBE…

通义灵码-IDEA的使用教程

通义灵码-IDEA的使用教程 1、通义灵码是什么&#xff1f; 通义灵码&#xff0c;是阿里云出品的一款基于通义大模型的智能编码辅助工具&#xff0c;提供行级/函数级实时续写、自然语言生成代码、单元测试生成、代码注释生成、代码解释、研发智能问答、异常报错排查等能力&#…

MATLAB矩阵

MATLAB 矩阵 矩阵是数字的二维数组。 在MATLAB中&#xff0c;您可以通过在每行中以逗号或空格分隔的数字输入元素并使用分号标记每行的结尾来创建矩阵。 例如&#xff0c;让我们创建一个45矩阵一- 示例 a [ 1 2 3 4 5; 2 3 4 5 6; 3 4 5 6 7; 4 5 6 7 8] MATLAB将执行上述语…

uniapp微信小程序开发踩坑日记:Vue3 + uniapp项目引入Echarts图表库

一、下载插件包 下载地址如下&#xff1a; lime-echart: 百度图表 echarts&#xff0c;uniapp、taro 使用 echarts 图表&#xff0c;全面兼容各平台小程序、H5、APP、Nvue 将以下两个文件夹放到项目的components里 同样地&#xff0c;将静态资源文件夹下内容放到自己项目的s…

什么是端口

啊&#xff0c;端口&#xff01;这可是计算机网络中一个非常重要的概念呢。 简单来说&#xff0c;端口就好比是网络通信中的门&#xff0c;用来区分不同的应用程序或服务。我们知道&#xff0c;计算机在进行网络通信时需要通过网络传输数据&#xff0c;而端口就是帮助计算机在…

vue echarts 饼图(环形图)

vue echarts 饼图(环形图) &#xff0c;echarts版本为5.3.3 可以自定义颜色 <template><div><div id"pieChart1" ref"pieChartRef1" style"width: 100%; height: 250px"></div></div></template><scri…

ASP.NET教务平台—学籍管理模块开发与设计

摘 要 教务平台之学籍管理模块是一个典型的教务信息管理系统(MIS)&#xff0c;其开发主要包括后台数据库的建立和前端应用程序的开发两个方面。对于后台数据库要求实现数据的完整性、一致性和安全性&#xff1b;对于前台应用程序开发则要求模块功能完备、界面友好、易使用等特…

java中http调用组件深入详解

目录 一、前言 二、http调用概述 2.1 什么是http调用 2.1.1 http调用步骤 2.2 HTTP调用特点 2.3 HTTP调用应用场景 三、微服务场景下http调用概述 3.1 微服务开发中http调用场景 3.2 微服务组件中http的应用 四、常用的http调用组件 4.1 java中常用的http组件介绍 4…

C++11新特性:lambda表达式

目录 1.lambda表达式 1.1 C98中的一个例子 1.2 lambda表达式 1.3 lamzbda表达式语法 1. lambda表达式各部分说明 2. 捕获列表说明 1.4 函数对象与lambda表达式 1.lambda表达式 1.1 C98中的一个例子 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0…

Kafka 3.x.x 入门到精通(04)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通&#xff08;04&#xff09;——对标尚硅谷Kafka教程 2. Kafka基础2.1 集群部署2.2 集群启动2.3 创建主题2.4 生产消息2.5 存储消息2.5.1 存储组件2.5.2 数据存储2.5.2.1 ACKS校验2.5.2.2 内部主题校验2.5.2.3 ACKS应答及副本数量关系校验2.5.2.4 日志文…

BEC写作和其他英语写作有什么区别?成人学英语去哪里柯桥有专业培训吗?

BEC中级考试的写作与其他英语类考试略有不同。除考查考生的整体写作水平之外&#xff0c;它也考查考生处理日常商务活动及解决商务运作中出现问题的能力。测试题材与体裁均与商务信函有关&#xff0c;往往涉及以下内容&#xff1a; 商务信函&#xff1a;这里所涉及的信函往往是…

CNAS软件测评报告收费标准

随着信息技术的快速发展&#xff0c;软件测评在保障软件质量、提升用户体验等方面扮演着越来越重要的角色。CNAS&#xff08;中国合格评定国家认可委员会&#xff09;作为国内权威的认可机构&#xff0c;其软件测评报告收费标准受到了广泛关注。本文旨在解析CNAS软件测评报告的…

(学习日记)2024.05.06:UCOSIII第六十节:User文件夹函数概览(uCOS-III->Source文件夹)第六部分

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

CentOS安装CRI--containerd

前言 CRI&#xff0c;Container Runtimes&#xff0c;通常直译成容器进行时因为kubernetes&#xff0c;从1.24开始&#xff0c;移除了Dockershim&#xff0c;需要额外安装CRI&#xff0c;保障Pod能顺利运行。网上有很多容器进行时的工具&#xff0c;本文采用containerd工具。 …

解决双击PDF文件出现打印的问题【Adobe DC】

问题描述 电脑安装Adobe Acrobat DC之后&#xff0c;双击PDF文件就会出现打印&#xff0c;而无法直接打开。 右键PDF文件就会发现&#xff0c;第一栏出现的不是用Adobe打开&#xff0c;而是打印。 重装软件多次仍然无法解决。 原因 右键菜单被改写了。双击其实是执行右键菜…