一. 函数的概念
在C语言也引入了函数的概念,有些翻译为:子程序,子程序这种翻译更加准确⼀些。
C语言中的函数就是⼀个完成某项特定的任务的一小段代码。这段代码是有特殊的写法和调用方法的。
在C语言中我们⼀般会见到两类函数:
• 库函数
• 自定义函数
二. 库函数
库函数是将函数封装入库,供用户使用的一种方式。这些函数通常被编译成二进制形式,存放在库文件中,但C语言并不提供库函数,然而C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库。
库函数相关头文件:https://zh.cppreference.com/w/c/header
1.库函数的使用方法
我们可以举个例子
int sqrt (int x);
//sqrt 是函数名
//x 是函数的参数,表示调用sqrt函数需要传递⼀个int类型的值
//int 是返回值类型 - 表示函数计算的结果是int类型的值
功能: 计算平方根和返回平方根
关于库函数的学习和查看我们可以去用这些工具
C/C++官方的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
2.头文件包含
库函数是在标准库中对应的头文件中声明的,所以库函数的使用,务必包含对应的头文件,不包含可能会出现⼀些问题的。
例如:在使用sqrt时需要包含#include<math.h>文件。
cs
#include<stdio.h>
#include<math.h>
int main()
{
int a = 16;
int c = sqrt(a);
printf("%d", c);
}
三.自定义函数
1.语法形式
ret_type fun_name(形式参数)
{
}
ret_type 是函数返回类型
fun_name 是函数名
括号中放的是形式参数
{}括起来的是函数体
2.代码举例
cs
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int r = Add(a, b);
printf("%d\n", r);
return 0;
}
四. 形参和实参
在函数使用的过程中,把函数的参数分为,实参和形参。
1.那么什么是形参,什么是实参呢?
- 实参:实参就是真实传递给函数的参数,既实际参数。
- 形参:是形式上存在的,不会向内存申请空间,不会真实存在的参数,既形式参数。
(形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。)
从上面的代码来看a和b为实参,x和y为形参。
2.实参和形参的关系
我们通过上面的例子来解释
在调试下它们的地址是不一样的,但x和y的确得到了a和b的值,所以我们可以理解为形参是实参的⼀份临时拷贝。
五. return 语句
- return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执行表达式,再返回表达式的结果。
- return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
- return返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。
- return语句执行后,函数就彻底返回,后边的代码不再执行。
- 如果函数中存在if等分支的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
六. 数组做函数参数
在这里我们需要记住几个重要的知识点
- 函数的形式参数要和函数的实参个数匹配
- 函数的实参是数组,形参也是可以写成数组形式的
- 形参如果是⼀维数组,数组大小可以省略不写
- 形参如果是⼆维数组,行可以省略,但是列不能省略
- 数组传参,形参是不会创建新的数组的
- 形参操作的数组和实参的数组是同⼀个数组
这样的话,我们可以利用数组做函数参数,建立两个函数,将数组的值全赋值为0,并打印数组。
cs
void set_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
arr[i] = 0;
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
set_arr(arr, sz);
print_arr(arr, sz);
return 0;
}
七.嵌套调用和链式访问
1.嵌套调用
简单来说在一个函数内部调用另一个函数
cs
#include<stdio.h>
void another(int z)
{
printf("%d\n", z);
}
void other(int x)
{
int y=3;
printf("%d\n", x);
another(y);
}
int main()
{
int a=5;
other(a);
}
2. 链式访问
所谓链式访问就是将一个函数的返回值作为另外一个函数的参数,像链条一样将函数串起来就是函数的链式访问。
cs
#include <stdio.h>
int main()
{
int len = strlen("abcdef");
printf("%d\n", len);
return 0;
}
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));//链式访问
return 0;
}
对于这两个代码,其实就是第二个代码直接将strlen的返回值直接作为printf函数的参数,这就是所谓的链式访问 。
八.函数的声明和定义
1.单个文件
cs
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int r=Add(a, b);
printf("%d\n", r);
return 0;
}
如果我们将Add函数调到下面会怎样
cs
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int r=Add(a, b);
printf("%d\n", r);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
在这里就会报一个警报,说Add为定义,所以我们要定义Add函数
cs
#include<stdio.h>
int Add(int x, int y);
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int r=Add(a, b);
printf("%d\n", r);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
cs
int Add(int x, int y);
我们只需在函数之前定义一个Add函数即可。
2.多个文件
我们只需要创建三个文件
并把函数的声明放在add.h文件里,将函数的定义放在add.c文件里,并在test.c文件里定义#include"add.h"即可
九.static 和 extern
static 和 extern 都是C语言中的关键字。
static 是 静态的 的意思,可以用来
• 修饰局部变量
• 修饰全局变量
• 修饰函数
extern 是用来声明外部符号的。
static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本 来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变 量和全局变量是⼀样的,生命周期就和程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才 回收。但是作用域不变的。
extern 是用来声明外部符号的,如果⼀个全局的符号在A文件中定义的,在B文件中想使用,就可以使用extern 进进声明,然后使用。
十.作用域和生命周期
作用域(scope)是程序设计概念,通常来说,⼀段程序代码中所用到的名字并不总是有效(可用) 的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
局部变量的作用域是变量所在的局部范围。
全局变量的作用域是整个⼯程(项目)。
生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。
- 局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域生命周期结束。 2. 全局变量的生命周期是:整个程序的生命周期。