Day11-函数2

函数的递归调用

  • 递归调用的含义:在一个函数中,直接或间接调用函数本身称之为函数的递归调用。

    //直接调用
    a()->a();
    ​
    //间接调用
    a()->b()->a();
    a()->b()->....->a();

  • 递归调用的本质: 是一种循环结构,它不同于之前所学的while,do-while,for这样的循环结构,这些循环结构是借助循环变量,而递归是利用函数自身实现循环结构,如果不加以控制,很容易产生死循环。

  • 递归调用的注意事项: 1.递归调用必须要有出口,一定要终止递归(否则会产生死循环)。

    2.对终止条件的判断一定要放在函数递归之前。

    3.进行函数的递归调用。

    4.函数递归的同时一定要将函数调用向出口逼近。

案例1:

/*
需求:递归案例-有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。
问第4个人岁数,他说比第3个人大2岁。
问第3个人,又说比第2个人大2岁。问第2个人,说比第1个人大2岁。
最后问第1个人,他说是10岁。请问第5个人多大。
*/
#include <stdio.h>
​
// 求年龄的递归函数
int age(int n)
{
  // 存放函数的返回值,也就是年龄
  int c;
​
  if (n == 1)
  {
    c = 10; // 第一个人的年龄是10岁
  }
  else if (n > 1)
  {
    c = age(n - 1) + 2; // 当前这个人的年龄 = 上一个人的年龄  +  2
  }
​
  return c;
}
​
int main()
{
  printf("%d\n", age(5));
​
  return 0;
}

案例2:

/*
  需求:递归案例:求阶乘(n!)
*/
#include <stdio.h>
​
// 编写一个函数,用来求阶乘
long fac(int n)
{
  // 因为int型表示的数据范围小,所以乘法操作使用long来接受计算结果
  long f;
​
  if (n < 0)
  {
    printf("n的范围不能是0以下的数! \n");
  }
  else if (n == 0 || n == 1) // 此时不满足阶乘条件
  {
    f = 1;
  }
  else
  {
    f = fac(n - 1) * n;
  }
  return f;
}
​
int main()
{
  int n;
  printf("请输入一个整数: \n");
  scanf("%d", &n);
​
  printf("%d!=%ld\n", n, fac(n));
  return 0;
}

数组做函数参数

注意:

当用数组做函数的实际参数时,则形参应该也要用数组/指针变量来结束,但请注意,此次并不代表传递了数组中所有元素数据,而是传递了第一个元素的内存地址(数组首地址),形参接受这个地址后,则形参和实参就代表了同一块内存空间,则形参的数据修改会改变实参的。这种数据传递方式我们可以称之为:“引用传递”

如果用数组做函数形式参数,那么我们提供另一个形参表示数组的元素个数。原因是数组形参代表的仅仅是实际数组的首地址。也就是说形参只获取到了实际数组元素的开始,并未获取元素的结束。所以提供另一个形参表示数组的元素个数,可以防止在被调函数对实际数组元素访问的越界。

但有一个例外,如果是用字符数组做形参,且实际数组中存放的是字符数组(形参是字符数组,实参是字符串)。则不用表示数组元素的个数的形参

案例-数组元素做函数实参:

/*
有两个数组a和b,各有10个元素,
将它们对应元素逐个地相比(即 a [0]与b [0]比,a[1]与b[1]比.)。
如果a数组中的元素大于b数组中的相应元素的数目多于b数组中元素大于a数组中相应元素的数目
(例如,a[i]>b]i]6次,b[i]>a[i]3次,其中i每次为不同的值),
则认为a数组大于b数组,
并分别统计出两个数组相应元素大于、等于、小于的个数。
*/
#include <stdio.h>
​
// 定义一个函数,实现两个数的比较
int large(int x, int y)
{
  int flag; // 用来存放比较结果
  if (x > y)
  {
    flag = 1;
  }
  else if (x < y)
  {
    flag = -1;
  }
  else
  {
    flag = 0;
  }
​
  return flag;
}
​
int main()
{
  // 比较用的两个数组,循环变量,最大,最小,相等
  int a[10], b[10], i, max = 0, min = 0, k = 0;
​
  printf("请给数组a添加10个整型数据: \n");
  for (i = 0; i < sizeof(a) / sizeof(int); i++)
  {
    scanf("%d", &a[i]);
  }
  printf("\n");
​
  printf("请给数组b添加10个整型数据: \n");
  for (i = 0; i < sizeof(b) / sizeof(int); i++)
  {
    scanf("%d", &b[i]);
  }
  printf("\n");
​
  // 遍历
  for (i = 0; i < sizeof(a) / sizeof(int); i++)
  {
    if (large(a[i], b[i]) == 1)
    {
      max++;
    }
    else if (large(a[i], b[i]) == 0)
    {
      k++;
    }
    else
    {
      min++;
    }
  }
​
  printf("max=%d,min=%d,k=%d\n", max, min, k);
}
/*
  需求:数组函数的参数案例-编写一个函数,
  用来分别求数组score_1(有5个元素)和数组score_2(有10个元素)各元素的平均值 。
*/
#include <stdio.h>
​
// 定义一个函数,用来求平均分
float avg(float scores[], int len)
{
  int i;                       // 循环变量
  float aver, sum = scores[0]; // 保存平均分和总成绩
​
  // 遍历集合
  for (i = 1; i < len; i++)
  {
    sum += scores[i];
  }
​
  aver = sum / len;
​
  return aver;
}
​
int main()
{
  // 准备两个测试数组
  float score_1[5] = {66, 34, 46, 37, 97};
  float score_2[10] = {77, 88, 66, 55, 65, 76, 87, 98, 75, 34};
​
  printf("这个班的平均分是:%6.2f\n", avg(score_1, sizeof(score_1) / sizeof(float)));
  printf("这个班的平均分是:%6.2f\n", avg(score_2, sizeof(score_2) / sizeof(float)));
}

变量的作用域

引入问题

我们在函数设计过程中,经常要考虑对参数的设计,换句话说,我们需要考虑函数需要几个参数,需要什么类型的参数,但我并没有考虑韩胡思是否需要提供参数,如果说函数可以访问到已定义的数据,则就不需要提供函数形参,那么我们到底要不要提供函数参数,取决于什么? 答案就是变量的作用域(如果函数在变量的作用域内,则函数可以直接访问数据)

变量的作用域

概念:

变量的作用范围,也就是说变量在什么范围是有效的。

变量的分类

根据变量的作用域不同,变量可分为全局变量和局部变量

局部变量

序号局部变量作用域
1形式参数(形参)函数作用域
2函数内定义的变量函数作用域
3复合语句中的变量块作用域
4for循环表达式1定义的变量块作用域

全局变量

定义在函数之外的变量,也称为外部变量或全程变量

序号全局变量作用域
1定义在函数之外的变量,也称为外部变量或全程变量从全局变量定义处到本源文件的结束

建议在全局变量定义时初始化。如果不初始化,系统会将全局变量初始化为0( 0 | \0 | 0.0 ).

  • 使用全局变量的优缺点:

    优点

    1.利用全局变量可以实现一个函数对外输出的多个结果数据。

    2.利用全局变量可以减少函数形参个数,从而降低内存消耗,以及因形参传递带来的时间消耗。

    缺点

    1.全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。

    2.过多的全局变量会引起程序的混乱,造成程序结果错误。

    3.降低程序通用性,特别是当我们进行函数移植时,不仅仅要移植函数,还要考虑全局变量。

    4.违反了“高内聚,低耦合”的程序设计原则。

    总结:我们发现弊大于利,建议尽量减少对全局变量的使用,函数之间要产生联系,仅通过实参-形参的方式产生联系。

作用域举例:

案例:

int p=1,q=5; /*外部变量p,q*/
float f1(int a) /*定义函数f1*/
{ int b,c;
…
}
char c1,c2; /*外部变量c1,c2*/
char f2 (int x, int y) /*定义函数f2*/
{ int i,j;
…
}
void main ( ) /*主函数*/
{ int m,n;
…
}

注意:

如果全局变量(外部变量)和局部变量同名,程序执行的时候,就近原则

int a =10;
int main()
{
    int a = 20;
    printf("%d\n",a); // 20就近原则
    for(int i = 0; i < 20; i++)
    {
        printf("i=%d",i); // 0 1 2 3 4  就近原则
    }
}

变量的生存周期

概念:变量在程序运行中的存在时间。

根据变量存在的时间不同,变量可分为静态存储方式动态存储方式

  • 变量的存储类型

    变量的完整定义格式: [存储类型] 数据类型  变量列表;

  • 存储类型

    auto

    auto存储类型只能修饰局部变量,被auto修饰的局部变量是存储在动态存储区的。auto也是局部变量缺省的存储类型。

    int a = 10; 等价于 auto

    ststic

    修饰局部变量:局部变量会被存储在静态存储区。。局部变量的生命周期被延长,但是作用域不发生改变。

    修改全局变量:全局变量的生命周期不变,但作用域被衰减。一般限制全局变量只能在本文件内。

    demo01.c

    //全局变量
    int fun_a = 10;
    int fun1()

    demo02.c

    #include "demo01.h"
    
    main()
    {
        //此时fun_a就能被其他文件访问
        fun_a = 20;
    }

    extern

    外部存储类型: 只能修饰全局变量,此全局变量可以被其他文件访问。相当于扩展了全局变量的作用域。

    extern修饰外部变量,往往是外部变量进行声明,声明该变量是在外部文件中定义的;不是变量定义。

    demo01.c

    //全局变量
    int fun_a = 10;
    int fun1(){...}

    demo02.c

    #include "demo01.h"
    //声明外部文件的变量
    extern int fun_a;
    //声明外部文件的函数
    main()
    {
        //此时fun_a就不能被其他文件访问
        fun_a = 20;
        
        fun1();
    }

    register

寄存器存储类型:只能修饰局部变量,用register修饰的局部变量会直接存储到CPU的寄存器中,往往将循环遍历设置为存储器变量类型。

面试题static关键字的作用

​	1.static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。

​	2.static修饰全局变量,不影响全局变量的生命周期,会限制全局变量的作用域仅限本文件内使用;

​	3.static修饰函数:此函数就称为内部函数,仅限本文件内调用。

值传递与引用传递

  • 值传递:发生在整型、浮点型、字符型,数据传递,传递的是数值,也就是内存空间只能被当前变量独享。

  • 引用传递:发生在数组、指针、结构体.,数据传递,传递的是地址值,也就是内存空间可以被多个变量共享

案例:

// 值传递(整型、浮点型、字符型..)
fun(int x)
{
printf("%d\n",x); // x = 10
x = 20; // x = 20
}
main()
{
int a = 10; // a = 10
fun(a);
printf("%d\n",a);// a = 10
    }
-------------------------------------------------------------------------------------
// 引用传递(数组、指针、结构体..)
fun(int x[10])
{
printf("%d\n",x[9]);// x[9] = 0
x[9] = 20; // x[9] = 20
}
main()
{
int a[10] = {1,2,3};
fun(a);
printf("%d\n",a[9]);// a[9] = 20
}

内部函数和外部函数

  • 内部函数:使用static修饰的函数,称作内部函数,内部函数只能在当前文件中调用。

  • 外部函数:使用extern修饰的函数,称作外部函数,extern是默认的,可以不写,也就是说本质上我们所写的函数都是外部函数,建议外部函数在被其他文件调用的时候,在其他文件中声明的时候,加上extern关键字。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值