Win32环境下函数调用的堆栈之研究

Win32环境下函数调用的堆栈之研究
Win32环境下函数调用的堆栈之研究

Win32环境下函数调用的堆栈之研究

1.背景说明

由于阅读《Q版缓冲区溢出教程》的需要理解和掌握栈的相关知识,故而使用VC 6.0工具来研究win32环境下函数调用时具体的栈操作。

阅读本文建议先看结论,大概了解相关概念,再看第4节,更易于理解。

2.C原程序

主函数main()和子函数func1都定义了不同类型的局部变量, 并且子函数定义了两个以上(包括两个)的参数. 这样实例源代码可以较充分地说明各种情况。在本实例没有体现的情况, 建议另外添加代码来运行分析。

#include

#include

void func1(int input1, int input2)

{

int j;

char c;

short k;

j = 0;

c = 'a';

k = 1;

printf("sum=%d\n", input1+input2);

return;

}

int main()

{

char output[8] = "abcdef";

int i, j;

i=2;

j=3;

func1(i,j);

printf("%s\r\n", output);

return 0;

}

3.汇编代码

在VC中,按F10进入DEBUG模式。右键弹出菜单,选择“Go To Disassembly”,则显示C源程序的相应汇编代码。注意:这里的汇编代码是DEBUG模式的,与RELEASE模式的汇编代码会有所不同,但我们将要研究的问题。下面的代码完全摘自VC中,未作任何修改。由此可见,子函数存储区(低址)和主函数存储区(高址)之间还有一些空白区。函数中调用的其他库函数存储在更高的地址,具体情况在实践在查看。

……(前面省略)

--- F:\QBufOverflow\rptBugDlgBox\rptBugDialogBox.c -------------------- 1: #include

2: #include

3:

4: void func1(int input1, int input2)

5: {

00401020 push ebp

00401021 mov ebp,esp

00401023 sub esp,4Ch

00401026 push ebx

00401027 push esi

00401028 push edi

00401029 lea edi,[ebp-4Ch]

0040102C mov ecx,13h

00401031 mov eax,0CCCCCCCCh

00401036 rep stos dword ptr [edi]

6: int j;

7: char c;

8: short k;

9:

10: j = 0;

00401038 mov dword ptr [ebp-4],0

11: c = 'a';

0040103F mov byte ptr [ebp-8],61h

12: k = 1;

00401043 mov word ptr [ebp-0Ch],offset func1+27h (00401047)

13:

14: printf("sum=%d\n", input1+input2);

00401049 mov eax,dword ptr [ebp+8]

0040104C add eax,dword ptr [ebp+0Ch]

0040104F push eax

00401050 push offset string "sum=%d\n" (0042001c)

00401055 call printf (00401130)

0040105A add esp,8

15:

16: return;

17: }

0040105D pop edi

0040105E pop esi

0040105F pop ebx

00401060 add esp,4Ch

00401063 cmp ebp,esp

00401065 call __chkesp (004011b0)

0040106A mov esp,ebp

0040106C pop ebp

0040106D ret

--- No source file ---------------------------------------------- (空白区) 0040106E int 3

0040106F int 3

00401070 int 3

00401071 int 3

00401072 int 3

00401073 int 3

00401074 int 3

00401075 int 3

00401076 int 3

00401077 int 3

00401078 int 3

00401079 int 3

0040107A int 3

0040107B int 3

0040107C int 3

0040107D int 3

0040107E int 3

0040107F int 3

--- F:\QBufOverflow\rptBugDlgBox\rptBugDialogBox.c --------------------- 18:

19: int main()

20: {

00401080 push ebp

00401081 mov ebp,esp

00401083 sub esp,50h

00401086 push ebx

00401087 push esi

00401088 push edi

00401089 lea edi,[ebp-50h]

0040108C mov ecx,14h

00401091 mov eax,0CCCCCCCCh

00401096 rep stos dword ptr [edi]

21: char output[8] = "abcdef";

00401098 mov eax,[string "abcdef" (00420f84)]

0040109D mov dword ptr [ebp-8],eax

004010A0 mov cx,word ptr [string "abcdef"+4 (00420f88)]

004010A7 mov word ptr [ebp-4],cx

004010AB mov dl,byte ptr [string "abcdef"+6 (00420f8a)]

004010B1 mov byte ptr [ebp-2],dl

004010B4 xor eax,eax

004010B6 mov byte ptr [ebp-1],al

22: int i, j;

23:

24: i=2;

004010B9 mov dword ptr [ebp-0Ch],2

25: j=3;

004010C0 mov dword ptr [ebp-10h],3

26: func1(i,j);

004010C7 mov ecx,dword ptr [ebp-10h]

004010CA push ecx

004010CB mov edx,dword ptr [ebp-0Ch]

004010CE push edx

004010CF call @ILT+0(_func1) (00401005)

004010D4 add esp,8

27:

28: printf("%s\r\n", output);

004010D7 lea eax,[ebp-8]

004010DA push eax

004010DB push offset string "%s\r\n" (00420024)

004010E0 call printf (00401130)

004010E5 add esp,8

29:

30: return 0;

004010E8 xor eax,eax

31: }

004010EA pop edi

004010EB pop esi

004010EC pop ebx

004010ED add esp,50h

004010F0 cmp ebp,esp

004010F2 call __chkesp (004011b0)

004010F7 mov esp,ebp

004010F9 pop ebp

004010FA ret

--- No source file ---------------------------------------------------- ……(后面省略)

4.跟踪与分析过程

在此过程,我们主要根据与堆栈相关的若干寄存器的变化,来跟踪栈内存区的变化。

4.1 栈是如何建造的?

栈是一种经典的数据结构,它遵循“先进后出”的操作特性。那么在实现栈时如何保证这种操作特性呢?我们来看一下栈的定义及相关概念。

栈的逻辑结构与线性表相同,但栈有其特殊的操作规则,所以说栈是运算受限的线性表。我们可以定义栈是限制在表的一端进行插入和删除的线性表,允许插入/删除的一端称为栈顶,另一端称为栈底。对于插入操作称为PUSH(入栈);删除操作称为POP(出栈)。

很明显,我们要对栈进行操作,需要且仅需要记录栈顶的位置。在Intel 80x86系统中,完成这一功能的就是寄存器ESP(Extended Stack Pointer)。所有的PUSH/POP操作都通过它来完成,因而寄存器ESP是经常变化。

聪明的读者这时会问:如何访问非栈顶的数据呢?Intel 80x86系统中解决的办法是设置另外一个寄存器EBP(Extended Base Pointer),我们用它来保存一个稳定不变的基准地址(基址),然后通过“基址+正负偏移量”的方式实现非栈顶的数据访问。

也可以把EBP看作是保存栈底的位置,那么寄存EBP和ESP一起就组成一个栈。

寄存器是CPU的记忆细胞,非常重要。我们的分析也是依靠寄存器来进行的。更多的寄存器知识可以参考计算机组成原理和汇编语言的相关教程。下面说明几个后文我们需要特别关注的Intel 80x86系统的32位寄存器。

指令地址寄存器EIP:永远保存下一步将要执行的指令的地址。在VC的调试模式下,就是黄色箭头所在行的地址。

基址寄存器EBP:保存当前函数层的栈内存基地址,要依靠它来完成栈内存的变量寻址。栈顶指针寄存器ESP:保存栈顶地址(指针),系统依靠它来完成PUSH、POP操作。栈顶指针所指向的位置是存储有效数据的。故PUSH操作是先往低址移动栈顶指针,再存储数据;而POP操作是先读取数据,再往高址移动栈顶指针。

4.2 函数调用之CALL

编译好工程,按F11进入单步调试模式,并切换到Disassembly视图。接着打开Register,Memory,Call Stack窗口。最原始的寄存器状态如图1所示。下面开始跟踪和分析之旅。再次强调,在其中,要利用相关寄存器的数据来获取想得到的一切东东。

图1 main()函数入口处的寄存器状态

4.2.2 CALL指令前做了什么?

如图2所示,在main()函数调用func1()函数前,还有一大堆代码,暂且不管,后文再叙。我们直接跳到调用func1()语句的地方往下看。

从上述汇编代码可以知道,执行call指令前,有两个push指令。从mov指令的操作数地址可得知,两个PUSH操作的作用是从右向左把子函数func1()的参数j,i先后入栈。这里的参数入栈顺序涉及到一个知识点:调用约定(Calling Convention),它决定了函数调用时如何传递函数参数以及由此而产生的其他操作的规则。高级语言中常用的调用约定有__stdcall、__cdecl、__pascal、__fastcall、thiscall、naked call等。C语言默认的调用约定是__cdecl,本文讨论的也遵循这种调用约定。更具体的内容参阅相关资料。

在函数参数入栈过程中,需要关注寄存器esp的变化和对应栈内存的修改。完成参数入栈后寄存器状态如图3所示。

图2 func1()调用前的寄存器状态

4.2.3 CALL指令中做了什么?

在完成参数入栈后,执行call指令,寄存器状态如图3所示。大家注意到了吗?与图2对比,可看出寄存ESP往低址偏移了4字节,说明其中执行了一个PUSH操作。查看寄存器ESP对应的内存地址的值:0x004010C4,此地址刚好是call指令后续第一条指令的地址。

可为什么没有看到push指令呢?原来在CALL操作中,隐式地把call指令后续第一条指令的地址0x004010C4入栈,然后再无条件地跳转到func1()函数继续执行。

大家是否奇怪call指令的操作数。从图3可以看到,func1()函数的入口地址是

0x00401020,那么call指令的操作数就应该是0x00401020,可现在却是一个乱七八糟的东西,还带了莫明其妙的标识符@ILT。

ILT是什么,@ILT又是什么?

在DEBUG版本中,VC汇编程序会产生一个函数跳转指令表,该表的每个表项存放一个函数的跳转指令。程序中的函数调用就是利用这个表来实现跳转到相应函数的入口地址。

ILT就是函数跳转指令表的名称,是Import Lookup Table的缩写;@ILT就是函数跳转指令表的首地址。在DEBUG版本中增加函数跳转指令表,其目的是加快编译速度,当某函数的地址发生变化时,只需要修改ILT相应表项即可,而不需要修改该函数的每一处引用。

注意:在RELEASE版本中,不会生成ILT,也就是说call指令的操作数直接是函数的入口地址,例如在本例中是这样的:call 00401020

图3 func1调用后的寄存器状态

4.2.4 函数入口做了什么?

到此,程序已经运行到子函数func1()了。此时的寄存器状态如图4所示,我们来看看func1()在入口处作了些什么处理。

首先是把main()函数层的栈内存基地址(由寄存器ebp指示)入栈保存起来。同时把当前的栈顶地址(由寄存器esp指示)作为func1()函数层的栈内存基地址,即更新寄存器ebp 为寄存器esp。此时两者相等,留神吧,后文会对此大做文章呢!

然后,栈顶指针esp向低地址偏移76(0x4C)字节。这里相当于为func1()函数层分配了栈内存。

接着把寄存器ebx,esi,sdi依次入栈保存,即保存main()函数层的上下文。

最后用0xCC初始化上述为func1()函数层所分配的栈内存的每个字节。这里每一步用F11单步跟踪,栈内存的变化你会看得更清楚。

完成入口处理后的寄存器状态如图5所示。

大家是否注意到func1()只定义了三个变量j、c、k,在32位系统中只需要7(=4+1+2)字节,可实际却分配了76个字节。即使考虑到四字节对齐的原因(从后文来看,系统确实也考虑了这一点),三个变量也只需要12(=4+4+4)个字节,也就是说系统额外预留了

64(=76-12)个字节,为什么要预留额外空间,为什么额外空间是64,而不是32,或其他数目?

嗯,预留栈空间是为了防止栈溢出。而对于为什么是64?#@#@$@%&*%$#@我也不知道。

图4 func1()函数入口处理前的寄存器状态

图5 func1()入口处理后的寄存器状态

4.2.5 栈变量如何寻址?

接下来,我们看一下是如何读写函数内的局部变量以及函数的入口参数的。在前文4.4.2小节我们知道func1()函数参数是PUSH入栈了,为什么在func1()函数内部没有看到POP出栈呢?

如图6所示,从汇编代码我们可以看出,对栈变量的寻址是通过“ebp+偏移量”来完成的,即以寄存器ebp为基点,若访问局部变量则向低址方向偏移(减);若访问入口参数则

向高址方向偏移(加)。

图6 func1()内读写栈数据

大家是否注意到,虽然局部变量c是char类型,只占一个字节,但实际上分配了4个字节,这是要求四字节对齐导致的,由编译器自动完成。变量k同理。这里也证实了

4.2.4小节有关栈内存分配的考虑。

4.2.6 栈也分层?

我们再回头看看main()函数。其入口处的汇编代码是否似曾相识?不,简直,根本就是与func1()函数入口处的汇编代码一样。也就是说main()函数入口也完成了保存当前寄存器ebp,更新寄存器ebp,分配/初始化栈内存,保存上下文等一系列操作。

这并不奇怪,main()函数虽然是C程序的执行入口点,但对于OS来说,它也是作为一个子函数被OS调用的,所以也存在我们讨论的子函数调用所涉及的一切堆栈操作。因此,也就不难想像,对于多层函数调用的情况下,以每层的ebp为分界线,那么栈也有清晰的层次结构了。动手编写一个多层调用的源程序,把其中的栈结构画出来,栈将不再神秘!

这里我们顺便讨论另一个知识点:字符串数组及其赋值。C语言定义字符串数组并初始化只用了一条语句

char output[8] = "abcdef";

但对应了8条汇编语句。如图7所示, 字符串“abcdef”并不直接存放在栈内存区,而在高址的全局数据内存区有相应的内存空间0x00420F84。从相应的8条汇编指令可以看出,对局部字符数组变量(栈变量)赋值,是利用寄存器从上述数据内存区把字符串“abcdef”拷贝到栈内存中,如图8所示。即使从函数中返回,数据内存区的字符串

“abcdef”也不会被销毁,但程序也访问不了。

图7 字符串常量存放在全局数据区

图8 局部字符串数组赋值

4.3 函数调用之RET

与call指令相对的是ret指令。下面我们看看是如何完成返回到main()函数

的。

4.3.1 函数出口做了什么?

如图9所示,一条return语句对应着9条汇编指令,最后一条是ret指令。那么ret指令前那些指令是做什么的呢?

图9 func1()函数出口处理前的寄存器状态

细心的读者可能已经注意到此时寄存器ebp、esp状态与func1()函数入口处理完后的状态一致(如图5所示)。是的,是时候退出func1()函数返回到main()函数了,一切准备就绪,只欠行动。

首先依次出栈恢复寄存器edi、esi、ebx,即恢复main()函数层的上下文;

然后栈顶指针往高址偏移76(0x4C)字节,即释放入口处分配的func1()函数的栈内存;

最后出栈恢复寄存器ebp、esp。

从上述过程来看,完全是func1()函数入口处理的逆过程嘛。挺公道的,谁破坏别人的现场就要给别人恢复回来,最终寄存器ebp、esp恢复到func1()函数入口时的状态,如图10所示(对比图4)。

结束了吗?等等,漏了两条指令。

令,并调用__chkesp来检查保存在寄存器EFL中的比较结果,如图11所示。

下面是__chkesp例程的指令代码。

4.3.2 RET指令中做了什么?

接下来就要执行ret指令了,按下F11,看看哪些寄存器发生了变化?嗯,

寄存器esp、eip变了。寄存器esp变大了?咋没看到pop指令呀?呵呵,又是

在暗处做了些“见不得人的勾当”。ret指令完成了两个动作:

(1) 把寄存器esp指向的栈数据取出来,存放到寄存器eip中;

(2) 按完成一个出栈动作更新寄存器esp。

看到了吗?寄存器eip存放的指令地址又是call指令后续的第一条指令地址

0x004010E4,也就是说又回main()函数中了,如图12所示。

图12 ret指令使得控制返回main()函数中

4.3.3 RET指令后做了什么?

大家可能已经注意到寄存器esp现在是指向func1()函数的入口参数的栈区。在前文我们知道func1()函数的入口参数是在main()函数中通过两次push指令入栈的,那么是否意味着有配对的两次pop指令出栈呢?可没有找到pop指令,只有这么一条add指令。

很明显,对于寄存器esp来说,上述add指令完成两次pop指令同样的功能,即把func1()函数的入口参数j、i出栈。为什么不用pop指令呢?大家考虑一下如果一个函数有10个入口参数,那么岂不是要执行10次pop指令才能把所有入口参数出栈,考虑到指令执行的时钟周期数,采用pop指令的执行时间将是采用add指令的10倍。既然入口参数出栈后不再使用,所以出栈的时间当然是越短越好。

完成入口参数出栈后,寄存器esp又恢复调用func1()函数之前那一刻的状态了,如图13所示(对比图2)。至此,一个完整的函数调用过程到此就圆满结束了。

图13 入口参数出栈后恢复函数调用前的寄存器状态我们继续讨论函数入口参数出栈的问题,它有一个专业术语“堆栈的平衡”。从上述跟踪过程来看,函数入口参数是由调用者main()函数完成入/出栈操作,即由调用者实现堆栈的平衡,这很合符逻辑。嗯,再来思考这样一个问题:能否由被调用者func1()函数完成上述出栈操作,即由被调用者负责堆栈的平衡呢?

答案是肯定的,因为在被调用者清楚的知道自己的入口参数的长度的情况下,技术实现上完全没有问题,只要在ret指令前清除入口参数的栈区就OK 了。

在4.2.2小节也提到上述的问题是由函数采用调用约定规则决定。函数声明定义时采用的调用约定告诉了编译器:如何传递参数,是通过堆栈还是寄存器?如果是通过堆栈,函数参数以何种方式入栈,是从右向左,还是从左向右?如果是通过堆栈,由谁负责堆栈的平衡,是调用者,还是被调用者?……下面是几种常见的调用约定。

(1) __cdecl:C/C++函数默认的调用约定,使用栈传递函数参数,从右向左压栈,由调用者负责栈的平衡,VC将函数编译后会在函数名前面加上下划线

“_”前缀;

(2) __sdtcall:Win32 API函数默认的调用约定,使用栈传递函数参数,从右向

左压栈,由被调用者负责栈的平衡,VC将函数编译后会在函数名前面加上下划

线“_”前缀,在函数名后加上"@"和参数的字节数;

(3) __fastcall:与__stdcall调用约定类似,区别在于函数的第一、二个DWORD 类型参数(或者类型长度更小的参数)通过寄存器ecx和edx传道,其余参数通过栈来传递,从右向左压栈,由被调用者负责栈的平衡,VC将函数编译后会在函数名前面加上下划线“@”前缀,在函数名后加上"@"和参数的字节数;

由上述规则可知,__cdecl调用约定产生的执行文件会比__stdcall调用约定的要大,因为采用前者,每一个调用者都会产生堆栈平衡的代码,而采用后者,只在被调用者内部产生一次堆栈平衡的代码。噢,由此看来,__cdecl调用约定岂不是要被淘汰了?非也,想想像printf()这种参数个数不确定的函数,

__stdcall调用约定应付不了,只能采用__cdecl调用约定,它允许函数的参数的个数是不固定的,这也是C语言的一大特色。

动手试试吧,在定义func1()时增加调用约定说明,然后Save,Rebuild All,再看看其汇编代码,一切尽在不言中。

5.结论

1.函数调用是由call和ret指令完美配对完成的,call指令作用是把控制传送给被调用

函数,ret指令作用是把控制返回给调用函数;

2.函数调用根据函数定义时采用的调用约定来完成函数参数的传递和堆栈平衡;

3.函数调用导致的入栈操作分三类:

执行call前入栈操作:显式地把被调用函数入口参数入栈;

执行call时入栈操作:隐式地把call指令后的那条指令的地址入栈;

执行call后入栈操作:显式地把调用函数的上下文寄存器入栈;

4.根据上述入栈内容对栈内存划分结构,则形成如下的典型栈结构

调用者完成入栈

被调用者完成入栈

低地址

6. 栈内存中自动完成四字节对齐;

7. 字符串常量不存储在栈区中,而存储在全局数据区中。

数据结构-堆栈和队列实验报告

实验二堆栈和队列 实验目的: 1.熟悉栈这种特殊线性结构的特性; 2.熟练并掌握栈在顺序存储结构和链表存储结构下的基本运算; 3.熟悉队列这种特殊线性结构的特性; 3.熟练掌握队列在链表存储结构下的基本运算。 实验原理: 堆栈顺序存储结构下的基本算法; 堆栈链式存储结构下的基本算法; 队列顺序存储结构下的基本算法;队列链式存储结构下的基本算法;实验内容: 3-18链式堆栈设计。要求 (1)用链式堆栈设计实现堆栈,堆栈的操作集合要求包括:初始化Stacklnitiate (S), 非空否StackNotEmpty(S),入栈StackiPush(S,x), 出栈StackPop (S,d),取栈顶数据元素StackTop(S,d); (2)设计一个主函数对链式堆栈进行测试。测试方法为:依次把数据元素1,2,3, 4,5 入栈,然后出栈并在屏幕上显示出栈的数据元素; (3)定义数据元素的数据类型为如下形式的结构体, Typedef struct { char taskName[10]; int taskNo; }DataType; 首先设计一个包含5个数据元素的测试数据,然后设计一个主函数对链式堆栈进行测试,测试方法为:依次吧5个数据元素入栈,然后出栈并在屏幕上显示出栈的数据元素。 3-19对顺序循环队列,常规的设计方法是使用対尾指针和对头指针,对尾指针用于指示当 前的対尾位置下标,对头指针用于指示当前的対头位置下标。现要求: (1)设计一个使用对头指针和计数器的顺序循环队列抽象数据类型,其中操作包括:初始化,入队列,出队列,取对头元素和判断队列是否为空; (2)编写一个主函数进行测试。 实验结果: 3-18 typedef struct snode { DataType data; struct snode *n ext; } LSNode; /* 初始化操作:*/

栈和队列习题答案

第三章栈和队列习题答案 一、基础知识题 设将整数1,2,3,4依次进栈,但只要出栈时栈非空,则可将出栈操作按任何次序夹入其中,请回答下述问题: (1)若入、出栈次序为Push(1), Pop(),Push(2),Push(3), Pop(), Pop( ),Push(4), Pop( ),则出栈的数字序列为何(这里Push(i)表示i进栈,Pop( )表示出栈) (2)能否得到出栈序列1423和1432并说明为什么不能得到或者如何得到。 (3)请分析1,2 ,3 ,4 的24种排列中,哪些序列是可以通过相应的入出栈操作得到的。 答:(1)出栈序列为:1324 (2)不能得到1423序列。因为要得到14的出栈序列,则应做Push(1),Pop(),Push(2),Push (3),Push(4),Pop()。这样,3在栈顶,2在栈底,所以不能得到23的出栈序列。能得到1432的出栈序列。具体操作为:Push(1), Pop(),Push(2),Push(3),Push(4),Pop(),Pop(),Pop()。 (3)在1,2 ,3 ,4 的24种排列中,可通过相应入出栈操作得到的序列是: 1234,1243,1324,1342,1432,2134,2143,2314,2341,2431,3214,3241,3421,4321 不能得到的序列是: 1423,2413,3124,3142,3412,4123,4132,4213,4231,4312 链栈中为何不设置头结点 答:链栈不需要在头部附加头结点,因为栈都是在头部进行操作的,如果加了头结点,等于要对头结点之后的结点进行操作,反而使算法更复杂,所以只要有链表的头指针就可以了。 循环队列的优点是什么如何判别它的空和满 答:循环队列的优点是:它可以克服顺序队列的"假上溢"现象,能够使存储队列的向量空间得到充分的利用。判别循环队列的"空"或"满"不能以头尾指针是否相等来确定,一般是通过以下几种方法:一是另设一布尔变量来区别队列的空和满。二是少用一个元素的空间,每次入队前测试入队后头尾指针是否会重合,如果会重合就认为队列已满。三是设置一计数器记录队列中元素总数,不仅可判别空或满,还可以得到队列中元素的个数。 设长度为n的链队用单循环链表表示,若设头指针,则入队出队操作的时间为何若只设尾指针呢答:当只设头指针时,出队的时间为1,而入队的时间需要n,因为每次入队均需从头指针开始查找,找到最后一个元素时方可进行入队操作。若只设尾指针,则出入队时间均为1。因为是循环链表,尾指针所指的下一个元素就是头指针所指元素,所以出队时不需要遍历整个队列。 指出下述程序段的功能是什么 (1) void Demo1(SeqStack *S){ int i; arr[64] ; n=0 ; while ( StackEmpty(S)) arr[n++]=Pop(S); for (i=0, i< n; i++) Push(S, arr[i]); } .. // 设Q1已有内容,Q2已初始化过 while ( ! QueueEmpty( &Q1) ) { x=DeQueue( &Q1 ) ; EnQueue(&Q2, x); n++;} for (i=0; i< n; i++) { x=DeQueue(&Q2) ; EnQueue( &Q1, x) ; EnQueue( &Q2, x);} 答: (1)程序段的功能是将一栈中的元素按反序重新排列,也就是原来在栈顶的元素放到栈底,栈底的

栈和队列的基本操作

《数据结构与算法》实验报告 专业班级学号 实验项目 实验二栈和队列的基本操作。 实验目的 1、掌握栈的基本操作:初始化栈、判栈为空、出栈、入栈等运算。 2、掌握队列的基本操作:初始化队列、判队列为空、出队列、入队列等运算。 实验容 题目1: 进制转换。利用栈的基本操作实现将任意一个十进制整数转化为R进制整数 算法提示: 1、定义栈的顺序存取结构 2、分别定义栈的基本操作(初始化栈、判栈为空、出栈、入栈等) 3、定义一个函数用来实现上面问题: 十进制整数X和R作为形参 初始化栈 只要X不为0重复做下列动作 将X%R入栈 X=X/R 只要栈不为空重复做下列动作 栈顶出栈输出栈顶元素 题目2: 利用队列的方式实现辉三角的输出。 算法设计分析 (一)数据结构的定义 1、栈的应用 实现十进制到其他进制的转换,该计算过程是从低位到高位顺序产生R进制数的各个位数,而打印输出一般从高位到低位进行,恰好与计算过程相反。因此,运用栈先进后出的性质,即可完成进制转换。 栈抽象数据结构描述 typedef struct SqStack /*定义顺序栈*/ { int *base; /*栈底指针*/ int *top; /*栈顶指针*/ int stacksize; /*当前已分配存储空间*/ } SqStack;

2、队列的应用 由于是要打印一个数列,并且由于队列先进先出的性质,肯定要利用已经进队的元素在其出队之前完成辉三角的递归性。即,利用要出队的元素来不断地构造新的进队的元素,即在第N行出队的同时,来构造辉三角的第N+1行,从而实现打印辉三角的目的。 队列抽象数据结构描述 typedef struct SeqQueue { int data[MAXSIZE]; int front; /*队头指针*/ int rear; /*队尾指针*/ }SeqQueue; (二)总体设计 1、栈 (1)主函数:统筹调用各个函数以实现相应功能 int main() (2)空栈建立函数:对栈进行初始化。 int StackInit(SqStack *s) (3)判断栈空函数:对栈进行判断,若栈中有元素则返回1,若栈为空,则返回0。 int stackempty(SqStack *s) (4)入栈函数:将元素逐个输入栈中。 int Push(SqStack *s,int x) (5)出栈函数:若栈不空,则删除栈顶元素,并用x返回其值。 int Pop(SqStack *s,int x) (6)进制转换函数:将十进制数转换为R进制数 int conversion(SqStack *s) 2、队列 (1)主函数:统筹调用各个函数以实现相应功能 void main() (2)空队列建立函数:对队列进行初始化。 SeqQueue *InitQueue() (3)返回队头函数:判断队是否为空,若不为空则返回队头元素。 int QueueEmpty(SeqQueue *q) (4)入队函数:将元素逐个输入队列中。 void EnQueue(SeqQueue *q,int x) (5)出队函数:若队列不空,则删除队列元素,并用x返回其值。 int DeQueue(SeqQueue *q) (6)计算队长函数:计算队列的长度。 int QueueEmpty(SeqQueue *q) (7)输出辉三角函数:按一定格式输出辉三角。 void YangHui(int n)

实验二 堆栈和队列基本操作的编程实现

实验二堆栈和队列基本操作的编程实现 【实验目的】 堆栈和队列基本操作的编程实现 要求: 堆栈和队列基本操作的编程实现(2学时,验证型),掌握堆栈和队列的建立、进栈、出栈、进队、出队等基本操作的编程实现,存储结构可以在顺序结构或链接结构中任选,也可以全部实现。也鼓励学生利用基本操作进行一些应用的程序设计。 【实验性质】 验证性实验(学时数:2H) 【实验内容】 内容: 把堆栈和队列的顺序存储(环队)和链表存储的数据进队、出队等运算其中一部分进行程序实现。可以实验一的结果自己实现数据输入、数据显示的函数。 利用基本功能实现各类应用,如括号匹配、回文判断、事物排队模拟、数据逆序生成、多进制转换等。 【思考问题】 1.栈的顺序存储和链表存储的差异? 2.还会有数据移动吗?为什么? 3.栈的主要特点是什么?队列呢? 4.栈的主要功能是什么?队列呢? 5.为什么会有环状队列? 【参考代码】 (一)利用顺序栈实现十进制整数转换转换成r进制 1、算法思想 将十进制数N转换为r进制的数,其转换方法利用辗转相除法,以N=3456,r=8为例转换方法如下: N N / 8 (整除)N % 8(求余) 3456 432 0 低 432 54 0 54 6 6 6 0 6 高 所以:(3456)10 =(6600)8 我们看到所转换的8进制数按底位到高位的顺序产生的,而通常的输出是从高位到低位的,恰好与计算过程相反,因此转换过程中每得到一位8进制数则进栈保存,转换完毕后依次出栈则正好是转换结果。 算法思想如下:当N>0时重复1,2 ①若N≠0,则将N % r 压入栈s中,执行2;若N=0,将栈s的内容依次出栈,算法结束。 ②用N / r 代替N 2、转换子程序

PTA第三章栈与队列练习题

1-1 通过对堆栈S操作:Push(S,1), Push(S,2), Pop(S), Push(S,3), Pop(S), Pop(S)。输出得序列为:123。(2分) T F 作者: DS课程组 单位: 浙江大学 1-2 在用数组表示得循环队列中,front值一定小于等于rear值。(1分) T F 作者: DS课程组 单位: 浙江大学 1-3 若一个栈得输入序列为{1, 2, 3, 4, 5},则不可能得到{3, 4, 1, 2, 5}这样得出栈序列。(2分) T F 作者: 徐镜春 单位: 浙江大学 1-4 If keys are pushed onto a stack in the order {1, 2, 3, 4, 5}, then it is impossible to obtain the output sequence {3, 4, 1, 2, 5}、(2分) T F 作者: 徐镜春 单位: 浙江大学 1-5 所谓“循环队列”就是指用单向循环链表或者循环数组表示得队列。(1分) T F 作者: DS课程组 单位: 浙江大学 1-6 An algorithm to check for balancing symbols in an expression uses a stack to store the symbols、(1分) T F 2-1 设栈S与队列Q得初始状态均为空,元素a、b、c、d、e、f、g依次进入栈S。若每个元素出栈后立即进入队列Q,且7个元素出队得顺序就是b、d、c、f、e、 a、g,则栈S得容量至少就是: (2分) 1. 1 2. 2 3. 3 4. 4 作者: DS课程组

栈和队列的基本操作的实现

封面: 安徽大学 网络工程 栈和队列的基本操作的实现 ______2010\4\12

【实验目的】 1.理解并掌握栈和队列的逻辑结构和存储结构; 2.理解栈和队列的相关基本运算; 3.编程对相关算法进行验证。 【实验内容】 (一)分别在顺序和链式存储结构上实现栈的以下操作(含初始化,入栈,出栈,取栈顶元素等): 1.构造一个栈S,将构造好的栈输出; 2.在第1步所构造的栈S中将元素e 入栈,并将更新后的栈S输出; 3.在第2步更新后所得到的栈S中将栈顶元素出栈,用变量e返回该元素,并将更新后的栈S输出。(二)分别在链队列和循环队列上实现以下操作(初始化,入队,出队,取队头元素等): 1.构造一个队列Q,将构造好的队列输出; 2.在第1步所构造的队列Q中将元素e入队,并将更新后的队列Q输出; 3.在第2步更新后所得到的队列Q中将队头元素出队,用变量e返回该元素,并将更新后的队列Q输出。

【要求】 1.栈和队列中的元素要从终端输入; 2.具体的输入和输出格式不限; 3.算法要具有较好的健壮性,对运行过程中的错误 操作要做适当处理。 三、实验步骤 1.本实验用到的数据结构 (1)逻辑结构:线性结构 (2)存储结构:程序一、四(顺序存储结构); 程序二、三(链式存储结构); 2.各程序的功能和算法设计思想 程序一:顺序栈 # include # include # include #define STACKINITISIZE 100 # define STACKINCREMENT 10 # define OK 1 # define ERROR 0 # define OVERFLOW -2 typedef int SElemtype; typedef int status; typedef struct { SElemtype *base; SElemtype *top; int stacksize; }sqstack; void Initstack (sqstack *s) { (*s).base = (SElemtype *)malloc(STACKINITISIZE * sizeof (SElemtype)); if(!(*s).base) exit(OVERFLOW);

数据结构基础练习(栈和队列)

数据结构基础练习(栈和队列) 学号姓名蓝礼巍班级 . 一、选择题 1.有5个元素a,b,c,d,e依次进栈,允许任何时候出栈,则可能的出栈序列是 c 。 A.baecd B.dceab C.abedc D.aebcd 2.下列有关递归的叙述,不正确的是 b 。 A.在计算机系统内,执行递归函数是通过自动使用栈来实现的。 B.在时间和空间效率方面,递归算法比非递归算法好。 C.递归函数的求解过程分为递推(进栈)和回推(出栈)两个阶段。 D.在递归函数中必须有终止递归的条件。 3.栈和队列均属于哪一种逻辑结构 A 。 A.线性结构B.顺序结构C.非线性结构D.链表结构4.设输入元素为1、2、3、P和A,输入次序为123PA,元素经过栈后得到各种输出序列,则可以作为高级语言变量名的序列有 d 种。 A.4 B.5 C.6 D.7 5.一个队列的入队序列为a,b,c,d,则该队列的输出序列是 b 。 A.dcba B.abcd C.adcb D.cbda 6.在一个链式队列中,假设f和r分别为队头和队尾指针,则插入s所指结点的运算是b 。 A. f->next=s; f=s; B. r->next=s; r=s; C. s->next=s; r=s; D. s->next=f; f=s; 7.如果5个元素出栈的顺序是1、2、3、4、5,则进栈的顺序可能是 c 。 A.3、5、4、1、2 B.1、4、5、3、2 C.5、4、1、3、2 D.2、4、3、1、5 8.若已知一个栈的入栈序列是1,2,3,…,n,其输出序列为p1,p2,p3,…,pn,若p1=n,则pi为。 A.i B.n-i C.n-i+1 D.不确定 二、填空题 1.栈和队列是一种特殊的线性表,其特殊性体现在是运算受限线性表。设现有元素e1,e2,e3,e4,e5和e6依次进栈,若出栈的序列是e2,e4,e3,e6,e5,e1,则栈S的容量至少是 3 。 2.顺序循环队列中,设队头指针为front,队尾指针为rear,队中最多可有MAX个元素,采用少用一个存储单元的方法区分队满与队空问题,则元素入队列时队尾指针的变化为 Rear=(rear+1)%MAX ;元素出队列时队头指针的变化为fort=(fotr+1)%MAX ;队列中的元素个数为 (rear-fort+MAX)%MAX 。若则可用表示队满的判别条件,队空的判别条件仍然为 rear==fort 。 三、解答题

栈和队列(必备)

栈和队列是操作受限的线性表,好像每本讲数据结构的数都是这么说的。有些书按照这个思路给出了定义和实现;但是很遗憾,这本书没有这样做,所以,原书中的做法是重复建设,这或许可以用不是一个人写的这样的理由来开脱。 顺序表示的栈和队列,必须预先分配空间,并且空间大小受限,使用起来限制比较多。而且,由于限定存取位置,顺序表示的随机存取的优点就没有了,所以,链式结构应该是首选。 栈的定义和实现 #ifndef Stack_H #define Stack_H #include "List.h" template class Stack : List//栈类定义 { public: void Push(Type value) { Insert(value); } Type Pop() { Type p = *GetNext(); RemoveAfter(); return p; }

Type GetTop() { return *GetNext(); } List ::MakeEmpty; List ::IsEmpty; }; #endif 队列的定义和实现 #ifndef Queue_H #define Queue_H #include "List.h" template class Queue : List//队列定义{ public: void EnQueue(const Type &value) { LastInsert(value); } Type DeQueue() {

Type p = *GetNext(); RemoveAfter(); IsEmpty(); return p; } Type GetFront() { return *GetNext(); } List ::MakeEmpty; List ::IsEmpty; }; #endif 测试程序 #ifndef StackTest_H #define StackTest_H #include "Stack.h" void StackTest_int() { cout << endl << "整型栈测试" << endl;

计算机专业基础综合数据结构(栈和队列)历年真题试卷汇编6

计算机专业基础综合数据结构(栈和队列)历年真题试卷汇编6 (总分:60.00,做题时间:90分钟) 一、单项选择题(总题数:14,分数:28.00) 1.为解决计算机主机与打印机之间速度不匹配问题,通常设置一个打印数据缓冲区,主机将要输出的数据依次写入该缓冲区,而打印机则依次从该缓冲区中取出数据。该缓冲区的逻辑结构应该是( )。【2009年 全国试题1(2)分】 A.栈 B.队列√ C.树 D.图 2.设栈S和队列Q的初始状态均为空,元素a,b,c,d,e,j,g=g依次进入栈S。若每个元素出栈后立即进入队列Q,且7个元素出队的顺序是b,d,c,f,e,a,g,则栈S的容量至少是( )。【2009年全国试题2(2)分】 A.1 B.2 C.3 √ D.4 按元素出队顺序计算栈的容量。b进栈时栈中有a,b出栈,cd进栈,栈中有acd,dc出栈,ef进栈,栈 中有aef,fea出栈,栈空,g进栈后出栈。所以栈S的容量至少是3。 3.若元素a,b,c,d,e,f依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次进行退栈操作,则不可能得到的出栈序列是( )。【2010年全国试题1(2)分】 A.d,c,e,b,f,a B.c,b,d,a,e,f C.b,c,a,e,f,d D.a,f,e,d,c,b √ 4.某队列允许在其两端进行入队操作,但仅允许在一端进行出队操作。若元素a,b,c,d,e依次入此队列后再进行出队操作,则不可能得到的出队序列是( )。【2010年全国试题2(2)分】 A.b,a,c,d, e B.d,b,a,c,e C.d,b,c,a,e √ D.e,c,b,a,d a先入队,b和c可在a的任一端入队,选项A、B、D都符合要求,只有选项C不可能出现。双端队列出队结果的分析可参见四、36。 5.元素a,b,c,d,e依次进入初始为空的栈中,若元素进栈后可停留、可出栈,直到所有元素都出栈,则在所有可能的出栈序列中,以元素d开头的序列个数是( )。【2011年全国试题2(2)分】 A.3 B.4 √ C.5 D.6 元素d进栈时,元素a,b,c已在栈中,d出栈后,P可以在a,b,c任一元素的前面进栈并出栈,也可以在元素a后出栈,c,b,a必须依次出栈,所以元素d开头的序列个数是4。 6.已知循环队列存储在一维数组A[0.n-1]中,且队列非空时front和rear分别指向队头元素和队尾元素。若初始时队列为空,且要求第1个进入队列的元素存储在A[0]处,则初始时front和rear的值分别是( )。[2011年全国试题3(2)分】 A.0,0 B.0,n—1 √ C.n一1,0

建立堆栈和队列的库函数

建立堆栈和队列的库函数 摘要 堆栈是一种只允许在表的一端进行插入和删除运算的特殊的线性表。链式存储结构:栈的链式存储结构称为链栈,通常用单链表示。链栈的插入和删除操作只需处理栈顶的情况。每次进栈的数据元素都放在原当前栈顶元素之前成为新的栈顶元素,每次退栈的数据元素都是原当前栈顶元素,最后进入堆栈的数据元素总是最先退出堆栈。 队列是允许在表的一端进行插入,而在表的另一端进行删除的特殊线性表。允许进行插入的一端称为队尾,允许进行删除的一端称为队头。用链式存储结构存储的队列称为链队列。链队列的基本操作的实现基本上也是单链表操作的简化。通常附设头结点,并设置队头指针指向头结点,队尾指针指向终端结点。插入数据时只考虑在链队列的尾部进行,删除数据时只考虑在链队列的头部进行。 关键词:堆栈;队列;线性表;存储结构

第1章前言 栈和队列是两种常用的数据结构,广泛应用在编译软件和程序设计,操作系统、事物管理等各类软件系统中。从数据结构角度看,栈和队列是受限制的线性表,栈和队列的数据元素具有单一的前驱和后继的线性关系;从抽象数据类型角度看,栈和队列又是两种重要的抽象数据类型。 第2章堆栈和队列定义 2.1 定义 栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为后进先出表。 队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。在队列这种数据结构中,最先插入的元素将是最先被删除的元素;反之最后插入的元素将是最后被删除的元素,因此队列又称为“先进先出”的线性表。 2.2 队列基本操作 2.2.1栈的建立和初始化: voidInitStack(SqStack * &s)

数据结构练习 第三章 栈和队列

数据结构练习第三章栈和队列 一、选择题 1.栈和队列的共同特点是( )。 A.只允许在端点处插入和删除元素 B.都是先进后出 C.都是先进先出 D.没有共同点 2.向顺序栈中压入新元素时,应当()。 A.先移动栈顶指针,再存入元素 B.先存入元素,再移动栈顶指针C.先后次序无关紧要 D.同时进行 3.允许对队列进行的操作有( )。 A. 对队列中的元素排序 B. 取出最近进队的元素 C. 在队头元素之前插入元素 D. 删除队头元素 4.用链接方式存储的队列,在进行插入运算时( ). A. 仅修改头指针 B. 头、尾指针都要修改 C. 仅修改尾指针 D.头、尾指针可能都要修改 5.设用链表作为栈的存储结构则退栈操作()。 A. 必须判别栈是否为满 B. 必须判别栈是否为空 C. 判别栈元素的类型 D.对栈不作任何判别 6.设指针变量front表示链式队列的队头指针,指针变量rear表示链式队列的队尾指针,指针变量s指向将要入队列的结点X,则入队列的操作序列为()。 A.front->next=s;front=s; B. s->next=rear;rear=s; C. rear->next=s;rear=s; D. s->next=front;front=s; 7.设指针变量top指向当前链式栈的栈顶,则删除栈顶元素的操作序列为()。 A.top=top+1; B. top=top-1; C. top->next=top; D. top=top->next; 8.队列是一种()的线性表。 A. 先进先出 B. 先进后出 C. 只能插入 D. 只能删除 9.设输入序列1、2、3、…、n经过栈作用后,输出序列中的第一个元素是n,则输出序列中的第i个输出元素是()。 A. n-i B. n-1-i C. n+l -i D.不能确定 10.设输入序列为1、2、3、4、5、6,则通过栈的作用后可以得到的输出序列为()。 A. 5,3,4,6,1,2 B. 3,2,5,6,4,1 C. 3,1,2,5,4,6 D. 1,5,4,6,2,3 11.队列的删除操作是在()进行。 A.队首 B.队尾 C.队前 D.队后 12.当利用大小为N 的数组顺序存储一个栈时,假定用top = = N表示栈空,则退栈时,用()语句修改top指针。 A.top++; B.top=0; C.top--; D.top=N; 13.队列的插入操作是在()进行。

第3章 栈和队列

《数据结构》 第3章栈和队列 共85题 一、单选 1. (1)分题目ID号:10705 题目难度:容易 设对一组数据的处理具有“后进先出”的特点,则应采用的数据结构是【1】 A. 队列 B. 栈 C. 顺序表 D. 二叉树题目答案:B 2. (1)分题目ID号:10706 题目难度:容易 若进栈序列为3、5、7、9,进栈和出栈可穿插进行,则不可能的出栈序列是【1】 A. 7,5,3,9 B. 9,5,7,3 C. 9,7,5,3 D. 7,5,9,3 题目答案:B 3. (1)分题目ID号:10707 题目难度:较难 设用一维数组A[m]存储栈,令A[m-1]为栈底,t指示当前栈顶的位置。如果栈不空,则出栈时应使【1】 A. t=t+l B. t=t-1 C. t=m-1 D. 不改变t 题目答案:A 4. (1)分题目ID号:10708 题目难度:容易 设用一维数组A[m]存储栈,令A[0]为栈底,top指示当前钱顶的位置,当把栈清空时所要执行的操作是【1】 A. top-- B. top=0 C. top=-1 D. top=m-1 题目答案:C 5. (1)分题目ID号:10709 题目难度:容易 设栈s的初始状态为空,如果进栈序列为1、2、3、4、5、6,出栈序列为3、2、5、6、4、1,则s的容量至少是【1】 A. 6 B. 4 C. 2 D. 3 题目答案:D 6. (1)分题目ID号:10710 题目难度:容易 设栈s最多能容纳4个元素,现有A、B、C、D、E、F六个元素按顺序进栈,以下可能的出栈序列是【1】 A. E、D、C、B、A、F B. B、C、E、F、A、D C. C、B、E、D、A、F D. A、D、F、E、B、C 题目答案:C

堆栈和队列

陕西科技大学实验报告 班级:学号:姓名:实验组别: 实验日期:报告日期:成绩: 报告内容:(目的和要求、原理、步骤、数据、计算、小结等) 实验名称:栈与队列 一、实验目的 1.掌握栈、队列的思想及其存储实现。 2.掌握栈、队列的常见算法的程序实现。 二、实验要求 1.编写函数,采用链式存储实现栈的初始化、入栈、出栈操作。 2.编写函数,采用顺序存储实现栈的初始化、入栈、出栈操作。 3.编写函数,采用链式存储实现队列的初始化、入队、出队操作。 4.编写函数,采用顺序存储实现队列的初始化、入队、出队操作。 5.编写一个主函数,在主函数中设计一个简单的菜单,分别调试 上述算法。 三、实验原理(流程图):

四、实验数据(源代码): package B_Stack; public class SqLinkStackDemo { // 主函数中设计一个简单的菜单,分别调试上述算法 public static void main(String[] args) throws Exception { /** 顺序栈 */ SqStack s = new SqStack(8); // 顺序栈栈的初始化 s.clear(); // 顺序栈的入栈 System.out.println("顺序栈入栈:"); s.push(5); s.push('c'); s.push("Hello"); s.display(); // 顺序栈的出栈 System.out.println('\r' + "顺序栈出栈:"); s.pop(); s.display(); /** 链表栈 */ LinkStack ls = new LinkStack(); // 链表栈的初始化 ls.clear(); // 链表栈的入栈 System.out.println('\r' + "链表栈入栈:"); ls.push(88); ls.push('Z'); ls.push("HelloWorld"); ls.display(); // 顺序栈的出栈 System.out.println('\r' + "链表栈出栈:"); ls.pop(); ls.display(); /** 顺序队列 */ CircleSqQueue circleSqQueue = new CircleSqQueue(8); // 顺序存储实现队列的初始化 circleSqQueue.clear(); // 顺序存储实现队列的入队 System.out.println('\r' + "顺序队列入队:"); circleSqQueue.offer(666); circleSqQueue.offer("队列"); circleSqQueue.offer('Q'); circleSqQueue.display();

栈和队列的基本操作实现及其应用

实验二栈和队列的基本操作实现及其应用 一_一、实验目的 1、熟练掌握栈和队列的基本操作在两种存储结构上的实现。 一_二、实验内容 题目一、试写一个算法,判断依次读入的一个以@为结束符的字符序列,是否为回文。所谓“回文“是指正向读和反向读都一样的一字符串,如“321123”或“ableelba”。 相关常量及结构定义: #define STACK_INIT_SIZE 100 #define STACKINCREMENT 10 typedef int SElemType; typedef struct SqStack { SElemType *base; SElemType *top; int stacksize; }SqStack; 设计相关函数声明: 判断函数:int IsReverse() 栈:int InitStack(SqStack &S )

int Push(SqStack &S, SElemType e ) int Pop(SqStack &S,SElemType &e) int StackEmpty(s) 一_三、数据结构与核心算法的设计描述 1、初始化栈 /* 函数功能:对栈进行初始化。参数:栈(SqStack S)。 成功初始化返回0,否则返回-1 */ int InitStack(SqStack &S) { S.base=(SElemType *)malloc(10*sizeof(SElemType)); if(!S.base) //判断有无申请到空间 return -1; //没有申请到内存,参数失败返回-1 S.top=S.base; S.stacksize=STACK_INIT_SIZE; S.base=new SElemType; return 0; } 2、判断栈是否是空 /*函数功能:判断栈是否为空。参数; 栈(SqStack S)。栈为空时返回-1,不为空返回0*/ int StackEmpty(SqStack S) { if(S.top==S.base) return -1; else return 0; } 3、入栈 /*函数功能:向栈中插入元素。参数; 栈(SqStack S),元素(SElemtype e)。成功插入返回0,否则返回-1 */ int Push(SqStack &S,SElemType e) { if(S.top-S.base>=S.stacksize) { S.base=(SElemType *)realloc(S.base,(S.stacksize+1) * sizeof(SElemType));

堆栈和队列基本函数

一.顺序栈 1.宏定义 #include #include #define MAXSIZE **** #define datatype **** 2.结构体 typedef struct { datatype data[MAXSIZE]; int top; }Seqstack; 3.基本函数 Seqstack *Init_Seqstack() /*置空栈函数(初始化)1.先决条件:无;2.函数作用:首先建立栈空间,然后初始化栈顶指针,返回栈s的地址*/ { Seqstack *s; s=(Seqstack *)malloc(sizeof(Seqstack)); s->top=-1; return s; } int Empty_Seqstack(Seqstack *s) /*判栈空函数1.先决条件:初始化顺序栈;2.函数作用:判断栈是否为空,空返回1,不空返回0*/ { if(s->top==-1) return 1; else return 0; } int Push_Seqstack(Seqstack *s,datatype x) /*入栈函数1.先决条件:初始化顺序栈2.函数作用:将数据x入栈,栈满则不能,成功返回1,因栈满失败返回0*/ { if(s->top==MAXSIZE-1) return 0; s->top=s->top+1; s->data[s->top]=x; return 1; } int Pop_Seqstack(Seqstack *s,datatype *x)

/*出栈函数1.先决条件:初始化顺序栈2.函数作用:从栈中出一个数据,并将其存放到x中,成功返回1,因栈空失败返回0*/ { if(s->top==-1) return 0; *x=s->data[s->top]; s->top--; return 1; } int Top_Seqstack(Seqstack *s,datatype *x) /*取栈顶元素函数1.先决条件:初始化顺序栈2.函数作用:取栈顶元素,并把其存放到x中,成功返回1,因栈空失败返回0*/ { if(s->top==-1) return 0; *x=s->data[s->top]; return 1; } int Printf_Seqstack(Seqstack *s) /*遍历顺序栈函数1.先决条件:初始化顺序栈2.函数作用:遍历顺序栈,成功返回1*/ { int i,j=0; for(i=s->top;i>=0;i--) { printf("%d ",s->data[i]);/*因datatype不同而不同*/ j++; if(j%10==0) printf("\n"); } printf("\n"); return 1; } int Conversation_Seqstack(int N,int r) /*数制转换函数(顺序栈)1.先决条件:具有置空栈,入栈,出栈函数2.函数作用:将N转换为r进制的数*/ { Seqstack *s; datatype x; printf("%d转为%d进制的数为:",N,r);/*以后可以删除去*/ s=Init_Seqstack(); do { Push_Seqstack(s,N%r); N=N/r;

数据结构第三章栈和队列习题及答案

习题三栈和队列 一单项选择题 1. 在作进栈运算时,应先判别栈是否(① ),在作退栈运算时应先判别栈是否(② )。当栈中元素为n个,作进栈运算时发生上溢,则说明该栈的最大容量为(③ )。 ①, ②: A. 空 B. 满 C. 上溢 D. 下溢 ③: A. n-1 B. n C. n+1 D. n/2 2.若已知一个栈的进栈序列是1,2,3,…,n,其输出序列为p1,p2,p3,...,pn,若p1=3,则p2为( )。 A 可能是2 B 一定是2 C 可能是1 D 一定是1 3. 有六个元素6,5,4,3,2,1 的顺序进栈,问下列哪一个不是合法的出栈序列?() A. 5 4 3 6 1 2 B. 4 5 3 1 2 6 C. 3 4 6 5 2 1 D. 2 3 4 1 5 6 4.设有一顺序栈S,元素s1,s2,s3,s4,s5,s6依次进栈,如果6个元素出栈的顺序是s2,s3,s4, s6, s5,s1,则栈的容量至少应该是() A.2 B. 3 C. 5 D.6 5. 若栈采用顺序存储方式存储,现两栈共享空间V[1..m],top[i]代表第i个栈( i =1,2)栈顶,栈1的底在v[1],栈2的底在V[m],则栈满的条件是()。 A. |top[2]-top[1]|=0 B. top[1]+1=top[2] C. top[1]+top[2]=m D. top[1]=top[2] 6. 执行完下列语句段后,i值为:() int f(int x) { return ((x>0) ? x* f(x-1):2);} int i ; i =f(f(1)); A.2 B. 4 C. 8 D. 无限递归 7. 表达式3* 2^(4+2*2-6*3)-5求值过程中当扫描到6时,对象栈和算符栈为(),其中^为乘幂。 A. 3,2,4,1,1;(*^(+*- B. 3,2,8;(*^- C. 3,2,4,2,2;(*^(- D. 3,2,8;(*^(- 8. 用链接方式存储的队列,在进行删除运算时()。 A. 仅修改头指针 B. 仅修改尾指针 C. 头、尾指针都要修改 D. 头、尾指针可能都要修改 9. 递归过程或函数调用时,处理参数及返回地址,要用一种称为()的数据结构。 A.队列 B.多维数组 C.栈 D. 线性表 10.设C语言数组Data[m+1]作为循环队列SQ的存储空间, front为队头指针,rear为队尾指针,则执行出队操作的语句为() A.front=front+1 B. front=(front+1)% m C.rear=(rear+1)%(m+1) D. front=(front+1)%(m+1) 11.循环队列的队满条件为 ( ) A. (sq.rear+1) % maxsize ==(sq.front+1) % maxsize; B. (sq.front+1) % maxsize ==sq.rear C. (sq.rear+1) % maxsize ==sq.front D.sq.rear ==sq.front

数据结构实验二(栈和队列)

实验二栈和队列的基本操作及其应用 一、实验目的 1、掌握栈和队列的顺序存储结构和链式存储结构,以便在实际中灵活应用。 2、掌握栈和队列的特点,即后进先出和先进先出的原则。 3、掌握栈和队列的基本运算,如:入栈与出栈,入队与出队等运算在顺序 存储结构和链式存储结构上的实现。 二、实验内容 本次实验提供4个题目,每个题目都标有难度系数,*越多难度越大,学生 可以根据自己的情况任选一个! 题目一:回文判断(*) [问题描述] 对于一个从键盘输入的字符串,判断其是否为回文。回文即正反序相同。如 “abba”是回文,而“abab”不是回文。 [基本要求] (1)数据从键盘读入; (2)输出要判断的字符串; (3)利用栈的基本操作对给定的字符串判断其是否是回文,若是则输出 “Yes”,否则输出“No”。 [测试数据] 由学生任意指定。 题目二:顺序栈和循环队列基本操作(*) [基本要求] 1、实现栈的基本操作 六项基本操作的机制是:初始化栈:init_stack(S);判断栈空:stack_empty(S);取栈顶元素:stack_top(S,x);入栈:push_stack(S,x);出栈:pop_stack(S);判断栈满:stack_full(S) 2、实现队列的基本操作 六项基本操作的机制是:初始化队列:init_queue(Q);判断队列是否为空:queue_empty(Q);取队头元素:queue_front(Q,x);入队:enqueue(Q,x);出队:outqueue(Q,x);判断队列是否为满:queue_full(Q) [测试数据]

由学生任意指定。 题目三:商品货架管理(**) [问题描述] 商店货架以栈的方式摆放商品。生产日期越近的越靠近栈底,出货时从栈顶取货。一天营业结束,如果货架不满,则需上货。入货直接将商品摆放到货架上,则会使生产日期越近的商品越靠近栈顶。这样就需要倒货架,使生产日期越近的越靠近栈底。 [基本要求] 设计一个算法,保证每一次上货后始终保持生产日期越近的商品越靠近栈底。 [实现提示] 可以用一个队列和一个临时栈作为周转。 [测试数据] 由学生任意指定。 三、实验前的准备工作 1、掌握栈的逻辑结构和存储结构。 2、熟练掌握栈的出栈、入栈等操作。 3、掌握队列的逻辑结构和存储结构。 4、熟练掌握队列的出队、入队等操作 四、实验报告要求 1、实验报告要按照实验报告格式规范书写。 *2、写出算法设计思路。 3、实验上要写出多批测试数据的运行结果。 4、结合运行结果,对程序进行分析。 题目四:Rails(ACM训练题) Description There is a famous railway station in PopPush City. Country there is incredibly hilly. The station was built in last century. Unfortunately, funds were extremely limited that time. It was possible to establish only a surface track. Moreover, it turned out that the

相关文档
最新文档