第5章 函数
前言
从理解函数的概念、清楚函数的运行原理、清楚函数重载问题和理清函数递归的思路以及结合着相关例题可以更加清晰,正确认识Java中的函数(方法),并且可以灵活运用。
一、函数的概念
1.什么是函数
函数的定义就是指一段具有独立功能的代码,减少代码冗余,提高程序的利用率和效率。Java中,函数又被称为方法。
函数的主要作用是为了提高代码的复用性。我们应该如何去定义函数呢?
- 需要一个封闭的空间,将这段独立性的代码进行封装,用一对大括号{};
- 需要对每一个封闭的空间进行命名,函数名;
- 函数所需要的一些原始数据;
- 函数所产生的一些结果数据;
2.函数的语法格式
> 修饰符 函数类型 返回值类型 函数名(数据类型 数据1,数据类型 数据2,...) {
> 独立功能的代码片段;
> return 函数的计算结果;
> }
- 修饰符:指的是函数的访问权限,public private 默认 protected;
- 函数类型:函数的分类,本地函数native,静态函数static,同步函数 synchronized;
- 返回值类型:指的就是函数计算结果的数据类型 如果函数没有返回值 则为void;
- 函数名:就是函数的名称;
- 参数列表:指的是外界向函数传入的数据(实际参数,由这些参数变量进行接收(形式参数);
- 函数体:具有独立功能的代码片段;
- return:仅仅表示函数结束!如果函数有返回值,则return后跟返回值;如果没有返回值,则 return可以不写,但是是存在的(隐藏的 在最后一行);
3.根据形参和返回值来看,函数有如下几个分类(根据实例结合理解)
- 有参数有返回值
1.求三个数字当中的最大值
public class Sample {
public static void main(String[] args) {
int max = getMax(1,2,3);
System.out.println(max);
}
public static int getMax (int a , int b , int c) {
/*
if (a >= b && a >= c) {
return a;
} else if (b >= a && b >= c) {
return b;
} else {
return c;
}
*/
if (a >= b && a >= c) {
return a;
} if (b >= a && b >= c) {
return b;
} if (c >= a && c >= b) {
return c;
}
return -10000;
}
}
2.计算一个数字a的b次幂
public class Sample {
public static double pow (double a , int b) {
if (a == 0) {
return 0;
}
if (b == 0) {
return 1;
}
double result = 1.0;
for (int i = 0; i < Math.abs(b) ; i++) {
result *= a;
}
return b > 0 ? result : 1 / result;
}
public static void main(String[] args) {
System.out.println(pow(2,2));
System.out.println(pow(2,-3));
}
}
- 有参数没返回值
将三个字符串的反转,并拼接打印
public class Sample {
public static void main(String[] args) {
print("123","456","789");
}
//123 456 789 =? 321654987
public static void print(String s1 , String s2 , String s3) {
System.out.println(reverse(s1) + reverse(s2) + reverse(s3));
}
public static String reverse(String s) {
String res = "";
for (int i = s.length() - 1; i >= 0; i--) {
res += s.charAt(i);
}
return res;
}
}
- 没参数有返回值
获取当前时间的字符串
public class Demo07{
/*
数据:毫秒数、总秒数、当前秒数、总分钟数、当前分钟数、总的小时数、当前小时数
*/
public static void main(String[] args){
String currentTime = getTime();
System.out.println(currentTime);
}
public static String getTime() {
//1.获取毫秒数
long totalMilliseconds = System.currentTimeMillis();
//2.计算总秒数
long totalSeconds = totalMilliseconds / 1000;
//3.计算当前秒数
long currentMillis = totalSeconds % 60;
//4.计算总分钟数
long totalMinutes = totalSeconds / 60;
//5.计算当前分钟数
long currentMinutes = totalMinutes % 60;
//6.计算总小时数
long totalHours = totalMinutes / 60;
//7.计算当前小时数
long currentHours = totalHours % 24;
return currentHours + ":" + currentMinutes + ":" + currentMillis;
}
}
- 没参数没返回值
获取当前时间的字符串
public class Demo07{
/*
数据:毫秒数、总秒数、当前秒数、总分钟数、当前分钟数、总的小时数、当前小时数
*/
public static void main(String[] args){
String currentTime = getTime();
System.out.println(currentTime);
}
public static String getTime() {
//1.获取毫秒数
long totalMilliseconds = System.currentTimeMillis();
//2.计算总秒数
long totalSeconds = totalMilliseconds / 1000;
//3.计算当前秒数
long currentMillis = totalSeconds % 60;
//4.计算总分钟数
long totalMinutes = totalSeconds / 60;
//5.计算当前分钟数
long currentMinutes = totalMinutes % 60;
//6.计算总小时数
long totalHours = totalMinutes / 60;
//7.计算当前小时数
long currentHours = totalHours % 24;
System.out.println(currentHours + ":" + currentMinutes + ":" + currentMillis);
}
}
总结定义函数时需要考虑的有哪些?
- 函数的运行有哪些未知的参数?
- 函数的运行结果有是什么?
- 明确参数和结果
- 明确内容和返回
- 函数到底要干什么?尽量将独立功能且重复性较高的代码片段提取出来
二、函数的运行原理
函数的运行是基于栈运行的
栈:是一种先进后出的容器,我们这里面所说的栈是指JVM中的栈内存空间。
每一个函数,叫做栈帧 —— 栈帧中所包含的内容有函数的定义,参数列表,函数的执行内容代码每一个函数要运行,就相当于这个栈帧进入到栈内存中 ——入栈,如果一个函数即将结束,将这个栈帧从栈顶移出——出栈
如果栈内存中有多个栈帧,运行的是最上面的栈帧,底下的栈帧暂停运行,直到该栈帧为栈顶元素。
比如:主函数先进栈,开始逐行运行,如果执行到第n行,调用另外一个函数A,则主函数在第n行暂停运行,将另一个函数A的栈帧入栈,再继续逐行运行,直到函数A的内容执行完毕,函数A出栈,主函数接着从第n行继续向下执行。以此类推。
函数运行进栈出栈图
public class Sample {
public static void main(String[] args) {
int a = 10;
int b = 20;
String s1 = "123";
String s2 = "456";
int c = test1(a,b);
System.out.println(a);
System.out.println(b);
System.out.println(c);
String s3 = test2(s1,s2);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
public static int test1(int a, int b) {
a = 20;
b = 30;
return a + b;
}
public static String test2(String s1, String s2) {
s1 = s1.replace("1","hehe");
s2 = s2.replace("4","heihei");
return s1 + s2;
}
}
三、函数重载
同一个类中可以出现多个同名函数,这个现象就叫做函数的重载(overload)
如何来区分同名函数是否是重载关系呢?前提必须是同名,和返回值类型无关(返回值类型只和函数的计算功能相关),和权限也没有关系,和形式参数的名称也无关!只和形式参数的数据类型有关(数量,排列组合)
public static void show(int a ,float b ,char c){}
下列哪些是该函数的重载:
- int show(int x, float y, char z) :不算重载 数据类型都是int float char
- void show(float b,int a,char c):算重载,顺序不一样float int char
- void show(int a,int b,int c):算重载,顺序不一样int int int
- double show():算重载,参数不一样
寻找重载函数的流程:
- 看是否有确切的参数定义匹配,int int 找 int int
- 看是否有可兼容的参数定义匹配,int int找double double或int double或double int
- 如果可兼容的参数定义匹配较多,会报引用确定报错 引用不明确
四、函数的递归
函数的递归就是指函数自身调用自身。
人用迭代,神用递归
- 但凡迭代能够解决的问题,递归都可以解决;递归能够解决的问题,迭代就不一定了
- 相对而言,从内存的角度而言,函数如果过多的自我调用,势必会对内存不友好,占用过多
- 通常来讲,同样的问题用递归写要比用迭代写代码量较少
写递归时,一定要先确定递归结束条件——递归边界。
什么情况下可以使用递归?迭代的,数学归纳法的问题
无限递归
public class Sample {
public static void main(String[] args) {
//show(); //无限递归
test(10);
}
public static void test(int n) {
System.out.println(n);
if (n == 1) {
return;
} else {
test(n - 1);
}
}
public static void show() {
System.out.println("Hello!");
show();
}
}
用递归实现1+2+…+99+100
import java.util.Scanner;
public class Sample {
//异常类型
/*
String s = "123";
System.out.println(s.charAt(7));
//StringIndexOutOfBoundsException
*/
/*
System.out.println(10/0);
//ArithmeticException
*/
/*
Integer.parseInt("789abc");
//NumberFormatException
*/
/*
Scanner input = new Scanner(System.in);
int number = input.nextInt();
//InputMismatchException
*/
public static void main(String[] args) {
System.out.println(f(100));
//System.out.println(f(10000000));//StackOverflowError
int sum = 0;
for (int i = 1; i <= 10000000; i++) {
sum += i;
}
System.out.println(sum);
}
/*
求1+2+...+99+100
设函数f(x) = 1 + 2 + 3 + ... + x-3 + x-2 + x-1 + x
f(3) = 1 + 2 + 3
f(2) = 1 + 2
f(3) = f(2) + 3
=>
1 ,x = 1
f(x) =
f(x-1) + x , x>1
f(x) = f(x- 1) + x
f(100) = f(99) + 100 1+2+3+...+99 + 100
f(99) = f(98) + 99 1+2+3+...+98 + 99
.....
f(4) = f(3) + 4 1+2+3 + 4
f(3) = f(2) + 3 1+2 + 3
f(2) = f(1) + 2 1 + 2
f(1) = 1 递归的边界
*/
public static int f(int x) {
if (x == 1) {
return 1;
} else {
return f(x - 1) + x;
}
}
}
递归实现斐波那契数列
public class Sample {
public static void main(String[] args) {
/*
1 1 2 3 5 8 13 21 34 55 ...
1 x=1,x=2
f(x) =
f(x-1) + f(x-2) x>2
f(5)
f(4) f(3)
f(3) f(2) f(2) f(1)
f(2) f(1)
*/
//递归O(2^n)
System.out.println(f(35));
//迭代O(n)
System.out.println(fibo_it(35));
}
public static int fibo_it(int x) {
if (x == 1 || x == 2) {
return 1;
}
/*
1 1 2 3
c
a b
*/
int a = 1;
int b = 1;
int c = 0;
for (int i = 3; i <= x; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
public static int f(int x) {
if (x == 1 || x == 2) {
return 1;
}else {
return f(x-1) + f(x-2);
}
}
}