【C++从0到王者】第二站:类和对象(上)

news2024/12/26 5:58:22

文章目录

  • 一、面向过程与面向对象
  • 二、类的引入
  • 三、类的访问限定符
  • 四、类的定义
  • 五、封装
  • 六、类的作用域
  • 七、类的实例化
  • 八、类对象模型
    • 1.如何计算类对象的大小
    • 2.类对象存储方式猜测
  • 九、this指针
    • 1.this指针的引出
    • 2.this指针的特性

一、面向过程与面向对象

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
在这里插入图片描述

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
在这里插入图片描述

比如说我们的外卖系统
如果是面向过程的话,那么我们需要关注的是商品的上架、点餐、派单、送单
如果是面向对象的话,那么我们需要关注的是商家、骑手、用户之间的交互关系
面向对象关注的是对象与对象之间的交互关系
现实世界的类和对象映射到虚拟计算机系统

二、类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。

总之,C++兼容C语言。struct以前的用法都可以用。
同时struct升级成了类

如下代码所示,就是将栈写成了类的形式。需要注意的是,在类域中,成员变量的位置可以写在任意位置。因为默认这个类是一个整体。在不同的类域中也就可以出现相同的函数名了。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

struct Queue
{
	void Init();
	//....
};
struct Stack
{
	//成员函数
	void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (a == nullptr)
		{
			perror("malloc fail");
			return;
		}
		top = 0;
		capacity = defaultCapacity;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			int* tmp = (int*)realloc(a, sizeof(int) * capacity * 2);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				return;
			}
			capacity *= 2;
			a = tmp;
		}
		a[top++] = x;
	}
	void Destory()
	{
		free(a);
		a = nullptr;
		top = 0;
		capacity = 0;
	}

	//成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	struct Stack st1;
	st1.Init(10);

	//Stack是一个类,可以直接用类名去定义结构体变量
	Stack st2;
	st2.Init();
	st2.Push(1);
	st2.Push(2);
	st2.Push(3);
	st2.Push(4);
	st2.Destory();
	return 0;
}

三、类的访问限定符

虽然struct也是可以使用的。但是在c++中更喜欢使用class来代替struct
但是当我们直接将上面代码的struct改为class时候,程序会报错。这里其实就涉及到一个权限的问题了

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

访问限定符有以下三种:public(公有)、protect(保护)、private(私有)
在这里插入图片描述

  1. public修饰的成员在类外可以直接被访问
  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到 } 即类结束。
  5. class的默认访问权限为private,struct为public(因为struct要兼容C)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别.

class Stack
{
public:
	//成员函数
	void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (a == nullptr)
		{
			perror("malloc fail");
			return;
		}
		top = 0;
		capacity = defaultCapacity;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			int* tmp = (int*)realloc(a, sizeof(int) * capacity * 2);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				return;
			}
			capacity *= 2;
			a = tmp;
		}
		a[top++] = x;
	}
	void Destory()
	{
		free(a);
		a = nullptr;
		top = 0;
		capacity = 0;
	}
private:
	//成员变量
	int* a;
	int top;
	int capacity;
};

四、类的定义

类的定义如下:

class className
{
 // 类体:由成员函数和成员变量组成
 
}; // 一定要注意后面的分号

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

类有两种定义方式:

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。因为,编译器默认就是内联,但是具体是否成为内联还取决于编译器
  2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::。这样需要注意的是,内联函数不可以定义和声明分离。因为会导致链接错误。如果想要使用内联,就直接在类里面定义即可
class Stack
{
public:
	//成员函数
	void Init(int defaultCapacity = 4);
	void Push(int x)
	{
		if (top == capacity)
		{
			int* tmp = (int*)realloc(a, sizeof(int) * capacity * 2);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				return;
			}
			capacity *= 2;
			a = tmp;
		}
		a[top++] = x;
	}
	void Destory()
	{
		free(a);
		a = nullptr;
		top = 0;
		capacity = 0;
	}
private:
	//成员变量
	int* a;
	int top;
	int capacity;
};

void Stack::Init(int defaultCapacity)
{
	a = (int*)malloc(sizeof(int) * defaultCapacity);
	if (a == nullptr)
	{
		perror("malloc fail");
		return;
	}
	top = 0;
	capacity = defaultCapacity;
}

上面是关于成员函数的定义和声明。那么对于成员变量而言,也有一些注意事项:

// 我们看看这个函数,是不是很僵硬?
class Date
{
public:
	void Init(int year)
	{
		// 这里的year到底是成员变量,还是函数形参?
		year = year;
	}
private:
	int year;
};

上面这个函数其实是将这个函数形参,赋给了成员变量。但是这个容易令人产生歧义
所以一般都选择在前面加下划线或者在后面加下划线,或者就在变量前加m,以代表是成员变量

// 所以一般都建议这样
class Date
{
public:
	void Init(int year)
	{
		_year = year;
	}
private:
	int _year;
};

五、封装

【面试题】 面向对象的三大特性:封装、继承、多态。

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

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

对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

六、类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};
void Person::PrintPersonInfo()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

只有局部域和全局域才有生命周期,类域和命名空间域是没有声明周期的

七、类的实例化

用类类型创建对象的过程,称为类的实例化

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信
  2. 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量信息。
  3. 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图

可以类似为,结构体的声明和定义。创建一个结构体变量就是实例化

八、类对象模型

1.如何计算类对象的大小

在下面这段代码中,计算结果为12。可见实际上成员函数并没有占用空间,只有成员变量才占据了空间。

class Stack
{
public:
	//成员函数
	void Init(int defaultCapacity = 4)
	{
		a = (int*)malloc(sizeof(int) * defaultCapacity);
		if (a == nullptr)
		{
			perror("malloc fail");
			return;
		}
		top = 0;
		capacity = defaultCapacity;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			int* tmp = (int*)realloc(a, sizeof(int) * capacity * 2);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				return;
			}
			capacity *= 2;
			a = tmp;
		}
		a[top++] = x;
	}
	void Destory()
	{
		free(a);
		a = nullptr;
		top = 0;
		capacity = 0;
	}
private:
	//成员变量
	int* a;
	int top;
	int capacity;
};
int main()
{
	Stack st1;
	st1.Init();
	cout << sizeof(st1) << endl;

	return 0;
}

2.类对象存储方式猜测

由上面的结果可知,类的对象函数是不占据空间的。那么类的各个成员是如何存储的呢?

  1. 对象中包含类的各个成员
    在这里插入图片描述

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多
个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?

  1. 代码只保存一份,在对象中保存存放代码的地址
    在这里插入图片描述
  2. 只保存成员变量,成员函数存放在公共的代码段
    在这里插入图片描述

事实上,在C++中,使用的是第三种方案,类种并不存储成员函数,而是将成员函数存储在公共代码区。成员对象才进行存储。并且要遵循内存对齐

我们可以看看下面这段代码的运行结果

// 类中既有成员变量,又有成员函数
class A1 {
public:
	void f1() {}
private:
	int _a;
};
// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};
// 类中什么都没有---空类
class A3
{};
int main()
{
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;
	cout << sizeof(A3) << endl;
	return 0;
}

运行结果为
在这里插入图片描述

其中我们可以注意到的是,如果是空类或者是只有成员函数的类。那么计算的结果是1。 这是因为没有成员变量的类对象,需要1byte,是为了占位,表示对象存在。这个一字节不存储有效数据。未来使用这个类定义出一个变量的时候,这个变量的大小就占据1字节

结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

九、this指针

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 d1;
	d1.Init(2023, 5, 2);
	Date d2;
	d2.Init(2023, 5, 3);
	d1.Print();
	d2.Print(); 
	return 0;
}

事实上,编译器会偷偷给成员函数进行一次处理,他会自动添加一个this指针。进行处理。而我们调用函数的过程也会被偷偷的处理传递一个地址过去
在这里插入图片描述在这里插入图片描述

但是要注意,我们不可以显示的进行传递这个地址和形参声明这个this指针,这个this指针是一个关键字。他是对我们透明的。由编译器自己实现。
这个this指针虽然我们不可以在形参声明,但是我们可以自己在函数内部显示直接使用。

我们可以试着打印出this指针的地址和d1和d2的地址
在这里插入图片描述
那么既然函数种可以直接的使用this,那么能否将其修改呢?答案是不能的。事实上,this指针是带了const修饰的。注意这个const的位置,this指向的内容可以被修改,但是this本身不可以被修改
在这里插入图片描述

2.this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
  1. this指针存在哪里?
    答案是存在栈里面。this是形参,所以跟普通参数一样存在函数调用的栈帧里面
    但是vs对this指针传递进行优化,对象地址放在ecx,ecx存储this指针的值
    在这里插入图片描述
  1. this指针可以为空吗?
    要回答这个问题,我们先看这两段代码
    在这里插入图片描述在这里插入图片描述

第一个为正常运行,第二个运行崩溃
第一个是因为p调用PrintA函数不会发生解引用。因为Print地址不在对象中。p会作为实参传递给this指针。this指针是空的,但是函数内部也没有发生对this指针解引用。所以正常运行
第二个是因为this指针是空指针,但是里面发生了对空指针的解引用,所以崩溃了。里面的本质是this->_a。
从汇编的角度看,也可以看到调用的时候并没有解引用,而是函数调用操作
在这里插入图片描述


好了本期内容就到这里了。
如果对你有帮助的话,不要忘记点赞加收藏哦!!!

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

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

相关文章

Web2与Web3开发的不同之处

Web2是引入交互功能的第二代互联网&#xff0c;也是我们今天所熟悉的。随着Web的不断发展&#xff0c;第三代互联网&#xff0c;也被称为Web3&#xff0c;正处于积极开发中。Web3引入了在区块链上运行的去中心化和无需许可的系统。但是Web2和Web3开发之间有什么区别呢&#xff…

23.5.1总结

这几天都在写项目&#xff1a; 在实现页面&#xff0c;调用数据库的时候&#xff0c;总是把数据库的表改了又改&#xff0c;然后完善了下数据库的表。 存储的思路大概是&#xff1a; 一个课程下可以有多个班级&#xff0c;所以以课程id作为主键建一个表&#xff0c;内容包括…

在win11搭建ubuntu目标机器的QT开发环境的实践

环境&#xff1a; 笔记本电脑 16G内存 win11 尝试wsl的方案&#xff1a; wsl2 ubuntu gnome xrdp wsl安装ubuntu并设置gnome图形界面详细步骤&#xff08;win11ubuntu18&#xff09;_heusjh的博客-CSDN博客 wsl2 ubuntu gnome VcXsrv Windows中WSL2 配置运行GNOM…

Centos7快速安装Elasticsearch 7.17.7

从 Elasticsearch 7.x 版本开始&#xff0c;Elasticsearch 发行版包括了自己的 JDK。因此&#xff0c;您不需要单独安装 Java。以下是在 CentOS 7 上安装 Elasticsearch 7.17.7 的完整步骤&#xff1a;&#xff08;数据默认保存在/var/lib/elasticsearch下&#xff0c;自行更改…

vmware安装arch linux

vmware安装arch linux 1、下载镜像2、安装2.1、VMware 系统版本选择 其他Linux 5.x 内核 64位2.2、进行磁盘分区 3、重启系统后登录进来发现没有地址 由于安装系统时没有安装任何软件 只安装了1个vim 无法动态获取地址4、安装必需的软件 最小化安装5、编辑/etc/ssh/sshd_config…

【Python】flask框架学习 flask框架的基本使用

flask框架是什么&#xff1f; Flask 是一个轻量级的 Web 框架&#xff0c;用于构建 Web 应用程序。它基于 Python 编程语言和 Werkzeug 工具包&#xff0c;提供了简单易用的 API&#xff0c;可以轻松地创建 RESTful API 和 Web 应用程序。 flask的特点 轻量级&#xff1a;Fl…

学会这些常用调试技巧,让你的C/C++代码调试起来如虎添翼

本篇博客主要讲解程序员最应该掌握的技能之一——调试。我个人认为&#xff0c;学习编程&#xff0c;有2件事情非常重要&#xff0c;一是画图&#xff0c;一是调试。下面我会以Visual Studio 2022为例&#xff08;VS的其他版本大同小异&#xff09;&#xff0c;演示如何调试一个…

怎么体验gpt4-国内怎么使用chatGPT

gpt4api要等多久 目前&#xff0c;OpenAI尚未公布GPT-4 API的发布计划和时间表。GPT-4 将是前置还增加强大的自然语言处理能力和推理能力&#xff0c;OpenAI正在为其开发和研究&#xff0c;以使其更加流畅、准确和智能。因此&#xff0c;GPT-4 API的发布时间尚未确定。但是&am…

云原生Istio安装和使用

目录 1 Kubernetes集群环境2 安装Istio2.1 快速部署Istio2.2 回顾K8S组件以及使用2.2.1 Deployment2.2.2 Labels and Selectors2.2.3 Namespace2.2.4 Service2.2.5 Ingress 2.3 初步感受istio2.4 手动注入2.5 自动注入sidecar 1 Kubernetes集群环境 Istio支持在不同的平台下安装…

华为OD机试真题(Java),计算字符串的编辑距离(100%通过+复盘思路)

一、题目描述 Levenshtein 距离&#xff0c;又称编辑距离&#xff0c;指的是两个字符串之间&#xff0c;由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符&#xff0c;插入一个字符&#xff0c;删除一个字符。编辑距离的算法是首先由…

STM32CubeMX时钟树配置详解(F103)

外部时钟配置 学习时使用的是stm32f103系列芯片&#xff0c;文档的时钟树属实不适合新手阅读&#xff0c;STM32cube的功能很强大&#xff0c;时钟树清晰明了&#xff1a; 首先我们要知道&#xff0c;芯片需要一个频率来进行工作&#xff0c;通常选用的是晶振来提供工作频率&a…

数据存储系统概要

可靠、可扩展与可维护性 现在有很多都属于数据密集型&#xff0c;而不是计算密集型。对于这些类型应用&#xff0c;CPU的处理能力往往不是第一限制性因素&#xff0c;关键在于数据量、数据的复杂度及数据的快速多边形。 数据密集型应用模块&#xff1a; 数据库&#xff1a;存…

不会JVM调优怎么进互联网大厂

&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3;&#x1f4e3; &#x1f38d;大家好&#xff0c;我是慕枫 &#x1f38d;前阿里巴巴高级工程师&#xff0c;InfoQ签约作者、阿里云专家博主&#xff0c;一直致力于用大白话讲解技术知识 &#x…

设计模式七大设计原则

文章目录 1、什么是设计模式2、单一职责原则3、开闭原则4、接口隔离原则5、依赖倒置原则6、迪米特法则&#xff08;最少知道原则&#xff09;7、里式替换原则8、组合优于继承 设计模式主要是为了满足一个字 变&#xff0c;这个字&#xff0c;可能是需求变更、可能是场景变更&a…

【VAR | 时间序列】以美国 GDP 和通货膨胀数据为例的VAR模型简单实战(含Python源代码)

以美国 GDP 和通货膨胀数据为例&#xff1a; 1. 数据集 下载数据我们需要从 FRED 数据库下载美国 GDP 和通货膨胀数据&#xff0c;并将它们存储在 CSV 文件中。可以在 FRED 网站&#xff08;https://fred.stlouisfed.org/&#xff09;搜索并下载需要的数据。在这里&#xff0…

非静压模型SWASH学习(7)——自制算例Lock-Exchange

自制算例Lock-Exchange 算例简介模型配置网格及参数设置网格与地形初始条件与边界条件物理参数设置数值求解方法模型输出计算时间 模拟结果 SWASH是由Delft大学开发&#xff0c;用于模拟非静压条件下的水动力/波浪运动的数值模型。 与模型原理相关的内容详见以下论文&#xff1…

Centos系统安装RabbitMQ消息中间件

记录一下在centos7.x下面安装RabbitMQ消息中间件 RabbitMQ是一个开源而且遵循 AMQP协议实现的基于 Erlang语言编写&#xff0c;因此安装RabbitMQ之前是需要部署安装Erlang环境的 先安装Erlang https://packagecloud.io/rabbitmq/ 点进去可以看到 因为使用的centos是7.x版本的…

内网渗透(六十二)之 NTLM Realy 攻击

NTLM Realy 攻击 NTLM Realy 攻击其实应该称为Net-NTLM Realy 攻击,它发生在NTLM认证的第三步,在Response 消息中存在Net-NTLM Hash,当攻击者获得了 Net-NTLM Hash 后,可以重放Net-NTLM Hash 进行中间人攻击。 NTLM Realy 流程如图所示,攻击者作为中间人在客户端和服务器…

asp.net基于web的音乐管理网站dzkf17A9程序

本系统主要包含了等系统用户管理、公告信息管理、音乐资讯管理、音乐类型管理多个功能模块。下面分别简单阐述一下这几个功能模块需求。 管理员的登录模块&#xff1a;管理员登录系统对本系统其他管理模块进行管理。 用户的登录模块&#xff1a;用户登录本系统&#xff0c;对个…

如何免费使用ChatGPT进行学术润色?你需要这些指令...

目录 1 ChatGPT4.0上线2 中科院ChatGPT学术版3 学术润色Prompts 1 ChatGPT4.0上线 2023年3月14日&#xff0c;OpenAI发布ChatGPT4.0。ChatGPT4.0比3.5更大&#xff0c;拥有更多的参数。这意味着它可以更好地捕捉和理解语言的复杂性和细微差别&#xff0c;而且ChatGPT4.0是多模…