C语言的函数
你好,这里是 Sunfor
这篇是我最近对于C语言函数的学习心得和错题整理
有任何错误欢迎指正,欢迎交流!
会持续更新,希望对你有所帮助,我们一起学习,一起进步
函数的概念
C语言函数,一句话, 参数返回类型包裹它。 花括号括住函数体, 功能实现真不差。 定义和声明头文件中, main函数为程序门。 递归调用自我身, 出口要有,莫心烦。 库函数调用,省时省力, 编程轻松又得意。
C语言中的函数就是 完成某段特定任务的一小段代码 ,C语言的代码就是由无数小的函数组成而成的,并且函数是可以复用的,这样极大地提升开发软件的效率
库函数
我们先了解计算机语言的初步发展
库函数是我们学会了就可以直接使用的函数,库函数可以实现一些常见的功能,这在一定程度上提升了运行效率和代码质量
库函数是根据功能划分的,在不同的头文件中进行声明,所以要尽量 包含头文件
库函数的使用方法
库函数很多,我们很难全部记住,这时就要向大家介绍两个学习库函数的工具
C/C++官⽅的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://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修饰全局变量十分相似,也是改变了函数的外部链接属性