【看了这篇,函数你就知道怎么学了】

一、函数是什么?

数学中我们常常看到函数的概念,但是你了解C语言中的函数吗?维基百科中对于函数的定义:子程序

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

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

在C语言中函数的分类:

1、库函数

2、自定义函数

库函数

为什么会有库函数?
1、 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想 把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格 式打印到屏幕上(printf)。
2、 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
3、 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。
像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。
那怎么学习库函数呢? 这里我们简单的看看:

http://www.cplusplus.com/

zh.cppreference.com

这两个网站绝对是学习库函数的利器

简单来说,C语言常用的库函数都有:

●IO函数:输入和输出函数,如scanf函数、printf函数。

●字符串操作函数:进行字符串处理的函数,字符串拷贝,计算长度,字符查找

●字符操作函数:对个别字符进行处理的函数。

●内存操作函数:内存复制、查找等操作的函数。

●时间/日期操作函数:处理日期型或日期时间型数据的函数

●数学函数:数值计算的函数,如sqrt函数

●其他库函数

学习STL模板库 笔者在C++的模板库中总结了一些标准模板库的知识,有兴趣移步

自定义函数

自定义函数是我们自己根据想实现的功能,进行自己定义的函数,定义要求:返回值类型,函数名,函数参数,根据实际情况来定义。

举个栗子:

比较a,b,c三个值的大小,我们自己定义一个函数

void get_max(int x,int y,int z)//我们发现a,b,c都是int 类型,所以函数参数也都是int 类型
    //但是呢,我们想实现的这个函数,只是想的刀这三个数字中最大的数字,是不需要返回值的,所以我们可以把返回值类型直接定义为void
    //void 就是无返回值
{
    int max=0;//定义一个max来接收a,b,c的最大值
    if(x>y)
        x=max;
    else
        y=max;
    if(z>max)
        z=max;
    printf("%d",max);//直接对max进行打印     
}
int main()
{
    int a=10;
    int b=20;
    int c=30;
    get_max(a,b,c);//对函数进行调用,我们在主函数外部进行函数的实现,参数有a,b,c我们要作为参数传到函数中去
    return 0;
}

在调用get_max函数的时候,会给get_max()函数传入实际参数,当我们只有两个实际参数的时候,我们可以这样实现这个代

int get_max(int x, int y)//我们发现a,b都是int 类型,所以函数参数也都是int 类型
{
    return (x > y ? x : y);

}
int main()
{
    int a = 10;
    int b = 20;
   
    get_max(a, b);//对函数进行调用,我们在主函数外部进行函数的实现,参数有a,b我们要作为参数传到函数中去
    printf("%d", get_max(a,b));
    return 0;
}

我们还可以自己去实现各种各样的自定义函数,例如对两个数相加减,对数进行取模运算等,我们可以自己去开动脑筋,进行想象。

函数的形式参数和实际参数

形式参数

在定义函数时函数名后面括号中的变量名称称为形式参数(简称形参),即形参出现在函数定义中。形参变量只有在被调用时才会为其分配内训单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效,只有当函数被调用时,系统才为形参分配存储单元,并完成实参与形参的数据传递。在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。

就像我们在函数get_max中定义的ix和y两个参数,就是形式参数,只要保证它们的变量类型与实际参数相同,就能完成所要实现的功能。

实际参数

主调函数中调用一个函数时,函数名后面括号中的参数称为实际参数(简称实参),即实参出现在主调函数中。

实参可以是常量,变量,表达式,函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传递给形参。因此应预先用赋值,输入等办法使实参获得确定值。

就像上文中在get_max函数中调用的a和b两个参数,就是实际参数,它们是我们实际传过去的值,所以被称为实际参数。

说明:在被定义的函数中,必须指定形参的类型。实参与形参的类型应相同或赋值兼容。实参和形参在数量上,类型上,顺序上应该严格一致,否则会发生类型不匹配的错误。

二、函数调用

1、传值调用

向函数传递参数的传值调用方法,把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。

我们实现对a和b两个值的互换

如果不用函数的形式

int main()
{
    int a=100;
    int b=200;
    int tmp=0;
    tmp=a;
    a=b;
    b=tmp;
    printf("%d %d",a,b);
    return 0;
}

这样我们就完成了对a和b的交换。

下面我们用函数的形式看一看能不能实现:

swap(int x,int y)//实际参数为int 类型,所以我们在这里也定义两个int 类型的形式参数
{
   int tmp=0; //定义一个临时变量来作为辅助,交换二者的值
    tmp=x;
    x=y;//交换三板斧就把这两个数交换完成了,我们去编译器中实现一下
    y=tmp;
}
int main()
{
    int a=100;
    int b=200;
    printf("交换前的a=%d b=%d",a,b);
    swap(a,b);//在前面我们知道,a和b是两个实际参数,在这里再次强调,眼睛向上看,我们来实现函数了
    printf("交换后的a=%d b=%d",a,b);
    return 0;
}

得到的结果不尽如人意

交换前的a=100 b=200
交换后的a=100 b=200

这样我们想一想为什么,虽然在函数内改变了 a 和 b 的值,但是实际上 a 和 b 的值没有发生变化。

有什么办法来解决这种问题呢?

我们想到了另外一种函数调用的方法:传址调用,下面我们来共同探究一下叭

2、传址调用

这种方式使用数组名或者指针作为函数参数,传递的是该数组的首地址或指针的值,而形参接收到的是地址,即指向实参的存储单元,形参和实参占用相同的存储单元,这种传递方式称为“参数的地址传递”,地址传递的特点是形参并不存在存储空间,编译系统不为形参数组分配内存。数组名或指针就是一组连续空间的首地址。因此在数组名或指针作函数参数时所进行的传送只是地址传送,形参在取得该首地址之后,与实参共同拥有一段内存空间,形参的变化也就是实参的变化。

下面我们来看一看能不能通过传址调用的方法来进行对a和b的值进行交换

swap(int *px,int *py)//实际参数为int 类型,所以我们在这里也定义两个int 类型的形式参数
//与前面传值调用不相同的是,我们需要知道一点指针的知识,可以这样理解:有一个星号代表是指针变量,指向哪里呢,int 类型的。
//所以我们的参数类型就是int*
{
   int tmp=0; //定义一个临时变量来作为辅助,交换二者的值
    tmp=*px;这样就是对px进行解引用,是访问地址存放的值
    *px=*py;//交换三板斧就把这两个数交换完成了,我们去编译器中实现一下
    *py=tmp;
}
int main()
{
    int a=100;
    int b=200;
    printf("交换前的a=%d b=%d",a,b);
    swap(&a,&b);//在前面我们知道,a和b是两个实际参数,在这里再次强调,眼睛向上看,我们来实现函数了
    //与传值调用不同的是,我们要把a和b的地址给取出来,传到函数的形式参数上去
    printf("交换后的a=%d b=%d",a,b);
    return 0;
}

哈哈相信大家已经知道答案了

我们成功了,这样我们知道传址调用可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量

交换前的a=100 b=200
交换后的a=200 b=100

学到这里,我们所看到的函数已经初见眉目了

暂且缓一缓

接下来我们所要学习的是对函数的嵌套调用,大家都听说过俄罗斯套娃吧,下面我们所要学习的就是和俄罗斯套娃差不多的东西咯。

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

1、嵌套调用

所谓 嵌套调用 就是在一个 函数 中 调用 其他 函数 的过程叫做 函数 的 嵌套 。 C++中 函数 的定义是平行的,除了main ()以外,都可以互相 调用 。 函数 不可以 嵌套 定义,但可以 嵌套调用 。 比如 函数 1 调用 了 函数 2, 函数 2 调用 了 函数 3,这便形成了 函数 的 嵌套调用 。

举个栗子:

实现对1!+2!+…+n!的和,我们来写两个函数,一个用来计算阶乘,另一个用来求阶乘的和。

·

long long factorial(int n)//计算阶乘
{
int i=0;
long long result=1;//定义一个长整型来接收factorial的返回值
for(i=1;i<=n;i++)
{
result*=i;

}
return result;
}

long long add_factorial(long long n)//将阶乘相加
{
int i=0;
long long sum=0;
for(i=1;i<=n;i++)
	{
sum+=factorial(i);//在这个函数体内部可以嵌套调用factorial的函数,使得factorial的函数值每次都会被加到add_factorial函数中
	}
    return sum;
}
int main()
{
printf("1!+2!+...+n!的阶乘为%lld",add_factorial(6));

return 0;
}

和嵌套调用初次见面的小伙伴可能需要一点时间来考虑考虑嵌套调用的实际过程啦

很容易我们得到这个结果

1!+2!+...+n!的阶乘为873

我在《算法笔记》上还找到一个函数嵌套调用的栗子🌰

求解三个函数中的最大值,这个看起来就比较轻松啦

int max_2(int a,int b)
{
    if(a>b)
        return a;
    else
        return b;
}
int max_3(int a,int b,int c)
{
    int temp=max_2(a,b);
    temp=max_2(temp,c);
    return temp;
}
int main()
{
    int a,b,c;
    scanf("%d %d %d",&a,&b,&c);
    printf("%d\n",max_3(a,b,c));
    return 0;
}

main函数首先调用max_3函数,max_3函数中调用了max_2函数来比较两个整数的大小。

在后文中讲到的函数的递归调用大概就是函数在嵌套调用自己本身。

2、链式访问

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

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

大家想一想,这个代码会输出什么东西呢?

最右边的printf输出43,返回2,这里会有疑惑吧?你看看43是几个数字?2个吧,所以返回值是2,再来进行下一步,输出2返回1,最后输出1.

所以答案就是4321啦。

四、函数的声明和定义

函数的声明

【返回类型】 函数名(参数1类型 参数1,参数2类型 参数2,……);

就是告诉大家函数叫什么,参数是什么,返回值类型是什么,具体有没有存在,是无所谓的。

定义

定义和声明有着很大区别

【返回类型】 函数名(参数1类型 参数1,参数2类型 参数2,……)

函数主体;

我们需要在定义里进行函数具体功能的实现,在此时这个函数就是必须存在的。

不知道大家有没有听说过工程类文件的定义方式

在这里插入图片描述

比如实现一个相加值的函数需要有

add.h头文件

add头文件

add.c

在这里插入图片描述

#include”add.h”是包含了头文件,这样的话我们就不用再写一遍#include<stdio.h>了并且在后期进行其他函数的声明的时候,我们可以更加便捷地使用函数头文件中的函数

test.c

在这里插入图片描述

接下来的这一部分是函数最重要的没有之一的部分了

函数的递归调用

五、函数的递归调用

函数的递归调用就是函数的嵌套调用的一种特殊形式,表现为在调用一个函数的过程中又直接或者间接地调用了自身,实现了循环,所以说递归的本质就是循环

递归的两个必要条件

①存在限制条件,当满足这个限制条件的时候,递归便不再继续。

②每次递归调用之后越来越接近这个限制条件。

你可能知道回溯和递推

①回溯:一层层调用直至满足终止条件的过程称为回溯

②递推:从满足终止条件向外层返回的过程称为递推

进行递归的时候,我们一定要画出图谱来哦

举个栗子🌰:

计算n的阶乘

int factorial(int n)
{
if(n==1)
	return 1;
else
	return factorial(n-1)*n;/进行factorial函数的递归调用
    
}

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

我们把图给画一下
在这里插入图片描述
使用 factorial 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。
在这里插入图片描述

我们此时可以用到迭代的方式来计算n的阶乘

int factorial(int n)
{
	int result = 1;
	while (n > 1)
	{
		result *= n;
		n -= 1;
	}
	return result;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	printf("%d", factorial(n));
	return 0;
}

使用迭代的方式进行

tips:

  1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开
    销。
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值