C语言中的函数就是一个完成某项特定的任务的一段代码
1.库函数
1.1标准库和头文件
C需要标准中规定了C语言的各种语法,C语言并不提供库函数,C语言的国际标准ANSIC规定了一些常用的函数标准,被称为标准库,不同的编译器厂商根据标准提供了一系列函数的实现,这些函数被称为库函数。
这些库函数根据功能的划分,在不同的头文件中进行了声明
库函数相关头文件参考网站:https://zh.cppreference.com/w/c/header
1.2库函数的使用方法
C/C++官方网站:https://zh.cppreference.com/w/c/header
cplusplus. com:https://legacy.cplusplus.com/reference/clibrary/
1.2.1举例:sqrt-计算平方根
double sqrt (double x);
解释:
sqrt是函数名
x是函数的参数,表示调用sqrt函数需要传递一个double类型的值
double 是函数返回值类型
- 注:库函数是在标准库中对应的头文件声明的,库函数的使用,需要包含头文件
1.2.2sqrt实践
sqrt函数需要包含头文件math.h
#include<math.h>
int main()
{
double r = sqrt(16.0);
printf("%lf\n", r);
return 0;
}
运行结果
1.2.3库函数文档的一般格式
- 函数原型
- 函数功能介绍
- 参数和返回类型
- 代码举例
- 代码输出
- 相关知识链接
2.自定义函数
2.1函数语法
ret_type fun_name(形式参数)
{
}
解释
函数一般会输入一些值,经过函数内计算,得出结果
- ret_type是函数返回类型,可以是void, 表示什么都不返回
- fun_name是函数名,为了方便调用,按需所取
- 括号放的是形式参数,可以是void, 表示没有参数,有参数时,需交代清楚类型和名字和个数
- {}括起来的是函数体,是完成计算的过程
2.2函数举例
加法函数,完成两个整型变量的加法
根据要求,函数取名Add, 需接受两个整型参数,返回整型结果
int Add(int x,int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int c=Add(a,b);
printf("%d\n", c);
return 0;
}
优化代码块
int Add(int x, int y)
{
return (x + y);
}
3.形参和实参
int Add(int x,int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
**int c=Add(a,b);
printf("%d\n", c);
return 0;
}
3.1实参与形参定义
实参,在上面代码中,前半部分是函数定义,后面部分调用函数,其传递给函数的参数a, b称为实际参数,简称实参(实际传递给函数的参数)
3. 2实参和形参的关系
实参和形参是有各自独立内存空间的
可以看到,x和y确实得到了a和b的值,但两者地址均不同,所以得出形参是实参的一份临时拷贝
4.return语句
注意事项
- return后面可以是数值,也可以是表达式,如果是表达式,先执行表达式,再返回表达式结果
- return后面也可以什么都不写,表示返回类型是void
- return语句执行后,函数彻底返回,后面的代码不再执行
- 如果有分支语句,则保证每个情况都有return返回,否则编译错误
5.数组做函数参数
写一个函数对一个整型数组内容,全部置1,在函数内部实现
#include <stdio.h>
void set_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
arr[i] = -1;
}
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[] = {1,2,3,4,5,6,7,8,9,10};
set_arr();//设置数组内容为-1
print_arr();//打印数组内容
return 0;
}
解释
数组作为参数传递给set_arr和print_arr函数
- 函数的形式参数要和实参个数匹配
- 函数的实参是数组,形参也可以写成数组
- 形参如果是一维数组,数组大小可以不写
- 形参如果是二维数组,行可以省略,列不能省略
- 数组传参,形参是不会创建新的数组
- 形参操作的数组和实参的数组是同一个数组
6.嵌套调用和链式访问
6.1嵌套调用
嵌套调用是函数之间的互相调用
计算某年某月有多少天
- is_leap_year(): 确认是否是闰年
- get_days_of_month()计算这个月的天数
int is_leap_year(int y)
{
if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0))
return 1;
else
return 0;
}
int get_days_of_month(int y, int m)
{
int days[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int d = days[m];
if (is_leap_year(y) && m == 2)
{
d += 1;
}
return d;
}
int main()
{
int y = 0;
int m = 0;
scanf("%d %d", &y, &m);
int d = get_days_of_month(y, m);
printf("%d\n", d);
return 0;
}
函数调用的体现
- main函数调用了scanf,printf,get_days_of_month
- get_days_of_month调用is_leap_year
- 注:函数可以嵌套调用,不能嵌套定义
6.2链式访问
将一个函数的返回值作为另一个函数的参数
/*int main()
{
size_t len = strlen("abc");
printf("%zd\n", len);
return 0;
}
*/
int main()
{
printf("%zd\n",strlen("abc"));
return 0;
}
有趣的printf函数
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//4321
return 0;
}
printf的返回值
int printf ( const char * format, ... );
解释:
- printf函数返回的是打印在屏幕上的字符的个数
- 第一个printf打印的是第二个函数的返回值,第二个printf大一的是第三个printf的返回值
- 第三个printf打印43,在屏幕上打印2个字符,再返回2
- 第二个printf打印2,打印1个字符,返回1
- 第一个printf打印1
- 最终结果:4321
7.函数的声明和定义
7.1单个文件
判断闰年
int is_leap_year(int y)
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
{
return 1;
}
else
return 0;
}
int main()
{
int y = 0;;
scanf("%d", &y);
if (is_leap_year(y))
{
printf("%d是闰年\n", y);
}
else
{
printf("%d不是闰年\n", y);
}
return 0;
}
上面部分的是函数定义,下面行是调用,定义在调用之前
如果定义在函数调用之后
#include <stdio.h>
int main()
{
int y = 0;
scanf("%d", &y);
int r = is_leap_year(y);
if(r == 1)
printf("闰年\n");
else
printf("⾮闰年\n");
return 0;
}
//判断⼀年是不是闰年
int is_leap_year(int y)
{
if(((y%4==0)&&(y%100!=0)) || (y%400==0))
return 1;
else
return 0;
}
解释
C语言编译器对源代码进行编译时,从第一行往下扫描,当遇到第七行is_leap_year函数调用时,并没有发现前面有is_leap_year的定义,就会报警告
所以在调用之前,需要声明一下,声明需要交代清楚函数名,函数的返回类型和函数参数
- 注:函数先声明后使用
7.2多个文件
一般情况下,
函数的声明,类型的声明放在头文件(. h)中,函数的实现放在源文件(. c)中,如下
//add.c
//函数的定义
int Add(int x, int y)
{
return x+y;
}
//add.h
//函数的声明
int Add(int x, int y);
//test.c
#include <stdio.h>
#include "add.h"
int main()
{
int a = 10;
int b = 20;
//函数调⽤
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
7.3static和extern
static和extern都是C语言中的关键字
static是静态的意思,可以用来
- 修饰局部变量
- 修饰全局变量
- 修饰函数
extern是用来声明外部符号的
7.3.1作用域和生命周期
作用域:一段代码中所用到的名字并不总是有效的,而限定这个名字的可用性的代码范围就是这个名字的作用域
生命周期:指变量的创建(申请内存)到变量的销毁(收回内存)之间的一个时间段
- 局部变量的生命周期是进入作用域生命周期开始,出作用域结束
- 全局变量的生命周期是整个程序的生命周期
7.3.2static修饰局部变量
//代码1
#include <stdio.h>
void test()
{
int i = 0;
i++;
printf("%d ", i);
}
int main()
{
int i = 0;
for(i=0; i<5; i++)
{
test();
}
return 0;
}
//代码2
#include <stdio.h>
void test()
{
//static修饰局部变量
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{
int i = 0;
for(i=0; i<5; i++)
{
test();
}
return 0;
}
解释
代码1的test函数中的局部变量i是每次进入函数先创建变量,并赋值0,然后++,在打印,出函数生命周期结束,释放内存
代码2中,i的值累加,i创建好后,出函数不会销毁,重新进入函数不会创建新变量,在上次数值累加计算
- 结论:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,局部变量应该放在栈区,被修饰后放在静态区,静态区的变量和全局变量一样,生命周期也因此变长,程序结束后才会销毁,内存才回收,作用域并没变
7.3.3static修饰全局变量
//add.c
1 int g_val = 2018;
//test.c
#include <stdio.h>
extern int g_val;
int main()
{
printf("%d\n", g_val);
return 0;
}
代码2
//add.c
1 static int g_val = 2018;
//test.c
#include <stdio.h>
extern int g_val;
int main()
{
printf("%d\n", g_val);
return 0;
}
extern用来声明外部符号,如果全局的符号在文件A中定义,B文件中想使用,就可以用extern声明
代码2会出现链接错误
- 结论:一个全局变量被static修饰,使得全局变量只能在本源文件使用,因为全局变量默认是具有外部链接属性的,外部使用时只需声明即可,在被static修饰后,变为内部链接属性,即使声明,也无法在外部正常使用
7.3.4static修饰函数代码
代码2会出现链接错误
与修饰全局变量一样,一个函数在整个工程都可以使用,被修饰后,只能在文件内部使用,因为函数默认是外部链接属性,只要声明就可以被使用,被修饰后,变成内部链接属性,无法在外部文件使用