《Java编程的逻辑》笔记6--函数

初识函数.png

函数

前面几节我们介绍了数据的基本类型、基本操作和流程控制,使用这些已经可以写不少程序了。
但是如果需要经常做某一个操作,则类似的代码需要重复写很多遍,比如在一个数组中查找某个数,第一次查找一个数,第二次可能查找另一个数,每查一个数,类似的代码都需要重写一遍,很罗嗦。另外,有一些复杂的操作,可能分为很多个步骤,如果都放在一起,则代码难以理解和维护。
计算机程序使用函数这个概念来解决这个问题,即使用函数来减少重复代码和分解复杂操作,本节我们就来谈谈Java中的函数,包括函数的基础和一些细节。

定义函数

函数这个概念,我们学数学的时候都接触过,其基本格式是 y = f(x),表示的是x到y的对应关系,给定输入x,经过函数变换 f,输出y。程序中的函数概念与其类似,也有输入、操作、和输出组成,但它表示的一段子程序,这个子程序有一个名字,表示它的目的(类比f),有零个或多个参数(类比x),有可能返回一个

结果(类比y)。我们来看两个简单的例子:

image

第一个函数名字叫做sum,它的目的是对输入的两个数求和,有两个输入参数,分别是int整数a和b,它的操作是对两个数求和,求和结果放在变量sum中(这个sum和函数名字的sum没有任何关系),然后使用return语句将结果返回,最开始的public static是函数的修饰符,我们后续介绍。

第二个函数名字叫做print3Lines,它的目的是在屏幕上输出三个空行,它没有输入参数,操作是使用一个循环输出三个空行,它没有返回值。

以上代码都比较简单,主要是演示函数的基本语法结构,即:

修饰符 返回值类型  函数名字(参数类型 参数名字, ...) {
      操作 ...
  return 返回值;
 }

函数的主要组成部分有:

  • 函数名字:名字是不可或缺的,表示函数的功能。

  • 参数:参数有0个到多个,每个参数有参数的数据类型和参数名字组成。

  • 操作:函数的具体操作代码。

  • 返回值:函数可以没有返回值,没有的话返回值类型写成void,有的话在函数代码中必须要使用return语句返回一个值,这个值的类型需要和声明的返回值类型一致。

  • 修饰符:Java中函数有很多修饰符,分别表示不同的目的,在本节我们假定修饰符为public static,且暂不讨论这些修饰符的目的。

以上就是定义函数的语法,定义函数就是定义了一段有着明确功能的子程序,但定义函数本身不会执行任何代码,函数要被执行,需要被调用。

函数调用

Java中,任何函数都需要放在一个类中,类我们还没有介绍,我们暂时可以把类看做函数的一个容器,即函数放在类中,类中包括多个函数,Java中函数一般叫做方法,我们不特别区分函数和方法,可能会交替使用。一个类里面可以定义多个函数,类里面可以定义一个叫做main的函数,形式如:

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

这个函数有特殊的含义,表示程序的入口,String[] args表示从控制台接收到的参数,我们暂时可以忽略它。Java中运行一个程序的时候,需要指定一个定义了main函数的类,Java会寻找main函数,并从main函数开始执行。

刚开始学编程的人可能会误以为程序从代码的第一行开始执行,这是错误的,不管main函数定义在哪里,Java函数都会先找到它,然后从它的第一行开始执行。

main函数中除了可以定义变量,操作数据,还可以调用其它函数,如下所示:

image

main函数首先定义了两个变量 a和b,接着调用了函数sum,并将a和b传递给了sum函数,然后将sum的结果赋值给了变量sum。调用函数需要传递参数并处理返回值。

这里对于初学者需要注意的是,参数和返回值的名字是没有特别含义的。调用者main中的参数名字a和b,和函数定义sum中的参数名字a和b只是碰巧一样而已,它们完全可以不一样,而且名字之间没有关系,sum函数中不能使用main函数中的名字,反之也一样。调用者main中的sum变量和sum函数中的sum变量的名字也是碰巧一样而已,完全可以不一样。另外,变量和函数可以取一样的名字,但也是碰巧而已,名字一样不代表有特别的含义。

调用函数如果没有参数要传递,也要加括号(),如print3Lines()。

传递的参数不一定是个变量,可以是常量,也可以是某个运算表达式,可以是某个函数的返回结果。如:System.out.println(sum(3,4)); 第一个函数调用 sum(3,4),传递的参数是常量3和4,第二个函数调用 System.out.println传递的参数是sum(3,4)的返回结果。

关于参数传递,简单总结一下,定义函数时声明参数,实际上就是定义变量,只是这些变量的值是未知的,调用函数时传递参数,实际上就是给函数中的变量赋值。

函数可以调用同一个类中的其他函数,也可以调用其他类中的函数,我们在前面几节使用过输出一个整数的二进制表示的函数,toBinaryString:

int a = 23;
System.out.println(Integer.toBinaryString(a));

toBinaryString是Integer类中修饰符为public static的函数,可以通过在前面加上类名和.直接调用。

函数基本小结

对于需要重复执行的代码,可以定义函数,然后在需要的地方调用,这样可以减少重复代码。对于复杂的操作,可以将操作分为多个函数,会使得代码更加易读。

我们在前面介绍过,程序执行基本上只有顺序执行、条件执行和循环执行,但更完整的描述应该包括函数的调用过程。程序从main函数开始执行,碰到函数调用的时候,会跳转进函数内部,函数调用了其他函数,会接着进入其他函数,函数返回后会继续执行调用后面的语句,返回到main函数并且main函数没有要执行的语句后程序结束。下节我们会更深入的介绍执行过程细节。

在Java中,函数在程序代码中的位置和实际执行的顺序是没有关系的。

函数的定义和基本调用应该是比较容易理解的,但有很多细节可能令初学者困惑,包括参数传递、返回、函数命名、调用过程等,我们逐个讨论下。

参数传递

数组参数

数组作为参数与基本类型是不一样的,基本类型不会对调用者中的变量造成任何影响,但数组不是,在函数内修改数组中的元素会修改调用者中的数组内容。我们看个例子:

image

在reset函数内给参数数组元素赋值,在main函数中数组arr的值也会变。

这个其实也容易理解,我们在第二节介绍过,一个数组变量有两块空间,一块用于存储数组内容本身,另一块用于存储内容的位置,给数组变量赋值不会影响原有的数组内容本身,而只会让数组变量指向一个不同的数组内容空间。

在上例中,函数参数中的数组变量arr和main函数中的数组变量arr存储的都是相同的位置,而数组内容本身只有一份数据,所以,在reset中修改数组元素内容和在main中修改是完全一样的。

可变长度的参数

上面介绍的函数,参数个数都是固定的,但有的时候,可能希望参数个数不是固定的,比如说求若干个数的最大值,可能是两个,也可能是多个,Java支持可变长度的参数,如下例所示:

image

这个max函数接受一个最小值,以及可变长度的若干参数,返回其中的最大值。可变长度参数的语法是在数据类型后面加三个点...,在函数内,可变长度参数可以看做就是数组,可变长度参数必须是参数列表中的最后一个参数,一个函数也只能有一个可变长度的参数。

可变长度参数实际上会转换为数组参数,也就是说,函数声明max(int min, int... a)实际上会转换为 max(int min, int[] a),在main函数调用 max(0,2,4,5)的时候,实际上会转换为调用 max(0, new int[]{2,4,5}),使用可变长度参数主要是简化了代码书写。

返回

return的含义

对初学者,我们强调下return的含义。函数返回值类型为void且没有return的情况下,会执行到函数结尾自动返回。return用于结束函数执行,返回调用方。

return可以用于函数内的任意地方,可以在函数结尾,也可以在中间,可以在if语句内,可以在for循环内,用于提前结束函数执行,返回调用方。

函数返回值类型为void也可以使用return,即return;,不用带值,含义是返回调用方,只是没有返回值而已。

返回值的个数

函数的返回值最多只能有一个,那如果实际情况需要多个返回值呢?比如说,计算一个整数数组中的最大的前三个数,需要返回三个结果。这个可以用数组作为返回值,在函数内创建一个包含三个元素的数组,然后将前三个结果赋给对应的数组元素。

如果实际情况需要的返回值是一种复合结果呢?比如说,查找一个字符数组中,所有重复出现的字符以及重复出现的次数。这个可以用对象作为返回值,我们在后续章节介绍类和对象。

我想说的是,虽然返回值最多只能有一个,但其实一个也够了。

函数命名

每个函数都有一个名字,这个名字表示这个函数的意义,名字可以重复吗?在不同的类里,答案是肯定的,在同一个类里,要看情况。

同一个类里,函数可以重名,但是参数不能一样,一样是指参数个数相同,每个位置的参数类型也一样,但参数的名字不算,返回值类型也不算。换句话说,函数的唯一性标示是:类名函数名参数1类型参数2类型...参数n类型。

同一个类中函数名字相同但参数不同的现象,一般称为函数重载。为什么需要函数重载呢?一般是因为函数想表达的意义是一样的,但参数个数或类型不一样。比如说,求两个数的最大值,在Java的Math库中就定义了四个函数,如下所示:

image

调用过程

匹配过程

在之前介绍函数调用的时候,我们没有特别说明参数的类型。这里说明一下,参数传递实际上是给参数赋值,调用者传递的数据需要与函数声明的参数类型是匹配的,但不要求完全一样。什么意思呢?Java编译器会自动进行类型转换,并寻找最匹配的函数。比如说:

char a = 'a';

char b = 'b';

System.out.println(Math.max(a,b));

参数是字符类型的,但Math并没有定义针对字符类型的max函数,我们之前说明,char其实是一个整数,Java会自动将char转换为int,然后调用Math.max(int a, int b),屏幕会输出整数结果98。

如果Math中没有定义针对int类型的max函数呢?调用也会成功,会调用long类型的max函数,如果long也没有呢?会调用float型的max函数,如果float也没有,会调用double型的。Java编译器会自动寻找最匹配的。

在只有一个函数的情况下(即没有重载),只要可以进行类型转换,就会调用该函数,在有函数重载的情况下,会调用最匹配的函数。

递归

函数大部分情况下都是被别的函数调用,但其实函数也可以调用它自己,调用自己的函数就叫递归函数。

为什么需要自己调用自己呢?我们来看一个例子,求一个数的阶乘,数学中一个数n的阶乘,表示为n!,它的值定义是这样的:

0!=1

n!=(n-1)!×n

0的阶乘是1,n的阶乘的值是n-1的阶乘的值乘以n,这个定义是一个递归的定义,为求n的值,需先求n-1的值,直到0,然后依次往回退。用递归表达的计算用递归函数容易实现,代码如下:

image

看上去应该是比较容易理解的,和数学定义类似。

递归函数形式上往往比较简单,但递归其实是有开销的,而且使用不当,可以会出现意外的结果,比如说这个调用:

System.out.println(factorial(10000));

系统并不会给出任何结果,而会抛出异常,异常我们在后续章节介绍,此处理解为系统错误就可以了,异常类型为:java.lang.StackOverflowError,这是什么意思呢?这表示栈溢出错误,要理解这个错误,我们需要理解函数调用的实现原理(下节介绍)。

那如果递归不行怎么办呢?递归函数经常可以转换为非递归的形式,通过一些数据结构(后续章节介绍)以及循环来实现。比如,求阶乘的例子,其非递归形式的定义是:

n!=1×2×3×…×n

这个可以用循环来实现,代码如下:

image

写在最后

都看到这里了,保存思维导图顺便给个赞呗!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容