C语言参悟-函数

C语言参悟-函数

  • 一、概述
  • 二、函数
    • 1. 函数的构成
    • 2. 函数参数
    • 3. 函数名
    • 4. 函数返回值
    • 5. 函数的工作
      • 1. 程序栈
      • 2、栈帧的组织
  • 三、函数递归
  • 四、函数指针

一、概述

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

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

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

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

当然我们还可以利用小的积木,拼成的二级小积木当成一个模块使用,就比如 C语言参悟-函数_第2张图片

C语言参悟-函数_第3张图片

为什么要使用函数?首先,使用函数可以省去编写重复代码的苦差。如果程序要多次完成某项任务(复用性),那么只需编写一个合适的函数,就可以在需要时使用这个函数,或者在不同的程序中使用该函数,就像许多程序中使用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语言参悟-函数_第4张图片

三、函数递归

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

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

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

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

四、函数指针

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

指针与函数

你可能感兴趣的:(▼,C/C++,参悟笔记,c语言,函数,函数定义)