函数和方法

如果我们经常要进行一些相似的处理过程,就可以把这个处理过程封装为函数

函数可以被多次重复调用,从而实现代码重用和隔离的目的。

在面向对象的语言中,函数经常和对象绑定在一起,为区分起见,这时它被称为方法

因为java是完全面向对象的,函数必须从属于某个类。所以java中的函数都被称为方法。

如果方法前以 static 修饰,则称为静态方法,可以粗略地认为,它与 c 语言的函数的概念大致相等了。

方法可以在其括号中列出调用它时需要准备的参数,叫形式参数

方法也可以有一个返回值(或没有,此时写void)。

下面,我们调用方法 f,求两个整数的末位是否相同。

1 public class A0403
 2 {
 3     static boolean f(int a, int b){
 4         int a1 = a % 10;
 5         int b1 = b % 10;
 6         return a1==b1;
 7     }
 8     
 9     public static void main(String[] args){
10         System.out.println(f(101,12));
11         System.out.println(f(65432,12));        
12     }
13 }

这个方法接受两个参数,都是整数,它进行一系列的处理后,返回一个布尔值。

return 语句结束 f 方法的执行,并返回一个指定的值。

在遇到 return 语句后,f 方法的其它代码就不再执行,而是要返回调用它的那个方法中。当然,在此方法中,return 语句后恰好没有更多的语句了。

如果一个方法定义了返回值,它在结束执行之前就一定会遇到 return 语句,否则会引发编译错误。

形参独立原理

如果在被调函数中改变了形参的值,会不会影响调用的一方呢? 不会!

我们看这个例子:

1 public class A0404
 2 {
 3     static void f(int x){
 4         int sum = x % 10;
 5         x /= 10;
 6         sum = sum * 10 + x % 10;
 7         x /= 10;
 8         sum = sum * 10 + x % 10;
 9         System.out.println(sum);
10         System.out.println("x=" + x);
11     }
12     
13     public static void main(String[] args){
14         int a = 368;
15         f(a);
16         System.out.println("a=" + a);    
17     }
18 }

这里的函数f的功能是:把传给它的3位数 x 的数位反转并输出。

为了观察,我们额外输出了 a 的值,以及 x 的值。

通过结果可以看到,虽然 x 的值在计算过程中发生了变化,但这并不会影响 a 的值。

实际上,在调用 f 之前,要为它准备需要的参数,这些参数必须新创建,所以才称为形式参数。

也就是说,在没有调用 f 函数的时候,这些形参变量是不存在的,在多次调用 f 的时候,这些形参就会被创建多次。

创建形参后,把实参的值(这里就是 a 的值)拷贝给它,然后才开始 f 的执行。

当 f  执行结束后,形参变量会被自动释放掉。

从更底层的机制看,这个形参的分配与释放的过程是通过栈来完成的。

函数调用前,要把返回的位置,需要的参数等信息压栈,在函数执行完毕后,自动弹栈,恢复执行前的样子。

如果被调用的函数还会去调用其它函数,这个过程还会继续上演。

这样,栈可能就会越涨越高,但函数的执行总有结束的时候,那时栈就会落回来。

如果由于某种设计失误,导致函数调用一直没能正确返回,而是不断地调用其它的函数,就会导致栈的溢出,这是很常见的程序错误。

从参数传递的原理上我们看到,实参与形参是各自独立的变量,除了开始的时候,实参拷贝给形参外,它们再不会有任何联系。这叫做

"形参独立原理"。

这种设计为我们省去了许多麻烦,但有时我们可能会想让被调方与主调方共享一个变量,而不是各自独立,这怎么办呢?

答案是传指针,在java中叫做:引用。

引用作为参数

引用的本质是持有另一个对象的地址。

如果对一个引用进行复制,只不过是两个引用指向了同一个对象,并没复制对象本身。

引用与面向对象的体系紧密联系在一起,所以要等到学了初步的面向对象的知识后我们才能更好地理解它。

但这里,我们可以先看看数组的行为,来窥其端倪。

数组就是一种对象类型,我们定义的数组变量,实际上是指向实际数组对象的指针,或说:引用。

1 public class A0404
 2 {
 3     static void f(int[] x){
 4         for(int i=0; i<x.length; i++){
 5             if(x[i] < 0) x[i] = -x[i];
 6         }
 7     }
 8     
 9     public static void main(String[] args){
10         int[] a = {5, -3, 6, 10, 0, -15};
11         f(a);
12         System.out.println(java.util.Arrays.toString(a));
13     }
14 }

这里的形参 x,实参 a 都不是数组本身,它们是指向数组的引用。

虽然  x 和 a 也是按照同样的规则,不会相互影响,但由于它们指向了同一个对象,这就引起了复杂的现象。

从结果上,我们可以观察到,在 f 方法中修改了数组对象的值,在主调方打印数组时也看到了这些变化。

其原理图:

java需要数学基础吗 java要学函数吗_System

引用是一种在主调函数和被调函数间共享数据的常用手段。

在上面的这个例子中,引用变量本身并没有发生变化。发生变化的是引用所指向的对象,所以“形参独立性原理”并没有因此而破坏。

我们可能会看到某些材料上写着: java 有两种传递参数的方式,一种是传值(传拷贝),另一种是传引用。

这可能会产生一些误导。实际上Java只有一种传递参数的方式,就是传值。

而这个被传递的值有可能会恰好是一个引用类型,就会引起一些类似共享变量的效果,其实并没有什么特殊的秘密,也不应该算是“另一种”传递方式。