目录
1.函数的概念
2.库函数
3.自定义函数
4.形参和实参
5.return语句
6.数组做函数的参数
7.嵌套调用和链式访问
8.函数的声明和定义
1.函数的概念
函数也叫子程序,C语言的函数就是一个完成某项特定任务的一小段代码。
C语言中的两类函数:
- 库函数
- 自定义函数
2.库函数
库函数的学习工具:
- C/C++官⽅的链接:https://zh.cppreference.com/w/c/header
- 中⽂版:https://cppreference.cn/w/
- cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
3.自定义函数
3.1函数的语法形式
ret_type fun_name(形式参数)
{
}
函数头由函数名、参数、返回值类型组成
- ret_type:表示计算结果的类型;返回类型是void,表示什么都不返回
- fun_name:函数名,尽量有意义
- 函数的参数:函数如果有参数,就需要交代清楚参数的类型和名字,以及参数个数;函数的参数也可以是void,表示函数没有参数
- { }:括起来的是函数体,完成计算过程
- return语句:函数中可能有也可能没有return语句(返回类型是void时,return语句可以不写,也可以写成 return;)
注:一个函数必须要有函数名、返回类型(/void)和函数体
3.2举例
写⼀个加法函数,完成2个整型变量的加法操作
#include<stdio.h> int Add(int x, int y) { int z = x + y; return z; } int main() { int a = 0; int b = 0; scanf("%d%d", &a, &b); int s = Add(a, b);// a,b的和,调用加法函数,完成a和b的相加 printf("%d\n", s); return 0; }add函数部分也可以简化为
int Add(int x,int y) { return x + y;//return后边可以是数值也可以是表达式,如果是表达式则先执行表达式,再返回表达式的结果 }4.形参和实参
实参:实参是真实传递给函数的参数
形参:上述例子中,在函数名Add 后的括号中写的 x 和 y 就是形参。
当没有调用Add函数时,不会创建x,y变量,只是形式上存在,不会向内存申请空间—形参是实参的临时拷贝;
只有在函数被调用的过程中,为了存放实参传来的值才向内存申请空间,这就是形参的实例化。
形参和实参的关系:实参是传递给形参的,但是形参和实参各自是独立的内存空间
int Add(int x, int y)//形参有自己独立的空间,是实参的临时拷贝 { return x + y; } int main() { int a = 0; int b = 0; scanf("%d%d", &a, &b);//实参是传递给形参的,但是形参和实参各自是独立的内存空间 int c = Add(a, b); printf("%d\n", c); return 0; }
5.return语句
(1)函数返回类型是void时,return语句可以不写,也可以写成 return;
(2)返回类型是非void时,若没有使用return语句,那么函数的返回值是未知的;
(3)return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果
(4)return语句执⾏后,函数就彻底返回,后边的代码不再执⾏
(5)return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型
int test() { return 3.14; } int main() { int r = test(); printf("%d\n", r);//3 return 0; }(6)如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误
(7)函数的返回类型如果不写,编译器会默认函数的返回类型是int
(8)小练习
判断一个年份是否是闰年
int is_leap_year(int y) { if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) return 1; else return 0; } int main() { int year = 0; scanf("%d", &year); if (is_leap_year(year) == 1) { printf("是闰年\n"); } else { printf("不是闰年\n"); } return 0; }6.数组做函数的参数
将数组作为参数传递给函数,在函数内部对数组进⾏操作
写⼀个函数将⼀个整型数组的内容,全部置为-1,再写⼀个函数打印数组的内容
void set_arr(int arr[],int sz) { int i = 0; for (i = 0;i < sz;i++) { arr[i] = -1; } } print_arr(int arr[],int sz) { int i = 0; for (i = 0;i < sz;i++) { printf("%d ", arr[i]); } } int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]); set_arr(arr,sz); print_arr(arr, sz); return 0; }数组传参:
• 函数的形式参数要和函数的实参个数匹配
• 函数的实参是数组,形参也是可以写成数组形式的
• 形参如果是⼀维数组,数组大小可以省略不写
• 形参如果是二维数组,行可以省略,但是列不能省略
•数组传参,形参是不会创建新的数组的
•形参操作的数组和实参的数组是同⼀个数组
7.嵌套调用和链式访问
(1)嵌套调用就是函数之间的互相调用
假设我们计算某年某月有多少天?如果要函数实现,可以设计2个函数
//确定是否是闰年——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[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//从0开始,月份正好和下标对应 int day = days[m]; if ((is_leap_year(y) == 1) && m == 2)//嵌套调用 day += 1;//闰年的2月有29天 return day; } int main() { int year = 0; int month = 0; scanf("%d%d", &year, &month); int d = get_days_of_month(year,month); printf("%d\n", d); return 0; }(2) 链式访问:将一个函数的返回值作为另一个函数的参数,像链条一样串起来
int main() { /*6*/ printf("%d\n", strlen("abcdef"));//链式访问 return 0; }int main() { printf("%d ", printf("%d ", printf("%d ", 46)));//46 3 2 return 0; } //printf()的返回值是字符个数 //注意有没有空格int main() { printf("%d", printf("%d", printf("%d", 46)));//4621 return 0; }8.函数的声明和定义
8.1单个文件
一般在写函数的时候会将函数的定义放在函数调用之前,如下:
橙色部分是函数的定义,绿色部分是函数的调用
这种写法没有问题,但是当把函数的定义放在函数调用之后,就会产生问题
这是因为C语⾔编译器对源代码进⾏编译的时候,从第⼀⾏往下扫描的,当遇到第7⾏的函数调⽤的时候,并没有发现前⾯有 is_leap_year的定义,就报出了上述的警告
解决方法:
只要在调用前声明一下is_leap_year这个函数就可以了,声明函数要交代清楚函数名,函数的返回类型和函数的参数。函数声明中参数只保留类型,省略掉名字也是可以的。
如上述例子中:将函数的声明 bool is_leap_year(int y); 放在函数调用之前就可以解决
总结:函数的使用一定要先声明后定义
函数的定义也是一种特殊的声明,所以如果函数定义放在调用之前也是可以的
8.2多个文件
一般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在源文件(.c)文件中。
那么该如何使用呢?只要在调用函数之前包含头文件就可以了,如下:
8.3 static和extern
先了解作用域和生命周期
(1)作用域和生命周期
作用域:⼀段程序代码中所⽤到的名字并不总是有效(可⽤) 的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域
1.局部变量的作用域是变量所在的局部范围
2.全局变量的作用域是整个工程(项目)
生命周期:指的是变量的创建(申请内存)到销毁(收回内存)之间的一个时间段
1.局部变量的生命周期是:进入作用域,变量创建,生命周期开始,出作用域生命周期结束
2.全局变量的生命周期是:整个程序的生命周期
(2)static和extern
static和extern都是C语言中的关键字
static可以:
- 修饰局部变量
- 修饰全局变量
- 修饰函数
extern是用来声明外部符号的
1 - extern
extern是用来声明外部符号的
如果一个全局的符号在A文件中定义,在B文件想使用,就可以使用extern进行声明
//声明来自外部的变量
//声明来自外部的函数
1 - static修饰局部变量
在上面代码中,n 是局部变量,当进入 test() 这个函数之后,变量n才创建,此时生命周期开始;出这个函数之后,变量销毁,生命周期结束,所以这里只会打印 11 11 11 11 11
而static修饰局部变量可以改变生命周期,如下:
static拉长了生命周期
总结:static修饰局部变量,可以拉长生命周期(相当于全局变量的生命周期),但是作用域不会改变。(生命周期的改变本质是改变了变量的存储类型,本来局部变量是存在内存的栈区的,但是被static修饰后存到了静态区。存在静态区的变量和全局变量是一样的,生命周期就和程序的生命周期一样了)
使用建议:未来⼀个局部变量出了函数后,我们还想保留值,等下次进⼊函数继续使用,就可以使用 static 修饰
2 - static修饰全局变量
全局变量默认具有外部链接属性
在上述代码中,文件 lesson_test.c 中,test变量被static修饰,则该变量在文件lesson_8_4_20.c中不能被使用;
结论:一个全局变量被static修饰,这个全局变量就只能在该文件中使用,不能在其他文件中使用
(全局变量默认具有外部链接属性,而被static修饰的全局变量,变成内部链接属性)
使用建议:如果一个全局变量,只想在所在的源文件内部使用,不想被其他文件发现,就可以用static修饰
3 - static修饰函数
结论:static修饰函数和修饰全局变量是一样的,一个函数被static修饰后变成了内部链接属性,使函数只能在自己所在的源文件内部使用
使用建议:一个函数只想在所在的源文件内部使用,不想被其他源文件使用,就可以用static修饰