C语言语法之函数的初步认知

1.函数是什么?

在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method,subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。

2.C语言中函数的分类

<1>库函数 <2>自定义函数

<1>我们在使用C语言编写代码时,常常会使用printf,scanf等函数去完成相应的工作。像这些基础功能,我们程序猿在开发的过程中都可能用得到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序猿。试想,倘若没有库函数,我们在使用基础功能的代码是时,都要手动编写一次这些实现基础功能的代码,可想有多么麻烦。

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

<2>自定义函数

上面刚才讲述了库函数一般都是实现基础功能,那么自定义函数就是帮助程序猿去实现更加复杂的功能。自定义函数和库函数一样,都有函数名,返回值类型和函数参数,下面是函数的组成模型:re_type fun_name(para1 ,* )//函数返回类型 函数名 函数参数

{  statement//语句项  }

3.函数的参数

<1>实际参数(实参)

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

<2>形式参数(形参)

形式参数是指函数名后括号的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

4.函数的调用

<1>传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不影响实参。

<2>传址调用:传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用参数。这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

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

函数和函数之间可以根据实际的需求进行组合的,也就是相互调用的。递归妙哉!

<1>嵌套调用

函数可以嵌套使用,但是不可以嵌套定义。

<2>链式访问

把一个函数的返回值作为另外一个函数的参数

6.函数的声明和定义

<1>函数声明:1.告诉编译器有一个函数,其函数名,参数,返回类型。但是函数具体存不存在,声明决定不了。2.函数的声明一般出现在函数的使用之前,要先满足先声明后使用。3.函数的声明一般放在头文件中。(函数的声明多放在工程量较大的项目中,这时候项目往往包含多个源文件,一个头文件,此时函数的声明能够使代码整体调理)。

     以下举一个函数声明的简单例子:(这样写是为了防止ADD函数被重复引用)

#idndef _TEST_H_

#define _TEST_H_

Int ADD(int x,int y)

#endid // _TEST_H_

<2>函数的定义

#include"text.h"

7.函数递归

<1>什么叫递归

递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

<2>递归的两个条件

存在限制条件,使得问题可以达到化简的极限,之后递归不在继续;每次递归调用后越来越接近这个限制条件。

<3>递归和迭代的选择

两者的使用取决于程序猿的思路和当前问题适用的场景,没有绝对的规则在解决问题时必须要用两者之一,可能两者都能用,可能某一个更优,另一个相对十分麻烦。下面就举两个例子来证明我的观点(一个是计算n的阶乘,另一个是计算斐波那契数列)。

首先是n的阶乘相关代码:

计算n的阶乘的代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Fac1(int n)//迭代方式
{
	int i = 1;
	int ret = 1;
	for (i = 1; i <= n; i++)
	{
		ret *= i;
	}
	return ret;
}
int Fac2(int n)//递归方式
{
	if (n <= 1)
		return 1;
	else
		return n * Fac2(n - 1);
}
int main()
{
	int n = 0;
	int ret = 0;
	scanf("%d", &n);
	ret = Fac1(n);
	printf("%d", ret);
	return 0;
}

 我们会发现用迭代和递归的方式其差异并不大,所以两者皆可。那么请看计算斐波那契数列的代码:

#include<stdio.h>
int count = 0;
int Fib1(int n)//递归
{
	if (n == 3)
	{
		count++;
	}
	if (n < 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}
int Fib2(int n)//迭代
{
	int a = 1;
	int b = 1;//初始化数列前两个计算数
	int c = 1;//如果是0则当计算目标为1,2时不符结果
	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;//以上三步完成a,b向目标斐波那契数的靠拢
		n--;//不断循环一直右移******
	}
	return c;
}
int main()
{
	int n = 0;
	int ret = 0;
	scanf("%d", &n);
	ret = Fib2(n);
	printf("%d\n", ret);
	printf("计算次数为=%d",count);
	return 0;
}

当我以递归的方式去计算第40个斐波那契数时,我会发现其运算的次数非常多,因为它并不是直接以第38,39的数去计算第40个数,而是在计算前面的所有数时,分别从1去计算,所以就会导致一个巨大的树状结构,其运算结果高达千万次,计算机也要经过几秒才能运算出结果。但是以迭代的方式去计算,会发现其计算十分迅速(相比于递归来说快多了),所以有时候递归并不一定比迭代要好。 

结论:<1>许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰.<2>但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。<3>当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿其运行时的开销。

<4>错误使用递归的结果

会导致程序崩溃,发生栈溢出现象,下面举个简单例子:

简单显示栈溢出代码
#include<stdio.h>
void text(int n)
	{
		if (n < 10000)
		{
			text(n + 1);
		}
	}
int main()
{
	text(1);
	return 0;
}

其运算结果如下图:

 以上就是对函数的基本介绍。不过别着急,在不久的将来,我们还会从内存角度去真正认识函数在栈空间的存储,那时候将帮助我们更细致的理解函数与内存的关系。最后,若有不足之处,请各位朋友不惜赐教,谢谢。》》》》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只爱喝coke的小鳄鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值