【函数省时又省力---C语言的函数】超详细理解&&例题、错题

你好,这里是 Sunfor
这篇是我最近对于C语言函数的学习心得和错题整理
有任何错误欢迎指正,欢迎交流!
会持续更新,希望对你有所帮助,我们一起学习,一起进步

函数的概念

C语言函数,一句话,
参数返回类型包裹它。
花括号括住函数体, 功能实现真不差。
定义和声明头文件中, main函数为程序门。
递归调用自我身, 出口要有,莫心烦。
库函数调用,省时省力, 编程轻松又得意。

C语言中的函数就是 完成某段特定任务的一小段代码 ,C语言的代码就是由无数小的函数组成而成的,并且函数是可以复用的,这样极大地提升开发软件的效率

库函数

我们先了解计算机语言的初步发展
在这里插入图片描述
库函数是我们学会了就可以直接使用的函数,库函数可以实现一些常见的功能,这在一定程度上提升了运行效率和代码质量
库函数是根据功能划分的,在不同的头文件中进行声明,所以要尽量 包含头文件

库函数相关头文件:https://zh.cppreference.com/w/c/header

库函数的使用方法

库函数很多,我们很难全部记住,这时就要向大家介绍两个学习库函数的工具

C/C++官⽅的链接https://zh.cppreference.com/w/c/header
cplusplus.comhttps://legacy.cplusplus.com/reference/clibrary/

自定义函数

函数的语法形式

ret_type fun_name(形式参数)
{

}
  • ret_type 是函数的返回类型
  • fun_name 是函数名
  • ( )中放的是形式参数
  • { }中放的是函数体

结合图片一起理解
在这里插入图片描述
举个例子

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 0;
	int b = 0;
	//输入
	scanf("%d %d", &a, &b);
	int c = Add(a, b);//使用函数(调用函数)
	//输出
	printf("%d", c);
	return 0;
}

形参和实参

形参

在函数定义部分,函数名后边的参数,叫:形式参数,简称形参

实参

在调用函数的时候真实传递给函数的参数叫:实际参数,简称实参

两者的关系

若函数只是定义的话,形参变量不会创建。函数在调用的过程中,为了存放传过去的实参,就会创建形参变量,这样才会为形参变量分配空间,这个过程就是 形参的实例化
就着我们上面的例子进行理解
在这里插入图片描述
根据调试我们可以观察到形参和实参的地址是不一样的,我们可以理解为 形参是实参的一份临时拷贝
来看一道错题

#include<stdio.h>
void swap(int x,int y)
{
  int temp = x;
  x = y;
  y = temp;
}
int main()
{
  int a = 5;
  int b = 10;
  printf("交换前:a = %d, b = %d\n",a,b);
  swap(a,b);
  printf("交换后:a = %d, b = %d\n",a,b);
  return 0;
}

a和b的值能成功交换吗?我们来看看运行结果
在这里插入图片描述
我们可以看到,a和b的值并没有完成交换,这是因为函数参数是按值传递的, 即函数内部对参数的修改不会影响到函数外部

return语句

在函数的使用过程中我们常常会用到return语句,接下来我们来看看return语句的注意事项

  • return 后面可以是 数值 也可以是 表达式 如果是表达式则先执行表达式,在返回表达式的结果
  • return后面也可以什么都没有,适用于返回值为void类型的函数
  • return的返回的值和函数返回类型不一致,系统会自动将返回的值隐式转化为函数的返回类型
  • return语句执行后,函数就彻底返回,后面的代码不再执行
  • 如果函数中参在if等分支语句,则要保证每种情况下都有return返回,否则会出现编译错误
  • 函数不写返回类型,默认返回 int 类型的值
  • 如果函数要求返回值,但是函数中没有使用return语句,那具体返回什么就 不确定

数组做函数参数

在用函数解决问题时,有时会遇到数组作为参数传递给函数,在函数内部对数组进行操作
举个例子

#include<stdio.h>
//写一个函数将数组内所有元素置为1,并打印所有元素
void set_arr(int arr[], int sz, int set)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		arr[i] = set;
	}

}
void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//计算数组内元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);
	//设置打印数组,打印初始数组
	print_arr(arr, sz);
	//设置重置数组
	set_arr(arr, sz, 1);
	//打印重置后的数组
	print_arr(arr, sz);
	return 0;
}

运行结果如下
在这里插入图片描述
值得注意的点

  • 数组在传参的时候,实参就写 数组名 就行,形参也是 数组 的形式
  • 实参和形参的名字是可以一样的,也可以不一样
  • 函数在设计的时候一定要 尽量 功能单一
  • 数组在传参的时候,形参的数组和实参的数组是 同一个 (具体内容会在指针知识给大家介绍)
  • 形参如果是一维数组,数组大小可以省略
  • 形参如果是二维数组,行可以省略,列不行
  • 数组传参,形参是不会创建新的数组的

嵌套调用和链式访问

函数就类似于各个零件,相互之间也可以调用,这样就可以完成大型、复杂的程序
举个例子

//计算某年某月有多少天
int is_leep_year(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int get_days_of_mouth(int y, int m)
{
	int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	//             0  1  2  3  4  5  6  7  8  9  10 11 12
	int day = days[m];
	if (is_leep_year(y) == 1 && m == 2)
	{
		day++;
	}
	return day;
}

int main()
{
	int year = 0;
	int mouth = 0;
	scanf("%d%d", &year, &mouth);

	//计算某年某月有多少天
	int day = get_days_of_mouth(year,mouth);
	printf("%d\n", day);

}

嵌套调用

以上的计算某年某月有多少天的例子就是典型的,函数的嵌套调用

链式访问

将一个函数的返回值作为另一个函数的参数, 像链条一样将函数串起来 就是函数的链式访问
举个例子

int main()
{
	/*int len = strlen("abcdef");
	printf("%d", len);*/

	printf("%d", strlen("abcdef"));//链式访问

	return 0;
}

函数的声明和定义

先以一个例子来引入

函数或变量都要满足,先声明,后使用

//函数声明
//int is_leap_year(int y);

//不要忘了后面的;(分号)
int is_leap_year(int);//在函数的声明中,形参的名字可以忽略


函数的定义是一种更强大的声明,如果在使用前定义了,就不需要声明了
//函数定义
int is_leap_year(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)|| (y % 400 == 0)))
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	int year = 0;
	scanf("%d", &year);

	//函数调用
	if (is_leap_year(year))
	{
		
		printf("%d 是闰年", year);
	}
	else
	{
		printf("%d 不是闰年", year);
	}
	return 0;
}

在这串代码里很好地显示了函数声明的作用

单个文件

还是以例子的方式展示

//函数声明
int Add(int, int);

//主函数
int main()
{
	int x, y = 0;
	scanf("%d %d", &x, &y);
	//函数调用
	int r = Add(x, y);
	printf("%d", r);
	return 0;
}

//函数定义
int Add(int a, int b)
{
	return a + b;
}

多个文件

把大型复杂的程序,拆分成多个文件的好处

  • 团队协作
  • 代码模块化,逻辑清晰
  • 代码的隐藏
  • 提高代码的可重用性
  • 减少编译时间

上一个例子改成多文件来试试
在这里插入图片描述

static && extern

我们先来了解一下
作用域 : 在一段代码中出现的名字并不是哪里都有用,限定这个名字的可用性的代码范围,就是作用域
局部变量的作用域是局部范围 全局变量的作用域是整个工程
生命周期:变量的创建(申请内存)到变量的销毁(收回内存)之间的时间段
局部变量的生命周期是进入作用域,生命周期创建,出作用域生命周期结束 全局变量的生命周期是:整个程序的生命周期

static

static修饰局部变量

举个例子来看
在这里插入图片描述

在这里插入图片描述
对比这两张图片,引起差异的就是 static,那产生差异的原因是什么呢?
static修饰局部变量改变了它的生命周期,生命周期的改变本质上是改变了变量的存储类型
来结合一张图片来理解何为内存
在这里插入图片描述

本来局部变量是存储在栈区的,被static修饰后存储在静态区,存储在静态区的变量和全局变量一样,生命周期变长了,但是作用域并没有改变

static修饰全局变量

还是以例子来看
在这里插入图片描述
在这里我们也看到 extern 的声明作用,当我们想在本文件中使用其他文件的参数,需要用 extern 进行声明
但是用 static 修饰后 extern 也失效了

static修饰函数

static修饰函数与static修饰全局变量十分相似,也是改变了函数的外部链接属性

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值