• 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

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

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

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

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

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

    int MultipleNum(int num1, int num2)
    {
    	return (num1+num2)*(num1+num2);
    }
    
    • 1
    • 2
    • 3
    • 4

    只有输出的函数

    int MultipleNum()
    {
    	return (12+12)*(14+14);
    }
    
    • 1
    • 2
    • 3
    • 4

    或者无输入输入

    void MultipleNum()
    {
    	printf(num: %d", 12+12)*(14+14));
    }
    
    • 1
    • 2
    • 3
    • 4

    2. 函数参数

    int MultipleNum(int num1, int num2)
    {
    	return (num1+num2)*(num1+num2);
    }
    
    • 1
    • 2
    • 3
    • 4

    以上面的例子说,函数参数其实就是指的 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)
    {
    	//....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

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

    三、函数递归

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

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

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

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

    四、函数指针

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

    指针与函数

  • 相关阅读:
    使用IDEA创建Vue3通过Vite实现工程化
    【Redis 实战】dump.rdb、appendonly.aof 文件路径
    第7章 C语言的函数指针数组 (四)
    爬虫基础入门
    Linux下多个命令串联执行(管道/xargs/exec)
    轻量容器引擎Docker安装及其架构
    2023,阿里巴巴走向了中年的十字路口
    sed命令在Mac和Linux下的不同
    MySQL(2)
    Collction的List方法,list特有方法,遍历方式,迭代器选择
  • 原文地址:https://blog.csdn.net/qq_43680827/article/details/130673233