目录:
1、函数的概念
2、库函数
3、自定义函数
4、形参和实参
5、return语句
6、数组做函数参数
7、嵌套调用和链式访问
8、函数的声明和定义
9、static和extern
一、函数的概念
数学中我们其实就见过函数的概念,比如:一次函数y=kx+b,k和b都是常数,给一个任意的x,就可以得到一个y值。其实在c语言也引入函数的概念,我么觉得称他为子程序比更加准确一些。c语言中的函数就是一个了完成某项特定的任务的一小段代码。这段代码是有特殊的写法和调用的方法的。
c语言的程序就是由无数个小的函数组成的!
函数可以复用
在c语言中我们一般会见到两种函数:
1、库函数
2、自定义函数
二、库函数
标准库和头文件:
库函数的意思就是:我们能直接学会之后拿来使用的,不用自己去创建和开发的函数,例如printf和scanf就是库函数
各种编译器的标准库中提供了⼀系列的库函数,这些库函数根据功能的划分,都在不同的头⽂件中进⾏了声明
库函数相关头⽂件:https://zh.cppreference.com/w/c/header
库函数的使用方法:
库函数的学习和查看⼯具很多,
⽐如:C/C++官⽅的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:C library - C++ Reference
三、自定义函数
下面我们举一个例子来更好的理解函数:
ret_type 是用来表示函数计算结果的类型,有时候返回类型可以是void,表示什么都不返回。
函数的参数就相当于,工厂中送进去的原材料,函数的参数也可以是 void ,明确表示函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
函数的举例:
举一个例子:
写一个加法函数完成两个整形变量的加法操作
如:
#include<stdio.h>
int Add(int a ,int b )//函数的定义,创建函数
{
int c = a + b;
return c;//也可以简化为return a + b;
}
int main()
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
int z = Add(x, y);//函数的调用,就是(使用函数)
printf("%d",z);
return 0;
}
四、形参和实参
在函数使用的过程中,把函数的参数分为,实参和形参。
例如下列代码:
实参:
实际参数就是真实传递给函数的参数
形参:
形参就是只是形式上存在的不会向内存申请空间,不会真实存在的,所以叫形式参数形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。
1、实参和形参是完全不同的内存空间
2、形参是实参的一份临时拷贝!
3、形参的修改不会影响实参
五、return语句:
1、return后边可以是一个值也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式的结果。
比如:
return x+y;
2、return后边也可以什么都没有,直接写return;这种写法适合函数返回类型是void的情况
比如:
当test(-1)函数调用是-1的时候,函数直接退出了,return可以直接提前结束函数
3、return返回的值和函数类型不一致,系统会自动将返回的值隐式转换为函数的类型
比如:
4、return语句执行后,函数就彻底返回,后边的代码不在执行
就比如第二点的那个代码
5、如果函数中存在if等分支语句,则要保证每种情况下都有return返回,否则会出现编译错误
int test(int n)
{
if(n%2==1)
{
return 1;
}
}
正确代码:
六、数组做函数的参数
1、函数的形式参数要和函数的实参个数匹配
如下:
2、 函数的实参是数组,形参也是可以写成数组形式的。(最好是实参数数组,形参也写成数组)
3、形参如果是一维数组,数组大小可以省略不写。
4、如果形参是二维数组,行可以省略不写,但是列不能省略不(最好是都不省略)
5、数组传参,形参是不会创建新的数组的。数组传参的时候,形参的数组和实参的数组是同一个数组(因为地址相同)。
6、形参操作的数组和实参的数组是同一个数组。
七、嵌套调用和链式访问
7.1嵌套调用:
代码如下:
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[]= {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 函数调用 scanf 、 printf 、 get_days_of_month
• get_days_of_month 函数调用 is_leap_year
未来的稍微大一些代码都是函数之间的嵌套调用,但是函数是不能嵌套定义的
7.2链式访问:
所谓链式访问就是讲一个函数的返回值作为另外一个函数的参数,项链条一样讲函数串起来就是函数的链式访问
比如:
#include<stdio.h>
int main()
{
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
printf函数返回的是打印在屏幕上字符的个数。
上面的例子中,我们第一个printf打印的时第二个printf的返回值,第二个打印的时第三个printf的返回值。
整体顺序就是,首先打印第三个printf函数 43 ,之后再打印第二个printf 2,最后打印第一个printf 1
所以屏幕上最终打印的时4321如下图所示:
八、函数的声明和定义:
8.1 单个文件:
我们一般情况写代码的时候是先定义再使用:
如果我们按照正常的逻辑先定义后调用就没有问题
但是如果有些时候我们不得不把函数的定义放在后面的话那么
vs就会提示以下错误:
这是因为c语言对代码进行编译的时候,是从头往下执行的,当他遇到函数调用的时候就会往上面去找有没有被调用函数的定义,如果没有发现就会出现上面的警告。
当我们把函数的定义放在main函数的下面的时候,就需要在main函数上面进行函数的声明,让编译器知道我们有这个函数。
什么时函数的声明呢?
函数的声明就是给编译器说我们有这个函数,这个函数的返回类型是什么,参数类型是什么,都要给编译器交代清楚
比如:
int is_leap_year(int y);就是函数的声明,int is_leap_year就是函数的返回类型,int y 就是参数类型
所以函数在只用之前要么先定义再使用,要么后面定义前面先声明
8.2 多个文件:
一般在企业中我们写代码的时候,代码可能比较多,不会将所有的代码都放在一个文件中;我们往往会根据程序的功能,将代码拆分在多个文件中。
一般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在源文件(.c)文件中。
比如:
add.h文件中放的是函数的声明,add.c文件中放的是函数的定义,test.c文件中放的是函数的调用
这样有什么好处? 比如:
1、逻辑清晰,更加直观
2、方便多人协同编写
3、可以适当的隐藏代码
九、static和extern:
这两个都是c语言中的关键字
static是静态的意思,可以用来:
修饰局部变量
修饰全局变脸
修饰函数
extern是用来声明外部符号的
在讲解static和extern之前先讲一下:作用域和生命周期
作用域:是程序设计概念,通常来说,一段程序代码中所用到的名字并不是总有效(可用)的,而限定这个名字的可用性的代码范围就是这个名字的作用域
1、局部变量的作用域是变量所在的局部范围
举个例子:
#include<stdio.h>
int main()
{
int i = 0;
for(int j = 0; j<10;j++)
{
printf("hehe\n");
}
return 0;
}
j就是局部变量,它的作用域只有在这个for循环的范围内
2、全局变量的作用域是整个工程(项目)
例如:
#include<stdio.h>
int a = 0;
int main()
{
return 0;
}
a就是全局变量,他的作用域就是整个工程。
生命周期:指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的一个时间段。
1、局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域生命周期结束
2、全局变量的生命周期是:整个程序的生命周期,整个程序的生命周期又是main函数的生命周期。
9.1 static修饰局部变量:
左边的int i = 0;
- 变量性质:
test
函数内的int i = 0;
定义的i
是普通局部变量,存储在栈区。栈区特点是函数调用时为局部变量分配空间,函数执行结束空间自动释放。 - 生命周期:仅在
test
函数每次调用时创建并初始化,函数执行完,i
的生命周期结束。下次再调用test
,又重新初始化i
为 0。 - 运行表现:若在
main
函数中多次调用test
函数,每次test
函数里的i
都是独立的,每次输出都是 1 。
右边被static修饰后的int i= 0;
- 变量性质:
test
函数内static int i = 0;
定义的i
是静态局部变量,存储在静态区。静态区在程序运行期间一直存在,变量一旦初始化,后续不会再重新初始化。 - 生命周期:在程序运行开始时初始化,直到程序结束才销毁。即使多次调用
test
函数,i
只会在第一次调用test
时初始化,后续调用使用上次修改后的值。 - 运行表现:在
main
函数中多次调用test
函数,i
会累加,依次输出 1、2、3、4、5 。
9.2 static修饰全局变量:
结论:
一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。本质原因是全局变量默认是具有外部链接属性的,在外部的文件中想使用,只要适当的声明就可以使用;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用,其他源文件,即使声明了,也是无法正常使用的,如上图
使用建议:
如果一个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以使用static修饰
比如:
9.3 static修饰函数:
其实static修饰函数和static修饰全局变量是一样的,一个函数在整个工程都可以使用,被static修饰后,只能在本文件内部使用,其他文件无法正常的链接使用了。本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个工程中只要适当的声明就可以被使用。但是被 static 修饰后变成了内部链接属性,使得函数只能在自己所在源文件内部使用。
使用建议:一个函数只想在所在的源文件内部使用,不想被其他源文件使用,就可以使用 static 修饰。
比如:
static int add(int x, int y)
{
return x + y;
}
int main()
{
int a = 0, b = 0;
scanf("%d %d",&a,&b);
int c = add(a, b);
printf("%d",c);
return 0;
}
9.4 extern外部符号的声明
用法如下图: