C语言参悟-函数-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

C语言参悟-函数

一、概述

首先什么是函数函数function是完成特定任务的独立程序代码单元。在现实生活中为了解决某一个问题我们需要把解决这个问题的大任务分解成单个单个的小任务。因为可能在这个任务里面有很多事重复类似的任务对这种重复的任务我们只需要共用一个方法去解决即可。

这样的方法同样适应计算机里的任务在C语言里面就提供了这样的方式就是用函数来代替处理重复任务的方法。

函数的目的也就解决一个小任务可以说函数算是C语言处理的最小单位了。

用一个一个的函数组合起来就能解决这个大的任务。可以说函数就是积木。我们为了搭建出下面这个房子用了不同形状的小积木块这些小积木块 如在这里插入图片描述在这里插入图片描述 构成这么大的积木。

当然我们还可以利用小的积木拼成的二级小积木当成一个模块使用就比如 在这里插入图片描述

在这里插入图片描述

为什么要使用函数首先使用函数可以省去编写重复代码的苦差。如果程序要多次完成某项任务复用性那么只需编写一个合适的函数就可以在需要时使用这个函数或者在不同的程序中使用该函数就像许多程序中使用putchar()一样。其次即使程序只完成某项任务一次也值得使用函数。因为函数让程序更加模块化从而提高了程序代码的可读性扩展性更方便后期修改、完善。

二、函数

1. 函数的构成

像下面的代码就是较为恰当的定义和使用函数就像 show_n_char() 函数是放在主函数之前的一个声明也叫函数原型这个能告诉编译器怎么去使用这个函数函数原型提供了函数外部的所有接口信息函数的实现其实就可以放在其他地方就行了。

对于函数原型可以有两种形式

就行下面的代码里面说的

// 函数原型方式1
void show_n_char(char ch, int num);

// 函数原型方式2
// void show_n_char(char, int);

//主函数
int main()
{
	show_n_char('d', 5);
	return 0;
}

//函数定义
void show_n_char(char ch, int num)
{
	printf("ch: %c, num: %d", ch, num);
}

一个完整的函数总是由 这几部分构成

返回值 函数名(函数参数)
{
函数体
}

以下有几个指的注意的事项

  1. 返回值、函数参数都可以设置为空 void或者返回有效值
  2. 函数名不可缺省
  3. 函数体也可以为空

一个函数可以理解为一个小的模块一个黑盒子函数也许需要外部输入、也可能需要输出东西或者不输出不输入调用即可更改某些属性。这种表现形式完全是由开发人员决定的。

比如需要输入输出的一个函数

int MultipleNum(int num1, int num2)
{
	return (num1+num2)*(num1+num2);
}

只有输出的函数

int MultipleNum()
{
	return (12+12)*(14+14);
}

或者无输入输入

void MultipleNum()
{
	printf(num: %d", 12+12)*(14+14));
}

2. 函数参数

int MultipleNum(int num1, int num2)
{
	return (num1+num2)*(num1+num2);
}

以上面的例子说函数参数其实就是指的 int MultipleNum (int num1, int num2) 里面的 (int num1, int num2)

函数参数是不定的括号里面可以没有东西的话可以写成 像这种 ()、(void)

多个参数就需要用 ‘,’ 逗号分隔每个参数都是有类型名和变量名这个变量名也叫形参这个变量是后面能参与到函数体里面去使用的。当然了也不是说形参变量定义了就必须被使用哈这些都是不确定的啦。

3. 函数名

这个没啥特殊的命名反正要体现出你需要的那种格式而且命名不能过长。

4. 函数返回值

函数的返回值也是可有可无的这个也是由使用者确定的。

在不需要返回值的时候就要写 void ,同时在函数体中可有不写return 语句或者写 return;

像下面两种的写法都是对的。

void MultipleNum(int num1, int num2)
{
	//....
	return ;
}

void MultipleNum(int num1, int num2)
{
	//....
}

5. 函数的工作

1. 程序栈

程序栈是支持函数执行的内存区域通常和堆共享。也就是说它们共享同一块内存区域。程序栈通常占据这块区域的下部而堆用的则是上部。

下图就简单说明程序栈堆的模型
在这里插入图片描述
调用函数时函数的栈帧被推到栈上栈向上“长出”一个栈帧。当函数终止时其栈帧从程序栈上弹出。可以理解为直到main函数从程序栈里弹出后程序就结束了。

  • 栈帧所使用的内存不会被清理但最终可能会被推到程序栈上的另一个栈帧覆盖

有时候我们听到的一个术语栈溢出

可以理解为程序栈一直压栈导致程序的的内存需要超过了真实物理内存然后程序奔溃我了解的情况就是有几个

  • 1、递归函数的递归条件错误导致一直递归一直无限调用递归函数
  • 2、存在调用函数的死循环

示意图

在这里插入图片描述

2、栈帧的组织

栈帧由以下几种元素组成。

  • 返回地址
    函数完成后要返回的程序内部地址。

  • 局部数据存储
    为局部变量分配的内存。

  • 参数存储
    为函数参数分配的内存。

  • 栈指针和基指针
    运行时系统用来管理栈的指针。我们写代码不用管系统帮我们来完成栈指针通常指向栈顶部。基指针帧指针通常存在并指向栈帧内部的地址比如返回地址用来协助访问栈帧内部的元素。这两个指针都不是C指针它们是运行时系统管理程序栈的地址

注意要点

  • 系统在创建栈帧时将参数以跟声明时相反的顺序推到帧上然后是返回地址最后推入局部变量
float average(int*arr,int size){
	int sum = 0;
	printf("arr:%p\n",&arr);
	printf("size:%p\n",&size);
	printf("sum:%p\n",&sum);
	for(int i=0;i<size;i++)
	{
		sum+=arr[i];
	}
	return(sum*1.0f)/size;
}

这个代码中在函数栈里分配空间顺序是 size–>arr–>返回计算值地址–>sum.

在实际程序里面的层层调用也是按照下面这些形式比如在 main函数中引入了 a()a() 引用了 b()。

实际的执行情况就按照下面的 1 2 3 4 5 6 7 8 9 顺序运行的。
在这里插入图片描述

三、函数递归

C允许函数调用它自己这种调用过程称为递归recursion)。

递归有时难以捉摸有时却很方便实用。

结束递归是使用递归的难点因为如果递归代码中没有终止递归的条件测试部分一个调用自己的函数会无限递归。无限递归的归属就是程序栈溢出崩溃。

可以使用循环的地方通常都可以使用递归。有时用循环解决问题比较好但有时用递归更好。递归方案更简洁但效率却没有循环高。

四、函数指针

因为函数指针和指针部分是有重叠的函数指针与内存的一些分布有关系感兴趣可以参考一下 我之前的笔记

指针与函数

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6