C语言:函数
(本文为自己学习记录,如有错误欢迎指正)
一、什么是函数
C语言中也可叫子程序,是完成某项特定任务的一段代码。
函数一般分为两类,
1.库函数:标准库中提供的函数,比如scanf,printf函数等,这些都是已经写好并校正多次的函数,bug相对较少,可以在https://zh.cppreference.com/w/c/header中查询。
(因为C语言并不提供现成的库函数,所以应用不同的头文件进行声明)
2.自定义函数
可用来实现自己的一个需求。
二、怎么使用函数
1.库函数:先声明再使用。
举例:可以实现两数平方的函数。
#include <stdio.h>
#include<math.h>
int main() {
float a;
a = sqrt(16.0);
printf("%lf", a);
return 0;
}
在这段代码中我们调用了printf和sqrt函数,他们分别包含在stdio.h和math.h头文件中,需先声明再调用。
2.自定义函数
在编写和调用自定义函数时,我们需要了解以下知识。
(1)语法形式:

ret_type是函数返回类型,
fun_name是函数名,
括号中放的是形式参数,
{}中的是函数体。
某些简单的函数,不需要参数和返回值,void表示不需要参数和返回值。
p.s.函数参数列表为 void 时(如 void func(void) ),明确说明该函数调用时不需要传入任何参数。
注意 :若仅写 void func() (省略参数列表中的 void ),在C语言中表示“参数数量和类型不确定”,而非“无参数”,二者含义不同,推荐始终用 void func(void) 表达无参数。
举例:
void print() {
printf("hello world\n");
}
图1
实现两数之和的函数:
int Add(int x, int y)
{
return x + y;
}
int main() {
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
printf("%d\n", Add(a, b));
return 0;
}
图2
(2)形参与实参
在图2代码中,a,b为实际参数,x,y为形式参数。
实际参数是真实传递给函数的参数。
形式参数是在定义了函数而没有调用时里面的参数,在没有调用时不会向内存申请空间。
当函数被调用后,实参传到形参,形参才开辟内存空间,形参与实参的地址不同,相当于是实参的临时拷贝。
(对形参的修改,不会影响实参)
(3)在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式 的结果。
举例
图二(return的是表达式)以及
//编写一个函数,判断数字是奇数还是偶数
//是奇数返回1
//是偶数返回0
int is_even_number(int n)
{
if (n % 2 == 1)
return 1;
else
return 0;
}
int main() {
int a = 0;
scanf("%d", &a);
int ret = is_even_number(a);
if (ret == 1)
printf("奇数\n");
else
printf("偶数\n");
}
图三(return的是数值)
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
(在一个函数执行的过程中,需要提前返回,但是不需要带回任何值,则写return)
例如
//这串代码只能返回1
void test()
{
printf("1\n");
int n = 5;
if (n % 2 == 1)
return;
printf("2\n");
}
int main()
{
test();
return 0;
}
//这串代码输入奇数只返回1
//输入偶数返回1,2
void test(n)
{
printf("1\n");
if ( n % 2 == 1)
return;
printf("2\n");
}
int main()
{
int a;
scanf("%d", &a);
test(a);
return 0;
}
图四
• return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
图四为例
• return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
例如
//return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
//如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
int test()
{
int n = 1;
if (n == 1)
return 3.5;
else
return -3.5;
}
int main() {
int a = test();
printf("%d", a);
return 0;
}
图五
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
//如果某些返回值不明确
int test()
{
int n = 3;
if (n == 1)
return 3.5;
}
int main() {
int a = test();
printf("%d", a);
return 0;
}
//这两段代码的返回值将不一样
int test()
{
int n = 3;
printf("hello\n");
if (n == 1)
return 3.5;
}
int main() {
int a = test();
printf("%d", a);
return 0;
}
图六(返回的值是不固定的,两个代码返回了不同的值,即返回值是不可预测的)
• 函数的返回类型如果不写,编译器会默认函数的返回类型是int。
//不写返回值类型默认是int类型
test()
{
printf("hello\n");
}
int main() {
int a = test();
printf("%d", a);
return 0;
}
图七(此代码的运行结果为整数)
• 函数写了返回类型,但是函数中没有使⽤return返回值,那么函数的返回值是未知的。
(4)数组传参
在函数里数组也是可以当作参数传递的。
例如:
//创建一个函数使数组中的元素全部改为1
void set_arr(int arr[10], 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");
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
//计算出数组中的元素个数
print_arr(arr, sz);
//写一个函数用于打印数组内容
set_arr(arr, sz);
//写一个函数将数组的内容全部置为-1
print_arr(arr, sz);
//再打印出来
return 0;
}
图八(一维数组传参)
数组作为参数传递给了set_arr 和 print_arr函数了,这里我们需要知道数组传参的几个重点知识:
函数的形式参数要和函数的实参个数匹配,有几个形参
函数的实参是数组,形参也是可以写成数组形式的
形参如果是一维数组,数组大小可以省略不写(因为本身就不创建新数组,这个大小不重要)(图八函数2)
形参如果是二维数组,行可以省略,但是列不能省略
数组传参,形参是不会创建新的数组的
形参操作的数组和实参的数组是同一个数组
//二维数组传参
void print_arr2(int arr[3][5], int r, int c)
{
int i = 0;
for (i = 0;i < r;i++)
{
int j = 0;
for (j = 0;j < c;j++)
{
printf("%d", arr[i][j]);
}
printf("\n");
}
}
int main() {
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
print_arr2(arr,3,5);
return 0;
}
图九(二维数组传参)
3.函数的嵌套调用和链式访问
(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[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int d = days[m];
//闰年的月要特殊处理
if (is_leap_year(y) && (m == 2))
d += 1;
return d;
}
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;
}
图十(在main函数中调用了函数1,在函数1中调用了函数2)
(2)链式访问
顾名思义就是将函数像链子一样串起来,用的就是一个函数的返回值被另一个函数调用这种方式来串起来。
//函数的嵌套
int main() {
printf("%zd", strlen("abcdef"));
return 0;
}
//以上代码等同于这个形式
int main() {
size_t a = strlen("abcdef");
printf("%zd", a);
return 0;
}
图十一(strien函数的返回值被printf函数调用)
p.s.一段有趣的代码
已知printf函数的返回值是

总共被写的字符个数
//printf函数的返回值为打印的字符的个数
int main() {
printf("%d", printf("%d", printf("%d", 43)));
printf("\n");
printf("%d", printf("%d", printf("%d\n", 43)));
return 0;
}
图十二
4.函数的声明
函数的定义和调用有一定顺序,一般是先定义再调用。
如果函数的定义在调用之后,应该在调用前先声明。
即:先声明再调用。
//函数如果在调用之后定义,应先声明再调用。
int is_leap_year(int y);
int main() {
int year = 0;
scanf("%d", &year);
if (is_leap_year(year))
printf("是闰年\n");
else
printf("不是闰年\n");
}
int is_leap_year(int y)
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
return 1;
else
return 0;
}
图十三(函数的先声明再调用)
以上代码是在同一个文件中的情况,但是一般我们不能把所有函数都放在同一个文件中,那么在多文件中我们又应该如何进行函数的声明和调用呢?
在一个.c文件中定义函数,在一个.h(头文件)中声明函数,再在主函数所在的.c文件中包含这个头文件(#include<xxxx.h>)即可。

图十四(一个简单的加法函数)
(易错点,我自己敲代码遇到的问题,自定义的头文件用 “ ” 而不是 < > ,用错了代码就运行不了啦)
这样就能实现函数和主函数分开(即不放在同一文件中)的效果,利于保留函数的使用和维护主权等,也便于多人的分工。
5.static和extern
(1)首先说下作用于和生命周期这两个概念:
作用域:限定一个名字可用性的代码范围就是这个名字的作用域。
生命周期:一个变量从创建到销毁之间的一个时间段。
能直观体现出作用域对变量定义和使用的影响的是 “ { } ” ,
1.它可以限定局部变量的作用域:在花括号内定义的局部变量,仅能在该花括号所包裹的代码块内部被访问和使用,出了花括号则失效(内存被释放)。
2. 隔离变量作用域:不同花括号内可定义同名变量,它们相互独立,互不干扰。
(不同的函数同理)
(2)extern:用来声明外部符号(声明或者定义函数时才用)
static:一个全局变量或函数被关键字static修饰后,就只能在本源文件中使用。(起到一个保护的作用),
修饰局部变量会改变其生命周期(不改变作用域),(即创建后不销毁还能继续用,只不过可用范围还是在相同函数,效果就是这个函数被再次调用后这个变量的值会被保留)将变量的储存位置从栈区改变到静态区,生命周期和程序的生命周期一样了。
这里举一个例子:
//static改变变量的生命周期
void test() {
int a = 1;
a++;
printf("%d", a);
}
int main()
{
int i = 0;
for (i = 0;i < 5;i++) {
test();
}
return 0;
}
//加上static后
void test() {
static int a = 1;
a++;
printf("%d", a);
}
int main()
{
int i = 0;
for (i = 0;i < 5;i++) {
test();
}
return 0;
}
图十五
这张图中上下两个代码作对比,第二个代码test函数中的a变量在定义时前面加上了static修饰,
第一组代码运行结果为22222,第二组运行结果为23456,显然,第二组的a的生命周期被改变了。

图十六
extern可以声明外部函数或变量,但如果该函数或变量被static修饰则不能被调用。如图十七

图十七
函数同理。
如果一个全局变量或函数只想在当前原文件中使用,不想被别的文件发现,就使用static修饰。
今天的分享就先到这里啦!
更多推荐

所有评论(0)