函数
库函数
什么是库函数
不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现如printf、scanf、sqrt等等,这些函数被称为库函数
库函数的实现是包含在对应的头文件里的,所以在使用库函数的时候需要包含相应的头文件,比如:使用printf函数,需要包含头文件<stdio.h>;使用sqrt函数,需要包含<math.h>头文件。
自定义函数
自定义函数包含三部分:1.函数类型 2.函数名 3.函数参数 4.函数主体 5.return语句。
int sum(int a,int b)//函数类型 函数名 函数参数
{
int ret=a+b; //函数主体
return ret; //return语句
}
函数的类型
有返回值的情况
函数的类型其实就是函数返回的值的类型,它可以是c语言中的任何数据类型,包括:基本类型(int,double,float,char)和复合类型(结构体,联合体等)
比如:
int sum(int a,int b)
{
return a+b;
}
这个函数的类型是int,它的返回值类型是也是int。
函数的类型和返回值类型不一致
当函数的类型和函数返回值的类型不一致时,系统会自动将返回的值隐式的转换成函数的类型。
sum是用来计算两数之和的一个函数,它的类型是double,但是返回值的类型是int,我们运行一下
很显然,当返回值ret和函数的类型不一致时,return会隐式的把返回值类型转换成函数的类型。
(如果不会隐式转换,那么在主函数的输出语句中将输出ret的值,但是我们实际运行后发现,它输出的值是0,这也证明了我前面所说的)
无返回值的情况
当函数不需要返回值的时候,函数的类型就是void,这表示该函数不需要返回任何值。
比如:
void prin_tf(int arr[],int len)
{
for(int i=0;i<len;i++)
{
printf("%d",arr[i]);
}
}
这个函数就不需要返回值,所以它是void类型,它的作用就是打印数组arr中存放的值。
当函数为无返回值的情况时,就不需要写return语句了,如果要写,可以写成如下形式:
void prin_tf(int arr[],int len)
{
...省略
return;
}
函数的参数
参数的分类
函数的参数分为实参与形参。
形参:在创建函数时在函数名后面的参数。
int sum(int a,int b);//这里的a和b就是形参
实参:在调用函数时,跟在函数名后面的参数。
int sum(int a,int b);
int main()
{
int a=1,b=1;
printf("%d",sum(a,b));//这里的a,b就是实参
}
那么实参和形参之间有什么关系呢?我们通过调试来观察。
//这是一段调用sum函数求和的代码
int sum(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);
//调⽤加法函数,完成a和b的相加
//求和的结果放在r中
int r = sum(a, b);
//输出
printf("%d\n", r);
return 0;
}
在vs里调试观察实参与形参的值和地址。
我们不难发现,实参与形参的值是相等的,但是地址不等,所以我们可以理解为:
形参是实参的一份临时拷贝。
数组做参数
在使⽤函数解决问题的时候,难免会将数组作为参数传递给函数,在函数内部对数组进⾏操作。
比如要将数组中的值全部赋为-1;
void set_arr(int arr[],int len)
{
for(int i=0;i<len;i++)
{
arr[i]=-1;
}
}
int mian()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
set_arr(arr,10);
for(int i=0;i<10;i++)
{
printf("%d",arr[i]);
}
return 0;
}
因为数组作为参数实际上传递的是地址,所以函数不需要返回值,函数的类型是void。
在数组作为参数时,要注意一下几点:
- 函数的形参与实参个数、类型要匹配。
- 函数的实参是数组,形参也可以写成数组的形式。
- 形参如果是一维数组,数组的长度可以省略。
- 形参如果是二维数组,数组的行数可以省略,但是列不能省略。
- 数组传参,形参不会创建新的数组的。
- 形参与实参的数组其实是同一个。
return语句
在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式
的结果。
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
• return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
• return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
函数的调用
嵌套调用
嵌套调用就函数之间相互调用。比如:
#include<stdio.h>
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[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day = days[m];
if (is_leap_year(y) && m == 2)
day += 1;
return day;
}
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函数里调用get_days_of_month函数得到月份的天数,在get_days_of_month函数里调用is_leap_year函数判断是否是闰年。
但是需要注意一点:函数是不能嵌套定义的。
链式调用
所谓的链式调用就将一个函数的返回值作为另一个函数的参数,想条链子一样将函数串起来。
比如:
int main()
{
printf("%d",printf("%d",printf("43")));
return 0;
}
这段代码的输出结果你知道是什么吗?
:43 2 1
printf函数返回的是打印在屏幕上的字符的个数,第一个printf输出43,返回一个2,第二个printf输出一个2,返回一个1,那最后一个printf输出的就是1了;
函数的定义和声明
单个文件
假设我们要写一个函数判断是否是闰年:
#include <stido.h>
//判断⼀年是不是闰年
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);
int r = is_leap_year(y);//这里是函数的调用
if(r == 1)
printf("闰年\n");
else
printf("⾮闰年\n");
return 0;
}
此时,程序是能正常运行的,但是当我们把函数的定义放在主函数后面,或者说放在函数调用的后面:
#include <stido.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语言编译器是从第一行往后扫描的,当遇到函数调用时,并没有发现之前有函数的定义,于是发出这个警告,那么我们该如何解决这个问题呢?
很简单,只需要在前面对函数进行声明一下就好了:
#include <stido.h>
int is_leap_year(int y);//函数声明
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;
}
此时代码就不会报警告了。
多个文件
一般企业中,我们不会把代码全放在一个文件下进行编写,往往会
根据程序的功能,将代码拆分放在多个⽂件中。
把函数的声明放在.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;
}
static和extern
static:是静态的意思。
extern:是用来声明外部符号的。
当用static修饰局部变量
代码1
#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;
}
代码2:
//代码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;
}
对比代码1和代码2,我们发现
代码1输出了5次1,而代码2输出的是“1 2 3 4 5”
static修饰局部变量改变了变量的生命周期,其本质是改变了变量的存储类型。原本局部变量是存储在栈区的,现在存储在了静态区,静态区的变量和全局变量一样,生命周期是整个程序,程序什么时候结束,变量什么时候消亡。
当用static修饰全局变量时:
static会将全局变量的外部链接属性改为内部链接属性,从而该全局变量只能在本文件下使用。