今天我们来学习一下Java当中的函数,那么在学习之前我们首先要知道为什么我们要在Java中学习函数呢?
在知道为什么要学习函数之前,我们首先要知道的是Java中函数这个概念。
函数(方法):定义在类中的具有特定功能的一段独立小程序。函数也称之为方法。
在实际当中,我们总是会遇到一些重复性的问题,这个时候去重复的写这些代码,那么势必会造成代码冗余占用内存空间这个问题,而且写出来的代码也不美观简洁,所以这个时候我们就要引入函数来解决这个问题。这个时候有人可能就会问了,循环不是也可以解决代码重复这个问题吗?与循环不同的是,函数是一个独立的代码块,而它本身具备有一些独立的功能。
函数的基本格式为:
函数类型(权限) 返回值类型 函数名(参数类型 形式参数1, 参数类型 形式参数2,)
{ 执行语句;
return 返回值;
}
函数类型:比如public,private等。
返回值类型:函数运行后结果的数据类型。
函数名:使用者给函数自定义的名称,也就是标识符。
参数类型:形式参数的数据类型。
形式参数:是一个变量,用于存储调用函数时传递给函数的实际参数。
实际参数:是具体的数据(常量、变量),在调用函数时,发送给函数体中的形式参数。
注意:在这里我们就可以说发送具体数值的一方就是实际参数,而接收数值的一方就是形式参数。
return:用于结束当前函数。
- 返回值:就是指这段具有独立功能的代码快的计算结果,该值返回调用者。
注意:
函数中有无返回值需要通过void关键字来定义。
函数运算后,如果没有具体的返回值时,这时返回值类型用一个特殊的关键字来标识。
该关键字为void。void代表的是函数没有具体的返回值类型。
当函数的返回值类型为void时,函数中的return语句可以省略不写。
- 函数的参数传递
在调用函数的时候,实际参数传递给了形式参数。
这里需要注意的是:参数传递的并不是具体的数值,本质上传送的是常量在常量池中的地址或者对象在堆内存中的地址。
- 函数运行时的本质
函数运行的本质其实是基于栈内存的
栈是一个先进后出的容器结构。
我们可以将一个栈理解为一个弹夹,子弹按照123456的顺序进入弹夹,而按照654321的顺序被打出。
而在函数运行时,我们就可以将一个函数理解为一个子弹(函数帧/栈帧),位于栈顶的主函数肯定时第一个进栈的。
代码实现如下:
class Demo{
public static void main(String[] args){
int a=4;
int b=4;
double c=pow(a,b);
System.out.println(c);
}
public static double pow(double a,int b){
if(b==0){
return 1;
}
double sum=1;
//2^4
for(int i=1;i<=Math.abs(b);i++){
sum*=a;
}
return b>0?sum:1/sum;
}
}
我们可以通过一个动图来观察一下整个代码的运行流程:
最后,一个小小的注意点 :
目前为止,定义函数的时候 ,只需要考虑返回值类型、函数名、参数列表、函数体、返回值 。
切记:千万不要在函数的内部创建函数,函数必须在类里面,函数们之间是平级关系。
在C/C++/Python当中 函数的定义必须在函数调用之前;而在Java当中 随便,之前、之后都可以。
函数(方法)的重载
在Java中,同一个类中的多个方法可以有相同的名字,只要它们的参数列表不同就可以,这被称为方法重载。
在这里要注意的是方法的重载与权限没关系,与返回值类型没关系,与参数名没关系只有和参数类型的排列组合有关系(注意一下参数类型的向下兼容问题)。
在知道方法的重载是什么后,那么到底为什么要使用方法重载那,它的有优点又是什么?
方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数或参数的顺序不同的方法。Java的方法重载,就是在类中可以创建多个方法,它们可以有相同的名字,但必须具有不同的参数,即或者是参数的个数不同,或者是参数的类型不同。调用方法时通过传递给它们的不同个数和类型的参数,以及传入参数的顺序来决定具体使用哪个方法。
重载的好处就在于我们可以扩展函数的功能(函数重名,但是参数类型不一样,执行内容也可以不一样)。
方法重载当中的一些寻找合适函数的流程:
寻找适当函数的流程
1.看是否有确切的参数定义 int+int 查看是否有(int,int)
2.看是否有可兼容的参数定义 int+int 查看是否有(double,double)
3.如果可兼容的参数定义有多个int+int,(double,int)或(int,double) 此时报错 引用不明确
函数(方法)的递归调用
递归就是一个函数在它的函数体内调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件。
当函数在一直递推,直到到达最底层后返回,这个最底层就是结束条件,一般情况下先写结束条件。
所以递归要有两个要素,结束条件与递推关系。
递归的图示:
1、前进:不停的向下深入问题,将问题一步步化小。
2、结束:到达最底层的时候,问题无法继续化小,则开始处理当前的问题。
3、返回:将小问题处理完毕后,向上返回(注意,并不是所有问题都需要返回)。
在学习递归的时候我们一般要知道以下一些问题:
- 一般而言,但凡能够被迭代(循环)解决的问题,递归都可以。
- 递归解决的问题,迭代就不一定了。
- 递归其实是分治法的一种实现方式(一种实现思路)。
- 递归就是函数在进栈,进栈的次数多了,势必会占内存,无法避免的在某些问题上,递归所写的代码要比迭代写的代码少
在某些问题上,迭代是写不出来的,所以只能用递归。
在上面我们提到递归其实是分治法的一种体现方式,那么我们就来了解一下分治法是什么。
分治法是一种算法思想,分治法主要解决的问题是将大问题,进行拆分,拆分成若干个小的问题进行求解,最终将每个小问题的解进行合并。
其实,分治法就是一种暴力破解法(穷举),也是一种搜索最优答案的算法。
在了解了递归的思想之后,我们用它来解决一下斐波那契问题。
斐波那契数列就是每一项是前两项之和。
1,2,3,5,8,13,21......
斐波那契数列 求前20项
1 1 2 3 5 8 13 21 34 55 ......
f(n)指的是斐波那契的第n项
f(n)=f(n-1)+f(n-2) f(1)=1 f(2)=1
f(5)
f(4) f(3)
f(3) f(2) f(2) f(1)
f(2) f(1)
class Test{
public static void main(String[] args){
for(int i=1;i<=20;i++){
System.out.println(fibo(i));
}
public static int fibo(int n){
if(n==1||n==2){
return 1;
}
return fibo(n-1)+fibo(n-2);
}
}