跳到主要内容

C 函数进阶

1. 栈的概念

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。

2. 函数的调用

每一个函数在运行时都会占用一段栈内存,栈内存的大小,由该函数内部定义的局部变量的具体情况而定。一个程序里面的所有函数所占据的栈内存,在逻辑上是连在一起的。

函数的每一次调用就是一次入栈,函数的嵌套调用如下所示:

ZYSNfq.png

#include <stdio.h>

int max_value(int x, int y);//函数声明

int main(int argc, char const *argv[])
{
int a = 1, b = 2;
int m;

m = max_value(a,b);

return 0;
}

int max_value(int x, int y)//函数定义
{
int z;
z = x>y ? x : y;

return z;
}
  • 一个 C 程序的栈是由若干段函数的栈帧组成的,每一段栈帧都用来存放对应函数的局部变量,栈帧的长度也取决于所对应函数的局部变量的个数和类型,一般最大值为8M,所以,不应该定义太多的局部变量来占用栈内存。
  • 每个函数的内部空间(栈帧)都是独立存在的,里面的局部变量也都是独立的,main( )函数里的a,b和max_value( )里的x、y都占据着不同的内部空间。
  • 函数调用时,实参(a和b)与形参(x和y)一一对应地赋值,换句话说,所有的形参被实参一一初始化,因此x的值就是a,y的值就是b。
  • 栈内存的另一个最重要的特性是:他是临时性的。一旦对应的函数退出,相应的栈帧将立即被释放(即被系统回收)。比如,当max_value()计算出z的值,执行return语句返回z之后,其所占的栈帧即被系统回收,变成:

ZYiXHA.png

3. 递归

递归,即一个函数调用自身,也就是一个函数 f( ) 的代码中,包含了对自身 f( ) 的调用。

#include <stdio.h>

int f(int n);

int main(int argc, char const *argv[])
{
f(100);

return 0;
}

int f(int n) // 这是一个递归函数
{
if(n>1)
{
f(n-1);
}
printf("%d\n", n);
}

ZYHYqc.png

  • 每一次递归调用都会为函数产生一个新的栈帧,使得栈的总大小不断增大,但是不能超过最大值8M,否则会溢出。
  • 递归函数一定要有一个可以直接返回的时候,否则代码将会陷于无穷的递归。
  • 递归函数的代码短小精悍,但同时它的效率很低,而且容易使得栈溢出,不常用。

4. 回调函数

回调函数主要可以用来实现软件的分层设计,使得不同软件模块的开发者工作进度可以独立出来,最终通过约定好的接口组合在一起。

  • 在 C 语言中回调函数只能通过函数指针实现。
  • 提供函数实现的一方在初始化的时候,将回调函数的的函数指针提供给调用者。
  • 当特定的事件发生时,调用者将使用函数指针调用回调函数来对事件进行处理。
include <stdio.h>

void arr(int *a, int len)
{
int i;
for(i=0; i<len; i++)
{
printf("%d\t", a[i]);
}
printf("\n");
}

void show_arr(void (*pf)(void *parr, int len), void *parr, int len)
{
pf(parr, len);
}

int main(int argc, char *argv[])
{
int a[5] = {1, 2, 3, 4, 5};

show_arr(arr, a, 5);

return 0;
}

5. 指针函数

函数的类型实际上指的就是函数的返回值。

一个函数可以返回字符型、整型和浮点型这些类型的数据,当然,它还可以返回指针类型的数据。定义的时候只需要跟定义指针变量一样,在类型后面加星号就行。所以,用指针变量作为函数返回值的就是指针函数。

int *p();

6. 函数指针

指向函数的指针。

int (*)p();
  • 函数指针可以作为参数传递,回调函数就是使用函数指针作为参数
#include <stdio.h>

int add(int, int);
int sub(int, int);
int calc(int (*fp)(int, int), int, int);

int add(int num1, int num2)
{
return num1 + num2;
}

int sub(int num1, int num2)
{
return num1 - num2;
}

int calc(int (*fp)(int, int), int num1, int num2)
{
return (*fp)(num1, num2);
}

int main(int argc, char *argv[])
{
printf("3 + 5 = %d\n", calc(add, 3, 5));
printf("3 - 5 = %d\n", calc(sub, 3, 5));

return 0;
}
  • 函数指针也可以作为返回值

示例:输入一个表达式,然后程序根据用户输入的运算符来决定调用 add 还是 sub 函数进行运算。

#include <stdio.h>

int add(int, int);
int sub(int, int);
int calc(int (*fp)(int, int), int, int);
int (*select(char op))(int, int);

int add(int num1, int num2)
{
return num1 + num2;
}

int sub(int num1, int num2)
{
return num1 - num2;
}

int calc(int (*fp)(int, int), int num1, int num2)
{
return (*fp)(num1, num2);
}

int (*select(char op))(int, int)
{
switch(op)
{
case '+': return add;
case '-': return sub;
}
}

int main(int argc, char *argv[])
{
int num1, num2;
char op;
int (*fp)(int, int);

printf("请输入一个式子(如:1+2):");
scanf("%d%c%d", &num1, &op, &num2);

fp = select(op); // 函数指针作为返回值
printf("%d %c %d = %d\n", num1, op, num2, calc(fp, num1, num2));

return 0;
}