You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

15 KiB

Java的函数和 C 类似但因为Java是面向对象的语言所以又有很多扩展的地方我们这里主要对其特殊的地方进行讲解。

广义上讲函数function与过程procedure的含义是一样的。在有些语言中例如Pascal函数是指有返回值的过程是指没有返回值的。

1. 函数

函数的定义和数学上的定义相似,可以理解为函数是一个抽象的算法,当传入一定的参数后,函数可以有一定的返回结果。当然函数不一定都需要返回,但是一定是包含一定的功能和算法,并且产生一定的效果。

从更广泛的意义上说一个应用程序或者是APP都可以说是一个复杂的函数它们接受输入通过计算产生一定的效果输出

1.1. 空函数

空函数在语法上将是存在的例如下面一个Java的空函数

void nullFunxtion(){
    // 没有算法,不返回,没有任何功能
}

这个函数虽然在语法上没有问题,也可以通过编译,也能被其他的代码所调用,但是这个函数本身是没有意义的。因为没有算法,没有任何的效果。这里我们不讨论空函数。

1.2. 没有返回值的函数

函数可以没有返回值,但是需要有算法,并且一定产生某些效果(影响)。

例如:

public class Test {

    static int sum = 0;

    public static void main(String[] args) {
        add(10);
        add(11);
        System.out.print(sum);
    }

    static void add(int i) {
        sum = sum + i;
    }
}

上面的函数 static void add(int i) 这个函数就是没有返回值的但是函数中的算法有效果对变量sum进行累加。当然其他的一些没有返回的值的函数可能产程其他的效果例如打印、文件输出、网络连接... 这些都是函数产生的影响,也是函数存在的意义。

1.3. 有返回值的函数

如果函数有返回值需要确定返回值的类型这一点和C语言也是一致的。任何的系统预定义类型和自定义类型Java中主要是类都可以作为函数的返回类型。下面是一个简单的例子

public class Test {

    public static void main(String[] args) {
        System.out.print(add(1, 10));
    }

    static int add(int i, int j) {
        return i + j;
    }
}

print也是一个函数它的输入参数刚好是add这个函数的返回值因此这里的结果是11。

1.4. 函数返回多个值

上面两个例子说明函数有没有返回值都可能产生一定的效果。在某些情况下一个函数可能会产生多种效果例如C语言的fwrite写文件函数他的效果是向文件写入一些数据同时其返回值是表示写入的数据是否成功。也就是说这个函数同有两种结果返回成功与否同时写入数据

那一个函数是否可以有多个返回值在C中一个函数只能有一个返回值但是可以通过一些技巧使其具有返回多个值的不同效果例如

  1. 传递参数的指针,这样在函数内部可以改变传入参数的值,这是指针传递;

  2. 使用多个全局变量,在函数内是可以改变这些全局变量的值;

  3. 使用结构体返回数据。

当然还有其他很多方法,使得函数可以返回多个值。

注意Java中没有指针的概念所以也没有指针传递参数的用法。但是Java有引用这是后面我们要讨论的问题。

Java没有指针但是有加强的结构体类-对象)。类与对象我们放在后面来讲,这里我们看一个通过“全局变量”返回多个值的例子:

public class Test {

    static int a = 0;
    static int b = 0;

    public static void main(String[] args) {
        System.out.printf("a=%d and b=%d\n", a, b);
        changeAB();
        System.out.printf("a=%d and b=%d\n", a, b);
    }

    static void changeAB() {
        a = 100;
        b = 200;
    }
}

Java并没有真正意义上的“全局变量”每个变量都被局限到一个类中类好比一个盒子把变量目前是静态变量、常量常量都是静态的和函数目前是静态函数包装起来了封装特性。在类这个盒子中可以自由调用这些静态变量、常量、静态函数。如果在这个盒子外边调用这个类的静态变量、常量、静态函数就需要先找到这些静态成员的盒子再调用例如前面使用到的Math.PI 常量;Math.abs(a,b) 静态函数;上个例子中的 变量 a 和 b静态变量

上个例子中的函数 changeAB 因为和静态变量在同一个空间(一个类中),因此在前面不用加上类名。当然加上类名也不错,例如:

public class Test {

    static int a = 0;
    static int b = 0;

    public static void main(String[] args) {
        System.out.printf("a=%d and b=%d\n", a, b);
        changeAB();
        System.out.printf("a=%d and b=%d\n", a, b);
    }

    static void changeAB() {
        Test.a = 100;
        Test.b = 200;
    }
}

2. 静态与非静态(实例化)

到目前为止我们学习的大多是静态函数。什么是静态函数简单说来静态函数是不依附对象就可以直接运行的函数。这个怎么解释要了解静态与非静态函数的区别首先需要知道什么是对象。前面说过类是抽象描述的像C语言中的类型定义对象是类的一个真实变量分配了具体的内存。这种关系我们第一章的结构体指针类型和一个分配了存储空间的结构体指针变量中进行了分析。

同样,静态变量也是不依赖对象存在的,在程序运行的时候就存在,而不需要创建和依附一个对象存在。

简单的理解静态函数相当于C语言中的函数静态变量相当于C语言中的全局变量只不过Java没有全局变量都是封装在类这个盒子当中。

2.1. 静态函数与实例函数

2.1.1. C语言中如何计算字符串的长度

接下来看看C的函数的语义。例如C语言如果需要确定字符串的长度这个函数的原型是这样的

int strlen(const char *str)

这个函数可以直接调用,有一个参数告诉这个函数是计算哪个字符串的长度。

2.1.2. Java中如何计算字符串的长度

注意C是非面向对象的因此它需要知道需要计算哪个字符串数据的长度。

接下来我们看看Java字符串长度的函数

      String s = "Hello world!";
      int size = s.length();

看到区别了吗Java计算字符串长度的意思是计算自己本身对象字符串的长度C是的含义是计算谁的字符串长度。这个就是最根本的区别。因为Java中的字符串是一个对象有数据因此在计算的时候就知道数据在哪里因此不再需要传入一个字符串的数据。这里的s是一个字符串对象的引用变量这样s.length()的语义是s这个字符串对象的长度s中本来就就数据计算自己字符串的长度。

对象是相关数据与在相关数据上操作函数的集合。

显然s这个对象String类型的实例本身含有数据字符串内容以及相关的操作函数例如 length 这个函数。因此length()并不需要传入字符串的参数因为length()在 s 这个对象上执行s本身包含数据。

一般说来,需要在对象上执行的函数叫做非静态函数;而不需要对象,直接可以运行的函数叫做静态函数。其实,静态变量也是一样的,后面会做解释。

String对象的length()函数是一个实例函数如果你们看这个函数的代码发现其并没有static进行修饰。实例函数是只能在实例对象上面进行调用不能在类上面进行调用。很明显String的类在没有实例化前是一个抽象的模板并没有保存任何的数据因此使用String.length()是没有意义的,也是非法的。

2.1.3. String中的静态函数

String中一个很有用的函数就是字符串格式化我们看看下面这个例子

public class Test {

    public static void main(String[] args) {
        String string = String.format("Hello %s !\n", "world");
        System.out.print(string);
    }

}

这个函数和System.printf函数一样,都是负责代码格式化,只不过,String.format是返回一个格式化后的字符串,System.printf 是打印格式化后的字符串。

一个类中可以同时拥有静态、或者是实例函数;同样可以同时拥有静态变量和实例变量。

2.2. 静态变量

有static修饰的变量就是静态变量。Java中的静态变量和C的全局变量很相似不过是封装在一个类中的在本页面的1.3中的例子讲了静态变量的使用。非动态变量(依附于对象存在的变量)将在类和对象的相关知识点中讲解。

3. 函数签名与函数重载

在java中一个函数肯能看起来很复杂不仅有返回值还有前面的public和static等修饰这些修饰是可选的。函数签名的概念是一个函数的函数名连同其形式参数列表叫做函数签名。

在一个类中可能出现两个同名的函数只要他们的参数列表不同就可以这种方式叫做函数重载对比C语言是没有函数重载的。函数重载避免了C语言中因为功能相似而参数名不同需要用多个不同的函数名的问题。大家可以看到C语言的函数名大多很长其实是因为C的所有函数都是全局存储的Java是把函数放在盒子Class里不同的Class中可以存在同名且同参数的函数另外就是C没有函数重载的机制。

public class TestMethodOverloading {
    /** Main method */
    public static void main(String[] args) {
        // Invoke the max method with int parameters
        System.out.println("The maximum of 3 and 4 is " + max(3, 4));

        // Invoke the max method with the double parameters
        System.out.println("The maximum of 3.0 and 5.4 is " + max(3.0, 5.4));

        // Invoke the max method with three double parameters
        System.out.println("The maximum of 3.0, 5.4, and 10.14 is " + max(3.0, 5.4, 10.14));
    }

    /** Return the max of two int values */
    public static int max(int num1, int num2) {
        if (num1 > num2)
            return num1;
        else
            return num2;
    }

    /** Find the max of two double values */
    public static double max(double num1, double num2) {
        if (num1 > num2)
            return num1;
        else
            return num2;
    }

    /** Return the max of three double values */
    public static double max(double num1, double num2, double num3) {
        return max(max(num1, num2), num3);
    }
}

上面这个例子说明了函数重载的使用,大家可能跟踪调试一下。当调用一个函数的时候,是通过参数类型和参数的个数来确定最终调用哪个函数的。上个例子中的语义并不存在歧义。

需要注意的是,参数类型不一样的函数重载不是总是成功,例如:

public class Test {

    public static void main(String[] args) {
        int rst1 = add(1, 100);
        long a = 100;
        long b = 200;
        int rst2 = add(a, b);
    }

    public static int add(int a, int b) {
        System.out.println("add version 1 is invoked!");
        return a + b;
    }

    public static long add(long a, long b) {
        System.out.println("add version 2 is invoked!");
        return a + b;
    }

}

上面这个例子中我们希望 int rst2 = add(a,b) 调用第二个版本的add函数但是编译会出错这是Java的限制大家需要根据实际情况来判断。

4. 局部变量的访问范围

A local variable: a variable defined inside a method. Scope: the part of the program where the variable can be referenced. The scope of a local variable starts from its declaration and continues to the end of the block that contains the variable. A local variable must be declared before it can be used.

You can declare a local variable with the same name multiple times in different non-nesting blocks in a method, but you cannot declare a local variable twice in nested blocks.

上面说明了什么叫做局部变量就是函数中的变量这个和C的定义是一致的以及局部变量的访问范围作用域。一句话局部变量的作用域在其定义的时候开始一致到该语法块结束。

public class Test {

    public static void main(String[] args) {
        correctMethod();
    }

    public static void correctMethod() {
        int x = 1;
        int y = 1;
        // i is declared
        for (int i = 1; i < 10; i++) {
            x += i;
        }
        // i is declared again
        for (int i = 1; i < 10; i++) {
            y += i;
        }

        System.out.printf("X = %d ; Y = %d", x,y);
    }

}

这个例子中,局部变量 xy 在for循环中是可以访问到的。

    public static void incorrectMethod() {
        int x = 1;
        int y = 1;
        for (int i = 1; i < 10; i++) {
            int x = 0;        //这里的x定义非法
            x += i;
        }
    }

上面这个例子会显示一个编译错误提示for循环中定义的x变量与函数中定义的x变量重名了这时候需要考虑修改一个变量的名字。

5. 访问其他类中的静态函数

直接借用书上的例子:

主类

public class TestRandomCharacter {
    /** Main method */
    public static void main(String args[]) {
        final int NUMBER_OF_CHARS = 175;
        final int CHARS_PER_LINE = 25;

        // Print random characters between 'a' and 'z', 25 chars per line
        for (int i = 0; i < NUMBER_OF_CHARS; i++) {
            char ch = RandomCharacter.getRandomLowerCaseLetter();
            if ((i + 1) % CHARS_PER_LINE == 0)
                System.out.println(ch);
            else
                System.out.print(ch);
        }
    }
}

使用 RandomCharacter.getRandomLowerCaseLetter() 这种方式来访问静态函数,getRandomLowerCaseLetter() 这个函数被装在 RandomCharacter 这个小盒子中。

被访问函数所在的类

public class RandomCharacter {
    /** Generate a random character between ch1 and ch2 */
    public static char getRandomCharacter(char ch1, char ch2) {
        return (char) (ch1 + Math.random() * (ch2 - ch1 + 1));
    }

    /** Generate a random lowercase letter */
    public static char getRandomLowerCaseLetter() {
        return getRandomCharacter('a', 'z');
    }

    /** Generate a random uppercase letter */
    public static char getRandomUpperCaseLetter() {
        return getRandomCharacter('A', 'Z');
    }

    /** Generate a random digit character */
    public static char getRandomDigitCharacter() {
        return getRandomCharacter('0', '9');
    }

    /** Generate a random character */
    public static char getRandomCharacter() {
        return getRandomCharacter('\u0000', '\uFFFF');
    }
}

6. 本章重点

  1. 掌握静态函数的编写与调用;
  2. 了解静态函数与实例函数的区别;
  3. 掌握函数签名和重载;
  4. 掌握局部变量的作用域。