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

文章目录

  • 函数(function)
    • **函数的概念**
    • **函数的作用**
    • 在本阶段一般会涉及到两类函数:库函数和自定义函数
    • 自定义函数
      • **函数的语法形式**
    • **形参和实参**
      • **实参和形参的关系**
    • 函数返回值
      • **函数返回值类型说明**
      • **return 语句**
    • 数组做函数参数
    • **函数嵌套调用和链式访问**
      • 函数嵌套调用
      • **函数链式访问**
      • **小插入:有趣的代码**
    • **内部函数和外部函数**
      • **函数声明和定义**
      • **单文件**
      • 多个文件(涉及到外部函数)
    • **局部变量和全局变量**
    • **作用域和生命周期**
    • **关键字static、extern**

函数(function)

请添加图片描述

函数的概念

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

函数的作用

程序其实是由无数个小的函数组成,比如:我们编写 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;
}

请添加图片描述

结论:从图中可以看出,虽然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天,其中二月份的天数会多一天

请添加图片描述
如果需要函数实现的,可以设计两个函数

  • 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函数
请添加图片描述
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;
}

请添加图片描述

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

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

如果使用该函数时,可以在调用函数的后边,但是需要提前声明,也可以在函数调用之前对该函数声明,声明函数需要交代:函数名、函数返回值类型和函数参数(当然一般也是将函数的定义放在上边,并且函数的声明一般在头文件中进行声明)
请添加图片描述
函数声明中参数可以只保留类型,省略掉名字也是可以的

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

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

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

比如:
请添加图片描述


局部变量和全局变量

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

作用域和生命周期

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

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

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

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

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

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

通过下列代码方便理解下上面的知识点:
请添加图片描述
**特别说明下:**这里的b是未定义的,因为局部变量b的作用域在if语句中,出了if语句(出了作用域)局部变量b就被销毁(生命周期结束)了。

关键字static、extern

static(静态)

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

extern

作用:声明外部符号

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

static修饰局部变量–静态局部变量
请添加图片描述
分析上面代码,对于上面知识稳固和理解static修饰局部变量的意义

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

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

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

静态局部变量的特点

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

static修饰全局变量
请添加图片描述
代码1正常,代码2在编译的时候会出现连接性错误。

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

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

结论:

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

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

static修饰函数

请添加图片描述

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

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

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

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

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


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

请添加图片描述

谢谢大家的观看,这里是个人笔记,希望对你学习C有帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/510415.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言:二叉树的构建

目录 一、二叉树的存储 1.1 顺序存储 1.2 链式存储 二、二叉树的顺序结构及实现 2.1堆的概念及结构 2.2堆的构建 2.3堆的插入 2.4堆顶的删除 2.5堆的完整代码 三、二叉树的链式结构及实现 3.1链式二叉树的构建 3.2链式二叉树的遍历 3.2.1前序遍历 …

【笔记】即时通讯设计

记录一下最近对im功能的设计 写扩散 1&#xff09;遍历群聊的成员并发送消息&#xff1b;2&#xff09;群聊所有人都存一份&#xff1b;3&#xff09;查询每个成员的在线状态&#xff1b;4&#xff09;在线的实时推送。 读扩散 1&#xff09;遍历群聊的成员并发送消息&#x…

【Go】二十、反射

文章目录 1、反射2、对基本数据类型反射3、对结构体进行反射4、获取变量的类别5、通过反射修改基本类型变量的值6、通过反射操作结构体的属性和方法 1、反射 //核心包 import ("reflect")通过反射&#xff1a; 可以在运行时动态获取变量的类型、获取结构体的信息&a…

云容器引擎CCE弹性伸缩

CCE弹性伸缩介绍 CCE的弹性伸缩能力分为如下两个维度&#xff1a; 工作负载弹性伸缩&#xff1a;即调度层弹性&#xff0c;主要是负责修改负载的调度容量变化。例如&#xff0c;HPA是典型的调度层弹性组件&#xff0c;通过HPA可以调整应用的副本数&#xff0c;调整的副本数会…

【数字图像处理】颜色空间的转换

颜色空间的转换 CMY 空间 CMY 颜色空间正好与 RGB 颜色空间互补&#xff0c; 即用白色减去 RGB 颜色空间中的某一颜色值就等于这种颜色在 CMY 颜色空间中的值。 { C 1 − R M 1 − G Y 1 − B \begin{cases}C1-R\\M1-G\\Y1-B\end{cases} ⎩ ⎨ ⎧​C1−RM1−GY1−B​ HSV 空…

非关系型数据库(缓存数据库)redis的基础认知与安装

目录 一.关系型数据库和非关系型数据库 关系型数据库 非关系型数据库 关系数据库与非关系型数据库的区别 ①非关系数据 关系型数据库 非关系型数据库产生背景 数据存储流向 非关系型数据库 关系数据库 二.redis的简介 1.概念 2.Redis 具有以下几个优点: 3.Redi…

数据结构二叉树顺序存储——堆

堆 1.堆的概念2.堆的实现 &#xff08;建小堆为例&#xff09;2.1 初始化和销毁2.2 判空2.3 获得堆顶元素和堆的大小2.4 插入2.5 删除 3.堆的构建&#xff08;建小堆为例&#xff09; 1.堆的概念 将若干数据或元素按照完全二叉树的存储方式顺序存储到一个一维数组中&#xff0…

LiDAR和Camera融合的BEV感知算法-BEVFusion

0. 简述 本次给大家讲解一篇非常经典的融合工作叫 BEVFusion&#xff0c;我们依旧从算法动机&开创性思路、主体结构、损失函数以及性能对比四个方面展开 BEVFusion 有两篇文章&#xff0c;本次主要讲解的是阿里和北大的&#xff1a;BEVFusion: A Simple and Robust LiDAR-…

Node | Node.js 版本升级

目录 Step1&#xff1a;下载 Step2&#xff1a;安装 Step3&#xff1a;换源 发现其他博客说的 n 模块不太行&#xff0c;所以老老实实地手动安装 Step1&#xff1a;下载 Node 中文官网&#xff1a;https://nodejs.cn/download 点击后&#xff0c;将会下载得到一个 .msi 文件…

打断点调试代码的思路(找bug的思路)二分法

现象&#xff1a; 当断点运行到此处&#xff0c;卡死 二分法&#xff1a; 用断点把程序切段&#xff0c;前一段&#xff0c;后一段 **前一段&#xff1a;检查变量值&#xff0c;如无问题&#xff0c;则说明没有任何问题 问题必然出在后一段 后一段&#xff1a;人为检查&…

了解游戏相关知识

个人笔记&#xff08;整理不易&#xff0c;有帮助点个赞&#xff09; 笔记目录&#xff1a;学习笔记目录_pytest和unittest、airtest_weixin_42717928的博客-CSDN博客 个人随笔&#xff1a;工作总结随笔_8、以前工作中都接触过哪些类型的测试文档-CSDN博客 目录 一&#xff1a…

鸿蒙TypeScript学习第7天:【TypeScript 循环】

1、TypeScript 循环 有的时候&#xff0c;我们可能需要多次执行同一块代码。一般情况下&#xff0c;语句是按顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句…

Polardb代理介绍

代理位于数据库和应用程序之间的网络代理服务&#xff0c;转发客户端的请求到DB&#xff0c;收到DB回包在转发到客户端&#xff0c;提供多种能力&#xff0c;支持&#xff1a;读写分离、负载均衡、一致性级别、连接池、过载保护功能。对外提供地址&#xff1a;主地址和集群地址…

Halcon3D倾斜平面矫正至水平面

前言 在相当多的3d检测中&#xff0c;由于各种因素的干扰&#xff0c;我们所检测的平面通常并不是一个水平面&#xff0c;或者被检测的面不是水平面的情况。尤其是在倾斜面的缺陷检测和平面度检测中&#xff0c;使用被测面与拟合基准面进行计算很难做到准确的定位到缺陷的情况…

全数字化病理,“根深”才能“叶茂”

现代医学之父William Osler曾言&#xff1a;病理学乃医学之本。 作为研究人体疾病发生的原因、发病机制、病理变化以及疾病过程中机体的形态结构、功能代谢变化和病变转归的一门基础医学科学&#xff0c;病理学一直被视为基础医学与临床医学之间的“桥梁学科”&#xff0c;在医…

分类预测 | Matlab实现TCN-BiGRU-Mutilhead-Attention时间卷积双向门控循环单元多头注意力机制多特征分类预测/故障识别

分类预测 | Matlab实现TCN-BiGRU-Mutilhead-Attention时间卷积双向门控循环单元多头注意力机制多特征分类预测/故障识别 目录 分类预测 | Matlab实现TCN-BiGRU-Mutilhead-Attention时间卷积双向门控循环单元多头注意力机制多特征分类预测/故障识别分类效果基本介绍模型描述程序…

Linux文件与进程交互的窥探者lsof

lsof 是一个 Linux 和 UNIX 系统中的实用工具&#xff0c;用于列出系统中打开文件的所有信息。这个名字代表 “List Open Files”&#xff0c;但它也可以显示进程相关的其他信息&#xff0c;如&#xff1a; 打开的文件描述符列表 打开网络连接的列表 被进程使用的信号和内核对…

HarmonyOS实战开发-为应用添加运行时权限

介绍 通过AbilityAccessCtrl动态向用户申请“允许不同设备间的数据交换”的权限&#xff0c;使用设备管理实例获取周边不可信设备列表。 说明&#xff1a; 查询周边不可信设备之前&#xff0c;请确保本设备与周边设备未进行配对。如果已配对&#xff0c;则恢复出厂设置之后重新…

Python中的全栈开发前端与后端的完美融合【第160篇—全栈开发】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python中的全栈开发&#xff1a;前端与后端的完美融合 全栈开发已成为当今软件开发领域中的…

Linux权限提升总结

几个信息收集的项目推荐 运行这几个项目就会在目标主机上收集一些敏感信息供我们参考和使用 一个综合探针&#xff1a;traitor 一个自动化提权&#xff1a;BeRoot(gtfo3bins&lolbas) 使用python2运行beroot.py就可以运行程序&#xff0c;然后就可以收集到系统中的大量信…