《交互式程序设计 第2版》一2.5 函数

简介:

本节书摘来华章计算机《交互式程序设计 第2版》一书中的第2章 ,第2.5节,Joshua Noble 著 毛顺兵 张婷婷 陈宇 沈鑫 任灿江 译更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.5 函数

函数是什么?
我们会把代码分组,每个组里有一行或多行代码,而函数则是这些代码组的名字。函数与变量很相似,函数也有类型和名字,只不过它不只是存放信息,还会处理信息。这跟基础代数的概念很接近,变量可以用一个字母x表示,而函数则是一个指令,向指令输入一些内容,期待它返还一个结果。如果用简单的文字描述一个函数,大概会像这样:“收到一笔钱,把收到的金额加在原来的金额上,最后把总金额告诉我。”
这件事可以分解成三部分:

  • 收到一笔钱
  • 累加金额
  • 报告总金额

以上三部分可以这样来考虑:函数收到什么,做了什么,返回什么。函数是程序中的指定行为,接收和处理特定类型的数据,完成后返回结果。

2.5.1 定义函数

我们就把上文提到的收钱的例子用代码实现一下。定义一个变量放你所有的钱:

int myBank = 0;

创建一个用来收钱的函数,把钱放到myBank里,返回钱的总数:

int moneyReceived(int money){ 
    myBank += money; 
    return myBank; 
}

现在你已经创建了一个名叫moneyReceived()的函数。伪代码和文字描述都有助于我们理解函数。“取得一个整数,加到银行里的现有金额中,报告总金额。”从图2-6可以看到函数做了些什么。
image

图2-6:函数的声明
注意,return语句所返回的内容要和标在函数名前面的数据类型一致,这里函数返回的myBank就是一个整型变量。

2.5.2 向函数传递参数

函数定义好之后,就可以开始调用了。要调用函数,需要向它传递类型匹配的参数。在函数moneyReceived()的例子里,函数指定输入一个整型参数。下面的语句都是正确的:

moneyReceived(4);

int priceOfDinner = 17; 
moneyReceived(priceOfDinner);

但是这样就不对了:

float f = 1.32; 
moneyReceived(f);

错误在于传递给函数的参数类型不对,所以要看清楚函数的声明,包括返回值的类型、名字和参数。
return语句指出这个方法函数返回的值的类型,就跟数据类型指定了变量可以存放什么数据的道理一样。不返回任何值的函数会被声明为void类型,其他情况下函数返回值的类型需要明确声明。例如,创建一个返回一个字符的函数,可以这样写:

char myFunction()

函数肯定是这样一种格式:类型、函数名、括号以及被传入的参数,如下所示。

int multiplyByTwo(int value){ 
    return value * 2; 
}

这个函数接收了一个整型值,把这个值和2相乘,返回相乘的结果。我们可以用带有返回值的函数去设置变量的值:

int x = 5; 
int twoTimesX = multiplyByTwo(x); // twoTimesX 等于10

而下面这个例子则是调用了一个返回字符的函数,返回的结果取决于输入的参数:

char convertIntToChar(int i){ 
    char ch = char(i); 
    return ch; 
}

新变量的值可由函数的返回值设置:

string addExclamationPoints(string s) { 
    return s+"!!!"; 
} 
string myStr = addExclamationPoints("hello"); // myStr被设置为'hello!!!'

看上去有点不合规矩,但其实在使用中,一个函数是可以等价于它返回的那个值的。上面这个例子中,最后一行的函数addExclamationPoints()就等价于它所返回的那个字符串。只要保证在赋值之前,函数内部的所有处理都完成了,你就可以放心地直接调用函数设置变量的值。
由此可见函数类型是多么重要。任何属于整型的东西都可以为整型变量赋值。

int squareOfEight = square(8);

其中square()定义如下:

int square(int val) { 
    return val*val; 
}

square()返回一个整型值,你可以用它来设置一个整型变量。如果它返回的是浮点型或是其他类型的结果,那就不可以用来设置整型变量了。再强调一次,函数返回值的类型很重要。

2.5.3 有关写函数的一些建议

给函数起个好名字,名字最好能指明函数的功能。上面例子里的square就是个好名字,指明了这个函数是用来做平方运算的。一般情况下,把函数命名为动词大有好处,因为这能从一开始就让你去考虑这个函数的功能,而此后回过头再次看这个函数的时候,函数名也能提醒你这个函数是做什么用的。
函数规模要适中。如果一个函数包含了两三百行的代码,那么我劝你还是把它分解为几个小函数。这样便于你把不同部分的代码用在别的地方,也便于定位问题。把一个大函数分解为几个小函数之后,原来两三百行代码的问题就可以精确定位为某几行代码的问题,从而能节省不少时间。
当你又要解决以前遇到过并解决好的问题时,就把以前使用过的代码包装成一个函数吧。例如你要经常调整图片尺寸,并把调好的图片存放到线上某个目录下,你就可以写一个函数来完成这些重复的操作:
resizeAndSave(int picHeight, int picWidth, String urlToSave)
这样看起来简洁、省事又方便调试。代码越短小,越容易查找内容和定位问题。

2.5.4 重载函数

函数声明之所以重要是因为两个原因。第一,函数声明告诉你向函数传递什么参数,函数对数据作一些什么处理,以及返回什么结果。第二,在编译器看来,函数声明以及它所接收的参数是独一无二的。就算两个函数拥有相同的名字,但如果其中一个接收的参数是2个字符串,而另一个接收3个字符串,则它们仍然是两个不同的函数。一系列名字相同但参数不同的函数,可以让你把同一个功能用在不同的场景。这种做法叫“重载”,同名函数拥有不同的参数。
我们拿动词“draw”(画)来打个比方。显然“drawing a picture”(画一幅画)和“drawing a card”(画一张卡片)是不一样的。编译器也是这样看函数的。我们大可放心地定义同名的函数,编译器能够根据传递参数的不同分辨出它们。例如我们可以让检测视频大小的函数接收整型或浮点型参数,编译器会把它看成两个独立的函数,一个接收整型参数,另一个接收浮点型参数,就算它们都是同一个名字。调用这个函数的时候,如果传入的参数是浮点型的,那么就是调用浮点型参数对应的那一个函数。
这里是Processing里重载函数的一个例子:

char multiplyByTwo(char value){ 

    return char(int(value) * 2); 
} 

String multiplyByTwo(String value) { 
    return value+value; 
} 

int multiplyByTwo(int value){ 

    return value * 2; 
} 

int[] multiplyByTwo(int value[]){ 
    for(int i = 0; i<value.length; i++) { 
        value[i] *= 2; 
    } 
    return value; 
}

这个函数接收整型、字符串、字符以及整型数组。哪个版本的函数被调用,取决于传入的参数的类型。
Processing里的可以这样做:

println(multiplyByTwo('z'));    // 打印出 
println(multiplyByTwo(5));     // 打印出10 
println(multiplyByTwo("string"));    // 打印出stringstring 
int[] foo = {1, 2, 3, 4}; 
println(multiplyByTwo(foo));    //打印出2, 4, 6, 8

警告:重载函数是非常强大的工具,一种方法只需要经过轻微的改动(通过接收不同类型的参数)就能用在不同的场合。不过要注意,指定不同类型的参数,函数重载有效,例如:

int function(int i) { 
} 
int function(float f) { 
} 
int function(char c) { 
}
而指定了不同类型的返回值,函数重载却未必总能凑效:
int function(int i) { 
} 
float function(float f) { 
} 
char function(char c) { 
}
上面三个声明在Arduino和C++里都会报错,Processing则不会。一般不建议用这种写法,但如果你坚持要这样做,相信肯定有你的原因。 
相关文章