C语言的函数

一、函数的概述

一个源程序文件可由一个或多个函数(非主函数)组成。从main()开始,调用其它函数后回到main()结束。

分类:

(1)标准库函数和用户自定义函数---用户使用角度

(2)无参函数和有参函数---函数参数角度

(3)有返回值函数和无返回值函数---函数返回值角度

二、函数的定义

定义形式:返回值类型 函数名(参数列表){函数体}

举例:如下。但是切记没有多返回值类型函数,因为在函数栈帧问题上我们会了解到:我们只有一个eax寄存器,所以c语言的函数只能有一个返回值。

//无参无返回值类型
void printStar()
{
    printf("********************\n");
}

//有参无返回值类型
void printChar(char ch)
{
    for(int i=0;i<10;i++)
        printf("%c",ch);
    printf("\n");
}

//无参有返回值类型
int getNumber()
{
    int number=1;
    return number;
}

//有参有返回值类型
int getTail(int number)
{
    int tail=number%10;
    return tail;
}

//多参数类型
int max(int a,int b)
{
    int m=(a>b? a:b);
    return m;
}

 Tips:

(1)参数列表是形式参数,我们只是将实际参数的值赋予了形式参数,形式参数在函数被调用时在栈区开辟空间,调用结束后,函数出栈,形参被销毁。即,形参改变不能影响实参的值。

(2)参数列表是一参一类型,不能定义为int x,y,z;且在参数列表中的变量相当于局部变量,无法在函数体内再次定义。

(3)函数返回值:通过return 语句返回主调函数,return语句可以有0-n个(在分支语句中需要返回的值不同,return的个数也不同),但每一次调用只能有一个起作用,并且return返回的数据的数据类型与函数类型必须一致或能够隐形转换。函数无返回值,函数类型为void。

三、函数的调用

调用的一般形式:函数名(实参列表);

例:max(a,b)   getchar()   ......

int max(int a,int b)
{
    return (a>b?a:b);
}

void main()
{
    int a,b,m;
    scanf("%d%d",&a,&b);
    m=max(a,b);
    printf("%d\n",c);
}

1.对被调函数的声明

运行函数的条件

(1)被调函数必须存在

(2)被调函数为库函数,用#include 命令包含库函数所在头文件

(3)被调函数为用户自定义函数,且被调函数必须放在主调函数后面时:在主调函数中必须包含对被调函数的声明。

(4)被调函数在其它.c文件中,需要使用extern关键字声明。但前提是被调函数在其他文件定义/声明时时未使用static关键字。

(5)函数不能嵌套声明,只能嵌套调用

省略声明情况

(1)在所有函数调用之前,对本文件所调函数进行了类型声明。

(2)被调函数在主调函数前定义

(3)被调函数类型为int---不提倡省略

2.调用方式 

函数语句

printf(......);

函数表达式

int m = max(a,b);

int m = max(a,b)+1;

函数作参数

printf("%d\n",max(a,b));

int m = max(max(a,b),c);

四、函数的嵌套调用(链式调用)

像循环一样,函数可以嵌套使用。嵌套调用的意义在于被调函数需要多次使用同种功能,那么这时候这个功能就可以提取出一个函数,被调函数在调用这个函数时成为了主调函数,依次类推,这就像一条链子一样,层层调用的情况就是链式调用。如果被调函数调用多个函数,被调用的函数也调用了一个或多个的函数,那么就是这就是嵌套调用。但不可互相调用,否则将会出现循环调用,无穷无尽,导致内存溢出的情况。

举一个简单的例子就是三个数求最值,调用两个数求最值:

int Max2(int a,int b)
{
    return (a>b?a:b);
}

int Max3(int a,int b,int c)
{
    int m = Max2(a,b);
    return (m>b?m:b);
}

五、函数的递归调用

递归的思想:为了解决当前问题F(n),就要解决F(n-1),而F(n-1)的解决依赖于F(n-2)的解决...就这样逐层分解,分解成很多相似的小事件,当最小的事件解决完后,就能解决高层次的事件。这种“逐层分解,逐层合并”的方式就构成了递归的思想。

函数的递归调用是一种及其特殊的链式调用,不过主调函数和被调函数都是自身。注意一点就是递归调用需要有递归公式和终止标志,不能无限调用。

来吧,看一个例子:求斐波那契数列的第n项

long Fib(int n)
{
	if (n <= 2)return 1;
	return Fib(n - 1) + Fib(n - 2);
}

//测试
int main() 
{
	int n;
	scanf_s("%d", &n);
	long f = Fib(n);
	printf("%ld\n", f);
	return 0;
}

六、函数涉及的变量问题

1.数组/指针作为函数的参数
 

2.变量的存储类别

(1).作用域角度:局部变量和全局变量

局部变量:在函数内部或者块内定义的变量,局部变量只在它的函数内部或者块内有效。每个函数中定义的变量,只在定义它的函数中有效。不同函数可以使用相同名字的变量,互不干扰且意义不同。形式参数就是局部变量。Tips:可以在复合语句中定义变量,但只在本复合语句内有效。

全局变量:又称外部变量。在函数之外定义的变量,作用范围是从变量定义的位置开始到程序结束。(本文件定义的非静态全局变量,可以被同一个文件夹的另一个文件通过extern声明使用)

“就近”原则:外部变量与局部变量名称相同时,屏蔽外部变量,使用内部变量,但外部变量依旧存在。

(2).生存周期角度:动态存储变量和静态存储变量

静态存储变量:程序运行期间分配的固定的存储空间

动态存储变量:根据需要动态分配的存储空间

*动态局部变量:(自动变量)[auto]函数调用后,变量值不予保留,自动销毁,释放存储空间,再次调用时,原值不能引用。此变量在定义时未赋初值,则随机赋值,极其不稳定。一般对这种变量,建议初始化。

*静态局部变量:[static] 程序编译初期就已经开辟了空间。函数调用后保留原值,存储空间在程序结束时释放,再次调用时,仍是原值。此变量在定义时未赋初值,则自动置零((int)0,(double)0.0,(char)'\0'......);

*静态全局变量:[static] 被static修饰的全局变量,将无法被本文件外的文件使用。

(*).内部函数和外部函数

根据函数能否被其它源文件调用,将函数分为内部函数和外部函数。

内部函数:static 返回值类型 函数名(形参列表){函数体};

外部函数:[extern] 返回值类型 函数名(形参列表){函数体};


*七、函数栈帧

函数栈帧小节内容转自Raizeroko-CSDN博客

从表面来看函数调用的过程就是写出一个函数后,只需要在调用时中通过函数名将实参传给形参就实现了整个过程,但实际上调用的过程远比你想的复杂,这其中函数栈帧起着关键作用。

在C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。为此,我们先来看一下计算机的内存分区:

内存主要分为栈区堆区静态区其他区域

栈区:由高地址往低地址增长,主要用来存放局部变量,函数调用开辟的空间,与堆共享一段空间。

堆区:由低地址向高地址增长,动态开辟的空间就在这里(malloc,realloc,calloc,free),与栈有一段共享空间。

静态区:主要存放全局变量和静态变量。

栈:一种先进后出的数据结构。涉及的两个过程分为压栈和出栈。 

 esp,ebp,eax寄存器( esp和ebp是两个指针,ebp指向当前栈帧栈底,esp指向函数栈栈顶)

ebpebp是基址指针,保存调用者函数的地址,总是指向当前栈帧栈底
espesp是被调函数指针,总指向函数栈栈顶
eax累加器,用来乘除法,与函数返回值(本篇主要关注第二个功能)

ebp并不是指向整个函数栈的栈底,而是指向当前栈帧的栈底,而由于esp总是指向栈顶,且栈只允许一个方向的操作,因此esp指向其实也是当前栈帧的栈顶,不过当前栈帧的栈顶始终与栈顶相同,因此说esp指向的是栈顶。 

 函数执行的全过程略(...),有需要的读者可以看本小节标注转文章。


感谢观看!

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值