【C语言】函数(涉及生命周期与作用域)

这篇具有很好参考价值的文章主要介绍了【C语言】函数(涉及生命周期与作用域)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

函数(function)

【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言

函数的概念

函数:是指程序中的实现某项特定需求的一小段代码(容易跟数学上函数混淆),程序中函数翻译称为子程序。通常也称为接口(接口是内外连续的窗口,实现不同的功能和效果)

函数的作用

程序其实是由无数个小的函数组成,比如:我们编写 int main() 也是属于函数。函数就是运用"大事化小"的思想,将一个大问题分为若干个小问题一个大功能分为几个小功能实现。遇到需求可以调用对应的函数解决问题并且函数可以复用,遇到相同需求直接调用该函数,不需要重新CV或者敲一遍一样的程序,减少了代码的冗长,提高了开发的效率,程序的可读性

在本阶段一般会涉及到两类函数:库函数和自定义函数

C语言标准规定许多语法法则,但是C语言不提供库函数,但是可以使用库函数中的函数。C语言的国际标准ANSI C规定了部分常用的函数的标准,被称为标准库,对于不同编译器厂商根据ANSI C给出标准库给出了常用函数的实现称为库函数

  • 标准库:调用函数某种标准规范
  • 库函数:部分常用的函数集合

对于一些常见的功能可以直接调用对应的库函数,比如打印函数,内存函数等,提高了开发的效率。

各种编译器的标准库中提供了⼀系列的库函数,这些库函数根据功能的划分,都在不同的头文件中进行了声明

库函数相关头文件:https://zh.cppreference.com/w/c/header (有兴趣可以自己学习)

函数调用:在不同的文件中实现函数调用需要进行函数声明,库函数是在标准库中对应的头文件声明所以在使用库函数时,需要所对应的头文件。

自定义函数

"自定义"意味更多的创造性,自定义函数更为重要,在不同实际需求中,灵活设计程序满足需求。

函数的语法形式

库函数和自定义函数是一样的(只不过是库函数已经有大佬敲出来,我们只负责调用)

//函数定义格式
ret_type(返回值类型) fun_name(函数名)(形式参数/形参)
{
	函数体语句
}
//函数调用格式
int main()
{
	ret_type(实参);
}

使用注意:

  • ret _type:表示返回函数计算结果的类型,当返回类型为void,表示没有返回值

  • fun_name:函数名为了方便调用对应的函数,所以函数名尽量根据函数功能取名更有意义(不要用拼音表示函数名,显得很俗,尽量使用英文)

  • 函数参数可以为void,明确表示函数没有参数,保留空号,不需要特殊声明

  • 函数有多个参数的话,需要用逗号分隔开表示独立的参数,要对应好参数的类型、名字、个数。

  • 形参的书写需完善,不能只写参数类型而省略参数名字

#include <stdio.h>
int Add(int x,int y)
{
	return x+y;
}

int main()
{
	int a=0,b=0;
    scanf("%d %d",&a,&b);
    printf("%d",Add(a,b));//Add函数会返回数值,这里可以直接嵌套在printf中
}

形参和实参

在调用函数的过程中,函数的参数为实参和形参

  • 实际参数: 简称为实参,是函数调用时的实际参数值

  • 形式参数:简称为形参,是函数声明和定义时指定的参数名(参数名尽量取得有意义)

如果只是定义这个函数,而不去调用的话,函数的参数部分只是形式上存在不会向内存申请空间(不是真实存在的)只有当函数被调用的过程中为存放(拷贝)实参传递过来的值,才向内存申请空间的,这个过程称为:形参的实例化

实参和形参的关系

既然只当函数调用时,向内存申请空间,则实参和形参都有各自独立的内存空间,那么实参和形参地址可能是不同的

void Swap(int x,int y)
{
	
}
int main()
{
    int a=1,b=6;
    Swap(a,b);
  return 0;
}

【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言

结论:从图中可以看出,虽然x和y确实得到了a和b的值,但是x和y跟a和b的地址是不同的。因为形参是实参的一份临时拷贝,将实参的数值拷贝到形参中,而不是连同地址拷贝给形参,那么意味着形参的改变不会影响到实参
如果我想要通过形参去影响实参,那么可以提前看会指针的知识,通过地址对进行修改–链接

函数返回值

函数返回值类型说明

  • 函数默认类型是int,可以省去前面的类型说明符(建议写上,提高代码的可读性)
  • 类型说明符如果是void,表示函数没有返回值

return 语句

return语句的两种用途

当函数处理函数体中的数据,需要返回数值,通过返回语句来传送返回值到函数调用点(递归时常见)

表示程序结束,从函数返回调用点,不返回函数的值,可以不用使用return语句

使用return语句的注意事项:

  • return后边可以是一个数值,也是可以是一个表达式。如果后边是一个表达式,那么会先执行表达式,再将表达式的结果返回

  • return 后边不带东西,比如return ;表示返回类型是void,跳出本次函数

  • 执行return语句,直接跳出本次函数,return后边代码不再执行

  • 当return返回的值和函数返回类型不一致,系统会自动将返回的值隐式转换为函数的返回类型

  • OJ常见的问题,如果函数中存在if等分支语句,则要保证每种情况下都有返回值,否则会出现编译报错

返回值规则

  • 任何C/C++函数都必须有类型,如果函数不需要返回数据(不要省略返回值类型),应该声明void类型。不然的话,不加类型说明的函数,一律自动按照整形处理,但是这样子容易误解为void类型
  • 不要将正常值和错误标记混在一起返回

数组做函数参数

设计函数时,有些情况需要调用外部数组,在函数中对数组元素进行修改

样例:

int f(int nums[],int sz)
{
	for(int i=0;i<sz;i++)
    {
        nums[i]=1;
		printf("%d ",nums[i]);
    }
}
int main()
{
    int arr[5]={1,2,3,4,5};
    int sz=sizeof(arr)/sizeof(arr[0)];
	f(arr,sz);
     return 0;
}

数组传参注意事项:

  • 函数形参和函数实参个数匹配

  • 函数实参是数组,形参也可以写成数组和指针类型(传递数组会被转化为指针)

  • 形参如果是一维数组,数组大小可以省略不写

  • 形参如果是二维数组,行可以省略,但是列不能省略

  • 数组传参,形参是不会创建新的数组,形参修改还是实参数组元素

  • 形参以数组的形式接收,不能省略[],如果是int arr,类型上实参和形参无法对应

问题:如果将外部数组传参到函数中,在函数中计算数组的大小,是否可行

答:不可行。数组名是数组首元素的地址,那么传参是传递指针,可以使用数组和指针类型接受(传递数组会被转化为指针),对此int sz=sizeof(arr)/sizeof(arr[0)];这里arr不再是数组名,而是一个指针变量,计算结果会有误差。

函数嵌套调用和链式访问

函数嵌套调用

嵌套调用:指一个函数的函数体中嵌套一个函数,函数之间有效的互相调用,大一些代码都是函数之间的嵌套调用,但是函数是不能嵌套定义的

函数链式访问

链式访问:指将一个函数的返回值作为另外一个函数的参数,像链条一样将函数串起来。

比如:printf("%d",Add(x,y));

对于上面两个知识点,通过一道题目加深理解

知识铺垫:假设我们需要计算某年某月又多少天,但是由于闰年的关系,在闰年这里一年中,一年变为366天,其中二月份的天数会多一天

【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言
如果需要函数实现的,可以设计两个函数

  • get_days_of_month():得到天数,在其函数中,需要对是否为闰年进行判断,再进行修改

  • is_leap_year(): 根据年份判断是否是闰年

打印闰年

is_leap_year(int y)
{
	if(((year%4==0) && (year%100!=0)) || (year%400==0))
		return 1;
	else
		return 0;
}
int get_days_of_month(int y, int m)
{
	int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };//首元素为0的目的,是方便输入月份得到相对的天数
	int day = days[m];
	if ((is_leap_year(y)) && m == 2)//1为真,0为假
		day++;
	return day;
}

int main()
{
	int y = 0; int m = 0;
	scanf("%d %d", &y, &m);
    printf("%d",get_days_of_month(y, m));
//那么函数参数部分能不能是函数呢?当然这些到后面才慢慢理解下了
	return 0;
}

小插入:有趣的代码

printf("%d", printf("%d", printf("%d", 43)));

这段函数会打印出什么结果,首先我们需要知道,printf函数的返回值是什么,如果想要了解某个库函数细节printf函数
【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言
printf函数返回的是打印屏幕上字符的个数,printf函数执行的方向是从右到左,当然在不同编译器有所差异

程序执行顺序:
1. 第一个printf打印第二个printf的返回值,而第二个返回值打印的是第三个printf的返回值
    
2. 先第三个printf打印43,在屏幕上打印2个字符,再返回2。其次第二个printf打印2,再屏幕上打印1个字符,再放回1,最后printf打印1

3. 最后屏幕上显示:4321

内部函数和外部函数

根据函数是否被其他源文件调用,将函数区分为内部函数和外部函数。主要是解决不同源文件中函数之间调用问题

  • 内部函数:一个函数只能被本文件的其他函数所调用

  • 外部函数:默认情况下,函数的定义是外部可见的,无需使用 extern 关键字来指定。在定义函数时,在函数最左端加关键字extern(外部),这样子的函数称为外部函数,如果当定义函数时省略extern,则默认为外部函数,外部函数可供其他文件调用

函数声明和定义

单文件

//函数声明
void f(void); 
//函数定义
void f(void)
{
    函数体;
};

通过判断闰年函数对了解下函数定义和声明使用

int leap_year(int year)
{
	if(((year%4==0) && (year%100!=0)) || (year%400==0))
        return 1;
    else
        return 0;
}//这里属于函数的定义
int main()
{
	int year=0;
    scanf("%d",&year);
    int ret=leap_year(year);
    if(ret==1)
     printf("%d是闰年",year)
      	 else
            printf("%d不是闰年",year);
}

问题:如果是在函数调用之前实现函数定义,是没有任何的报错的,如果是将函数的定义放在函数的调用后边,会不会出现报错呢?

int main()
{
	int year=0;
    scanf("%d",&year);
    int ret=leap_year(year);
    if(ret==1)
     printf("%d是闰年",year)
      	 else
            printf("%d不是闰年",year);
}
int leap_year(int year)
{
	if(((year%4==0) && (year%100!=0)) || (year%400==0))
        return 1;
    else
        return 0;
}

【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言

输入栏这里有提示调用函数前,没有定义该函数,检查不够严格,没有直接报错,这个代码VS2022上编译是会报错的

是因为编译器对源文件进行编译时,是从第一行往下扫描的,当遇到函数调用时,并没有发现前面有该函数的定义

如果使用该函数时,可以在调用函数的后边,但是需要提前声明,也可以在函数调用之前对该函数声明,声明函数需要交代:函数名、函数返回值类型和函数参数(当然一般也是将函数的定义放在上边,并且函数的声明一般在头文件中进行声明)
【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言
函数声明中参数可以只保留类型,省略掉名字也是可以的

多个文件(涉及到外部函数)

在企业中写代码时,当代码量比较多,不会将所有代码都放在一个文件中,往往会根程序的功能,将代码拆分在多个文件中

⼀般情况下:函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在原文件(.c)文件中

比如:
【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言


局部变量和全局变量

  • 局部变量:定义在某一函数或某一部程序内部的变量,称为局部变量。局部变量只能在其所定义的局部范围(作用域)内起作用,离开该范围,它们将会自动销毁(结束生命周期),因此,又称为局部自动变量
  • 全局变量:定义在所有函数之外,可供所有函数访问的变量,称为全程变量或全局变量

作用域和生命周期

作用域(scope):是程序设计概念,通常来说,一段程序代码中所用到的名字并不是总是有效的,而限定这个名字的可用性的范围就是这个名字的作用域,简而言之:

  • 局部变量的作用域:变量所在的局部范围
  • 全局变量的作用域:整个工程

生命周期:指变量创建(申请空间)到变量销毁(收回空间)之间的一个时间段

  • 局部变量的生命周期:进入作用域生命周期开始,出作用域生命周期结束
  • 全局变量的生命周期:整个程序的生命周期(int main->return结束后)

小知识点:就近原则,了解即可,一般不会这样子设计变量名

  • 当局部变量和全局变量同名时采取–>就近原则(一般这样子会出现重命名的问题,但是这里有两个域,不同的域取相同的名字,不会有命名冲突)
  • 优先使用离使用地方近的变量

通过下列代码方便理解下上面的知识点:
【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言
**特别说明下:**这里的b是未定义的,因为局部变量b的作用域在if语句中,出了if语句(出了作用域)局部变量b就被销毁(生命周期结束)了。

关键字static、extern

static(静态)

作用:1.修饰局部 2.全局变量 3.修饰函数

extern

作用:声明外部符号

通过局部、全局变量、函数来深入了解这两个关键字

static修饰局部变量–静态局部变量
【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言
分析上面代码,对于上面知识稳固和理解static修饰局部变量的意义

代码1:在test函数中,创建局部变量i(生命周期开始)并且赋值为0,再++,打印,退出函数(生命周期结束)。

代码2:从输出结果上来看,变量i的值有累加的效果,因为在test函数中创建局部变量i之后,出函数是不会销毁的,重新进入函数也不会重新创建变量,被static修饰只能定义一次,直接累加的数值参与计算中

结论:static修饰局部变量改变了变量的生命周期生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区中,但是被static修饰后存储到了静态区中。全局变量是存储在静态区中,那么存储在静态区的变量和全局变量,生命周期就和程序的生命周期一样的,只有当程序结束,变量才被销毁,内存才能被回收,但是作用域是不变的

静态局部变量的特点

  • 只能被定义一次,需要在定义同时初始化
  • 生命周期是全局的,作用域是局部的,出作用域不会销毁

static修饰全局变量
【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言
代码1正常,代码2在编译的时候会出现连接性错误。

extern是用来声明外部符号,如果一个全局变量的符号在A文件中定义,想在B文件使用,可以使用extern进行声明,之后可以被使用

本质原因:全局变量默认具有外部链接属性在外部文件中使用,只需要声明下就可以使用

结论:

一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能被其他源文件内使用。因为全局变量被static修饰之后外部链接属性就变成内部链接属性,只能在自己所在的源文件内部使用了,其他源文件,即使声明,也不能正常使用的

使用建议:一个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰

static修饰函数

【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言

代码1正常,代码2在编译的时候会出现连接性错误。

static修饰函数和static修饰全局变量是一样的,一个函数可以在整个工程中使用,被static修饰后,只能在本文件内部使用,其他文件无法正常链接使用

本质:函数默认是具有外部链接属性,使得在整个工程中,只需要适度的声明就可以使用。

是被static修饰后变成了内部链接属性,使得函数只能在自己所在源文件内部使用

使用建议:一个函数,只想在所在的源文件内部使用,不想被其他源文件使用,就可以使用static修饰


当然针对上面指针相关的问题,我们留到后边来讲,函数的参数和返回值的传递方式有两种:值传递(pass by value)和指针传递(pass by pointer)。

【C语言】函数(涉及生命周期与作用域),C语言,c语言,开发语言

谢谢大家的观看,这里是个人笔记,希望对你学习C有帮助。文章来源地址https://www.toymoban.com/news/detail-847443.html

到了这里,关于【C语言】函数(涉及生命周期与作用域)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言趣味教学】(6) 作用域:局部变量 | 全局变量 | 局部变量优先原则 | 利用大括号限制作用域 | 变量的生命周期

        🔗 【C语言趣味教程】专栏介绍👈 猛戳了解!!! 0x00 引入:什么是作用域? 变量和常量在程序中都是有作用范围的,这个范围我们称之为变量的  作用域 (scope) 。 作用域也称为 \\\"定义域\\\",是程序中 定义的变量所存在的区域,出了该区域变量就不能被访问。 变量的作

    2024年02月14日
    浏览(33)
  • MyBatis:生命周期、作用域、结果集映射 ResultMap、日志、分页、使用注解开发、Lombok

    理解不同 作用域 和 生命周期 类别是至关重要的,因为错误的使用会导致非常严重的 并发问题 。 SqlSessionFactoryBuilder 一旦创建了 SqlSessionFactory,就不再需要它了; 最佳作用域 是方法作用域(也就是局部方法变量)。 SqlSessionFactory :相当于 数据库连接池 一旦被创建就应该在

    2024年02月02日
    浏览(56)
  • 【C语言趣味教程】(4) 变量:代码注释 | 变量的声明 | 初始化与赋值 | 作用域与生命周期 | 局部变量与全局变量

      🔗 《C语言趣味教程》👈 猛戳订阅!!! 0x00 引入:注释的作用 \\\"程序员最讨厌两种人:一种是不写注释的人,一种是让我写注释的人。\\\" 相信大家对注释早已有所耳闻,对于注释,C 语言有两种注释风格,我们下面会逐个讲解。   但在这之前,我们先来了解了解注释的作

    2024年02月15日
    浏览(49)
  • C语言系列(所需基础:大学C语言及格)-1-编译器/简单的求和代码/数据类型/变量的分类/变量的作用域和生命周期

    为了方便,我使用的是在线的C语言编译器进行程序的运行。 链接:https://www.bejson.com/runcode/c740/ 效果如下: 分为局部变量和全局变量 说白了,放在main函数外面的变量为全局变量,其他为局部变量。

    2024年02月19日
    浏览(53)
  • Bean 作用域和生命周期

    Spring 容器是用来存储和读取 Bean 的 , 因此 Bean 是 Spring 中最核心的操作资源. 编写代码过程中 , bean 对象如果有多个属性 , 创建 Getter , Setter, 构造方法 等方法 , 会产生大量冗长的代码. 那么为了使代码更加简洁 , 我们可以使用 Lombok 框架 , 只需要一行注释 , 就可以避免大量冗长

    2024年02月05日
    浏览(79)
  • Bean作用域和生命周期

    hi,今天为大家带啦Bean的作用域和生命周期的相关知识 Bean的作用域和我们之前学过的不一样,我们之前学的作用域是一个范围,而现在指的是 Bean在Spring框架中的某种行为模式,也就是一个动作. 这样干巴巴的说看我可能无法理解,我们来举个例子 创建一个公共类的一个公共对象

    2024年02月15日
    浏览(53)
  • Bean的作用域和生命周期

    目录 1.作⽤域定义 1.1Bean的6个作用域 1.singleton:单例作用域 2.prototype:多例作用域 3.request:请求作用域 4.session:会话作用域 5.application:全局作用域 6.websocket:HTTP WebSocket作用域 单例作⽤域(singleton) VS 全局作⽤域(application) 1.2设置作用域 1.直接设置值@Scope(\\\"potptype\\\") 2.用枚举设置:@Scop

    2024年02月02日
    浏览(88)
  • Spring的作用域与生命周期

    lombok插件可以提供给我们一些注释,这些注释可以很好的帮助我们消除Java代码中大量冗余的代码,可以使得我们的Java类可以看起来非常的干净整洁 1.安装lombok插件 2.pom.xml导入依赖 3.基本使用 我们创建一个简单的User实体类 如果我们手动的在类中加入setter+getter,toString等等,如

    2023年04月24日
    浏览(35)
  • C++的作用域、变量作用域、生命周期和变量分类介绍

    C++ 中的作用域(scope)指的是变量、函数或其他标识符的可见和可访问的范围。 变量作用域(Variable Scope)是指变量的生命周期和可见性,也就是变量在程序中的哪些部分可以使用。按照作用域(Scope)变量可分类为全局变量和局部变量。 还可按照生命周期(Lifetime)进行分

    2024年02月13日
    浏览(46)
  • Spring(Bean 作用域和生命周期)

    目录 1. 案例1: Bean作用域的问题 2. 作用域 3. 设置 Bean 的作用域 4. Spring 的执行流程 5. Bean 的生命周期 现在有一个公共的 Bean,通过给 A 用户 和 B 用户使用, 然后在使用的过程中 A 偷偷的修改了公共 Bean 的数据, 导致 B 在使用时发生了预期之外的逻辑错误 (1) 公共 Bean:   [名称是

    2024年02月19日
    浏览(99)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包