C语言学习八:函数
函数入门
前言
在 C 语言的编程中,其实用到很多的函数,而每一个函数就可以理解为一个独立的模块,因此 C 语言也称为模块化编程。
我们封装函数应该尽可能左到:低耦合,高内聚

对于函数的使用者来说,应该尽可能简单地去使用该函数接口,使用者只管往函数中输入需要的数据,通过该函数获得一个结果即可

函数的定义
-
函数头:函数对外公开的接口信息。 比如:
void *calloc(size_t nmemb, size_t size);- 函数的返回值 , 该函数运行结束后会返回什么东西给你 , 比如:
void * - 函数名, 命名规则跟变量一致。应该尽量顾名思义。比如:
calloc - 参数列表,告诉用户该函数需要输入的数据以及类型,有可能有多个也可能没有 , 比如
(size_t nmemb, size_t size)
- 函数的返回值 , 该函数运行结束后会返回什么东西给你 , 比如:
-
语法:
1
2
3
4
5
6返回值类型 函数名 ( 参数1 , 参数2 ,参数3 , ... , 参数N )
{
// 函数体
return 返回值 ;
} -
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 函数声明
int add ( int , int ); // 函数声明中可以把形参的名字省略
int main(int argc, char const *argv[])
{
int x = 100 ;
int y = 250 ;
int tmp = 0 ;
// 调用add函数, 把 x 和 y 的值 传递过去,
// x , y 是实参, 作为形参 a , b 初始值
// a = x , b = y ;
// 使用 tmp 来接受 add函数的返回值
tmp = add(x,y);
printf("x + y = %d \n" , tmp );
return 0;
}
// 设计一个函数 ,接收 两个整型参数, 并返回 它们的和
int add ( int a , int b ) // a , b 属于函数add 的局部变量
{
int tmp = a + b ;
return tmp ;
} -
练习:
- 使用函数来实现求 3个整型中最大的并返回。
- 使用函数来实现交换两个变量的值(指针)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 使用函数来实现求 3个整型中最大的并返回。
static int max( int a , int b , int c )
{
int max = a > b ? a : b ;
max = max > c ? max : c ;
return max ;
// a>b?(a>c?c:a):(b>c?b:c)
}
//使用函数来实现交换两个变量的值(指针)
static bool swap( int * a , int * b )
{
if ( a == NULL || b == NULL )
{
printf("参数有误, 大意了!!\n");
return false ;
}
printf("a:%d b:%d\n" , *a , *b );
*a^=*b;
*b^=*a;
*a^=*b;
printf("a:%d b:%d\n" , *a , *b );
return true ;
}
int main(int argc, char const *argv[])
{
int x = 129 ;
int y = 245;
int z = 350;
int num = max( x , y , z ) ; // x , y , z 都是实参
printf("max:%d\n" , num );
if( swap( &x , &y) )
{
printf("x:%d y:%d\n" , x , y );
}
else
{
printf("交换失败 !!\n");
}
return 0;
} -
总结:
- 当函数的返回值类型为
void的时候, 表示该函数不会有返回的值 - 当参数列表中为
void的时候,表示该函数不需要参数 - 当返回值为具体的类型的时候 , 函数的结尾就应该返回一个对应类型的数据
return只能够携带一个数据进行返回- 当返回值类型为
void的时候,可以不写return, 如果写的话一般就直接写return ;
- 当函数的返回值类型为
形参与实参
-
概念
函数调用的时候传递的值,称为实参
max(123.034 , 'C' , "87") ;
函数定义当中出现的参数列表,称为形参max(float a , char b , char * c) -
实参与形参的关系
- 实参与形参应该是一一对应的。(顺序+类型)
- 形参的值是由实参进行初始化。
- 形参与实参是处于两个完全不相关的栈空间中。彼此是独立的
函数调用的过程
函数调用的时候,进程会进行上下文切换,当指向完被调用的函数后,将会切换会被调用的语句,继续往下执行。

局部变量与栈内存
-
局部变量简介
被函数体的一对大括号所包含的变量,称为局部变量(在函数体内部定义)
-
局部变量的特点
- 属于函数内部的变量, 所存储的位置是该函数所拥有的栈空间
- 局部变量不会被其它函数所访问,因此不同的函数内部可以拥有完全一样的两个变量名字, 但是从内存来看它们是完全独立的。
- 当函数退出的时候, 局部变量所占用的内存,将会被系统回收,因此局部变量也称为临时变量
- 形参当中的变量,虽然没有在大括号的范围内, 但是也是属于该函数的局部变量。
-
栈内存的特点
- 当有一个函数被调用的时候,栈内存将会增长一段, 用来存放该函数的局部变量。
- 当函数退出时,他所占用的栈内存将会被释放回收。
- 系统分配栈空间遵循从上往下增长的原则。
- 栈空间的内存相对来说比较少, 不建议用来存放大量的数据。

特殊函数
静态函数
-
背景:普通的函数都是跨文件可见的,也就是比如
a.c里面有个函数swap(), 在b.c中也可以调用该函数。 -
静态函数:只能够在当前定义的文件中使用,称为静态函数
-
语法:
1
2
3
4
5static int func (int , int b )
{
// 函数体
} -
注意:
- 静态函数用来缩小可见范围,减少与其它文件中同名函数冲突的问题
- 静态函数一般会定义在头文件中, 然后被需要使用该函数的源文件包含即可。
递归函数
-
递归函数的概念:如果一个函数的内部,包含了对自己的调用则称为递归函数。
-
递归的问题:阶乘、幂运算、字符反转
-
要点:
- 只有可以被递归的问题,才可以使用递归函数来实现。
- 递归函数必须有一个可以直接退出的条件,否则会进入无线递归,最后导致栈溢出。
- 递归函数包含了两个过程: 逐渐递进 , 逐步回归。
-
示例:
要求使用递归来输出n个自然数
思路: 先输出n-1个自然数, 最后才输出N

-
总结:
- 递归的时候函数的栈空间,会随之从上往下不断增长,栈空间会越来越少,直到耗尽或者开始返回。
- 在一层一层递进的时候,问题的规模会随之缩小,当达到某一个条件的时候,则开始一层一层回归。
函数指针
-
概念:指向一个函数的指针,称为函数指针;
-
语法:
1
int (*p) (int , float ) ;
-
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18int Printf( int a , float f )
{
printf("a:%d , f:%f\n" , a , f );
return 0 ;
}
int main(int argc, char const *argv[])
{
// 定义一个函数指针, 名字为 p 。它指向的函数 有一个整型返回值
// 需要 一个整型参数 以及 浮点参数
int (*p) (int , float ) ;
p = Printf ; // 函数名其实也是这个函数的入口地址
Printf(10 , 3.14);
p(56 ,9.8888);
return 0;
}
指针函数
-
概念:一个返回指针的函数,就称为指针函数。
1
2
3
4
5
6
7
8
9
10
11int * func(int a , int b)
{
int kk ;
/*
..
...
*/
return &kk ;
}
回调函数
-
概念:函数实现不方便调用该函数,而由接口提供方来调用该函数,就称为回到函数。
-
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void func(int num )
{
printf("当前收到 3 号信号 , 军师让我蹲下 !!\n");
}
int test( int num , void (*p)(int) )
{
for (size_t i = 0; i < num ; i++)
{
printf("num:%d\n" , num‐‐ );
if (num == 50 )
{
p(1);
}
}
return 0 ;
}
int main(int argc, char const *argv[])
{
void (*p)(int); // 定义一个函数指针
p = func ; // 让指针p 指向函数 func
test( 100 , p );
return 0;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void func(int num )
{
printf("当前收到 3 号信号 , 军师让我蹲下 !!\n");
}
int main(int argc, char const *argv[])
{
void (*p)(int); // 定义一个函数指针
p = func ; // 让指针p 指向函数 func
// 设置进程捕获信号 ,如果信号值 为 3的时候 , 会自行调用 p 所指向的函数 (代码/指令)
signal( 3 , p );
while(1);
return 0;
} -
总结:
signal函数是一个用于捕获信号的函数, 当他捕获到指定信号的时候则会执行用户所提供的函数。- 由于
signal函数是内核提供的函数,修改内核的代码不现实,因此它提供的接口是一个函数指针,只要某个条件满足则会自动执行我们所给的函数。 - 使得不同软件模块的开发者的工作进度可以独立出来,不受时空的限制,需要的时候通过约定好的接口(或者标准)相互契合在一起。
内联函数
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ZのBlog!
评论






