本文共 3581 字,大约阅读时间需要 11 分钟。
本节书摘来华章计算机《交互式程序设计 第2版》一书中的第2章 ,第2.5节,Joshua Noble 著 毛顺兵 张婷婷 陈宇 沈鑫 任灿江 译更多章节内容可以访问云栖社区“华章计算机”公众号查看。
函数是什么?
我们会把代码分组,每个组里有一行或多行代码,而函数则是这些代码组的名字。函数与变量很相似,函数也有类型和名字,只不过它不只是存放信息,还会处理信息。这跟基础代数的概念很接近,变量可以用一个字母x表示,而函数则是一个指令,向指令输入一些内容,期待它返还一个结果。如果用简单的文字描述一个函数,大概会像这样:“收到一笔钱,把收到的金额加在原来的金额上,最后把总金额告诉我。”这件事可以分解成三部分:以上三部分可以这样来考虑:函数收到什么,做了什么,返回什么。函数是程序中的指定行为,接收和处理特定类型的数据,完成后返回结果。
我们就把上文提到的收钱的例子用代码实现一下。定义一个变量放你所有的钱:
int myBank = 0;
创建一个用来收钱的函数,把钱放到myBank里,返回钱的总数:
int moneyReceived(int money){ myBank += money; return myBank; }
现在你已经创建了一个名叫moneyReceived()的函数。伪代码和文字描述都有助于我们理解函数。“取得一个整数,加到银行里的现有金额中,报告总金额。”从图2-6可以看到函数做了些什么。
图2-6:函数的声明
注意,return语句所返回的内容要和标在函数名前面的数据类型一致,这里函数返回的myBank就是一个整型变量。函数定义好之后,就可以开始调用了。要调用函数,需要向它传递类型匹配的参数。在函数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!!!'
由此可见函数类型是多么重要。任何属于整型的东西都可以为整型变量赋值。
int squareOfEight = square(8);
其中square()定义如下:
int square(int val) { return val*val; }
square()返回一个整型值,你可以用它来设置一个整型变量。如果它返回的是浮点型或是其他类型的结果,那就不可以用来设置整型变量了。再强调一次,函数返回值的类型很重要。
给函数起个好名字,名字最好能指明函数的功能。上面例子里的square就是个好名字,指明了这个函数是用来做平方运算的。一般情况下,把函数命名为动词大有好处,因为这能从一开始就让你去考虑这个函数的功能,而此后回过头再次看这个函数的时候,函数名也能提醒你这个函数是做什么用的。
函数规模要适中。如果一个函数包含了两三百行的代码,那么我劝你还是把它分解为几个小函数。这样便于你把不同部分的代码用在别的地方,也便于定位问题。把一个大函数分解为几个小函数之后,原来两三百行代码的问题就可以精确定位为某几行代码的问题,从而能节省不少时间。当你又要解决以前遇到过并解决好的问题时,就把以前使用过的代码包装成一个函数吧。例如你要经常调整图片尺寸,并把调好的图片存放到线上某个目录下,你就可以写一个函数来完成这些重复的操作:resizeAndSave(int picHeight, int picWidth, String urlToSave) 这样看起来简洁、省事又方便调试。代码越短小,越容易查找内容和定位问题。函数声明之所以重要是因为两个原因。第一,函数声明告诉你向函数传递什么参数,函数对数据作一些什么处理,以及返回什么结果。第二,在编译器看来,函数声明以及它所接收的参数是独一无二的。就算两个函数拥有相同的名字,但如果其中一个接收的参数是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
这个函数接收整型、字符串、字符以及整型数组。哪个版本的函数被调用,取决于传入的参数的类型。
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则不会。一般不建议用这种写法,但如果你坚持要这样做,相信肯定有你的原因。
转载地址:http://jmaex.baihongyu.com/