1.函数概念
在数学中我们学习过函数的概念,例如一次函数y=kx+b,k和b都是常数,给一个任意的x,就得到一个y值。
在C语言中我们也要学习函数的概念,也有种说法翻译为:子程序。这种翻译其实更加准确。C语言的函数就是一个完成某项特定任务的一小段代码。这段代码是有特殊的写法和调用方法的。
2.库函数
2.1标准库和头文件
2.2库函数的使用
库函数⽂档的⼀般格式1. 函数原型2. 函数功能介绍3. 参数和返回类型说明4. 代码举例5. 代码输出6. 相关知识链接
sqrt头文件:#include<math.h>double sqrt (double x);//sqrt 是函数名//x 是函数的参数,表⽰调⽤sqrt函数需要传递⼀个double类型的值//double 是返回值类型 - 表⽰函数计算的结果是double类型的值功能:Returns the square root of x.(返回平方根)
#include <stdio.h> /* printf */
#include <math.h> /* sqrt */
int main ()
{
double param, result;
param = 1024.0;
result = sqrt (param);
printf ("sqrt(%f) = %f\n", param, result );
return 0;
}
3.自定义函数
自定义函数的语法形式:
ret_type fun_name(形式参数){
}• ret_type 是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表示什么都不返回。• fun_name 是为了⽅便使⽤函数;就像⼈的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调用,所以函数名尽量要根据函数的功能起的有意义。• 函数的参数是定义函数时在函数声明中指定的参数,它们在函数被调用时接收传递进来的数据。函数的参数也可以是 void ,明确表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。• {}括起来的部分被称为函数体,函数体就是完成计算的过程。
我们可以把函数想象成一个加工厂,工厂输入原料,经过加工产出产品,函数也是一样,输入参数(函数可以没有参数),经过函数内的运算,得出结果。
示例代码:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d %d", &num1, &num2);
int ret = Add(num1, num2);
printf("%d\n", ret);
return 0;
}
4.形参和实参
int Add(int x, int y)
{
return x + y;
}
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d %d", &num1, &num2);
int ret = Add(num1, num2);
printf("%d\n", ret);
return 0;
}
4.1 实参
在上述代码中,main函数中调用Add函数时,传递给函数的参数num1和num2,称为实际参数,简称实参,实际参数就是真是传递给函数的参数。
4.2 形参
在上述代码中,定义函数时,在函数名Add后的括号中的x和y,称为形式参数,简称形参。
4.3 实参与形参的关系
虽然我们提到了实参是传递给形参的,他们之间是有联系的,但是形参和实参各⾃是独⽴的内存空 间。这个现象是可以通过调试来观察的。请看下⾯的代码和调试演示例:
5.return语句
在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。• return后边可以是⼀个 数值,也可以是⼀个 表达式,如果是表达式则先执⾏表达式,再返回表达式的结果。• return后边 也可以什么都没有,直接写 return; 这种写法适合函数返回类型是 void的情况。• return返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。• return语句执行后,函数就彻底返回,后边的代码不再执⾏。• 如果函数中存在 if等分⽀的语句,则要保证 每种情况下都有return返回,否则会出现编译错误。
6.数组做函数参数
在进行数组传参时我们要注意:• 函数的形式参数要和函数的实参个数匹配• 函数的实参是数组,形参也是可以写成数组形式的• 形参如果是⼀维数组,数组大小可以省略不写• 形参如果是⼆维数组,行可以省略,但是列不能省略• 数组传参,形参是不会创建新的数组的• 形参操作的数组和实参的数组是同⼀个数组
void set_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
arr[i] = -1;
}
}
void print_arr(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[5] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
set_arr(arr, sz);
print_arr(arr, sz);
return 0;
}
7.嵌套调用
_Bool is_leap_year(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
return true;
}
else
{
return false;
}
}
int get_days_of_month(int year)
{
int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int month = 0;
scanf("%d", &month);
int day = arr[month];
_Bool ret = is_leap_year(year);
if ((ret == true) && (month != 0))
{
if (month == 2)
{
return day + 1;
}
else
{
return day;
}
}
else
{
return day;
}
}
int main()
{
int year = 0;
scanf("%d", &year);
int day = get_days_of_month(year);
printf("%d", day);
return 0;
}
假设我们计算某年某月有多少天?如果要函数实现,可以设计2个函数,代码如下:
这⼀段代码,完成了⼀个独⽴的功能。代码中反应了不少的函数调⽤:• main 函数调⽤ scanf 、 printf 、 get_days_of_month• get_days_of_month 函数调⽤ is_leap_year未来的稍微⼤⼀些代码都是函数之间的嵌套调⽤,但是 函数是不能嵌套定义的。
8.链式访问
int main()
{
int num = strlen("abcdef");
printf("%d\n", num);
return 0;
}
int main()
{
printf("%zd\n", strlen("abcdef"));
return 0;
}
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
printf - C++ Referencehttps://legacy.cplusplus.com/reference/cstdio/printf/?kw=printfprintf函数返回的是打印在屏幕上的字符的个数.
上⾯的例⼦中,我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个printf的返回值。第三个printf打印43,在屏幕上打印2个字符,再返回2第⼆个printf打印2,在屏幕上打印1个字符,再放回1第⼀个printf打印1所以屏幕上最终打印:4321
9.函数的声明和定义
9.1 单个文件
int Add(int x, int y)//函数定义
{
return x + y;
}
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d %d", &num1, &num2);
int ret = Add(num1, num2);//函数调用
printf("%d\n", ret);
return 0;
}
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d %d", &num1, &num2);
int ret = Add(num1, num2);
printf("%d\n", ret);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
编译器将会报错,如下:
int Add(int x, int y);
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d %d", &num1, &num2);
int ret = Add(num1, num2);
printf("%d\n", ret);
return 0;
}
int Add(int x, int y)
{
return x + y;
}
9.2 多个文件
//函数的定义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 num1 = 20;
int num2 = 30;
int ret = Add(num1, num2);//函数调用
printf("%d\n", ret);
return 0;
}
运行结果:
有了函数声明和函数定义的理解,我们写代码就更加⽅便了。
10.static和extern
static 和 extern 都是C语言中的关键字。static 是 静态的 的意思,可以用来:• 修饰局部变量• 修饰全局变量• 修饰函数extern 是用来声明外部符号的。
作用域(scope)是程序设计概念,通常来说,⼀段程序代码中所用到的名字并不总是有效(可用) 的,而限定这个名字的可用性的代码范围就是这个名字的作用域。1. 局部变量的作用域是变量所在的局部范围。2. 全局变量的作用域是整个工程(项目)。生命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。1. 局部变量的生命周期是:进入作用域变量创建,生命周期开始,到出作用域生命周期结束。2. 全局变量的生命周期是:整个程序的生命周期。
10.1 extern声明
看示例代码:
extern 是用来声明外部符号的,如果⼀个全局的符号在A文件中定义的,在B文件中想使用,就可以使用 extern 进行声明,然后使用。
10.2 static修饰局部变量
结论:static修饰局部变量改变了变量的生命周期,生命周期改变的本质是改变了变量的存储类型,本来一个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变量和全局变量是⼀样的,生命周期就和程序的生命周期⼀样了,只有程序结束,变量才销毁,内存才回收。但是作用域不变的。
10.3 static修饰全局变量
在左上角的代码中可以正常运行,extern成功在另一个文件中声明了num1,而下面的代码则运行失败,原因出在num2在定义时有static修饰。(num1,num2和main函数并不在同一个文件中)。
结论:一 个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源⽂件内使用。本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使用,只要适当的声明就可以使用;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源⽂件内部使用了,其他源文件,即使声明了,也是无法正常使用的。
10.4 static修饰函数
上方的代码可以运行成功,而一旦在下方的函数Add定义前加上static,代码就运行失败了。(Add函数和main函数并不在同一个文件中)。
结论:其实 static 修饰函数和 static修饰全局变量是⼀模⼀样的,⼀个函数在整个工程都可以使用, 被static修饰后,只能在本文件内部使用,其他文件无法正常的链接使用了。 本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个工程中只要适当的声明就可以被使用。但是被 static 修饰后变成了内部链接属性,使得函数只能在自己所在源文件内部使用。