C语言基础:函数

目录

一、函数的定义

二、函数的分类

 1. 库函数

2.自定义函数

函数的组成:

三、函数的参数

1. 实际参数(实参)

2.形式参数(形参)

四、函数的调用 

1.传值调用

2.传址调用

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

1.嵌套调用

2.链式访问

六、函数的声明和定义

七、函数递归


一、函数的定义

函数是一个子程序。子程序(英文:Subroutine, procedure, function, routine, method, subprogram, callable unit)是大型程序中的一部分代码,由一个或者多个语句块组成,负责完成某项特定的任务,具有相对的独立性。

一般会有输入参数和返回值,对过程进行封装同时也提供对细节的隐藏。这些代码通常被集成为软件库。

二、函数的分类

 1. 库函数

C语言常用的库函数包括:

                IO函数

                字符串操作函数

                字符操作函数

                内存操作函数

                时间/日期函数

                数学函数

                其他库函数

注意使用库函数一定要包含#include对应的头文件。这里推荐微软的MSDN Library,MSDN Library汇集了所有微软的开发技术文档,我们不需要记住所有的库文件,但是需要学会使用查询工具。

2.自定义函数

库函数无法包含我们日常所有的需求,所以我们需要自己设计函数,这些函数称为自定义函数。

函数的组成:

ret_type fun_name(para1,*)

{

        statement;//语句项

}

ret_type是函数的返回类型,fun_name是函数名,para1是函数参数,函数往往可以有多个参数。

三、函数的参数

1. 实际参数(实参)

实参是真实传给函数的参数。它可以是常量,变量,表达式,函数等等,但在进行函数调用时,必须有确定的值,以便把这些值传给形参。

2.形式参数(形参)

形式参数是指函数名后括号中的变量,只有在函数被调用的过程中形参才会实例化,当函数调用完成之后形参会自动销毁,所以形参只在函数中有效。

注意:形参实例化之后相当于实参的一份临时拷贝。

看如下代码:通过自定义函数交换两个整形变量的内容。

 可以看出num1和num2的值并没有交换成功,这就是因为实参num1和num2传给形参x,y的时候,形参将是实参的一份临时拷贝,改变形参x,y,不会影响实参num1和num2.

正确版本:

四、函数的调用 

1.传值调用

传值调用是指调用参数时,不对原参数进行操作,创建参数的拷贝并对其进行操作。函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

2.传址调用

传址调用:把函数外部创建变量的内存地址传递给函数参数的一种调用函数方式。

这种传参方式可以让函数和函数外部的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

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

函数之间可以根据需求进行组合,即互相调用。

1.嵌套调用

注意:函数可以嵌套调用,但是不能嵌套定义。

2.链式访问

链式访问是把一个函数的返回值作为另外一个函数的参数。

#include <stdio.h>
#include <string.h>
int main()
{
  char arr[20] = "hello";
int ret = strlen(strcat(arr,"bit"));
printf("%d\n", ret);
return 0;
}

再看如下这段代码,思考一下结果是什么?
#include <stdio.h>
int main()
{
  printf("%d", printf("%d", printf("%d", 43)));
  return 0;
}

博主在刚碰到这段代码的时候,乍一看以为结果就是43,但是实际上的结果如上图所示,是4321。为什么会出现这种现象呢,因为printf函数的返回值是打印在屏幕上字符的个数 ,43返回的值是2,而对于2的返回值是1,所以结果是4321.这就是一个经典的链式访问的例子。

六、函数的声明和定义

1.函数声明

函数声明是告诉编译器函数的名称,参数,返回类型。但是一个函数具体是不是存在,函数声明决定不了。
函数的声明一般出现在函数的使用之前。也就是先声明后使用。通常会建立一个头文件,如test.h放置函数声明。如下图就是声明了一个Add的函数。

2.函数定义

函数的定义指的是函数的具体实现,它交待函数的功能实现。下图就是上图中声名的Add函数的具体实现。

七、函数递归

1.什么是递归?

递归( recursion)是程序调用它自身。它是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。

递归的主要思考方式就是:大事化小。通常只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

2.递归的两个必要条件

1)递归存在限制条件,当满足限制条件的时候,递归就不再继续。
2)每次调用递归后会越来越接近这个限制条件。

举一个递归小例子:

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

正常思路:如下图所示,通过求余,然后除以10,再求余,再除以10...以此循环直到最后一位,最后倒着打印就可以了。

 再看看递归怎么做:

实现思路如下图所示,本着大事化小的原则,定义了一个print函数,它可以打印参数部分数字的每一位。当传入的数字大于9的时候,通过除法操作(除以10)来获取传入数字的每一位,再通过求余打印出该数字。

3.递归与迭代

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

int fib(int n)
{
if (n <= 2)    
return 1;
  else
  return fib(n - 1) + fib(n - 2);
}

这是以递归的方式求第n个斐波那契数,但是在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。如果数字再大一点比如求10000的阶乘(不考虑结果的正确性),程序会崩溃。

这是因为 fib 函数在调用的过程中很多计算其实在一直重复,修改一下代码:

int count = 0;//全局变量
int fib(int n)
{
if(n == 3)
count++;
if (n <= 2)    
return 1;
  else
  return fib(n - 1) + fib(n - 2);
}

最后我们输出看看count,是一个很大很大的值。在调试的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)这样的信息。
因为系统分配给程序的栈空间是有限的,当出现了死循环,或者(死递归),这样有可能导致一
直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。

那怎么取解决这种现象呢?

 1)将递归改写成非递归。
2)使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代
nonstatic 局部对象(即栈对象),不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

如下面的代码采用了非递归的方式解决了求斐波那契数的问题:

int fib(int n)
{
  int result;
  int pre_result;
  int next_older_result;
  result = pre_result = 1;

  while (n > 2)
  {
     n -= 1;
     next_older_result = pre_result;
     pre_result = result;
     result = pre_result + next_older_result;
  }
  return result;
}



我们平时遇到的许多问题是以递归的形式进行解释的,因为它比非递归的形式更为清晰,但是这些问题的迭代实现往往比递归实现效率更高。当一个问题相当复杂,难以用迭代去实现,此时递归实现的简洁性便足以补偿它运行时的开销。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值