C++类与对象(1)—初步认识

news2024/11/19 9:34:01

目录

一、面向过程和面向对象

二、类

1、定义

2、类的两种定义方式 

3、访问限定符

4、命名规范化 

5、类的实例化

6、计算类对象的大小

7、存储方式

三、this指针 

1、定义 

2、存储位置

3、辨析

 四、封装好处


一、面向过程和面向对象

  • C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
  • C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

面向过程和面向对象是两种主要的编程范式。它们的主要区别在于如何组织代码和处理数据。

1. 面向过程编程:

面向过程编程是一种编程范式,它以过程(也称为函数)为中心,数据和过程是分开的。在面向过程编程中,程序是一系列被调用的函数或过程,数据被视为辅助函数运行的附属物。这种编程范式的主要特点是流程化,按照事物处理的逻辑顺序来编写程序。

例如,假设我们要编写一个程序来制作一杯咖啡。在面向过程的编程中,我们会创建一个函数来热水,一个函数来研磨咖啡豆,一个函数来混合水和咖啡,等等。每个函数都有一个明确的任务,并按照特定的顺序执行。

2. 面向对象编程:

面向对象编程是一种编程范式,它将数据和处理数据的函数组合成一个整体,称为“对象”。在面向对象编程中,程序是由对象组成的。每个对象都包含数据(称为属性)和一组处理数据的函数(称为方法)。这种编程范式的主要特点是封装性,继承性和多态性。

还是以制作咖啡为例,面向对象编程会创建一个“咖啡”对象,这个对象有一些属性(如水,咖啡豆等)和一些方法(如热水,研磨咖啡豆,混合水和咖啡等)。所有的操作都是通过操作对象的方法来完成的。

二、类

C语言结构体中只能定义变量,而在C++中,结构体不仅可以定义变量,也可以定义函数。

在C++中把结构体升级成了类,而且结构体的名字就是类型。

//C++兼容C结构体用法
typedef struct ListNode
{
	int val;
 //C struct ListNode是类型
	struct ListNode* next;
}LN;

struct ListNode
{
	int val;
 //C++ ListNode是类型
	ListNode* next;
};

1、定义

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

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

2、类的两种定义方式 

在之前数据结构中学过的栈,因为用C语言实现,结构体中只能定义变量,现在我们可以通过C++的方式实现栈,这样在结构体中也可以定义函数。  

第一种:声明和定义全部放在类体中 

注意:

  • 成员函数和成员变量定义在类中的位置没有要求,在调用时会在整个类中查找,不会像类之外使用变量或函数时,编译器只会向上查找。 
  • 成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

这里出现了public和private,他们是访问限定符,稍后进行讲解。 

class Stack
{
public:
	// 成员函数
	void Init(int n = 4)//缺省参数
	{
		a = (int*)malloc(sizeof(int) * n);
		if (nullptr == a)
		{
			perror("malloc fail");
			return;
		}
		capacity = n;
		size = 0;
	}
	void Push(int x)
	{
		//...
		a[size++] = x;
	}
private:		
	// 成员变量
	int* a;
	int size;
	int capacity;
};
int main()
{
	Stack st;
	st.Init();
	st.Push(1);
	st.Push(2);
	st.Push(3);
	return 0;
}

第二种:类声明放在.h文件中,成员函数定义放在.cpp文件中(常用这种)

 头文件中:

//struct Stack无需访问限定符
class Stack//必须有访问限定符,否则报错
{
public:
	// 成员函数
	void Init(int capacity = 4);
	void Push(int x);

private:
	// 成员变量
	int* a;
	int size;
	int capacity;
};

源文件中:

函数名前要加类名 : : (域作用限定符)。

#include "Stack.h"

void Stack::Init(int n)
{
	a = (int*)malloc(sizeof(int) * n);
	if (nullptr == a)
	{
		perror("malloc申请空间失败");
		return;
	}

	capacity = n;
	size = 0;
}

void Stack::Push(int x)
{
	//...
	a[size++] = x;
}

3、访问限定符

在头文件中使用 stuct 定义类:

struct Stack
{
	void Init(int capacity = 4);
	void Push(int x);

	int* a;
	int size;
	int capacity;
};

成功编译: 

在头文件中使用 class 定义类:

class Stack
{
	void Init(int capacity = 4);
	void Push(int x);

	int* a;
	int size;
	int capacity;
};

 结果编译后报错如下:

我们可以发现成员函数无法访问类的成员,这是因为未加访问限定符的class中默认访问权限为私有private,stuct 默认为公有public。

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

 

 

访问权限符说明:

  • public修饰的成员在类外可以直接被访问
  • protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  • 如果后面没有访问限定符,作用域就到 } 即类结束。
  • class的默认访问权限为private,struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

 

4、命名规范化 

我们来看这个代码:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		year = year;
		month = month;
		day = day;
	}
private:
	int year;
	int month;
	int day;
};

 在这个代码中,存在一个问题。在Init函数中,你试图将传入的参数赋值给类的成员变量,但是由于参数和成员变量的名称相同,会导致赋值操作无效。

我们可以选择修改参数名或类成员名,比较好的是在成员名前加上符号_ ,这样小改动保证成员名和函数参数名之间的关系。 

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

5、类的实例化

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

  • 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
  • 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间、存储类成员变量。

类中成员变量是声明,没有分配空间。

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 d1;//实例化
	d1.Init(2023, 2, 3);
	return 0;
}

 我们通过下面这段代码看看类实例化之后,有没有占用实际的物理空间。

class A2 {
public:
	void f2() {}
};

int main()
{
	A2 aa1;
	A2 aa2;

	//实例化就能打印地址
	cout << &aa1 << endl;
	cout << &aa2 << endl;

	return 0;
}

通过打印地址可以发现,实例化后变量确实被分配空间用来存储类成员变量了。 

 

6、计算类对象的大小

类的大小也遵循与结构体一样的计算方式,详细请看这篇文章:结构体内存对齐

7、存储方式

我们先来看下面这段代码:

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 d1;
	d1.Init(2023, 2, 2);

    //打印d1的大小
	cout << sizeof(d1) << endl;

	return 0;
}

我们由输出结果可知: d1的大小为12字节,由此可以推断出类的对象d1中只存储了成员变量。

 

那么为什么成员变量在对象中,成员函数不在对象中呢?

因为每个对象成员变量是不一样的,需要独立存储;每个对象调用成员函数是一样的,它们被放到共享公共区域(代码段) 。

class A2 {
public:
	void f2() {}
};

int main()
{
    A2 a;
	cout << sizeof(a) << endl;

	return 0;
}

类中只有成员函数,这样定义的对象A2大小是1字节,这1字节不存储有效数据,是用来占位的,标识对象被实例化定义出来了。 

三种类的大小对比

// 类中既有成员变量,又有成员函数
class A1 {
public:
    void f1(){}
private:
    int a;
};

// 类中仅有成员函数
class A2 {
public:
    void f2(){}
};

// 类中什么都没有---空类
class A3
{};
  • 一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
  • 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

 

三、this指针 

class Date{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << this << endl;
	}

private:
	int _year; 
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(2022, 2, 2);
	d2.Init(2023, 2, 2);

	return 0;
}
对于上述类,有这样的一个问题:
Date类中有 Init 成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

1、定义 

C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏
的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”
的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编
译器自动完成。
如下图所示: 

我们在初始化Init函数中打印this指针,看看this指针有没有发挥作用。

class Date
{
public:
	void Init(int year, int month, int day)
	{
		cout << this << endl;
    //可以显式使用this,不能显式定义this
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(2022, 2, 2);
	d2.Init(2023, 2, 2);

	return 0;
}

 输出了两个地址,我们通过调试检查一下这两个地址是不是对象d1和d2的地址。

 

在调试中地址如下,上下两幅图对比可以得知,每次初始化时,this指针会指向参数的地址。

在实际中类的成员函数一般不需要加this指针。 

2、存储位置

this存在哪里?

栈,因为他是隐含形参,vs下在ecx寄存器中。

 通过之前计算类的对象大小不包括this,所以this在栈上,它是一个隐含的形参。

 3、辨析

class Date
{
public:
	void Init(int year, int month, int day)
	{
		cout << this << endl;
    //可以显式使用this,不能显式定义this
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	void func()
	{
		cout << "func()" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date* ptr = nullptr;
    ptr->func();

	return 0;
}

程序正常运行: 

 

如果调用这句呢? 

ptr->Init(2022, 2, 2);

结果运行崩溃:

 

下面来解释这两种情况: 

  1. ptr->func()这行代码尝试通过空指针ptr调用成员函数func()。虽然ptr是空指针,但是在这种情况下,由于func()函数没有使用或修改任何成员变量,它可以被静态调用。这意味着它不依赖于具体的对象实例,不需要借助this指针,因此不会引发崩溃。

  2. ptr->Init(2022, 2, 2)这行代码尝试通过空指针ptr调用成员函数Init()。由于Init()函数内部使用了this指针来访问对象的成员变量,而空指针没有有效的对象实例,对空指针的解引用无效,因此在访问this->_yearthis->_monththis->_day时会导致程序崩溃。

同理这段代码也可以正常运行: 

(*ptr).func();

 

 四、封装好处

C++中: 

  1. 数据和方法都封装到类里面
  2. 控制访问方式,愿意给你访问公有,不愿意给你访问私有。

C语言中: 

  1. 数据和方法是分离的
  2. 数据访问控制是自由的,不受限制的

 C++实现栈:

typedef int DataType;
class Stack
{
public:
	void Init()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 3);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = 3;
		_size = 0;
	}
	void Push(DataType data)
	{
		CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		_size--;
	}
	DataType Top() { return _array[_size - 1]; }
	int Empty() { return 0 == _size; }
	int Size() { return _size; }
	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	void CheckCapacity()
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity * 2;
			DataType* temp = (DataType*)realloc(_array, newcapacity *
				sizeof(DataType));
			if (temp == NULL)
			{
				perror("realloc申请空间失败!!!");
				return;
			}
			_array = temp;
			_capacity = newcapacity;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
}; ​
C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需用用户自己维护。

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

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

相关文章

新版mmdetection3d将3D bbox绘制到图像

环境信息 使用 python mmdet3d/utils/collect_env.py收集环境信息 sys.platform: linux Python: 3.7.12 | packaged by conda-forge | (default, Oct 26 2021, 06:08:21) [GCC 9.4.0] CUDA available: True numpy_random_seed: 2147483648 GPU 0,1: NVIDIA GeForce RTX 3090 …

11月19日

一、选择题1.一般认为,世界上第1台电子数字计算机诞生于(&#xff09;年。 A. 1946 B. 1952 C. 1959 D. 1962 1946 记&#xff1a;4 5 6 7 8 平均十年一代 一、计算机发展五代1946年&#xff0c; 世界上第一台数字电子计算机ENIAC 1.1946年开始&#xff0c;第一代电子管计算机…

<Linux>权限管理|权限分类|权限设置|权限掩码|粘滞位

文章目录 Linux权限的概念Linux权限管理a. 文件访问者的分类b. 文件类型和访问权限c. 文件权限表示方法d. 文件权限的设置权限掩码file指令粘滞位 权限总结权限作业 Linux权限的概念 Linux下有两种用户&#xff1a;超级用户(root)和普通用户。 超级用户&#xff1a;可以在Lin…

Python操作Excel常用方法汇总

目录 引言 一、使用pandas库操作Excel 1、读取Excel文件 2、写入Excel文件 3、处理Excel数据 二、使用openpyxl库操作Excel 1、读取Excel文件 2、写入Excel文件 3、处理Excel数据 三、高级功能 总结 引言 Python是一种功能强大的编程语言&#xff0c;它可以用来处理…

MongoDB相关基础操作(库、集合、文档)

文章目录 一、库的相关操作1、查看数据库2、查看当前库3、创建数据库4、删除数据库 二、集合的相关操作1、查看库中所有集合2、创建集合2.1、显示创建2.2、隐式创建 3、删除集合 三、文档的相关操作1、插入文档1.1、插入单条文档1.2、插入多条文档1.3、脚本方式 2、查询文档3、…

电商平台革新:食派士小程序的无代码开发与广告推广集成

食派士小程序&#xff1a;无代码开发的连接神器 食派士小程序&#xff0c;作为上海食派士商贸发展有限公司的专利产品&#xff0c;是一种凭借无代码开发&#xff0c;就能实现与各种系统的连接和集成的电商解决方案。它采用无代码开发的方式&#xff0c;避免了API开发的复杂过程…

原型网络Prototypical Network的python代码逐行解释,新手小白也可学会!!-----系列8

文章目录 前言一、原始代码二、对每一行代码的解释&#xff1a;总结 前言 这是该系列原型网络的最后一段代码及其详细解释&#xff0c;感谢各位的阅读&#xff01; 一、原始代码 if __name__ __main__:##载入数据labels_trainData, labels_testData load_data() # labels_…

常见树种(贵州省):001松类

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、华山松…

麻将馆电脑计费系统,棋牌室怎么用电脑控制灯计时,佳易王计时计费系统软件下载

麻将馆电脑计费系统&#xff0c;棋牌室怎么用电脑控制灯计时&#xff0c;佳易王计时计费系统软件下 棋牌室电脑灯控系统&#xff0c;需要安装一个灯控器&#xff0c;软件发出开灯和关灯的指令&#xff0c;相应的灯就打开或关闭。在点击开始计时的时候&#xff0c;开灯&#xff…

黔院长 | 为什么要调经络?原来通经络对人体健康如此重要!

人体的组成较为复杂&#xff0c;在外有皮肤、毛发&#xff1b;在内有经络、五脏&#xff1b;其他还有我们看不到的精气、津液等等&#xff0c;也因此人体会生各种各样的疾病。 为什么说经络畅通对人体健康如此重要&#xff1f;身体内外始终是一个统一的整体&#xff0c;内外之间…

vue3基础学习

##以前怎么玩的? ###MVC Model:Bean View:视图 Controller ##vue的ref reactive ref:必须是简单类型 reactive:必须不能是简单类型 ###创建一个Vue项目 npm init vuelatest ###生命周期 ###setup相关 ####Vue2的一些写法 -- options API ####Vue3的写法 组合式API Vu…

Python —— Mock接口测试

前言 今天跟小伙伴们一起来学习一下如何编写Python脚本进行mock测试。 什么是mock? 测试桩&#xff0c;模拟被测对象的返回&#xff0c;用于测试 通常意义的mock指的就是mock server, 模拟服务端返回的接口数据&#xff0c;用于前端开发&#xff0c;第三方接口联调 为什么…

数据结构与算法-哈夫曼树与图

&#x1f31e; “永远积极向上&#xff0c;永远豪情满怀&#xff0c;永远热泪盈眶&#xff01;” 哈夫曼树与图 &#x1f388;1.哈夫曼树&#x1f52d;1.1树与二叉树的转换&#x1f52d;1.2森林与二叉树的转换&#x1f52d;1.3哈夫曼树&#x1f50e;1.3.1哈夫曼树的概念&#x…

大数据HCIE成神之路之数学(2)——线性代数

线性代数 1.1 线性代数内容介绍1.1.1 线性代数介绍1.1.2 代码实现介绍 1.2 线性代数实现1.2.1 reshape运算1.2.2 转置实现1.2.3 矩阵乘法实现1.2.4 矩阵对应运算1.2.5 逆矩阵实现1.2.6 特征值与特征向量1.2.7 求行列式1.2.8 奇异值分解实现1.2.9 线性方程组求解 1.1 线性代数内…

基于STM32单片机数字电压表自动切换量程及源程序

一、系统方案 1、本设计采用这STM32单片机作为主控器。 2、液晶1602显示。 3、内部ADC采集电压0-12V&#xff0c;自动切换档位。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 u8 i; u16 a,b,c,d; u16 adcx; float adc; unsigned char datas…

AIGC实战 - 使用变分自编码器生成面部图像

AIGC实战 - 使用变分自编码器生成面部图像 0. 前言1. 数据集分析2. 训练变分自编码器2.1 变分自编码器架构2.2 变分自编码器分析 3. 生成新的面部图像4. 潜空间算术5. 人脸变换小结系列链接 0. 前言 在自编码器和变分自编码器上&#xff0c;我们都仅使用具有两个维度的潜空间。…

Alien Skin Exposure2024免费版图片颜色滤镜插件

Alien Skin Exposure一款非常专业的图片后期处理软件&#xff0c;内含500多种照片滤镜。是一款图片后期处理功能非常强大的软件。这款软件可以对图片的后期效果做很好的处理。 打开Alien Skin Exposure软件&#xff0c;会显示下面这个界面&#xff0c;如图1. ExposureX8win-安…

爱奇艺大数据离在线混部

混部作为一种提高资源利用率、降低成本的的方案&#xff0c;被业界普遍认可。爱奇艺在云原生化与降本增效的过程中&#xff0c;成功将大数据离线计算、音视频内容处理等工作负载与在线业务进行了混部&#xff0c;并且取得了阶段性收益。本文重点以大数据为例&#xff0c;介绍从…

图解API设计风格,看一眼就忘不了了!

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 免责声明~ 任何文章不要过度深思&#xff01; 万事万物都经不起审视&#xff0c;因为世上没有同样的成长环境&#xff0c;也没有同样的认知水平&#xff0c;更「没有适用于所有人的解决方案…

反弹Shell

概述 反弹shell&#xff08;reverse shell&#xff09;就是控制端监听在某TCP/UDP端口&#xff0c;被控端发起请求到该端口&#xff0c;并将其命令行的输入输出转到控制端。reverse shell与telnet&#xff0c;ssh等标准shell对应&#xff0c;本质上是网络概念的客户端与服务端…