1.函数概念
数学中我们其实就⻅过函数的概念,⽐如:⼀次函数 y=kx+b ,k和b都是常数,给⼀个任意的x,就得到⼀个y值。
其实在C语⾔也引⼊函数(function)的概念,有些翻译为:⼦程序,⼦程序这种翻译更加准确⼀些。**C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码。**这段代码是有特殊的写法和调⽤⽅法的。C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的,也可以说:⼀个⼤的计算任务可以分解成若⼲个较⼩的函数(对应较⼩的任务)完成。同时⼀个函数如果能完成某项特定任务的话,这个函数也是可以复⽤的,提升了开发软件的效率。
在C语⾔中我们⼀般会⻅到两类函数:
• 库函数
• ⾃定义函数
2.库函数
2.1标准库和头文件
C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSI C规定了⼀些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。
我们前⾯内容中学到的 printf 、 scanf 都是库函数,库函数的也是函数,不过这些函数已经是现成的,我们只要学会就能直接使⽤了。有了库函数,⼀些常⻅的功能就不需要程序员⾃⼰实现了,⼀定程度提升了效率;同时库函数的质量和执⾏效率上都更有保证。
各种编译器的标准库中提供了⼀系列的库函数,这些库函数根据功能的划分,都在不同的头⽂件中进⾏了声明。
库函数相关头⽂件:[link](https://zh.cppreference.com/w/c/header
有数学相关的,有字符串相关的,有⽇期相关的等,每⼀个头⽂件中都包含了,相关的函数和类型等
信息,库函数的学习不⽤着急⼀次性全部学会,慢慢学习,各个击破就行
3.自定义函数
相对于库函数,自定义函数更为重要
3.1语法形式
ret_type fun_name(形式参数)
{
}
- ret_type是函数返回的类型
- fun_name是函数的名字,最好取得有意义,方便阅读
- ()内是形式参数
- {}是函数的主体
函数就是返回计算结果
3.2实例
完成加法函数
int add(int x, int y)
{
int z = x + y;
return z;//或直接return x+y
}
int main()
{
int i = 0;
int j = 0;
scanf("%d %d", &i, &j);
int q = add(i, j);
printf("%d", q);
return 0;
}
函数的参数部分需要交代清楚:参数个数,参数类型,参数名称
4.形参和实参
通俗得说形式参数就是上述x、y,实参就是上述输入的2、3
形参是实参的一份临时拷贝
5.return语句
函数中return语句的注意事项
- return语句后可以是一个数值,也可以是一个表达式,如果是表达式,先执行表达式
- return后什么也没有适合void的类型
- return返回的值和函数返回类型不一样,则强制类型转换
- return语句执行后,函数返回,后面代码不再执行
- 如果函数存在if等分支语句,则每种情况都需要return
6.数组做函数参数
在使用函数解决问题时,难免要用数组作为参数在函数内部对数组进行操作
例如:写一个函数将一个整型数组的内容全部置为-1,在写一个函数打印这个数组
void set_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
arr[i] = -1;
}
return;
}
void print_arr(int arr[], int sz)
{
int j = 0;
for (j = 0; j < sz; j++)
{
printf("%d", arr[j]);
}
return;
}
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;
}在这里插入代码片
7.嵌套调用和链式访问
7.1嵌套调用
嵌套调用就是函数间相互调用
例如:假设我们计算某年某月有多少天,可以设计两个函数
- is_leap_year():判断是否是闰年?
- get_days_of_month():调用is_leap_year判断是否是闰年后,再根据月计算天数
int is_leap_year(int year,int mon)
{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
return 1;
else
return 0;
}
void get_days_of_month(int year,int mon)
{
int arr[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int i = arr[mon];
if (is_leap_year(year,mon) && mon == 2)
i++;
printf("这月有%d天\n", i);
return;
}
int main()
{
int year = 0;
int mon = 0;
printf("请输入年份和月份:");
while (scanf("%d %d", &year, &mon))
{
is_leap_year(year,mon);
get_days_of_month(year,mon);
}
return 0;
}
main函数调用printf、scanf等函数
get_days_of_month调用了is_leap_year函数
7.2链式访问
指一个函数的返回值作为另一个函数的参数
例如:
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
由于printf函数打印返回的是打印字符的个数,所以出现4321
8.函数的声明和定义
8.1单个文件
在一个文件中,在函数调用前要声明函数,或者将函数定义放在调用前面
因为函数的定义也是一种声明
8.2多个文件
一般将函数的声明放在头文件中,将函数的实现放在源文件中
add.h
int add(int x,int y);
add.c
int add(int x,int y)
{
return x+y;
}
8.3static和extern
static和extern都是C语言中的关键字
static是静态的意思,extern是用来声明外部符号的
先提提作用域和生命周期
作用域(scope)是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是有效(可⽤)的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。
- 局部变量的作⽤域是变量所在的局部范围。
- 全局变量的作⽤域是整个⼯程。
生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。 - 局部变量的⽣命周期是:进⼊作⽤域⽣命周期开始,出作⽤域⽣命周期结束。
- 全局变量的⽣命周期是:整个程序的⽣命周期。
8.3.1static修饰局部变量
显然第一张图test函数的局部变量i是每次进入函数开始创建(生命周期开始)并赋值为1,出函数时变量销毁(生命周期结束)
而第二张图i的值具有累加效果,是由于static修饰局部变量改变生命周期,即改变变量的储存类型,本来一个变量是储存在栈区,后改为静态区,静态区的变量和全局变量一样,作用域是整个程序
使用建议:未来一个变量出了函数后,我们还想保留值,等下次进入函数时继续使用,static可胜任
8.3.2static修饰全局变量
代码1正常,代码2编译错误
extern是用来声明外部符号的,如果一个全局符号在A文件中被定义,想在B文件中使用,就可用extern声明
由于全局变量自带外部链接属性,但如果全局变量被static修饰,则被改为内部链接属性,其他源文件即使声明也无法使用
8.3.3static修饰函数
static修饰函数和修饰全局变量一样,函数在整个工程都可以使用,但被static修饰则只能在本文件中使用