函数
huan? han? 这两个读音早就搞不清楚了,怪不得函数也搞不清楚。
讲函数之前感觉十分有必要先对这个东西进行一个浅显易懂的认识。
额 刚去百度了下,答案十分统一都是怎么说的呢?
函数主要解决的是那些重复的且具有独立功能的代码段......巴拉巴拉一大堆,也没有说是举个栗子,就比较难。
4.1 函数的定义
函数存在的意义(一句一句来)
函数主要解决的是那些重复的且具有独立功能的代码段
将这些具有独立功能的代码可以进行再次封装 封装出来的东西就成为是函数
(什么意思呢?也就是说你写的程序里出现很多功能重复的代码,只不过就是这些代码解决的问题是不一样的,但是功能相同,举个栗子,你想要在一段代码里求一个绝对值,然后你巴拉巴拉写下一大堆,这个问题解决了,但是你一看题发下需要你再求一个函数的绝对值,然后你又巴拉巴拉写下一大堆,麻不麻烦?冗余不冗余?当时胖胖老师讲的时候我就真想说不麻烦,但后来慢慢的理解了,确实是有点麻烦,只不过从主函数到其他函数有点不太理解而已,慢慢的熟练了就好了。)
上面的这个例子刚好承上启下,怎么硕呢?这样写的优点是什么呢
- 降低了代码冗余,复用函数即可。
- 降低了主函数的代码量,将主函数进行适当的拆分,以便内存优化
(这个又怎么理解呢?现在解决的都是比较小的问题,那么一旦这是一个特别大的问题,你的主函数就好长好长,这个时候编译报错,即便你知道错误是啥,你找也得好长时间,因为你得从头向下找,但是如果有函数,你就可以把主函数拆分成好多个部分,这个时候你找问题就可以在主函数里找,然后对应的去实现这个功能的函数里找问题,是不是方便多了。)
函数的格式
-
访问权限
指的就是函数的使用范围(内部和外部)
就好比你的东西有权限,比如你的钱包是你的隐私,你的外套是公用的!!!(因为我突然想起上高中那会,你要是穿个好看的衣服,和你一起的那些小伙子们就要穿。。。这个栗子有点故事),这些权限就是,protect,public,private。还有一种就是默认不写就自动的认为是private,就比如你的好多东西没给其他人说这些是什么权限,但是别人都会默认这些东西是你自己的隐私呗。
-
函数类型
指的就是函数类型分类,说的就是函数的具体使用场景和场合
static 静态函数 abstract 抽象函数 native 本地函数 synchronized 同步函数,先不说这几个,因为我也不知道
后边碰到了再说
-
函数名
就是程序员给这一段代码自定义的名称(标识符)
(虽然这么说,但是人家国际上早就有固定的函数名,咦,比如主函数,人家说main是标识符,但是你好意思改吗,就算好意思,你早就把public static void main背的滚瓜烂熟了)
-
参数列表
参数列表有若干个 参数类型,参数名... 组成 主要用于接收外界传递给函数的一些数据
(就是一个函数后边跟一个(),构造函数的意思,你比如说你的括号里是int那么这个int代表的是你的参数列表允许接收外界传来的int类型的值,如果不是int的话就报错了)
-
函数体
就是那些具有独立功能的代码块
(这不扯犊子吗,函数体不就是大括号里边的东西么)
-
return
仅仅表示结束当前函数,如果有返回值,则函数结束前将返回值返回给调用者
(以前学c的时候不管有没有返回值结尾总要跟个return现在,乍一看java中的主函数后边一般都没有,实际上,只是隐藏了不是默认没有,其他函数在实现的时候就会出现这个return,这个return就是将返回值返回调运这个函数的那个变量吧(应该是变量,反正就是调用这个函数的东西))
-
返回值
指的就是这一具有独立功能的代码块的计算结果,需要传递给外界 配合return
(这没啥说的,意思就是你要返回的值么)
-
返回值类型
就是返回值的数据类型(可兼容)
4.2 函数的分类
按照有无返回值和有无参数把函数分为了四部分
有返回值的函数,参与运算、输出、赋值 无返回值的函数,仅仅调用(怎么通俗易懂的说呢,就是说有返回值的能干实在事,无返回值好比是废物)看他有没有用主要就是看他有没有返回值,就好比参数列表是你食的俸禄,而返回值就是你做的事情,你无参数无返回值 这就是无业游民,有参数无返回值就是贪官,无参数有返回值这是为人民服务,有参数有返回值这叫拿一份钱干一份事。
-
有返回值有参数
-
有返回值无参数
-
无返回值有参数
-
无返回值无参数
4.3 函数传参
实参和形参的区别(摘自百度用户:“从头再来好风彩”)
一、函数中使用:
形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。
实参出现在主调函数中,进入被调函数后,实参变量也不能使用。
二、调用
形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量。
实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值。
三、不是指针类型在内存中位置不同:
当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。
在一般传值调用的机制中只能把实参传送给形参,而不能把形参的值反向地传送给实参。(就是说形参这个人比较自私,实参把东西分享给形参,形参一辈子都不会把值分享给实参)因此在函数调用过程中,形参值发生改变,而实参中的值不会变化。(越想越气,原来形参还是一个如此多变的一个人,看看人家实参多老实)
实际参数-实参
就是在调用函数的时候,给函数传递的数据(常量,变量)叫做实参
(举个栗子,int a=4;a就是实参)
形式参数-形参
就是定义函数的时候,参数列表当中的数据,叫做形参
就是等待老实人形参传值的就是形参
实参是将什么东西传递给了形参呢
前边学习变量的时候就是知道了,传的是地址,地址也有两种一是常量在常量池中的地址,二是对象在堆内存中的地址
局部变量
但凡是在函数中创建的变量 称之为是局部变量 局部变量的作用域仅仅在当前的函数当中,所以形参一定是局部变量。
也不一定都是在其他函数里才有局部变量,比如在for循环里定义的一个变量,出了循环就不能被识别了。
4.4 函数栈
函数的运行是基于栈内存的
栈是啥玩意呢?通俗的来说栈可以是一个量筒也就是一个容器,所谓先进后出是啥意思呢,好比你给量筒里放乒乓球,第一个放进去的一定是最后一个出来的,这就是所谓的栈(一个先进后出的容器结构),每一个乒乓球就是(函数栈或者是栈帧),位于栈顶即量筒的顶部的函数帧是优先运行的。
最重要的是主函数永远是第一个进栈的,什么时候结束呢?当且仅当return结束了当前函数后,该函数弹栈(即出栈)
4.5 函数的重载
函数的重载指的就是同一个类中出现的同名函数
什么意思呢?看代码
class Test01{
public static void main(String[] args){
int a=3;
int b=4;
System.out.println(add(a,b));
double d=3.14;
double e=5.44;
System.out.println(add(d,e));
System.out.println(add(3,3.14));
System.out.println(add(3.13,9));
}
//对两个小数进行加法运算
// public static double add(double c,double d){
// return c+d;
// }
public static String add(String a,String b){
System.out.println("String+String");
return a+b;
}
// public static double add(int a,double b){
// System.out.println("int+double");
// return a+b;
// }
// public static double add(double a,int b){
// System.out.println("double+int");
// return a+b;
// }
public static double add(double a,double b){
System.out.println("double+double");
return a+b;
}
//对两个整数进行加法运算
// public static int add(int a,int b){
// System.out.println("int+int");
// return a+b;
// }
}
这段代码中定义了两个整形参数两个浮点型的参数
且调用了相同的函数add()只不过add的参数列表和函数类型不一样,会不会觉得多此一举呢?我们可以编译试一下,当求两个浮点型的变量的和时调用了int类型的函数且参数列表的传参列表也是整形,这个时候会出现编译报错,(意思是从double转int会有损失,和我们前边学的变量之间的转化一样的),这就解释了为什么会有同名函数,函数的重载与权限没关系,与返回值类型没关系,与参数名没关系,只有和参数类型的排列组合有关系(注意一下参数类型的向下兼容问题)。
那么重载有什么好处呢?直接写一个double不就得了?然而重载的好处就在于我们可以扩展函数的功能(函数重名,但是参数类型不一样,执行内容也可以不一样)同样的,怎么寻找适当的函数流程呢?
寻找适当函数的流程
1.看是否有确切的参数定义 int+int 查看是否有(int,int)
2.看是否有可兼容的参数定义 int+int 查看是否有(double,double)
3.如果可兼容的参数定义有多个int+int,(double,int)或(int,double) 此时报错 引用不明确
4.6 函数的递归调用
递归的体现就是函数自身调用函数自身
那么用for循环不好吗?(迭代)为什么还要搞得这么麻烦
一般而言,但凡能够被迭代(循环)解决的问题,递归都可以,但是递归解决的问题,迭代就不一定了。
递归其实是分治法的一种实现方式(一种实现思路)所谓分治法即:
分治法是一种算法思想,分治法主要解决的问题是将大问题,进行拆分,拆分成若干个小的问题进行求解,最终将每个小问题的解进行合并。其实,分治法就是一种暴力破解法(穷举),也是一种搜索最优答案的算法
递归就是函数在进栈,进栈的次数多了,势必会占内存,无法避免的(也就是说,相同的问题迭代和递归同时解决,所用的时间迭代是比递归要短的,而递归虽然代码量不多,但是递归因为占内存,所以时间会很长)
在某些问题上,递归所写的代码要比迭代写的代码少,在某些问题上,迭代是写不出来的,所以只能用递归。
递归:先递,后归(就是把大问题变成许许多多的小问题)而递归之所以慢是因为递归的时间复杂度高(关于时间复杂度,后边说)
前进段:指的就是讲问题从大化小
结束段:问题无法再继续化小,则处理当前的问题
返回段:将小问题处理完毕之后,向上返回(有些问题是不需要返回的)
问题1:计算1+2+3+4+5+...+98+99+100
f(n) 是求1~n的累加
f(100)就是我们当前最大的问题
写递归时,一定要先写它的end!
↓ 递
f(n)=f(n-1)+n
f(100)=f(99)+100 return 1+2+3+..+99 +100
f(99)=f(98)+99 return 1+2+...+98 +99
...
f(4)=f(3)+4 return 1+2+3+4
f(3)=f(2)+3 return 1+2+3
f(2)=f(1)+2 return 1+2
f(1)=1 return 1
→ end ↑ 归
这就是利用递归去解决前100项和的思想
class Test02{
public static void main(String[] args){
System.out.println(f(10));
}
public static int f(int n){
if(n==1){
return 1;
}
return f(n-1)+n;
}
}
4.7 常用函数
具体参照
JavaTM Platform Standard Edition 6
API 规范