关于函数的一些小总结

本文深入探讨了C语言函数的概念、分类(库函数与自定义函数)、参数传递(实参与形参)、调用方式(传值与传址)、嵌套调用与链式访问,以及函数声明、定义和递归的原理与应用。实例演示了如何避免递归陷阱,提升代码效率。
摘要由CSDN通过智能技术生成

        复工复工!!!

        今天哦我们来讲一讲关于函数的一些事情。

1.函数是什么

        关于函数是什么,其实在前面有提到过,这里再简单的说一下。其实C语言中的函数定义和我们数学中的函数有异曲同工之妙,其实就是它的功能就是:给他一个数,进入他之后经过处理,出来另一个数或者执行相关的一些结果。


2.函数的分类

        在C语言中,将函数分为两类:库函数和自定义函数。

2.1库函数

        库函数其实就是C语言的基础库中提供的一系列的函数,就是人家写好的代码,包装完毕后可以直接供给程序员直接使用的函数。为什么要有库函数呢?因为在日常开发的时候,并不是所有的代码都是业务性的代码,很多基础功能需要频繁的使用,比如打印信息到屏幕上(printf)、字符串拷贝(strcpy)、计算n的k次方(pow)等等,这些基础功能代码被写成固定的函数,存在基础库中,方便程序员直接调用来进行软件开发。

        对于库函数,我们可以去www.cplusplus.com 中查找其具体功能以及一些返回值、数据类型等细节问题,下面是举例字符串拷贝函数相关搜索结果的介绍。

         我们在引入库函数的时候,务必要记得在前面包含对应的#include 头文件,否则无法使用。函数的默认返回值为 int,但是平时写代码应该标明返回值类型。

2.2 自定义函数

         库函数是前辈们对于编程开发过程中一些常用功能的模块化,就好像我们盖大楼的时候,我们需要砖块,我们不一定要去自己烧制,而是直接拿来别人做好的,但实际盖楼的时候砖块的正确使用、砖块与砖块之间的连接以及很多需要自己浇筑的流程,砖块对于盖大楼的作用就好像我们写代码时候,库函数对于整个过程的作用。方便后面的使用而写好的,实际开发中,更加重要的是自定义函数,白话来说就是自己定义出来的函数,与库函数不同的就是,这是你自己来设计的。我们来简单实现一个交换数值的函数:

#include<stdio.h>

void swap(int x, int y)
{
    int tmp = 0;
    tmp = x;
    x = y;
    y = tmp;
}

int main()
{
    int a = 10;
    int b = 20;
    swap(a,b);
    printf("a=%d,b=%d",a,b)
    return 0;
}

        不知道有没有这样写,或者是和这个类似的同学,我最开始的想法就是这样的,可结果输出来之后,却发现两个数并没有交换。简单说一下原因:这里面a和b我们称为 实际参数,是实际在空间中开辟并存储的,而swap中接收参数的x和y虽然在数值上是一样的,但本质却并不是a和b,而是属于形式参数。我们在调用函数时候,函数内部负责接收实参的形参需要自己开辟新的空间,有着属于自己的地址,在这个地址中存放和实参一样的数值,但地址不相同。而形式参数在函数结束之后是会随之销毁的,形式参数无论怎么变化,是无法影响实际参数的。我们只有通过地址找到实际存放的a和b,再对其进行交换,才能实际的改变数值,因为地址是唯一的,实参在开辟空间存放的时候,有着属于自己的地址,一旦将确定的地址里面存放的相关值改变,实参就改变了。这么说可能有点绕,其实我们调用函数传参数的时候,就是又复制一遍实际参数,然后将复制的东西拿走了,你无论怎么改变你备份出来的那两个数值,都没办法改变原来的数值。所以正确的写法是:

#include<stdio.h>

void swap(int* x, int* y)     //用指针来接收传过来的地址
{
    int tmp = 0;
    tmp = *x;               //通过解引用指针,获得整形数值并重新赋值
    *x = *y;                //交换
    *y = tmp;               //交换
}

int main()
{
    int a = 10;
    int b = 20;
    swap(&a,&b);    //传递实际参数的地址
    printf("a=%d,b=%d",a,b)
    return 0;
}

3. 函数的参数

         上面我们有提到实际参数和形式参数,下面我们来仔细接介绍一下:

3.1实际参数

        实际参数简称为实参,是指真传给函数的参数,可以是常量、变量、表达式、函数等。无论是参数哪种类型的量,在进行函数调用的时候,必须有确定的值,以便把这些值顺利的传给形参。

3.2形式参数

        形式参数简称为形参,指的是函数名后面括号中用来设置接收实参的变量,形式参数只有在函数被调用后才会开辟内存空间来存放接收(复制过来)的实参,当函数结束调用的时候就会自动销毁,将占据的内存空间重新的归还给内存,因此形式参数只在函数中有效。

        形式参数可以简单的被认为是对实际参数的一份开辟了一个属于自己空间的临时拷贝。上面的交换函数中,&a和&b其实就是实际参数,而x和y就是形式参数。


4. 函数的调用

        关于函数的调用分为两种:传值调用和传址调用。传值调用就是传递数值进入到函数中,对于实际参数没有影响;传址调用就是传递地址,这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操 作函数外部的变量。上面提到的两个函数示范就分别为以上两种方式。

        传值:

        传址:


 5、嵌套调用和链式访问

        函数和函数之间可以根据开发人员的需要来进行组合使用,互相调用。

#include <stdio.h>
void answer()
{
    printf("我不是大傻子!\n");
}

void question()
{
    printf("你是大傻子吗?\n");
    answer()
}

int main()
{
    question();
return 0;
}

        如例子所示,函数question()中就调用了函数answer(),同样的,answer()中也可以继续调用其他函数。函数可以嵌套调用,但不允许嵌套定义。关于嵌套定义,举个例子给大家看一下。

void question()
{
    printf("你是大傻子吗?\n");
    void answer()
    {
        printf("我不是大傻子!\n");
    }
}

        嵌套定义就是在一个函数定义中又定义另一个函数,这是不允许的。

        函数的链式访问就是把一个函数的函数值作为另一个函数的参数,所以这就要求被作为参数的函数必须有返回值。

#include<stdio.h>
int main()
{
	printf("%d",printf("%d",printf("%d",43)));
}

这段代码就是函数的链式访问,最里面的返回一个值给第二个printf,第二个返回一个值给最外面的printf,实现了这段代码的功能。printf的返回值其实就是其在屏幕上打印字符的个数,所以最里面在屏幕上打印完“43”之后返回一个“2”给第二个printf,第二个实际意义就为printf("%d",2),所以又在屏幕上打印出来一个“2”,返回值为“1”,最后打印出“1”,最终屏幕显示结果为:4321。


6、函数的声明和定义

6.1 函数的声明

        函数的声明其实就是告诉电脑的编译器,有一个函数叫什么、参数是什么、返回类型是什么,至于函数实现的功能或者具体存不存在都是他无法决定的。事先声明在这里也是适用的,在哦我们使用函数的时候,一定要先声明,一般声明都放在头文件中。

int Add(int x,int y)

        这就是一段简单的函数声明,意思就是有一个叫做 Add 的函数,有两个均为整型的参数,分别为 x 和 y ,函数的返回类型为整型。 

6.2函数的定义

         函数的定义指的是函数实现功能的具体过程,交代清楚函数是如何实现特定功能的。

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

         上面代码就是函数的定义。


7、函数递归

 7.1 什么是递归

        程序调用自身的这种编程技巧称之为递归。一个过程或者一个函数在其定义或者说明中有直接和间接调用自身的过程都属于递归。这种方法通常可以把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题,然后进行求解。这种方法的特点是只需要通过少量的程序就可以描述出解题过程中需要的多次重复计算,一定程度上减少了程序的代码量。

        递归需要两个必要条件:有一个限制条件,当满足这个限制条件时,递归便不再继续;每次递归调用后都要越来越接近这个限制条件。

        举例:输入 1234 输出 1 2 3 4

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

int main()
{
    int a = 0;
    scanf("%d",&a);
    print(a);
    return 0;
}

        这段代码就是通过简单的递归方式来实现的对应功能。

7.2 递归与迭代

        其实很多时候,一种函数的实现既可以使用递归方法,也可以使用非递归的方法来解决,比如迭代(属于循环的一种)方法,有时候很好的能够解决一些问题,比如求一个数的阶乘。

int fac(int n) 
{
    int result = 1;
    while (n > 1)
    {
         result *= n ;   //实现result=n*(n-1)*(n-2)……
         n -= 1;
    }
    return result; 
}

int main()
{
    int n=0;
    int a=0;
    scanf("%d",&n);
    a=fac(n);
    printf("%d",a);
}

        有些时候递归的方式虽然代码写起来简单精练,但实际操作却繁琐复杂,比如我们来求一下第n个斐波那契数。首先介绍一下什么是斐波那契数列:1,1,2,3,5,8,13……第一个和第二个数为1,从第三个数开始为前两个数之和。

//递归方式求第n个斐波那契数

int fib(int n)
{
    if(n<=2)
    return 1;
    else
    return fib(n-1)+fib(n-2);  //这段代码看上去很简练,就是一直向下传递,直到找到 n-1=2和n-2=1这两个等式的时候,到达底部,然后依次加回来,归到最后的结果。比如我们输入一个50,程序执行的计算顺序大概是:
50
49 48
48 47 47 46
47 46 46 45 46 45 45 44
……
会发现有很多重复的计算,越往底层分支越多,计算量就越大,系统分配给程序的栈空间是有限的,如果计算值过大或者出现死循环、死递归的现象,就会导致栈溢出。
}

int main()
{
    int a=0;
    int result=0;
    scanf("%d",&a);
    result=fib(a);
    printf("%d",result);
}
//非递归方式
int fib(int n)
{
    int a=1;
    int b=1;
    int c=1;
    while(n>=3)
    {
        c=a+b;
        a=b;
        b=c;
        n-=1;
    }
    return c;
}
        许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值