类和对象 -上(C++)

news2024/12/26 10:57:38

目录

认识面向过程和面向对象

类的引入

类的定义

语法:

类的两种定义方式:

成员变量命名规则建议

类的访问限定符及封装

访问限定符

C++ 中 class 和 struct 的区别?

封装

类的作用域

类的实例化

类对象模型

如何计算类对象的大小

结构体的内存对齐规则

面试题

1、结构体怎么对齐?如何对齐?

2、如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

3、什么是大小端?如何测试一台机器是大端还是小端?

this指针

this指针的引出

this指针的特性

面试题

1、this指针是存在哪里?

2、this指针可以为空嘛?

3、下面程序运行的结果是?

C语言和C++实现Stack(栈)的对比

C语言实现

C++实现


认识面向过程和面向对象

面向过程:

C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题

洗衣服例子:

C语言关注的是过程:


 面向对象:

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象。靠对象之间的相互交互完成!

洗衣服例子:

C++关注的是参与的对象:


类的引入

C++中把结构体升级成了类:

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


例如:

数据结构栈的实现,C语言的方式实现栈,结构体中只能定义变量。现在以C++的方式,结构体中既可以定义变量,也可以定义函数!


代码:

#include <iostream>

using namespace std;

//C++实现栈
// 在C++中将struct升级成了类,内部既可以定义变量,也可以定义函数
//用C++实现栈
typedef int DataType;
struct Stack
{
	//初始化函数
	void Init(int capacity = 4)
	{
		_a = (DataType*)malloc(sizeof(DataType) * capacity);
		//判断是否开辟成功
		if (_a == nullptr)
		{
			perror("malloc tail: ");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}

	//扩容函数
	void Expansion()
	{
		//扩容
		if (_size == _capacity)
		{
			DataType* tmp = (DataType*)realloc(_a, sizeof(DataType) * 2 * _capacity);
			if (nullptr == tmp)
			{
				perror("realloc tail: ");
				return;
			}
			_a = tmp;
			_capacity *= 2;
		}
	}

	//入栈函数
	void Push(const DataType x)
	{
        Expansion();
		_a[_size] = x;
		++_size;
	}

	//出栈函数
	void Pop()
	{
		//判断栈是否为空
		if (Empty())
		{
			printf("Stack is  Empty\n");
		}
		--_size;
	}

	//返回栈顶元素
	DataType Top()
	{
		return _a[_size - 1];
	}

	//判断栈是否为空
	bool Empty()
	{
		return _size == 0;
	}

	//栈里面的元素个数
	int Count()
	{
		return _size;
	}

	//销毁
	void Destory()
	{
		if (_a != nullptr)
		{
			free(_a);
			_a = nullptr;
			_size = 0;
			_capacity = 0;
		}
	}

	DataType* _a;
	int _size;
	int _capacity;
};
int main()
{
	//对象实例化
	Stack st1;

	//初始化
	st1.Init();

	//入栈
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);
	st1.Push(4);

	//栈顶元素
	cout << st1.Top() << endl;

	//出栈
	st1.Pop();

	//栈里面的元素个数
	cout << st1.Count() << endl;

	//销毁栈
	st1.Destory();

	return 0;
}

总:

以上结构体的定义,C++中更喜欢用Class关键字来定义类


类的定义

语法:

在C++中常用Class关键字定义类

语法:

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

};  // 一定要注意后面的分号

Class是定义类的关键字。className是类名(我们自己起的)。{}内是类的主体,可以有变量,也可以有函数。注意类定义结束时一定不能省略分号;


类中的成员:

类中的内容称为类的成员。类中的变量称为类的属性或成员变量。类中的函数称为类的方法或者成员函数。

类的两种定义方式:

1、声明和定义全在类体内

声明和定义全部放在类体中。需注意在类中定义的成员函数默认都是用inline修饰的。至于编译器是否让它成为inline函数,取决于编译器本身!


代码:

//类的方法(成员函数)的声明和定义全都放在类体内
//在类中定义的方法默认时inline修饰的
// 至于是否成为inline函数是由编译器决定的!

//定义一个时间日期类
class Data
{
	//打印方法,成员函数的方法和定义都放到类体内
	//默认是inline修饰的!
	void Print()
	{
		cout << _year <<" - ";
		cout << _month << " - ";
		cout << _day << endl;
	}

	int _year; //年
	int _month; //月
	int _day;  //日
};

2、声明和定义分离

类声明放在.h文件中,成员函数定义在.cpp文件中。注意:成员函数名前需要加类名和作用域限定符(类名::函数名)。否则编译器无法确认该函数时那个类中的!


代码


注意:

声明和定义分离的成员函数默认不是inline修饰的! 


总结:

一般情况下,建议采用第二种方式来定义类!


成员变量命名规则建议

成员变量的命名规则建议:

一般情况下,为了防止成员变量名和成员函数中的形参或者定义的变量名同名。为了提高代码可读性,更好的区分两者。我们在对类的成员变量命名时,加一些区分标记用于区分。一般情况下在类的成员变量名前加一个下划线(_year) 、或者在类的成员变量的后面加一个下划线(year_)。又或者在成员变量的前面加一个特定字母和下划线(m_year) ……等


代码:

//一般情况下,为了区分类里面的成员变量
// 给其加一个特定的修饰(这个是自己来定)

class Date
{
public:
	//区分成员变量和形参,将成员变量名进行特定修饰
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Pint()
	{
		cout << _year <<" - ";
		cout << _month << " - ";
		cout << _day << endl;
	}

	int _year;  //年
	int _month; //月
	int _day;   //日
};

建议:

一般采取前置下划线_ 或后置_来修饰变量名!


类的访问限定符及封装

访问限定符

C++实现封装的方式:

用类将对象的属性与方法结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部用户使用!


访问限定符:

public (公有):

用public 修饰,类中从public开始直到遇到下一个访问限定符为止之间的内容,类外面和类里面都可以访问!若没有下一个访问限定符。类中从public位置开始直到类域结束之间类体中的内容即可以通过外部进行访问,也可以通过内部进行访问!因为是公有的!


protected(保护)和private(私有):

此处protected和pirvate是类似的,protected和private修饰的成员在类外不能直接被访问。从protected 或 pribate 位置开始,直到遇到下一个访问限定符为止,两者之间的内容是不能在类外面直接访问。若没有下一个访问限定符, 从protected 或 pribate 位置开始直到类结束,之间的内容也是不能在类外面直接访问的!目前私有和保护可以看做是类似的!后续再做区别!


说明:

1、public 修饰的成员在类外可以直接被访问

2、protected 和 private 修饰的成员在类外不能直接被访问(此处protected和private是类似的)

3、访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止

4、如果后面没有访问限定符,作用域就到 } 即类结束。

5、class的默认访问权限private,struct默认为public(因为struct要兼容C的结构体用法)


注意:

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


C++ 中 class 和 struct 的区别?

解答:

因为C++要兼容C语言,所以C++中的struct 可以当作结构体来使用,另外C++中的struct还可以定义类。和class定义类是一样的!区别:C++ 中的class 定义类的成员默认访问是用private修饰的!而struct定义的类默认访问是public修饰的!


注意:

在继承和模板参数列表中,class和struct 也有区别!(后续文章讲解)


封装

封装:

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


本质:

封装本质上是一种管理,为了更好的管理类内的数据,进行封装操作,让用户方便使用类,实现交互


例如:

对于电脑这样一个复杂的设备,提供给用 户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日 常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。 对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如 何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计 算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以 及键盘插孔等,让用户可以与计算机进行交互即可。


C++中的封装:

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


类的作用域

类域:

类定义了一个新的作用域,叫类域。类中的所有成员都在类作用域中。在类体外定义成员时,要加上作用域限定符,通过::作用域限定符指明成员属于那个类域!


代码:

#include <iostream>
using namespace std;

//定义时间日期类
class Date
{
public:
	void Init(int year = 2023, int month = 4, int day = 29)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//打印
	//类里面给声明,类外面定义
	void Print();

	int _year;
	int _month;
	int _day;
};


//在类外面定义的时候,需要通过::域访问限定符
//指定该成员是那个类的
void Date::Print()
{
	cout << _year <<" - ";
	cout << _month << " - ";
	cout << _day << endl;
}

int main()
{
	//实例化对象
	Date st1;
	st1.Init();
	st1.Print();
	return 0;
}

类的实例化

实例化:

用类的类型创建对象的过程叫做类的实例化!


类是对 对象进行描述的:

如同是一个模型一样的东西,实际类里面的成员变量都是声明并没有开空间,只有在类进行实例化之后才开辟了空间,而且用类定义的每个对象都有一个独立的空间!


一个类可以实例化出多个对象:

一个类是可以实例化出多个对象的。类本身是没有空间的。而类实例化出的对象是具有空间的,且每个对象都有一个独立的空间!


打个比方:

类实例化对象,就像现实中使用建筑设计图建造出房子,类就是设计图,只是设计出需要做什么东西,是一张图纸,并没有实体建筑存在。而类实例化出的对象就是真正的房子建筑!即类只是声明,而实例化出的对象才能开辟空间,实际存储数据!总的来说:类实例化出对象就是通过图纸建造出了房子!


 代码:

#include <iostream>
using namespace std;

//定义时间日期类
//类中的成员变量只是声明,并未开辟空间
class Date
{
public:
	void Init(int year = 2023, int month = 4, int day = 29)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//打印
	//类里面给声明,类外面定义
	void Print()
	{
		cout << _year <<" - ";
		cout << _month << " - ";
		cout << _day << endl;
	}

	int _year;
	int _month;
	int _day;
};

int main()
{
	//类实例化对象
	//开辟了空间
	Date st;
	//通过实例化的对象调用类的成员函数
	st.Init();
	st.Print();
	return 0;
}

类对象模型

如何计算类对象的大小

类中既然有成员函数(方法),也有成员变量(类的属性),那么一个类的大小该怎么计算呢?


代码:

#include <iostream>
using namespace std;

//用class 定义时间日期类
class Date
{
public:
	//打印函数
	void Print();
};

//定义一个Std类
class Std
{

};

int main()
{
	cout << sizeof(Date) << endl;
	cout << sizeof(Std) << endl;
	return  0;
}

注意:

类中大小只计算的是成员变量的大小,对于成员函数是不计入空间的,因为他是一个单独的栈空间,在类中若是没有成员变量,类的总大小是1byte。在实例化对象的时,若类中没有成员变量,对象饿大小也是1byte,因为该对象需要占一个位置。如同我们以上所述的一样,定义类是一张图纸,而实例化对象是建造房子,类中的对象如同是卧室客厅等!假设我们没有建造房子,那么我们就得有一个地基,这个地基就是用来占位的,类中的大小也是如此,没有成员变量的类的大小 1 个字节只是用来占位的,实际不存储有效数据!类的存储依旧是遵从结构体的内存对齐的!因为C++要兼容C语言的结构体,所以C++ 中类的存储依旧是执行内存对齐的!


总节:

一个类的大小实际是各个成员变量的总和,当然要注意内存对齐问题,注意空类的大小,因为空类比较特殊,编译器给了空类一个字节来标识类的对象!


结构体的内存对齐规则

对齐规则:

1、第一个成员在与结构体偏移量为0的地址处

2、其它成员变量要对齐到某个数字(对齐数)的整数倍的地址处!

注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的的较小值。Vs中默认对齐数为8

3、结构体的总大小为:最大对齐数(所有成员变量类型的最大者,与默认对齐数的最小值)的整数倍!

4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的默认对齐数的整数倍处,结构体整体的大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍!


面试题

1、结构体怎么对齐?如何对齐?

解答:

结构体是按照结构体的对齐规则来进行对齐:

1、第一个成员变量从偏移量为0的地址处开始存储

2、后续的成员变量首先取到该类型的的大小,与默认对齐数两者之间的最小值,作为该变量类型的对齐数,按照这个对齐数,该变量在存储的时候,对应到取到的对齐数的整数倍处。进行存储,存完前一个成员,若下一个位置的偏移量不是该对齐数的整数倍,则对齐到该对齐数的整数倍进行存储!

3、最后将所有成员都对齐存储完毕之后,取到所有成员的最大值和默认对齐数进行比较,取到两者之间的最大值。最后结构体的大小对齐到该最大值的整数倍!

4、结构体的大小就是对齐到最大值的整数倍的大小!


2、如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

解答:

结构体的对齐是有规定的,不可任意对齐,但有一条指令可以修改默认对齐数(#pragma pack)

结构体进行规定对齐是跟底层的硬件有关系的,每种硬件对于数据的读取是不同的,为了提高硬件读取数据的效率,所以在代码层进行了结构体内存对齐。

比如:假设一台机器有32根地址线,即该机器每次能够读取32位的数据,也就是4byte的数据。例如将以下代码在该机器下进行读取:

 若内存对齐的方式进行读取

char _a变量总共读取一次(先读取四个字节,只取到第一个字节的数据即可),int _b总共读取一次。当读取完char _a 之后接着读取int _b 的时候恰好读取四个字节的内容,而这四个字节的内容恰好是int _b 的内容,刚好读取完成!


若不用内存对齐的方式进行存储:

char _a 读取一次,而 int _b 却要读取两次,读取一两个效率没啥影响,但是每种程序中读取的数据可能成千上万,效率就会有所降低! 


总结:

不能进行任意对齐,也不可不进行对齐,因为可能会降低效率! 


3、什么是大小端?如何测试一台机器是大端还是小端?

解答:

大小端是数据的两种存储方式,因为市面上电脑硬件的制造不同,不同的硬件对于数据的处理方式不同,常用的市面上的硬件机器存储方式大致有两种:大端存储和小端存储!


概念:

大端存储:数据的低权值位放到高地址处,高权值位放到低地址处,是大端存储

小端存储:数据的低权值位放到内存的低地址处,高权值位放到高地址处,是小端存储


测试一台机器是大端还是小端:

利用联合体的特性,可以得出机器是大端还是小端。因为联合体共用同一块空间,所以我们给两个成员 一个char 和一个int  给int成员数字1,随后通过char成员去读取数据,访问成员的时候是从低地址到高地址开始访问的!若取到的char结果是1,是小端存储。若是0则是大端存储!

代码:

#include <iostream>
using namespace std;

union S
{
	char a;
	int b;
};

int main()
{
	S s1;
	//给整型变量赋值1
	s1.b = 1;

	//通过char进行访问
	cout << (int)s1.a << endl;

	return 0;
}

结论:

通过联合体共用同一块空间的特性来判断大小端!

博主当前的机器是小端机器!

this指针

this指针的引出

我们首先来定义一个日期类:

#include <iostream>
using namespace std;

//定义一个日期类
class Date
{
public:
	//初始化
	void Init(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//打印函数
	void Print()
	{
		cout << _year << " - ";
		cout << _month << " - ";
		cout << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//类的实例化对象 
	Date d1;
	Date d2;

	//调用Init方法
	d1.Init(2023, 4, 30);
	d2.Init(2023, 5, 1);

	//打印
	d1.Print();
	d2.Print();

	return 0;
}

问题:

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


解答:

在C++中引入this指针来解决该问题。

C++编译器给每个非静态的成员函数,增加了一个隐藏的指针参数,让该指针指向当前对象(调用该函数的对象)在函数体中所有对成员变量的操作都是通过该指针去访问!且参数是编译器自动传递的,不需要用户来传递!而该参数指针起名为this


this指针的特性

1、this指针类型:

this指针的类型就是类的类型加*号并用const修饰指针,比如Date类 它的this指针就是:Date * const this


2、使用范围:

this指针只能在成员函数的内部使用,因为出了函数作用域形参就被销毁了!在成员函数内部可以通过显式的书写this指针进行访问成员变量。也可以默认用隐式的访问。而this指针本身是不能在函数内部修改的因为是用const修饰的!


3、存储本质:

this指针本质上是函数的形参,只不过是隐式传递的参数(编译器自动传递)当对象调用成员函数时,将对象的地址隐式的传给this形参。所以this指针不在对象中,而是存储在栈区中!


4、传递方式:

this指针是成员函数,第一个隐含的指针形参,一般情况下都是由编译器自动传递的(编译器通过ecx寄存器自动传递),不需要用户自己传(用户自己显式传递会报错)!


代码:

#include <iostream>
using namespace std;

//定义一个日期类
class Date
{
public:
	//初始化
	void Init(int year = 1, int month = 1, int day = 1)
	{
		//编译器会自动将对象的地址传进来 用this指针形参接收
		//可在函数内部显式的写this指针进行访问
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	//打印函数
	void Print()
	{
		//切记this指针是不能被修改的因为是用const修饰的参数
		// this = nullptr;  会报错error:不可修改的左值

		//编译器会自动将对象的地址传进来 用this指针形参接收
		//可在函数内部显式的写this指针进行访问
		cout << this->_year << " - ";
		cout << this->_month << " - ";
		cout << this->_day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//类的实例化对象 
	Date d1;

	//调用Init方法,
	//调用的时候编译器会自动的将对象的地址传进去
	// 且用this指针接收地址
	d1.Init(2023, 4, 30);

	//打印
	//调用的时候编译器会自动的将对象的地址传进去
	// 且用this指针接收地址
	d1.Print();

	//但我们不能显式的去传递对象的地址
	//会报错 error:函数调用中的参数太多
	//因为编译器已经默认传了对象的地址了
	//我们在传递就会出现错误
	//d1.Print(&d1);

	return 0;
}

图片:


总结:

对象在调用成员函数的时候,编译器会自动将对象的地址传给this指针,用户不可在进行显式传对象地址。在成员函数内部可以显式的通过this指针去访问成员变量,this指针默认是用const修饰的不可在成员函数内部修改this指针的值。this指针本质是一个形参,是存储在栈区上的!


面试题

1、this指针是存在哪里?

解答:

this指针本质是成员函数的形参,只是被编译器隐式的传递操作了。因为是函数的形参,是存在栈区上的,不存在对象中!


2、this指针可以为空嘛?

解答:

this指针本质是用const修饰的,所以我们不能在成员函数内部将this指针置为空。而对象实例化之后必然是有地址的,空对象也是占用1个字节的空间。但是外部给this指针传一个空指针进来 this指针是可能为空的,但为空之后this指针是没有啥意义的,空指针是不能进行访问成员的,很危险!即:this指针可能为nullptr


3、下面程序运行的结果是?

1、第一个:

#include <iostream>
using namespace std;

class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

A、编译报错

B、运行崩溃

C、正常运行


解答:C

上面定义了一个A类 的指针p并给其赋予空值,通过指针去调用成员函数Print,此时在传的时候给this指针形参传过去的是p指针的值 也就是nullptr,而在成员函数内部并没有通过this指针去访问成员(进行解引用操作),this指针啥都没做,也没有用到this指针。即程序是可以正常运行的!


2、第二个

#include <iostream>
using namespace std;

class A
{
public:
    void PrintA()
    {
        cout << _a << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

A、编译报错

B、运行崩溃

C、正常运行


解答:B

上面定义了一个A类 的指针p并给其赋予空值,通过指针去调用成员函数Print,此时在传的时候给this指针形参传过去的是p指针的值 也就是nullptr,而在成员函数内部通过this指针去访问成员_a(进行解引用操作),而此时的this指针是一个nullptr,对空指针解引用是不可行的。上述语法是没有问题的,编译时候是不会报错的,但访问是有问题的。即程序会出现运行崩溃!


C语言和C++实现Stack(栈)的对比

C语言实现

//C语言实现栈
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//定义结构体
typedef int DateType;
typedef struct Stack
{
	DateType* a;
	int size;
	int capacity;
}Stack;

//初始化
void StackInit(Stack* ps)
{
	ps->a = (DateType*)malloc(sizeof(DateType) * 4);
	if (NULL == ps->a)
	{
		perror("malloc fail");
		return;
	}
	ps->capacity = 4;
	ps->size = 0;
}

//增容
void CheckCapacity(Stack* ps)
{
	if (ps->size == ps->capacity)
	{
		DateType* tmp = (DateType*)realloc(ps->a, sizeof(DateType) * ps->capacity * 2);
		if (NULL == tmp)
		{
			perror("realloc tail ");
			return;
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
}

//入栈
void StackPush(Stack* ps, DateType x)
{
	assert(ps);
	//是否要增容
	CheckCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;
}

//判断栈是否为空
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->size == 0;
}

//出栈
void StackPop(Stack* ps)
{
	//栈为空就不能在出栈了
	if (StackEmpty(ps))
	{
		printf("Stack is NULL \n");
		return;
	}

	ps->size--;
}

//取到栈顶元素
DateType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->size - 1];
}

//栈元素个数
int StackSize(Stack* ps)
{
	return ps->size;
}

//销毁
void StackDestroy(Stack* ps)
{
	free(ps->a);
	ps->a = NULL;
	ps->capacity = 0;
	ps->size = 0;
}

int main()
{
	Stack s;
	StackInit(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);
	StackPush(&s, 4);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackPop(&s);
	StackPop(&s);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackDestroy(&s);
	return 0;
}

用C语言实现时,Stack相关操作函数有以下共性:

1、每个函数的第一个参数都是Stack*

2、函数中必须要对第一个参数检测,因为该参数可能会为NULL

3、函数中都是通过Stack*参数操作栈的 调用时必须传递Stack结构体变量的地址

结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据 的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出 错。


C++实现

//C++实现栈
#include <iostream>
using namespace std;
typedef int DateType;
//定义类(栈)
class Stack
{
public:
	//初始化
	void Init()
	{
		_a = (DateType*)malloc(sizeof(DateType) * 4);
		if (_a == nullptr)
		{
			perror("malloc fail ");
			return;
		}
		_capacity = 4;
		_size = 0;
	}

	//入栈
	void Push(DateType x)
	{
		//是否增容
		CheckCapacity();
		_a[_size] = x;
		_size++;
	}

	//出栈
	void Pop()
	{
		if (Empty()) return;
		_size--;
	}

	//判空
	bool Empty()
	{
		return 0 == _size;
	}

	//元素个数
	int Size()
	{
		return _size;
	}

	//栈顶元素
	DateType Top()
	{
		return _a[_size - 1];
	}

	//销毁
	void Destroy()
	{
		if (_a != nullptr)
		{
			free(_a);
			_a = nullptr;
			_size = 0;
			_capacity = 0;
		}
	}

	//增容
	void CheckCapacity()
	{
		if (_size == _capacity)
		{
			DateType* tmp = (DateType*)realloc(_a, sizeof(DateType) * _capacity * 2);
			if (nullptr == tmp)
			{
				perror("realloc fail");
				return;
			}
			_a = tmp;
			_capacity *= 2;
		}
	}

private:
	DateType* _a;
	int _size;
	int _capacity;
};

int main()
{
	Stack s;
	s.Init();
	s.Push(1);
	s.Push(2);
	s.Push(3);
	s.Push(4);

	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Pop();
	s.Pop();
	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Destroy();

	return 0;
}

C++实现栈较为C语言好:

C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在 类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。 而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack * 参数是编译器维护的,C语言中需要用户自己维护。

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

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

相关文章

想保护你的网站?用Python来生成验证码图片

前言 随着互联网的发展&#xff0c;我们越来越多地依赖于网站和应用程序&#xff0c;而这些网站和应用程序也面临着各种各样的安全威胁&#xff0c;其中之一就是用户可能会通过脚本攻击你的网站。为了缓解这些安全风险&#xff0c;一个常见的做法是在用户进行操作时&#xff0…

关于电信设备进网许可制度若干改革举措的通告

Q&#xff1a;3月1日后&#xff0c;不再实行进网许可管理的11种电信设备是否还需要继续申请和使用标志&#xff1f; A&#xff1a;3月1日起&#xff0c;对不再实行进网许可管理的11种电信设备停止核发进网许可标志&#xff0c;已申请的标志可在证书有效期内继续使用。 Q&#…

应用启动时aerospike客户端查询rt高原因

在应用刚起步时&#xff0c;发到预发测试或者生产小部分流量进来时&#xff0c;发现aerospike的rt特别高&#xff0c;在流量稍微大点时&#xff0c;rt恢复正常。基本可以断定客户端存在预热问题。 应用没有设置连接池配置&#xff0c;因此check下默认配置 可以看到&#xff0…

c++类 笔记(陆续更新该文档)

派生类 #include <iostream> using namespace std; class Box{private://类私有&#xff0c;只有成员可以调用 也就是说你不可以通过box1.a来调用 ,这些变量其实你默认不用写private 这个变量&#xff0c;只要放在最上面他默认就是 私有int a1;protected://protected&am…

AlgoC++:课程总结

目录 课程总结前言1. 未讲解内容2. 复习2.1 矩阵求导2.2 优化方法2.3 具体的算法 3. 未来怎么学C(必看&#xff01;&#xff01;&#xff01;) 课程总结 前言 手写AI推出的全新面向AI算法的C课程 Algo C&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课…

【嵌入式环境下linux内核及驱动学习笔记-(8-内核 I/O)-信号驱动】

目录 3 信号驱动的异步通知3.1 linux异步通知编程3.1.1 什么是信号3.1.2 信号的工作流程: 3.2. 应用层3.2.1 信号接收 signal函数3.2.2 应用层 fcntl 函数3.2.3 应用层信号驱动机制步骤 3.3 驱动层3.3.1 驱动层模板3.3.2 驱动层 实现fasync函数3.3.3 fasync_helper3.3.4 struct…

Golang-常见数据结构Slice

Slice slice 翻译成中文就是切片&#xff0c;它和数组&#xff08;array&#xff09;很类似&#xff0c;可以用下标的方式进行访问&#xff0c;如果越界&#xff0c;就会产生 panic。但是它比数组更灵活&#xff0c;可以自动地进行扩容。 了解 slice 的本质, 最简单的方法就是…

MySQL 一条SQL语句是如何执行的?

总览 ​ 所以今天我们把MySQL拆解一下&#xff0c;看看里边有哪些零件。下边是MySQL的基本架构示意图。 大体来说&#xff0c;MySQL分为Server层和存储引擎两部分。 Server 层包括连接器、查询缓存、分析器、优化器、执行器等&#xff0c;涵盖 MySQL 的大多数核心服务功能&am…

小白也能懂的可转债配债价格计算

可转债配债如何计算 先给理论公式&#xff1a; 配债10张/1手所需的钱数 配债所需股数 * 当前的股价 这个公式应该很好理解&#xff0c;不需要做过多的解释。 那&#xff0c; 为什么如此简单的公式&#xff0c;还是很多人不会算&#xff0c;是因为&#xff1a; 配债所需的股数跟…

类与对象之构造函数

文章目录 导读类的6个默认构造函数构造函数概念特性 析构函数概念特性 拷贝构造函数概念特性 赋值运算符重载运算符重载赋值运算符重载前置和后置重载 导读 本文是md导入的可能格式有点乱&#xff0c;希望各位理解一下 类的6个默认构造函数 默认成员函数&#xff1a;用户没有…

五一堵车 | AI“高速”车辆检测轻而易举监测大家安全

点击蓝字关注我们 关注并星标 从此不迷路 计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 计算机视觉研究院专栏 作者&#xff1a;Edison_G 五一节不管是离开小城镇还是进入大城市&#xff0c;每个高速路口都是堵车&#xff0c;现在人工智能愈来愈发达&#xff0c…

git fetch时,FETCH_HEAD和.git\refs\remotes\origin会有哪些变化

目录 github远程仓库状态clone 到本地对新clone的仓库直接 fetchgit fetchgit fetch origingit fetch origin test1git fetch origin test2:test22 结论 github远程仓库状态 clone 到本地 git fetchgit fetch origingit fetch origin test3git fetch origin test2:test22 git f…

Photon AI Translator 和做产品的一些思考

近 4 个月内我一直在做 Apple 平台的产品&#xff0c;虽然从使用量来说「简体中文」用户是占多数&#xff0c;但我一直有做多语言的支持&#xff1a;英语、简体中文和繁体中文。习惯上 Google 翻译的我&#xff0c;基本上在使用 Xcode 过程中也会一直在浏览器开着 Google Trans…

复古决战快速施法穿墙秒怪分析流程及安全防护

《决战》是一款非常古老的RPG游戏&#xff0c;作为热血传奇同期的热门游戏也深受7080后的喜爱。 在十几年前玩这个游戏时&#xff0c;我也使用过瞬移穿墙&#xff0c;快速施法秒怪等功能的辅助。 下面我们就用一个自己架设的单机版来回顾一下当年辅助开发的流程&#xff0c;并…

【三十天精通Vue 3】 第二十二天 Vue 3的UI框架详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、常用的Vue 3 UI框架概览1. 常用的Vue 3 UI框架有哪些&…

不同主题增删改查系统【控制台+MySQL】(Java课设)

有很多顾客都是只要实现各种各样的增删改查系统即可&#xff0c;只是主题和数据库表不一样&#xff0c;功能都是增删改查这四个功能&#xff0c;做出来的效果和下面的截图是一样的&#xff0c;后续这样的增删改查系统的运行效果请参考下面的截图&#xff0c;我就不一一演示了&a…

OSPF-MGRE综合实验

拓扑结构&#xff1a; 要求&#xff1a; 1、R6为ISP只能配置IP地址&#xff0c;R1~R5的环回为私有网段 2、R1/4/5为全连的MGRE结构&#xff0c;R1/2/3为星型的拓扑结构&#xff0c;R1为中心站点 3、所有私有网段可以互相通讯&#xff0c;私有网段使用OSPF协议完成 使用的设备…

【Java笔试强训 13】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;参数解析…

忆暖行动|“ 还可以留一点做成柿饼,做法也很简单,就是挑硬柿子把皮削掉,用开水烫个几秒”

追忆过往 感恩现在 我们知道&#xff0c;现在的生活与之前相比发生了翻天覆地的变化&#xff0c;您觉得有什么变化呢&#xff1f; 现在的生活好啊&#xff0c;家家房子都盖起来了&#xff0c;你瞅我这房子&#xff0c;是我子女们大前年给我盖的&#xff0c;我原来都是住的土房…

【Unity-UGUI控件全面解析】| Image 图片组件详解

🎬【Unity-UGUI控件全面解析】| Image 图片组件详解一、组件介绍二、组件属性面板2.1 Image Type三、代码操作组件四、组件常用方法示例4.1 简易血条制作4.2 简易技能冷却条制作五、组件相关扩展使用5.1 Mask 遮罩💯总结🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本…