萌新的学习笔记,写错了恳请斧正。
目录
函数的概念
库函数
标准库
自定义函数
自定义函数的语法格式
形参与实参
实参
形参
形参与实参的关系
return语句
数组作为函数的参数
嵌套调用与链式访问
嵌套调用
链式访问
函数的声明与定义
单文件
多文件
static与extern
static
static修饰局部变量
static修饰全局变量
static修饰函数
extern
完成特定功能的小段代码(子程序)。将一个大的程序拆解为实现不同功能的函数,有利于提高开发效率,也方便部分功能的反复使用。
一般函数分两类:
C语言规定了一些常用的函数(比如printf、scanf)的标准,即标准库。然后各个编译器厂商按照标准给出这些函数的实现,即库函数。这些函数不需要程序员自己实现,只要会用即可。这大大提高了开发效率。
这些库函数根据功能的划分,在不同的头文件中进行了声明。如果想要使用,只要包含头文件即可。
C语言自带头文件:头文件
自定义函数很重要,它赋予了代码更多的可能性
returntype func_name(形参)
{
...
}
比如说,我们可以写一个加法函数并在主函数中使用:
#include
int Add(int x, int y)
{
int sum = x + y;
return sum;
}
int main()
{
int a = 0, b = 0;
scanf("%d%d", &a, &b);
printf("%d", Add(a, b));
return 0;
}
其中,return是确定函数返回值的关键字,下面会讲
上方加法函数代码也可简化为:
int Add(int x, int y)
{
return x + y;
}
在函数使用时我们把函数的参数分为形参与实参
上方示例中主函数中向Add函数传递的a、b即为实参,是真实传递给函数的参数
上方代码中定义函数时,函数名后面括号里的x与y即为形式参数
形式参数只在形式上存在,不会向内存申请空间
只有在运行时函数被调用时,为了存放实参传递过来的值,才会向内存申请一块临时的空间——这个过程被称为形参的实例化
在程序运行到调用函数时,就会在内存开辟一块空间,并复制一份实参的值在函数中使用
所以说形参是实参的一份临时拷贝
如果将数组作为参数传递给函数,那么情况会有所不同:
每一个函数组件施行某个特定功能,可以看做一个零件
而函数中也可以调用函数,甚至调用自身,以达成复杂目的
但是函数可以嵌套调用但不能嵌套定义
而且注意不要互相调用造成死循环
链式访问就是将一个函数的返回值作为另一个函数的参数
比如说上方举例的Add函数的返回值就作为了printf函数的参数
这里就有一道有趣的题:
printf("%d", printf("%d", printf("%d", 123)));
上方代码的输出结果是什么?
是:12331
为什么?
先执行了最里层的printf打印123,而printf的返回值是打印字符的长度,则中间的printf接收到123的长度3并打印出来,最终外层的printf接受3的长度1并打印出来
类似的,下方代码将输出123 4 2 (3、4、2后有空格)
printf("%d \n", printf("%d ", printf("%d ", 123)));
一般我们直接就把函数在调用函数的函数前面写出来用了
如上方举例的Add函数的定义直接写在调用其的主函数(main)前
如果将函数的定义放在调用它的函数的后面,就会报警告了,如下方示例:
#include
int main()
{
int a = 0, b = 0;
scanf("%d%d", &a, &b);
printf("%d", Add(a, b));
return 0;
}
int Add(int x, int y)
{
int sum = x + y;
return sum;
}
编译器在编译时从上往下扫描结果突然遇到没见过的函数,不知道这个函数的返回类型,就会警告
像解决这个问题,除了把函数挪到前面,也可以直接在前面对函数进行声明
函数的声明需要包括函数名、返回类型、和参数
比如说,把上面的代码改成这样就能正常运行了:
#include
int Add(int x, int y);
int main()
{
int a = 0, b = 0;
scanf("%d%d", &a, &b);
printf("%d", Add(a, b));
return 0;
}
int Add(int x, int y)
{
int sum = x + y;
return sum;
}
由于函数声明只需要告诉编译器参数的类型,不需要定义一个形参,所以也可以直接写成这样:
int Add(int, int);
综上,函数必须要先声明再使用(函数的定义本身也是一种声明,不能重定义但是可以重声明)
在企业中,程序员实际编写代码时,不会把所有代码放在一个文件中,而是将代码按照功能拆分在多个文件中。这即方便了程序员之间的协作、增强了代码的泛用性,也能起到保密的作用(需要结合其他内容实现)
一般来说函数的声明与类型的声明会放在头文件(.h)中,而函数的实现放在源文件(.c)中
比如我们把上方Add的实例代码拆分:
add.h
//加法声明
int Add(int, int);
add.c
//加法定义
int Add(int x, int y)
{
return x + y;
}
test.h
#include
#include "add.h"
int main()
{
int a = 0, b = 0;
scanf("%d%d", &a, &b);
printf("%d", Add(a, b));
return 0;
}
这样我们依旧能正常运行编译出来的可执行程序
注意这样多文件协作需要在test这里包含我们自己写的头文件add.h
包含自己的头文件应该用双引号而不是尖括号!!!
注意,头文件可以包含头文件,如果我们在add.h前面加上包含stdio.h
那我们就不要在test.c中包含stdio.h了,直接包含add.h就同时包含了stdio.h
static与extern都是C语言中的关键字
static是静态的意思,可以用来修饰局部变量、全局变量、函数
static修饰局部变量改变了变量的生命周期,本质上改变了变量的存储类型。将本身存储在栈区的局部变量改为存储在静态区。这样,被修饰的变量就与全局变量的生命周期一样了,直到程序结束才会被销毁内存回收。但是static不会改变变量的作用域,这点与全局变量不同。
如下方代码的运行结果为1 2 3 4 5 ,而不是1 1 1 1 1
#include
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;
}
如果一个变量出了函数我们还想保留其值等下一次进入函数使用,就可以用static修饰
全局变量本身可以在整个工程中被调用(需要extern声明),但是被static修饰后就只能在本文件中使用了,如果被其他文件声明只会报链接错误
与static修饰全局变量相同,被static修饰的函数将不能在其他文件中调用。
extern用来声明外部符号,如果在a文件定义了一个全局的符号,想在b文件使用,就要用extern声明,然后再使用,如:
a.c
int val = 114514;
test.c
#include
extern int val;
int main()
{
printf("%d\n", val);
return 0;
}