什么是栈,为什么函数式编程语言都离不开栈?

这篇具有很好参考价值的文章主要介绍了什么是栈,为什么函数式编程语言都离不开栈?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


一、什么是栈,什么是FILO

​ 栈是一种具有特殊访问方式的存储空间,它的特殊性在于,最后进入这个空间的数据,最先出去,可以画图来描述一下这种操作方式。

假设有一个盒子和三本书,依次将三本书他们放入盒子中。

入栈模拟图

什么是栈,为什么函数式编程语言都离不开栈?

​ 现在有一个问题,如果一次只能取一本,我们如何将书从盒子中取出来?

​ 显然必须从盒子的最上边开始取,依次取出,和放入的顺序刚好相反。

出栈模拟图

什么是栈,为什么函数式编程语言都离不开栈?

​ 从程序化的角度来讲,应该有一个标记,这个标记一直指示着盒子最上边的书。

​ 如果说,上图中的盒子就是一个栈,我们可以看出栈的两个基本操作:入栈和出栈。入栈就是将一个新元素放到栈顶,出栈就是从栈顶取出一个元素,栈顶的元素总是最后入栈,需要出栈时,有最先被从栈中取出。栈的这种操作被称为:LIFO(last in first out),后进先出

二、栈的作用是什么,为什么编程语言函数调用都选择用栈?

​ 为何现如今所有的编程语言都会采用栈来进行函数调用?函数调用的状态之所以用栈来记录是因为这些数据的存活时间满足“先进后出”(FILO)顺序,而栈的基本操作正好就是支持这种顺序访问的。下面用一组程序举例。

void test1(){}

void test2() {
	test1();
}

void test3() {
	test2();
}

int main() {
	test3();
}

这个程序运行的顺序为:main() ——》 test3() ——》 test2() ——》 test1() ——》 return from test1() ——》 return from test2() ——》 return from test3() ——》 return from test4().

​ 可以看到,调用者的生命周期总是长于被调用者的生命周期,并且后者在前者之内。被调用者的数据总是后于调用者的,而其释放顺序总是先于调用者,所以正好可以满足LIFO顺序,选用栈这种数据结构来实现函数调用则是一种自然而然的选择。

什么是栈,为什么函数式编程语言都离不开栈?

三、使用C模拟实现解析栈

​ 前面说过栈的两个基本操作,入栈和出栈,都只是对栈顶进行操作,栈可以用数组实现也可以用链表实现,这里讲解用数组实现,因为数组实现相对来说更优一些,数组实现在尾部插入数据代价比较小。

1.结构体的定义

​ 由于各种操作都只在栈顶进行,选择定义一个top来记录栈顶的位置,为了节省空间选择定义一个capacity来记录最大长度,如果等于top的话进行扩容。

typedef int STDataType;
typedef struct StackNode {
	STDataType* arr;
	int capacity;
	int top;
}STNode;

2.栈的创建及销毁

​ 由于函数内有对参数指针的引用,加上assert预防程序崩溃,易于调试,top初始化为-1(也可以初始化为0,个人偏向初始化为-1),两者的区别就是一个是栈顶指向栈顶数据,一个指向栈顶数据的下一个,其实本质上差不多。

void STInit(STNode* pst)
{
	assert(pst);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = -1;
}

void STDestroy(STNode* pst)
{
	assert(pst);
	free(pst->arr);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = 0;
}

3.实现入栈操作

​ 如果栈已经满了,则进行扩容,将数据放到栈顶

void STPush(STNode* pst,STDataType x)
{
	assert(pst);
	if (pst->capacity==pst->top+1)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* temp = NULL;
		temp = (STDataType*)realloc(pst->arr,sizeof(STDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("malloc error");
			return;
		}
		pst->arr = temp;
		pst->capacity = newcapacity;
	}
	pst->arr[++pst->top] = x;
}

4.获取栈顶元素及出栈操作

​ 出栈操作将栈顶元素向下移动即可,甚至无需删除数据,因为再次进行入栈会将其覆盖掉。获取栈顶元素要获取top的下一个位置,因为top是先存后加

bool STEmpty(STNode* pst)
{
	return pst->top == -1;
}

void STPop(STNode* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}

STDataType STTop(STNode* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	return pst->arr[pst->top];
}

5.获取栈中有效元素个数

由于数组偏移度是从0开始存数据,所以指向栈顶的top比实际上个数要少1,真正有效元素个数要将top+1。

int StackSize(STNode* pst)
{
	assert(pst);
	return pst->top+1;
}

源代码分享

//stack.h
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct StackNode {
	STDataType* arr;
	int capacity;
	int top;
}STNode;


void STInit(STNode* pst);
void STPush(STNode* pst,STDataType);
void STPop(STNode* pst);
void STDestroy(STNode* pst);
bool STEmpty(STNode* pst);
STDataType STTop(STNode* pst);
//stack.c
#include "stack.h"


bool STEmpty(STNode* pst)
{
	return pst->top == -1;
}



void STInit(STNode* pst)
{
	assert(pst);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = -1;
}

void STDestroy(STNode* pst)
{
	assert(pst);
	free(pst->arr);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = 0;
}

void STPush(STNode* pst,STDataType x)
{
	assert(pst);
	if (pst->capacity==pst->top+1)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* temp = NULL;
		temp = (STDataType*)realloc(pst->arr,sizeof(STDataType) * newcapacity);
		if (temp == NULL)
		{
			perror("malloc error");
			return;
		}
		pst->arr = temp;
		pst->capacity = newcapacity;
	}
	pst->arr[++pst->top] = x;
}

void STPop(STNode* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}

STDataType STTop(STNode* pst)
{
	assert(pst);
	assert(!STEmpty(pst));
	return pst->arr[pst->top];
}

int StackSize(STNode* pst)
{
	assert(pst);
	return pst->top+1;
}

//test.c
#include "stack.h"

void test1()
{
	STNode ST;
	STInit(&ST);
	STPush(&ST, 1);
	STPush(&ST, 2);
	STPush(&ST, 3);
	STPush(&ST, 4);
	STPush(&ST, 5);
	STPush(&ST, 6);
	STPush(&ST, 7);
	while (!STEmpty(&ST))
	{
		printf("%d ", STTop(&ST));
		STPop(&ST);
	}
	STDestroy(&ST);
}


int main()
{
	test1();
}

什么是栈,为什么函数式编程语言都离不开栈?

✨本文收录于数据结构理解与实现

下几期会继续带来栈的练习题以及与栈刚好相反的堆(FIFO)。如果文章对你有帮助记得点赞收藏关注。文章来源地址https://www.toymoban.com/news/detail-457591.html

到了这里,关于什么是栈,为什么函数式编程语言都离不开栈?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c语言中为什么函数传参大多数用指针类型

    在C语言中,函数传参大多数使用指针类型的原因主要有两个: 允许在函数内部修改实参的值:C语言中的函数参数传递是按值传递的,即将实参值拷贝一份到形参中进行操作,对参的修改不会影响实参。而通过使用指类型参数,可以将实参的地址传递给函数,从而在函数内部

    2024年02月09日
    浏览(19)
  • C语言文本为什么不包括库函数和预处理命令

    C语言的文本不包括库函数和预处理命令 是因为库函数和预处理命令并不是C语言本身的一部分, 它们是由 C语言标准库 和 预处理器 提供的功能。 C语言 标准库 是一组预定义的函数和常量, 用于提供常见的功能,如输入输出、字符串处理、数学计算等。 这些库函数是由C语言

    2024年02月09日
    浏览(19)
  • 【C语言】scanf和strcpy这类关键字和函数为什么不安全,使用VS编译会报错

    首先先说解决方法: 在程序最顶端加入这个代码段 这主要是微软的 C 运行时库实现将这些函数标记为不安全,主要原因是这些函数缺乏对输入长度的边界检查,容易导致缓冲区溢出漏洞。 会产生这样的报错: 即: C4996    \\\'strcpy\\\': This function or variable may be unsafe. Consider usin

    2024年02月14日
    浏览(26)
  • 【编程语言 · C语言 · 函数指针】

    由于指针可以指向任何存储器位置中的地址,因此它们也可以指向可执行代码的开头。 函数指针或函数指针指向内存中函数的可执行代码。函数指针可以存储在数组中,也可以作为参数传递给其他函数。 函数指针声明使用 * 就像使用任何指针一样: (*func_name)  周围的括号很

    2024年02月10日
    浏览(24)
  • 什么是可视化编程?为什么它如此重要?

    可视化编程,又叫可视化程序设计,一直以来就是备受讨论的“热门技术”。一方面,程序员抵触它,觉得它不如用代码开发。另一方面,对于产品经理等稍微懂点开发的业余人员,它确实能提供价值。所以,它到底是什么呢?本文将从可视化编程的定义、应用、优势等三个

    2024年02月12日
    浏览(24)
  • C# 编程语言有什么特点?

    在开始前我有一些资料,是我根据网友给的问题精心整理了一份「C#的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!C#(C Sharp)是一种由Microsoft开发的多范式编程语言,最初发布于2000年。以下是C#编程语言的一

    2024年01月22日
    浏览(28)
  • 为什么编程都建议不要用拼音命名

    我们看看知乎答主举的搞笑例子,一句话全部都是shi,表达起来确实困难。 上面这个回答,一句话全部都是“shi”,表达起来确实困难。并且让人误解 那么编程都建议不要用拼音命名,主要有以下原因: 可读性差 :使用拼音命名的变量、函数名等很难被其他人理解,特别是

    2024年02月04日
    浏览(36)
  • Rust编程语言入门之函数式语言特性:-迭代器和闭包

    闭包(closures) 迭代器(iterators) 优化改善 12 章的实例项目 讨论闭包和迭代器的运行时性能 闭包:可以捕获其所在环境的匿名函数。 闭包: 是匿名函数 保存为变量、作为参数 可在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算 可从其定义的作用域捕获值

    2023年04月08日
    浏览(16)
  • inline内联函数为什么不能是虚函数?

    1. inline内联函数为什么不能是虚函数? 虚函数可以是内联函数 ,内联是可以修饰虚函数的, 但是当虚函数表现多态性的时候不能内联 。 理由如下:内联是在发生在编译期间,编译器会自主选择内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此

    2024年02月21日
    浏览(21)
  • 编程语言MoonBit新增矩阵函数的语法糖

    1. 新增矩阵函数的语法糖 新增矩阵函数的语法糖,用于方便地定义局部函数和具有模式匹配的匿名函数: 2. 新增使用 T::{ ... } 构造结构体的语法 这个新语法可用于显式的指定结构体的类型,并会使得结构体内有更好的补全: 3. 正式移除 var id = expr 的语法 4. 增加了新的关键

    2024年01月23日
    浏览(17)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包