JS++ 函数
一个函数是包含一组指令的一段代码,这些指令描述了如何完成一个特定的任务。函数被声明,然后可以被调用一次或多次。声明一个函数包括指定该函数将包含的指令。当一个函数被调用时,它执行这些指令。一般来说,函数是计算机编程的基础,它们在JS++中起着核心作用。
注意:本教程不包括Js++中的外部函数。一个外部函数用函数关键字声明,并返回一个外部类型。我们将在第9章研究外部函数和外部类型。(预览一下,外部类型是指不是JS++类型的类型;通常,这些类型是JavaScript类型。)本教程将研究内部JS++函数。内部函数不使用函数关键字来声明,并且可以返回任何类型。
声明和调用函数
在我们使用一个函数做任何事情之前,我们必须先声明它。因此,让我们先看一下函数是如何声明的。建立一个新的文件夹,将其命名为 “函数”。然后建立一个新文件,命名为 “Functions.jspp”。写入以下代码。
external $;
string getFavoriteAnimalString(string animal) {
return "My favorite animal is the " + animal;
}
保存Functions.jspp到你的Functions文件夹。我们写的代码声明了一个名为getFavoriteAnimalString的函数。这个名字很合适,因为这个函数的目的是返回一个说明自己最喜欢的动物的字符串。这个任务是由函数主体中的返回语句(大括号内的部分)完成的:返回语句对返回关键字右边的表达式进行评估,然后将评估后的表达式送回函数的调用者(例如,在那里它可能被分配给一个变量)。由于 getFavoriteAnimalString 的目的是返回一个字符串,我们可以说这个函数的返回类型是字符串,我们通过在函数名称的左边写上字符串来指定。
正如getFavoriteAnimalString的输出是一个字符串一样,它也需要一个字符串输入。为了组成报告一个人最喜欢的动物的字符串,该函数需要知道哪个特定的动物是一个人的最爱。它通过名为animal的字符串参数接收这个输入信息,我们把它写在函数名称右边的括号里。当我们在程序的后期调用getFavoriteAnimalString来让它执行时,我们将把一个特定的字符串传给它的输入参数:我们传给它的特定字符串将是该函数的参数。
注意:一个函数的参数和它的参数之间的区别可能会引起混淆。澄清一下,参数是在声明一个函数时写的一个变量。参数类型指定了函数接受的输入类型。相比之下,参数是当函数被调用时作为输入传递给函数的实际值。参数成为参数变量的值。
在我们调用getFavoriteAnimalString之前,让我们设置一个HTML文档,我们将用它来显示结果。做一个名为 “Functions.html “的第二个文件,并写入以下内容。
<!DOCTYPE html>
<title>Functions program</title>
<body>
<p id="content"></p>
<script src="http://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="Functions.jspp.js"></script>
</body>
</html>
保存Functions.html到你的Functions文件夹。现在回到Functions.jspp,在你声明getFavoriteAnimalString的代码下面,写上以下内容。
string favoriteAnimalString = getFavoriteAnimalString("cat");
$("#content").text(favoriteAnimalString);
在这些新行中的第一行,我们调用getFavoriteAnimalString,将字符串 “cat “作为参数传给它。这个函数调用返回字符串 “我最喜欢的动物是猫”,我们把这个字符串分配给变量favoriteAnimalString 。在第二行,我们使用jQuery选择Functions.html的 “content “元素,并将其文本设置为favoriteAnimalString的值。编译 Functions.jspp,然后在浏览器中打开 Functions.html。如果一切顺利,你的文档应该显示 “我最喜欢的动物是猫”。
从一个函数返回
在我们的例子中,getFavoriteAnimalString返回一个字符串。然而,一般来说,JS++函数可以返回任何有效的类型,只要返回的值属于声明中函数名称左边指定的返回类型。
一个函数不能有一个以上的返回类型,但它根本不需要返回任何东西。如果一个函数不返回任何东西,它应该用void关键字来声明。
void displayFavoriteAnimalString(string animal) {
string favoriteAnimalString = "My favorite animal is the " + animal;
$("#content").text(favoriteAnimalString);
}
空白函数可以不使用返回语句来声明(如上面的例子),也可以用返回语句声明;它不评估或返回任何表达式。
当一个函数执行一个返回语句时,它立即退出。这意味着任何写在返回语句之后的代码都不会执行。
参数
函数可以接受零个、一个或多个参数。如果一个函数需要多个参数,那么这些参数可以是同一类型,也可以是不同类型。
void displayFavoriteAnimalIfLucky(string animal, int number) {
string favoriteAnimalString = "My favorite animal is the " + animal;
if(number == 7){
$("#content").text(favoriteAnimalString);
}
}
注意:一个函数也可以接受一个变量参数,这使得该函数可以为该单一参数接受无限多个参数。然而,要充分理解变量参数,需要对数组有所了解,这要到下一个教程中才会涉及。因此,我们将把变量参数的讨论推迟到那时。
递归函数
递归函数是指在其执行过程中可以调用自己的函数。下面是一个著名的递归函数的例子。
int factorial (int input) {
if (input <= 1) {
return input;
} else {
return input * factorial(input - 1);
}
}
阶乘的目的是计算并返回输入参数的阶乘。例如,如果我们调用阶乘并输入 5,它将返回 120。
阶乘说明了许多递归函数所共有的一般结构。该函数对两种情况的处理方式不同,取决于输入参数的值。在基本情况下,参数的值意味着函数的任务可以轻松完成,几乎不需要进一步的工作。因此,该函数返回时不需要再调用自己。相反,在递归情况下,需要做大量的进一步工作。为了实现这一点,函数以某种方式简化或减少了进一步的工作,这涉及到用修改过的输入值调用自己。修改后的输入值将更接近于基本情况所需的值。
阶乘的基本情况是当输入参数小于或等于1时,在这种情况下,函数只是返回输入。相反,如果输入值大于 1,函数将返回输入 *
阶乘(输入-1)。因此,派生将重复调用自己,每次递归调用时输入的值都会减少1。当阶乘以 1 的输入值调用自己时,该特定的递归调用会返回,然后使之前的每个递归调用都能返回。
递归函数应谨慎使用。特别是,必须确保每次递归调用时,输入参数的值更接近于基本情况所需的值。如果不这样做,就有可能产生无限递归的危险:这将导致运行时错误。此外,使用迭代函数(即使用循环的函数)往往比递归更有效。当使用递归有好处时,它通常包括使一个人的代码更容易阅读,而不是提高计算效率。
重载
JS++允许函数被重载。重载包括声明两个具有相同名称的函数,但具有不同的参数列表。例如,我们可以重载一个名为displayFavoriteAnimalString的函数,如下所示。
void displayFavoriteAnimalString(string animal) {
string favoriteAnimalString = "My favorite animal is the " + animal;
("#content").text(favoriteAnimalString);
}
void displayFavoriteAnimalString(string firstAnimal, string secondAnimal) {
string favoriteAnimalString = "我喜欢的动物包括" + firstAnimal + " 和 " + secondAnimal;("#content").text(favoriteAnimalString);
}
编译器允许你以这种方式声明两个同名的函数,因为它们的参数列表不同:第一个函数需要一个字符串参数,而第二个函数则需要两个。这种差异意味着,当你调用这两个函数中的一个时,不会有任何歧义:如果你提供一个字符串参数,那么它一定是第一个函数,如果你提供两个字符串参数,那么它一定是第二个。
在给出的例子中,这两个重载函数有相同的返回类型:void。然而,这并不是必须的。只要重载函数有不同的参数列表,它们的返回类型就可以相同或不同。然而,你不能用相同的参数列表和不同的返回类型来重载函数:这将导致调用者对哪个函数的含义产生歧义,所以编译器不允许这样做。
当重载的函数执行概念上相似的角色时,重载是有意义的,这种相似性从调用者的角度来看应该是明显的。当重载的函数执行概念上不同的角色时,不应该使用重载,因为这将使你的代码更难理解。如果可能的话,要避免的一个特别技巧是给重载的函数提供不同的返回类型:这被认为不是好的编码风格。
回调和函数表达式
考虑一下下面的代码。
external $;
int(int, int) processNumbers = int(int a, int b) {
return a + b;
};
如果你仔细看一下这个语法(请注意后面的分号!),你会发现它不是正常的函数声明语法。相反,我们在这里看到的是一个变量赋值。该变量名为processNumbers,其类型由名称左边的代码指定:int(int, int) 。这是一个回调类型。这种类型的变量可以将一个函数作为其值。然而,该变量可以接受的函数范围被限制在那些参数和返回类型与回调类型相匹配的函数。因此,考虑到processNumbers的回调类型为int(int, int),只有当一个函数接收两个int参数并返回一个int时,它才能被分配给processNumbers。
在”=”号右边的代码将一个函数表达式分配给 processNumbers。一个函数表达式与一个函数声明的不同之处在于,它评估为一个值(函数表达式)。这意味着函数表达式可以在变量赋值中使用(如这里),或作为参数传递给另一个函数,或从另一个函数返回。
分配给 processNumbers 的函数是一个加法函数。它把两个int参数加在一起,并返回结果。赋值没有问题,因为加法函数具有变量回调类型所指定的参数和返回类型:它接收两个int参数并返回一个int。现在我们可以调用 processNumbers 。
int result = processNumbers(3, 4);
$("#content").text(result);
然而,在我们的程序中,我们可能决定给processNumbers分配一个不同的函数。
processNumbers = int(int a, int b) {
return a * b;
};
这个赋值和之前的赋值一样有效,因为赋值的乘法函数也有正确的参数和返回类型:它接受两个int参数并返回一个int。如果我们在给这个新函数赋值后再调用processNumbers,其结果将与我们先前调用所返回的结果不同。