目前主要分为三个专栏,后续还会添加:
专栏如下: C语言刷题解析 C语言系列文章 我的成长经历
感谢阅读!
初来乍到,如有错误请指出,感谢!
目录
- 函数的概念
- 库函数
- 自定义函数
- 形参和实参
- return语句
- 数组做函数参数
- 嵌套调用和链式访问
- 函数的声明和定义
1. 函数的概念
- 库函数
- 自定义函数
2. 库函数
2.1 标准库和头文件
C语言标准中规定了C语言的各种语法规则,C语言并不提供库函数;语言的国际标准ANSI C规定了⼀些常用的函数的标准,被称为标准库。那不同的编译器厂商根据ANSI提供的C语言标准就给出了⼀系列 函数的实现。这些函数就被称为库函数。
2.2 库函数的使用方法
2.2.1 功能
2.2.2 头文件包含
double sqrt (double x);
//sqrt 是函数名
//x 是函数的参数,表示调用sqrt函数需要传递⼀个double类型的值
//double 是返回值类型 - 表示函数计算的结果是double类型的值
#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;
}
2.2.3 实践
#include <stdio.h>
#include <math.h>
int main()
{
double d = 16.0;
double r = sqrt(d);
printf("%lf\n", r);
return 0;
}
2.2.4 库函数文档的一般格式
- 函数原型
- 函数功能介绍
- 参数和返回类型说明
- 代码举例
- 代码输出
- 相关知识链接
3. 自定义函数
3.1 函数的语法形式
ret_type fun_name(形式参数)
{
}
- ret_type 是函数返回类型
- fun_name 是函数名
- 括号中放的是形式参数
- { } 括起来的是函数体
- ret_type 是用来表示函数计算结果的类型,有时候返回类型可以是 void ,表示什么都不返回
- fun_name 是为了方便使用函数;就像人的名字⼀样,有了名字方便称呼,函数有了名字方便调用,所以函数名尽量要根据函数的功能起的有意义。
- 函数的参数就相当于,工厂中送进去的原材料,函数的参数也可以是 void ,明确表示函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
- { } 括起来的部分被称为函数体,函数体就是完成计算的过程。
3.2 函数的举例
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
//输⼊
scanf("%d %d", &a, &b);
//调⽤加法函数,完成a和b的相加
//求和的结果放在r中
//to do
//输出
printf("%d\n", r);
return 0;
}
#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x+y;
return z;
}
也可以简化成这样:
int Add ( int x, int y){return x+y;}
整合之后 :
#include <stdio.h> // 引入标准输入输出库
// 定义一个函数Add,用于计算两个整数的和
int Add(int x, int y)
{
int z = 0; // 初始化变量z为0,用于存储结果
z = x + y; // 将x和y相加的结果赋值给z
return z; // 返回计算结果
}
int main()
{
int a = 0; // 定义变量a,用于存储输入的第一个整数
int b = 0; // 定义变量b,用于存储输入的第二个整数
// 使用scanf函数从标准输入读取两个整数,分别赋值给变量a和b
scanf("%d %d", &a, &b);
// 调用Add函数,将变量a和b作为参数传递,计算它们的和
// 将求和的结果存储在变量r中
int r = Add(a, b);
// 使用printf函数输出求和结果r
printf("%d\n", r);
return 0; // 程序正常结束,返回0
}
创建 C 函数时,会定义函数做什么,然后通过调用函数来完成已定义的任务。
当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。
调用函数时,传递所需参数,如果函数返回一个值,则可以存储返回值。
例如:
#include <stdio.h>
/* 函数声明 */
int max(int num1, int num2);
int main ()
{
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret;
/* 调用函数来获取最大值 */
ret = max(a, b);
printf( "Max value is : %d\n", ret );
return 0;
}
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
4. 形参和实参
4.1 实参
4.2 形参
实际上,如果只是定义了 Add 函数,⽽不去调⽤的话, Add 函数的参数 x 和 y 只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形式的实例化。
4.3 实参和形参的关系
形参是实参的⼀份临时拷贝。
5. return 语句
- return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果。
- return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
- return返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。
- return语句执行后,函数就彻底返回,后边的代码不再执行。
- 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
6.数组做函数参数
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
set_arr();//设置数组内容为-1
print_arr();//打印数组内容
return 0;
}
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
set_arr(arr, sz);//设置数组内容为-1
print_arr(arr, sz);//打印数组内容
return 0;
}
- 函数的形式参数要和函数的实参个数匹配
- 函数的实参是数组,形参也是可以写成数组形式的
- 形参如果是⼀维数组,数组大小可以省略不写
- 形参如果是⼆维数组,行可以省略,但是列不能省略
- 数组传参,形参是不会创建新的数组的
- 形参操作的数组和实参的数组是同⼀个数组
void set_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
arr[i] = -1;
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
7. 嵌套调用和链式访问
7.1 嵌套调用
- is_leap_year():根据年份确定是否是闰年
- get_days_of_month():调用is_leap_year确定是否是闰年后,再根据月计算这个月的天数
// 函数用于判断给定的年份是否为闰年
int is_leap_year(int y)
{
// 如果年份能被4整除且不能被100整除,或者能被400整除,则为闰年
if(((y%4==0)&&(y%100!=0))||(y%400==0))
return 1; // 返回1表示是闰年
else
return 0; // 返回0表示不是闰年
}
// 函数用于获取给定年份和月份的天数
int get_days_of_month(int y, int m)
{
// 定义一个数组,存储每个月的天数,数组的第0个元素未使用
int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 获取数组中对应月份的天数
int day = days[m];
// 如果是闰年且月份为2月,则2月的天数加1
if (is_leap_year(y) && m == 2)
day += 1;
// 返回该月份的天数
return day;
}
// 主函数,用于接收用户输入的年份和月份,并输出该月的天数
int main()
{
// 定义变量 y 用于存储用户输入的年份
int y = 0;
// 定义变量 m 用于存储用户输入的月份
int m = 0;
// 使用 scanf 函数从标准输入读取年份和月份
scanf("%d %d", &y, &m);
// 调用 get_days_of_month 函数,传入年份 y 和月份 m,获取该月的天数
int d = get_days_of_month(y, m);
// 使用 printf 函数输出该月的天数
printf("%d\n", d);
// 主函数返回0,表示程序正常结束
return 0;
}
- main 函数调⽤ scanf 、 printf 、 get_days_of_month
- get_days_of_month 函数调⽤ is_leap_year
这段代码中包含两个函数和一个主函数(main
),下面是对这些函数调用的详细解释:
is_leap_year
函数:
- 这个函数接收一个整数参数
y
,代表需要判断的年份。- 函数内部首先检查年份是否满足闰年的条件:
- 如果年份能被4整除且不能被100整除,或者能被400整除,则认为是闰年。
- 如果条件满足,函数返回
1
表示是闰年;如果不满足,返回0
表示不是闰年。
get_days_of_month
函数:
- 这个函数接收两个整数参数:
y
(年份)和m
(月份)。- 函数内部定义了一个数组
days[]
,用于存储每个月的标准天数(非闰年情况下)。- 根据输入的月份
m
,从数组days[]
中获取对应月份的天数,并存储在变量day
中。- 如果输入的年份
y
是闰年,并且月份m
为2月(即二月),则在day
的基础上加1,因为闰年的二月有29天。- 最后,函数返回变量
day
的值,即该月的天数。
main
函数:
- 这是程序的入口点,也是主函数。
- 首先定义了两个整型变量
y
和m
,用于存储用户输入的年份和月份。- 使用
scanf
函数从标准输入读取用户输入的年份和月份,并将它们存储在变量y
和m
中。- 接下来,调用
get_days_of_month
函数,并将读取的年份y
和月份m
作为参数传递。函数的返回值(即该月的天数)被存储在变量d
中。- 使用
printf
函数输出变量d
的值,即用户查询的月份的天数。- 最后,
main
函数返回0
,表示程序正常结束。
在实际运行时,用户会被提示输入一个年份和一个月份,程序会根据输入的值调用
get_days_of_month
函数
来计算并输出该月的天数。
如果是闰年且用户查询的是2月,输出的天数将是29天;
否则,输出的是该月的标准天数。
7.2 链式访问
#include <stdio.h>
int main()
{
int len = strlen("abcdef");//1.strlen求⼀个字符串的⻓度
printf("%d\n", len);//2.打印⻓度
return 0;
}
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));//链式访问
return 0;
}
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
这个代码的关键是明⽩ printf 函数的返回是啥?
printf - C++ Reference (cplusplus.com)
int printf ( const char * format, ... );
printf函数返回的是打印在屏幕上的字符的个数。上⾯的例子中,我们就第一个printf打印的是第二个printf的返回值,第二个printf打印的是第三个printf的返回值。第三个printf打印43,在屏幕上打印2个字符,再返回2第二个printf打印2,在屏幕上打印1个字符,再放回1第一个printf打印1所以屏幕上最终打印:4321
8. 函数的声明和定义
8.1 单个文件
#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 main()
{
int y = 0;
scanf("%d", &y);
int r = is_leap_year(y);
if (r == 1)
printf("闰年\n");
else
printf("⾮闰年\n");
return 0;
}
上⾯代码中橙⾊的部分是函数的定义,绿⾊的部分是函数的调⽤。
这种场景下是函数的定义在函数调用之前,没啥问题。
那如果我们将函数的定义放在函数的调用后边,如下:
这个代码在VS2022上编译,会出现下⾯的警告信息:
这是因为C语⾔编译器对源代码进⾏编译的时候,从第⼀⾏往下扫描的,当遇到第7⾏的is_leap_year 函数调⽤的时候。
并没有发现前⾯有is_leap_year的定义,就报出了上述的警告。
把怎么解决这个问题呢?
就是函数调⽤之前先声明⼀下is_leap_year这个函数,声明函数只要交代清 楚:函数名,函数的返回类型和函数的参数。
如:int is_leap_year(int y);
这就是函数声明,函数声明中参数只保留类型,省略掉名字也是可以的。
代码变成这样就能正常编译了。
#include <stdio.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;
}
8.2 多个文件
// 函数的定义int Add ( int x, int y){return x+y;}
// 函数的声明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 ;}
运⾏结果:
有了函数声明和函数定义的理解,我们写代码就更加方便了
8.3 static 和 extern
static 和 extern 都是C语⾔中的关键字。
static 是 静态的 的意思,可以⽤来:
- 修饰局部变量
- 修饰全局变量
- 修饰函数
extern 是⽤来声明外部符号的。
在讲解 static 和 extern 之前再讲⼀下:作⽤域和⽣命周期。
1. 局部变量的作用域是变量所在的局部范围。2. 全局变量的作用域是整个工程(项目)。
1. 局部变量的生命周期是:进⼊作用域变量创建,⽣命周期开始,出作用域生命周期结束。2. 全局变量的生命周期是:整个程序的生命周期。
8.3.1 static 修饰局部变量:
8.3.2 static修饰全局变量
int g_val = 2018 ;
test.c
# include <stdio.h>extern int g_val;int main (){printf ( "%d\n" , g_val);return 0 ;}
static int g_val = 2018 ;
test.c
# include <stdio.h>extern int g_val;int main (){printf ( "%d\n" , g_val);return 0 ;}
结论:
一个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使用,不能在其他源⽂件内使用。
本质原因是全局变量默认是具有外部链接属性的,在外部的文件中想使用,只要适当的声明就可以使用;
但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源文件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。
使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使用,不想被其他⽂件发现,就可以使用static修饰。
8.3.3 static 修饰函数
int Add ( int x, int y){return x+y;}
test.c
# include <stdio.h>extern int Add ( int x, int y);int main (){printf ( "%d\n" , Add( 2 , 3 ));return 0 ;}
static int Add ( int x, int y){return x+y;}
test.c
# include <stdio.h>extern int Add ( int x, int y);int main (){printf ( "%d\n" , Add( 2 , 3 ));return 0 ;}
代码1是能够正常运行的,但是代码2就出现了链接错误。
使用建议:一个函数只想在所在的源文件内部使用,不想被其他源文件使用,就可以使用 static 修 饰。