“栈”,一种数据结构,一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。进行数据操作的这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作入栈或压入(PUSH),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈(POP),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
顾名而思义,栈,在汉字里指的是存储货物或供旅客住宿的地方,所以栈一字引入到计算机领域里,就是为了表示数据暂时存储的地方,所以才有进栈、出栈的说法。你也可以理解成往手枪的弹匣里压子弹,然后一枪一枪得射出,最后压入的子弹总是第一发射出,这本质上就是一个“栈”「线性,运算(子弹增减)位置受限」。
对于一个程序而言,它存在于计算机的内存里,分为代码区(code area)【程序各个函数的代码块】、全局数据区【存放全局数据和静态数据】,堆区(heap area)【存放动态数据,数据结构一种】栈区(stack area)【存放局部数据,即各个函数内部的局部数据】。
这里需要明晰,栈是有限的资源,每调用、嵌套一个函数,剩余的栈空间都会减少,过多的设置局部变量尤其是数组,会使栈空间有枯竭的危险,因此程序有个永恒的命题,就是平衡程序的可读性(艺术性)和效率性。
一个函数对栈空间的占用大概是运行-->局部变量占据栈空间,地址等等占据栈空间-->运行完毕,将返回值储存在临时变量空间(堆)里(如果有返回值的话)-->在恢复调用函数的运行状态(一般是恢复main的运行状态)-->最后释放栈空间。一般而言,在被调用函数运行完毕后,在栈中的局部变量数据都会被释放,但如果在局部变量数据类型前加上static(静态的)之后,就可以将局部变量变为静态局部变量从而从栈中变为存储在全局数据区,不随函数之结束而消失,也不会为其重新分配空间直至整个程序结束。它可以用于判断一个函数是否被调用,因为初始值为0,如果不为0,则证明被调用(静态局部变量只会初始化一次,不会再初始化,因为已经出栈),这是它成为“指示”的一个原因。
聊到“栈”,你很难不提一嘴递归函数,为什么递归函数永远被你的老师批上一嘴?因为它自己调用自己的特性可能在短时间占用许多的栈空间,导致栈空间告急,程序崩溃。而理论上已经证明,所以的递归函数都可以用循环来代替,但可读性远比不上递归函数,况且,递归算法应用面并不广泛,在狭窄的应用范围里其实可以规避栈溢出的风险,更何况,现代计算机性能十分强悍,似乎递归被嫌弃的日子也要丢入老皇历里了。
既然提到了递归,就已经涉及到了程序可读性与效率性的矛盾了,这就引出了人们的解决方案——内联函数。它是用于在保留程序可读性的同时提高程序运行效率的,因为函数之调用需要穿件栈内存环境,需要阐述传递,然后产生程序并执行,他们需要时间(尽管在人看来很短,但是积少成多就是一笔大帐)有些函数或许只有两三行,却被平凡调用(例如递归),有一个办法,就是将函数直接替换为代码,这样就不用经历以上诸多不便,但是这样会使可读性急剧下降,于是人们参考预处理的做法(宏),在函数声明和定义时,在类型前加一个inline,编译器读到inline就会心领神会,自动将函数用相应代码替换,这就是内联函数。当然,它也有局限性,例如不能包涵复杂的结构控制语句、体量要小(1-5行),但是你可以就这样写,究竟是将它作为内联函数还是普通函数处理,就交给编译器吧。