当前位置:网站首页>C语言函数详解

C语言函数详解

2022-04-23 17:07:00 Snakra

一、C语言中函数的分类

1.库函数

我们在学习C语言编程的过程中,会经常使用到许多的函数,我们写完一个代码,迫切的想要知道结果,我们利用到printf()函数来打印结果。我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。

C语言中常用的库函数
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他库函数

学习库函数,我推荐使用MSDN学习,这里面不仅会讲解函数的返回值,使用方法等,还有可供参考的实际代码,比较方便。
在这里插入图片描述

2.自定义函数

除了以上的库函数,我们还可以自己定义函数,与库函数类似,我们自定义的函数也有返回值、函数名和参数。我们来看一个例子。
在这里插入图片描述
如图,我们定义的ADD()函数,在第 5 行,int 是该函数的返回类型,因为要返回 a 和 b 的和,是一个整型值用 int。后面ADD是函数名,int x,int y 是函数的参数,第 13 行我们的函数接收了变量 a 和变量 b,都是整型,所以定义时使用 int x ,int y创建两个临时变量,这两个变量与 a ,b同名也是可以的。程序运行起来我们得到结果 3 。
在这里插入图片描述

二、函数的参数

1.实参

真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

2.形参

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才分配内存单元,所以叫形式参数。形式参数当函数调用完成之后就自动销毁了,因此形式参数只在函数中有效。
在这里插入图片描述

三、函数的调用

1.传值调用

形参和实参分别占不同的内存块,传值调用是把变量的值传给函数,函数再进行操作。还是我们之前的例子,这就是一个传值调用。
在这里插入图片描述
把变量 a 和变量 b 的值传给ADD()函数,函数算得结果返回。

2.传址调用

传址调用就是把变量的地址传给函数,函数操作完后会对实参进行改变。
我们写一个代码,输入变量 a 和 b 的值,函数进行交换,交换完后再打印。

#include <stdio.h>

void Swap(int x, int y)
{
    
	int c = 0;
	c = x;
	x = y;
	y = c;
}

int main()
{
    
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	printf("交换前:a = %d b = %d\n",a,b);
	Swap(a, b);
	printf("交换后:a = %d b = %d\n",a,b);

	return 0;
}

在这里插入图片描述

此段代码是错误的,我们可以发现输入的 a 和 b 的值并没有交换,是因为我们传给Swap的参数是值而不是地址。接下来我们进行逐条分析,打开VS2022,按F11,控制台选到调试,窗口,监视。
在这里插入图片描述
我们进行逐句分析如下:
在这里插入图片描述
如图,我们可以发现变量 a ,b 与变量 x,y的地址不相同,所以此代码交换的是临时变量 x,y的值,并不会影响变量 a 和 b。

接下来我们对函数进行传址。

#include <stdio.h>

void Swap(int* x, int* y)      //用指针变量接收 a 和 b 的地址
{
    
	int c = 0;
	c = *x;     //解引用操作
	*x = *y;
	*y = c;
}

int main()
{
    
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	printf("交换前:a = %d b = %d\n",a,b);
	Swap(&a, &b);    //将变量 a 和变量 b 的地址传给函数
	printf("交换后:a = %d b = %d\n",a,b);

	return 0;
}

我们把 a 和 b的地址传给函数Swap(),创建两个临时指针变量接收,进行解引用操作找到 a 和 b,再交换。输出结果是这样的。
在这里插入图片描述
我们可以发现 a 和 b的值进行了交换。
通过上面两个例子,说明传址调用和传值调用是不同的,那么什么时候用传值调用,什么时候又用传址调用呢?
我认为如果想对实参进行改变时,用传址调用。
当形参实例化时,形参是实参的一份临时拷贝,对于形参的改变并不会使实参发生变化。

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

1.嵌套调用

函数与函数之间可以进行组合,互相调用。

void Print1()
{
    
	printf("hello world!\n");
}
void Print2()
{
    
	int n = 0;
	for (n = 0; n < 3; n++)
	{
    
		Print1();
	}
}
int main()
{
    
	Print2();

	return 0;
}

在这里插入图片描述
由此可见,函数可以嵌套调用,但是不可以嵌套定义,例如
在这里插入图片描述

2.链式访问

我们也可以把一个函数的返回值作为另一个函数的参数。
例如

#include <stdio.h>

int main()
{
    
    printf("%d",printf("%d",printf("%d",43)));
    
    return 0;
}

这个程序会输出什么呢? 答案是 4321
通过在MSDN上面对printf()函数查资料可知
在这里插入图片描述
我们可以看到,printf()函数返回每一个打印的字符数,若发生错误则返回负值。也就是说,打印 1 个字符返回 1 ,打印 2 个字符返回 2 。
我们看这段代码,最内层的printf()函数先工作,在屏幕上打印了 43,两个字符,这个printf()函数返回2,而这个返回值又被第二个printf()函数利用,打印 2 ,这是一个字符,返回 1,又被第一个printf()函数利用,最后打印 1。
在这里插入图片描述

五、函数的声明和定义

1.函数的声明

(1)函数声明是先告诉编译器有一个函数、返回值、函数名和参数,但是具体有没有还不知道。
(2)函数声明一般放在函数使用前,要先声明后使用
(3)函数声明一般放在头文件中。

2.函数的定义

int ADD (int x,int y)
{
    
    return x + y;
}

定义返回值、函数名和参数以及函数的使用。

六、函数的递归

1.递归的概念

函数的递归通俗来说就是函数自己调用自己的过程。递归可以分解,“递”可以解释成递推,“归”可以解释成回归。后面我们会用代码和图来进行解释。
递归是解决问题的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

2.递归的两个必要条件(附小练习)

(1)递归存在限制条件,当满足这个限制条件时,递归便不再继续。
(2)每次递归后都越来越接近这个限制条件。
注意:这两个限制条件都存在,递归不一定正确,但这两个限制条件缺失,递归一定不正确!所以写完递归要先检查这两个限制条件。

小练习
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4

解:
首先我们先整理思路(大事化小),我们可以这样想:

//pint(123) 4
//print(12) 3 4
//print(1) 2 3 4
//1 2 3 4

对于1234这个数值,我们可以先print(123),再打印4。对于123这个数值,我们可以print(12)再打印 3 ,以此类推。

#include <stdio.h>

void print(int x)
{
    
    if (x > 9)
    {
    
        print(x / 10);
    }
    printf("%d ",x % 10);
}

int main()
{
    
    int a = 1234;
    print(a);

    return 0;
}

我们再利用图来分析一下。
在这里插入图片描述
左边的图称为左图,右边上方第一个图称为右一,中间称为右二,下方称为右三。
我们来分析一下,左图拿到数值1234,进行if条件判断,符合条件,再将123传入右一种函数,接收到123,判断if条件,符合,然后将12传给右二函数,判断if条件,又符合,再将1传给右三函数,发现if条件不符合,然后打印 1 % 10,也就是打印 1。(这个过程称为递推,也就是之前概念中说的递推)
打印完 1 之后就开始回归了,如下:
在这里插入图片描述
右三打印完 1 之后,回归到右二的if语句后面,也就是if语句中执行完了,继续执行下一行,打印 2 ,打印完后回归到右一中if的后面,打印 3,最后回归到左图中的if语句后,打印 4。此过程称为回归。
在这里插入图片描述
最后我们得到结果1 2 3 4

3.递归与迭代(附小练习)

递归我们前面说过了,迭代包括循环,循环是迭代的一种,如图。
在这里插入图片描述
我们一起来看一个小练习:

求第n个斐波那契数。(不考虑溢出)

想要求解,我们首先要搞明白一个问题,什么是斐波那契数。举个例子
1 1 2 3 5 8 13 21 34 55 … …
我们可以发现规律,其中的每个数都是之前两个数的和。
假设我们定义一个函数,那么斐波那契数的规律是这样的
在这里插入图片描述
解:

#include <stdio.h>

int Fib(int x)
{
    
    if (x <= 2)
        return 1;
    else
        return Fib(x - 2) + Fib(x - 1);
}

int main()
{
    
    int a = 0;
    scanf("%d",&a);
    int c = Fib(a);
    printf("%d\n",c);

    return 0;
}

例如我们要求第10个斐波那契数,结果是55,求的速度非常快。
在这里插入图片描述
那么我们要求第50个斐波那契数,这时候程序就非常缓慢了。
在这里插入图片描述
这是因为利用递归此代码中产生了很多不必要的重复计算,例如我们定义一个变量count,用它来看看第三个斐波那契数一共计算了多少次。

#include <stdio.h>

int count = 0;    //全局变量 count
int Fib(int x)
{
    
    if(x == 3)
        count++;  //计数第三个斐波那契数计算次数
    if (x <= 2)
        return 1;
    else
        return Fib(x - 2) + Fib(x - 1);
}

int main()
{
    
    int a = 0;
    scanf("%d",&a);
    int c = Fib(a);
    printf("%d\n",c);
    printf("%d\n",count);

    return 0;
}

当我们求第40个斐波那契数的时候,我们可以看出,第三个斐波那契数计算了三千多万次,这是非常多的的重复计算,严重拖慢了程序的运行速度。

在这里插入图片描述
接下来我们用迭代来求解一下这个问题

#include <stdio.h>

int Fib(int x)
{
    
	int a = 1;
	int b = 1;
	int c = 1;
	while (x >= 3)
	{
    
		c = a + b;
		a = b;
		b = c;
		x--;
	}
	return c;
}
int main()
{
    
	int n = 0;
	scanf("%d",&n);
	int ret = Fib(n);
	printf("%d\n",ret);

	return 0;
}

这种方法大大减少了重复的运算,这种方法是正着算,而递归是倒着推。

注意:(1)很多问题用递归的方式来进行解释,因为递归的形式更加清晰。
(2)有的时候迭代的效率比递归的效率更高,虽然代码可读性较差。

注:本次学习暂时结束了,文章中有错误、不足之处欢迎大佬指正,让我们共同学习,共同进步,一切成功的秘诀,都在于我们不懈的追求,加油!

版权声明
本文为[Snakra]所创,转载请带上原文链接,感谢
https://blog.csdn.net/Snakra/article/details/124363349