首页 > 其他分享 >函数的作用域

函数的作用域

时间:2024-08-06 22:23:53浏览次数:14  
标签:10 函数 作用域 int 数组 全局变量 变量

函数的递归调用

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

  • 递归调用的本质:

      是一种循环结构,它不同于之前所学的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;
}

数组做函数参数

注意

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

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

  但有一个例外,如果是用字符数组做形参,且实际数组中存放的是字符串数据(形参是字符数组,实参是字符串)。则不用表示数组元素的个数的形参,原因是字符串本身会自动结束符\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添加十个整型数据:\n");
    for(i = 0;i < sizeof(a)/sizeof(int);i++)
    {
        scanf("%d",&a[i]);
    }
    printf("\n");
    printf("请给数组b添加十个整型数据:\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);
    return 0;
}

案例2:

/**
* 需求:数组函数的参数案例-编写一个函数,用来分别求数组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)));
    return 0;
}

变量的作用域

引入问题

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

变量的作用域

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

变量的分类

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

局部变量

序号局部变量作用域
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 i = 20;
    printf("%d\n",a); // 20 就近原则
    for(int i = 0;i < 5; i++)
    {
        printf("i=%d ",i); // 0 1 2 3 4 就近原则
    }
}

变量的生命周期

  • 概念:变量在程序运行中的存在时间。
  • 根据变量存在的时间不同,变量可分为静态存储方式和动态存储方式。

请添加图片描述

  • 变量的存储类型

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

存储类型

auto

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

int a = 10; 等价于 auto int a = 10;
static
  • 修饰局部变量:局部变量会被存储在静态存储区。局部变量的生命周期被延长,但是作用域不发生改变。
  • 修改全局变量:全局变量的生命周期不变,但作用域被衰减。一般限制全局变量只能在本文件内。

demo01.c

#include "demo01.h"
// 全局变量
static int fun_a = 10;
int fun1()

demo02.c

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

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

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

demo01.c

#include "demo01.h"
int fun_a = 10;
int fun1(){..}

demo02.c

#include "demo01.h"
// 声明外部文件的变量
extern int fun_a;
// 声明外部文件的函数
extern int fun1();
main()
{
    fun_a = 20;
    fun1();
}
register

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

置为寄存器存储类型。

面试题

static关键字的作用

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

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

  3. static修饰函数:此函数就称为内部函数,仅限本文件内调用。 static int funa(){…}

值传递与引用传递

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

    请添加图片描述

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

请添加图片描述

// 值传递(整型、浮点型、字符型..)
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修饰的函数,称作内部函数,内部函数只能在当前文件中调用。
    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,函数,作用域,int,数组,全局变量,变量
From: https://blog.csdn.net/qixi_ao/article/details/140966688

相关文章

  • Python 中的生成器函数有什么作用及如何使用?
    生成器函数是一种特殊的函数,可以在迭代过程中动态生成值,而不是一次性返回所有值。它的作用有以下几点:节省内存:生成器函数一次只生成一个值,并在生成后立即释放内存,这样可以减小内存的占用,特别是在处理大数据集时非常有用。延迟计算:生成器函数可以按需生成值,只在需要的时......
  • C语言:函数
    函数是对步骤的封装。函数分两类:一类是系统函数,一类是自定义的函数。系统自带的函数如我们现在一直在用的printf。而今天我们主要说的是自定义函数。首先,我们要明白自定义函数的目的就是为了把一些麻烦复杂的东西封装起来,当我想用的时候可以直接调用,当然除此之外函数还有其......
  • 概率生成函数学习
    https://www.cnblogs.com/zzctommy/p/14256844.htmlhttps://www.cnblogs.com/HenryHuang-Never-Settle/p/14702997.html概率生成函数,设多项式\(F(x)=\sumP(X=i)x^i\)。则:\(F(1)=1\);\(E(x)=F'(1)\);\(E(x^{\underline{k}})=F^{(k)}(1)\),\(k\)阶导。\(......
  • 【NumPy 入门:常用函数与方法总结】
    文章目录前言1、np.array()函数2、np.arange函数(用于生成数值序列的函数)3、np.linspace函数(用于生成数值序列的函数)4、ndarray.dtype和ndarray.dtype.name属性5、矩阵乘积6、ravel方法、T和flat属性7、np.vstack和np.hstack函数8、column_stack函数9、np.r_和......
  • 关于简单的部分数学函数用python求导的示例
    1.求常数的导数题目代码1.求常数的导数:$f(x)=c$ 运行代码fromsympyimport*x,c=symbols('xc')c.diff(x)结果 2.求幂函数导数:题目代码2.求幂函数导数:$$f(x)=x^\mu$$运行代码fromsympyimport*x,mu=symbols('xmu')(x**mu).diff(x)结果  3.求三角......
  • 机器学习中的两个重要函数--sigmoid和softmax
    机器学习中,常常见到两个函数名称:sigmoid和softmax。前者在神经网络中反复出现,也被称为神经元的激活函数;后者则出现在很多分类算法中,尤其是多分类的场景,用来判断哪种分类结果的概率更大。本文主要介绍这两个函数的定义,形态,在算法中的作用,以及两个函数之间的联系。1.sigmoid函数......
  • 详细介绍c语言函数
    今天带大家学习c语言的函数文章目录1.函数的概念2.库函数3.自定义函数语法形式4.形参和实参5.return语句6.数组做函数参数7.嵌套调用和链式访问嵌套调用链式访问8.函数的声明和定义9.static和extern练习练习1.写一个函数判断一年是否是闰年。代码运行结......
  • 【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
    C++语法相关知识点可以通过点击以下链接进行学习一起加油!命名空间缺省参数与函数重载本章将分享C++增加的几种常见特性,主要内容为引用与内联函数|auto关键字与for循环|指针空值,这些知识看似很多,实际也不少。本章篇幅长,耐心享用,若有不足,欢迎指出!......
  • MySQL SQL函数
    MySQL数值函数1、CEILING()-返回最小的整数,使这个整数大于或等于指定数的数值运算。2、FLOOR()-返回最大整数,使这个整数小于或等于指定数的数值运算。3、ROUND()-四舍五入一个正数或者负数,结果为一定长度的值。=============================MySQL填充字符函数1、L......
  • 定义一个C++的类,析构的时候输出当前函数执行耗时
    背景介绍:有时候我们需要知道一个函数的执行耗时。按照传统方法,至少要定义一个start,end,然后计算差值,输出差值,4个步骤。这里,我们定义一个  ElapseMillsec类,然后在类的生命周期结束的时候,在析构函数里面计算出差值。此时  ElapseMillsec类的生命周期,就是函数执行耗......