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

news2025/1/17 0:10:03

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

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

所属专栏: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;//ok
	struct Date d2;//ok
	return 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.coloradmin.cn/o/1628341.html

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

相关文章

plsql 新建sql窗口 初始化慢的问题

问题描述&#xff1a; 新建sql窗口当sql语句多的情况下初始化很慢。 解决方法&#xff1a; 采用导入表的方式。 具体方式 工具->导入表->sql插入。 使用命令窗口 导入文件&#xff0c;然后点击导入按钮。

【力扣 Hot100 | 第七天】4.22(找到字符串中所有字母异位词)

文章目录 2.找到字符串中所有字母异位词2.1题目2.2解法&#xff1a;滑动窗口2.2.1解题思路2.2.2代码实现 2.找到字符串中所有字母异位词 2.1题目 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺…

Spring Security认证流程分析

我自己的思路 先分别实现 userdetailsService&#xff0c;userDetails&#xff0c;passwordEncoder三个接口&#xff0c; 然后就是写登录逻辑 本文章用的是继承UsernamePasswordAuthenticationFilter这个接口 因为这个框架默认登录逻辑是在这里面的&#xff0c;里面的核心就是…

vivado 使用“链路 (Links)”窗口查看和更改链路设置

使用“链路 (Links) ”窗口查看和更改链路设置 创建链路后 &#xff0c; 就会将其添加到“ Links ”视图 &#xff08; 请参阅下图 &#xff09; 中 &#xff0c; 该视图是更改链路设置和查看状态的主要方法 &#xff0c; 也是最佳方法。 “ Links ”窗口中的每一行都对应 1 …

018基于SSM的音乐系统网站

018基于SSM的音乐系统/网站 开发环境&#xff1a; Jdk7(8)Tomcat7(8)MysqlIntelliJ IDEA(Eclipse)Maven 数据库&#xff1a; MySQL 技术&#xff1a; SpringSpring mvcMybatisJqueryVideo jsJSPJSTLEasyUI 适用于&#xff1a; 课程设计&#xff0c;毕业设计&#xff0c;学习…

航拍图像拼接 | 使用C++实现的无人机航拍图像拼接

项目应用场景 面向无人机航拍图像拼接场景&#xff0c;项目使用 C 实现&#xff0c;使用 harris 角点查找特征点 非极大值抑制&#xff0c;由于航拍图像没有严重的尺度旋转变化&#xff0c;使用了 berief 描述子&#xff0c;然后使用 RANSAC 求 H&#xff0c;最后进行图像拼接…

python入门完结篇(6)

插入: 1.使用append的方法,使用append往列表末尾新增一个元素。此处的append的是搭配列表对象来使用的&#xff0c;而不是作为一个独立的函数. 而 type print input len自定义函数都是独立的函数&#xff0c;不用搭配任何对象. 而这种需要搭配对象&#xff08;变量&#xff09…

Ubuntu Mysql修改密码时遇到的问题

参考&#xff1a; ubuntu18.04 首次登录mysql未设置密码或忘记密码解决方法_ubuntu中mysql设置密码-CSDN博客 1. use mysql; #连接到mysql数据库 2. update mysql.user set authentication_stringpassword(123456) where userroot and Host localhost; #修改密码123456是密码…

解决iOS开发不能使用HTTP请求的问题

苹果公司在iOS9中升级了应用网络通信安全策略&#xff0c;默认推荐开发者使用HTTPS协议来进行网络通信&#xff0c;并限制HTTP协议的请求。为了解决这个问题&#xff0c;我们需要在info.plist文件中增加如下配置来实现广告的网络访问&#xff1a;&#xff08;信任HTTP请求&…

【算法基础实验】图论-构建无向图

构建无向图 前提 JAVA实验环境 理论 无向图的数据结构为邻接表数组&#xff0c;每个数组中保存一个Bag抽象数据类型&#xff08;Bag类型需要专门讲解&#xff09; 实验数据 我们的实验数据是13个节点和13条边组成的无向图&#xff0c;由一个txt文件来保存&#xff0c;本…

数字旅游引领智慧化浪潮:科技创新重塑旅游体验,智慧服务打造旅游新高度

在科技飞速发展的今天&#xff0c;数字旅游正以其独特的魅力引领着智慧化浪潮&#xff0c;深刻改变着旅游行业的面貌。数字技术的广泛应用&#xff0c;不仅为旅游行业注入了新的活力&#xff0c;也极大地提升了旅游体验的品质。科技创新与智慧服务的融合&#xff0c;正推动着旅…

如何写好代码?

文章目录 前言内容代码应当易于理解命名注释格式循环和逻辑设计函数设计类其它&#xff08;编程规范、静态检查工具&#xff09;重构 前言 在软件开发领域&#xff0c;写好代码是至关重要的一环。不论是在学校学习的学生&#xff0c;刚刚毕业的应届生&#xff0c;还是刚步入企…

迁移学习基础知识

简介 使用迁移学习的优势&#xff1a; 1、能够快速的训练出一个理想的结果 2、当数据集较小时也能训练出理想的效果。 注意&#xff1a;在使用别人预训练的参数模型时&#xff0c;要注意别人的预处理方式。 原理&#xff1a; 对于浅层的网络结构&#xff0c;他们学习到的…

每日一题(错题分析)

shy的子序列的个数 这一题&#xff0c;我一开始就没什么思路&#xff0c;我就开始暴力枚举了&#xff0c;就是定义i&#xff0c;j(找i后面的)&#xff0c;k(找j后面的)&#xff0c;很显然这种方法超时了。所以我就想使用动态规划&#xff0c;但是空间又超了&#xff0c;我就很懵…

【漏洞复现】IP-guard WebServer 权限绕过漏洞

0x01 产品简介 IP-guard WebServer 是 IP-guard 网络安全管理系统的一部分,用于提供 Web 界面以进行用户权限管理、监控和审计。 0x02 漏洞概述 IP-guard WebServer的权限验证机制中存在设计缺陷,未授权的攻击者能够规避安全验证,通过后端接口执行文件的任意读取和删除操…

学习经验分享【33】YOLOv5 / YOLOv7 / YOLOv8 / YOLOv9 / RTDETR 基于 Pyside6 的图形化界面

大论文可以写两章关于算法创新模型&#xff0c;最后一章可以写对前两章提出方法进行封装&#xff0c;利用PyQT5搭建YOLOv5可视化界面&#xff0c;并打包成exe程序&#xff0c;构建检测平台实现简单的应用。用来凑大论文的字数和工作量&#xff0c;是简单又快速的方法&#xff0…

DELL PowerEdge服务器通过iDRAC升级BIOS遇到的问题

本文对PowerEdge 12G系统&#xff0c;也就是iDRAC 7版本升级BIOS中遇到的几个问题做个总结&#xff0c;对于其他版本理论上应该也是适用的。如果还遇到其他问题&#xff0c;可以添加VX&#xff0c;VX号为 StorageExpert 进行进一步的分析探讨。 第一个问题&#xff0c;成功下载…

【嵌入式AI开发】轻量级卷积神经网络MobileNet项目实战——文末完整源码工程文件

前言:本文介绍轻量级卷积神经网络MobileNet网络实战,包含MobileNetV1、MobileNetV2、ResNet50三个预训练模型可供选择。 实现:1.预训练MobileNet图像分类,2.调用摄像头实时MobileNet图像分类,3.MobileNet视频图像分类。 MobileNet网络理论详解:【嵌入式AI开发】轻量级卷…

keil把c语言函数转成汇编

汇编可以让开发人员从根源上理解程序的运行逻辑&#xff0c;本文介绍如何在keil环境下如何把一个c文件中的某一个函数&#xff0c;转换为汇编函数&#xff0c;并编译运行。 右击某个c文件&#xff0c;选择Option for File。。。 图1 然后把下图中的Generate Assembler SRC Fi…

Post请求中常见的Content-Type类型

Post请求中常见的Content-Type类型的结构 &#xff08;1&#xff09;application/x-www-form-urlencoded 这是浏览器原生的form表单类型&#xff0c;或者说是表单默认的类型。 下面是一个请求实例&#xff1a; 请求报文&#xff1a; 可以看得出&#xff0c;post将请求参数以k…