【C语言】第三章-函数-2

第三章 函数

第2节

函数调用

  函数调用一般有两种方式,一种是形参不会影响实参的传值调用,另一种是形参会影响实参的传址调用。

传值调用

  传值调用是将实参的值传入函数体中,传入的不过是实参的副本,不会改变实参。这个在上一节已经讲过其中的原因正式因为C语言副本传参的这个特性,这也为我们带来了很多麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>
void Swap(int x, int y)//交换x, y的值
{
int tmp = x;
x = y;
y = tmp;
}
int main()//主函数,函数入口
{
int a = 4;
int b = 5;
Swap(a, b);
printf("a = %d, b = %d", a, b);
system("pause");
}

  运行后大家会发现,a, b的值并没有交换,这正是因为我们在这里只用了传值调用,传入的副本不会使实参改变。

传址调用

  传址调用是将参数的地址进行传入,其实就是把指针作为参数,之前我们说过地址就像是变量的门牌号,所以当我们将变量的地址传入的时候,实参就被锁定了,形参的改变,也会使实参改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
int main()//主函数,函数入口
{
int a = 4;
int b = 5;
Swap(&a, &b);
printf("a = %d, b = %d", a, b);
system("pause");
}

  上面这行代码运行后a, b的值就会被交换,然而我们所做的改变不过是将函数的参数类型改为了指针,因此我们如果想要函数能够改变实参的值,我们就必须将参数的指针传入,前提是写一个参数为指针的函数。在CPP中我们会学到比传址调用更加方便的改变实参的方式。

函数的嵌套调用和链式访问

嵌套调用

  嵌套调用是构成C语言最基础的语法,简单来说就是允许在函数内调用其它函数,比如我们在main函数中调用printf函数,这种方式相信大家都不陌生了。

链式访问

  链式访问是在函数参数里调用函数,这种调用方式也很简单,不过是将一个有返回值的函数在另一个函数的参数列表中进行调用,运行时会优先调用参数列表中的函数然后根据返回值进行判断外函数如何运行。这两种调用都十分简单,今后大家会经常用到,在这里不做过多赘述。

函数的声明和定义

函数声明

  因为我们在写函数的时候必须将函数的定义写在函数调用之前(之前我们都是写在main函数前的),因此当我们想要将函数定义写在调用之后以达到美观的易读的效果时我们要怎么做呢?

  这就要用到函数的声明了,所谓函数的声明,不过是在函数的定义写在函数调用之后的情况时为了让编译器依旧可以找到函数的定义的位置我们需要在调用之前所做的一个声明罢了。

  1、声明要告诉编译器又一个函数叫什么,参数是什么,返回类型是什么,但是具体函数存在不存在,无关紧要。

  2、函数声明一般出现在函数调用之前,满足先调用后使用。

  3、函数的声明一般是放在头文件中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>
int func(int);//函数的声明可以省略写参数名
int main()//主函数,函数入口
{
int num = 0;
func(num);
system("pause");
}
int func(int num)
{
/*
something;
*/
return num;
}

  在这个例子中,我们将函数的定义写在了函数调用之后,之所以可以这么做全部都多亏与我们在函数调用之前加上了函数的声明,函数的声明不是必须的,但是我们一般将函数的声明写在头文件中,这样会使我们的代码更加易读和管理。

头文件

  既然谈到了函数的定义我们就不得不提一个和函数定义紧密相连的东西,头文件,上文也说过函数一般是定义在头文件中的。

1
2
3
//头文件
#pragma once
int func(int);//函数的声明可以省略写参数名

1
2
3
4
5
6
7
8
//另一个.c文件
int func(int num)
{
/*
something;
*/
return num;
}
1
2
3
4
5
6
7
8
9
10
11
//主函数所在的文件
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>
#include "test.h"
int main()//主函数,函数入口
{
int num = 0;
func(num);
system("pause");
}

  可以发现我将自己定义的函数写在了另外两个文件中,而在主函数所在文件中引入了函数声明所在的头文件即可使用函数了。这种书写函数的方式使得我们的代码看起来更加整洁。

  值得一提的是,在主函数加入我们自己写的头文件的时候,最好用""来包含文件名比较好,这样可以更方便我们更快的找到函数。在头文件中大家还可以看到#program once这句话,它的作用是保证我们在多次调用同一个我文件的时候不会造成多次调用,发生错误,可以说是为了弥补C语言头文件使用的弊端,也是头文件必须的。
  可能也会有人在其他教材中发现头文件中还有这么一种书写格式。

1
2
3
4
5
6
7
8
9
10
//头文件
#ifndef TEST
#define TEST


int func(int);//函数的声明可以省略写参数名



#endif // !TEST

  其实这几个语句的效果和#program once效果类似,不过这是以前的写法并且带着很多的弊端,所以今后大家写头文件还是放弃使用这种格式。

函数的递归

  所谓函数的递归简单来说就是在函数内部调用他自身,达到一种循环调用的效果,是很重要的一种程序设计方法,有时使用递归设计程序会给我们带来很多方便,也会使程序运行更加流畅,不过有时依然是迭代来的更加自然。

  递归的程序设计是使用一种减而治之的思想,从局部处理考虑到整体处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>//添加头文件
#include <stdlib.h>
int factor(int num)//递归求阶乘
{
if (num == 1)//如果数字等于一则返回它本身
{
return 1;
}
return num * factor(num - 1);//num! = num * (num - 1)!
}
int main()//主函数,函数入口
{
printf("3的阶乘是:%d!\n", factor(3));
system("pause");
}

  上面这个例子就是一个很好的利用递归进行的程序设计,在某些方面递归就是可以使你的代码更好书写,但是递归也在某些方面有着很麻烦的十分不可理喻的效率,比如在利用递归求斐波那契数的时候,就会产生大量的重复运算,大大降低了效率。
  递归是很重要的程序设计思想,要完全掌握这种设计理念还需要今后多多练习。

-------------本文结束感谢您的阅读!-------------
记录学习每一分,感谢您的赞助