5.1 方法的结构
本质上,方法是一块具有名称的代码。可以使用方法的名称执行代码,也可以把数据传入方法并接收数据输出。
方法是类的函数成员。方法有两个主要部分,方法头和方法体。
- 方法头指定方法的特征,包括:
- 方法是否返回数据,如果返回,返回什么类型;
- 方法的名称;
- 什么类型的输入可以传入方法。
- 方法体包含可执行代码的语句序列。执行从方法体的第一条语句开始,一直到整个方法结束。
- 本地变量
- 控制流结构
- 方法调用
- 内嵌的块
- 本地变量的存在性仅限于创建它的块及其内嵌的块。
- 它从声明它的那一点开始存在。
- 它在块完成执行时结束存在。
- 可以在方法体内任意位置声明本地变量。
实例字段 | 本地变量 | |
---|---|---|
生存期 | 从实例被创建时开始直到实例不再被访问时结束 | 从它在块中被声明的那点开始在块完成结束 |
隐式初始化 | 初始化成该类型的默认值 | 没有隐式初始化,如果变量在使用之前没有被赋值,编译器会产生一条错误信息 |
存储区域 | 类的所有字段都存储在堆里,无论它们是值类型的还是引用类型的。 | 值类型:存储在栈里 引用类型:引用存储在栈里,数据存储在堆里 |
类型推断和var关键字
var关键字并不是某种特别类型的符号。它只是句法上的速记,表示任何可以从初始化的右边推断出的类型。
使用var关键字有一些重要的条件:
- 只能用于本地变量,不能用于字段。
- 只能在变量声明中包含初始化时使用。
- 一旦编译器推断出类型,它就是固定且不能更改的。
- 可以有任意数量的块,并且它们即可以是顺序的也可以更深层嵌套的。块可以嵌套到任何级别。
- 本地变量可以在嵌套块的内部声明,并且和所有的本地变量一样,它们的生存期仅限于声明它们的块及其内嵌块。
- 常量在声明中必须初始化
- 常量在声明后不能改变
- 在类型之前增加关键字const。
- 必须有初始化语句。初始化值必须在编译期决定,通常是一个预定义简单类型或由其组成的表达式。它还可以是null引用,但它不能是某对象的引用,因为对象在引用是在运行时决定的。
5.4 控制流
术语控制流指的是程序从头到尾的执行流程。默认情况下,程序执行持续地从一条语句到下一条语句,控制语句允许你改变执行的顺序。
- 选择语句:选择哪条语句或语句块来执行
- if:有条件地执行一条语句;
- if...else:有条件地执行一条或另一条语句;
- switch:有条件执行一组语句中的一条。
- 迭代语句:在一个语句块上循环或迭代
- for:循环—在顶部测试;
- while:循环——在顶部测试;
- do:循环——在顶部测试;
- foreach:为一组中每个成员执行一次。
- 跳转语句:从块或方法体内部的一个地方跳到另一个地方。
- break:跳出当前循环;
- continue:到当前循环的底部;
- goto:到一个命名的语句;
- return:返回到发起调用的方法。
5.5 方法调用
可以从方法的内部调用其他方法。
- 英文中call(调用)方法和invoke方法是同义的。
- 调用方法时要使用方法名并带上参数列表。
5.6 返回值
方法可以向调用代码返回一个值。返回的值被插入到调用代码中发起调用的表达式所在的位置。
- 要返回值,方法必须在方法名前面声明一个返回类型。
- 如果方法不返回值,它必须声明void返回类型。
返回语句和void方法
void方法不需要返回语句。当控制流到达方法体的关闭大括号时,控制返回到调用代码,并且没有值被插入到调用代码中。
不过,当特定条件符合的时候,会提交退出方法以简单程序逻辑。
- 可以在任何时候使用下面形式的返回语句退出方法,不带参数:return;
- 这种形式的返回语句只能用于声明为void类型的方法。
5.7 参数
调用命名代码单元,它能把一个值返回给调用代码。返回一个值的确有用,但如果需要返回多个值呢?还有,能在方法开始执行的时候把数据传入方法也会有用。参数就是允许你做这两件事的特殊变量。
形参
形参是声明在方法的参数列表中而不是方法体中的本地变量。
- 形参是变量,它们有类型和名称,并能被写入和读出。
- 和方法中的其他变量不同,参数在方法体的外面定义并在方法开始之前初始化。但有一种类型例外。
- 参数列表中可以有任意数目的形参声明,而且声明必须用逗号隔开。
- 用于初始化形参的表达式或变量称为实参。
- 实参放在方法调用的参数列表中。
调用方法时,必须满足下列要求:
- 实参的数目必须和形参的数目相同,(有一种情况例外)
- 每个实参必须和相应形参的类型匹配
- 在栈中为形参分配空间。
- 复制实参到形参。
引用参数
第二种参数类型称为引用参数。
- 使用引用参数时,必须在方法的声明和调用中都使用ref修饰符。
- 实参必须是变量,在用作实参前必须被赋值。如果是引用类型变量,可以赋值为一个引用或值。
记住,对于值参数,系统在栈里为形参分配内存。相应地,引用参数有以下特征:
- 不在栈中为形参分配新的内存。
- 形参的名称相当于实参变量的别名,引用与实参相同的内存位置。
输出参数
输出参数用于从方法体内把数据传出到调用代码,它们非常类似引用参数。如同引用参数,输出参数有以下要求:
- 必须在声明的调用中都使用修饰符。输出参数的修饰符是out而不是ref。
- 实参必须是变量,不能是其他表达式类型。
- 在方法内部,输出参数在被读取之前必须被赋值。这意味着参数的初始值是无关的,而且没有必须在方法调用之前为实参赋值。
- 每个输出参数在方法返回之前必须被赋值。
至此,以上所述的参数类型都必须严格地一个实参对应一个形参。参数数组不一样,它允许零个或多个实参对应一个特殊的形参。参数数组的重点如下:
- 在一个参数列表中只能有一个参数数组。
- 如果有,它必须是列表中的最后一个。
- 在数据类型前使用params修饰符。
- 在数据类型后入围一组空的方括号。
方法的调用
有两个方法提供实参,可以使用如下的形式:
- 一个逗号分隔的该数据类型元素的列表。所有元素必须是方法声明中指定的类型。 ListInts(10,20,30);
- 一个该数据类型的一维数组 int[] intArray={1,2,3}; ListInts(intArray);
- 其他参数类型要么使用修饰符,要么不使用修饰符。
- 值参数的声明和调用都不带修饰符。
- 引用参数和输出参数在两个地方都需要修饰符。
- 然而参数数组:
- 在声明中需要修饰符。
- 在调用中不允许有修饰符。
- 接受实参列表,用它们在堆中创建并初始化一个数组。
- 把数组的引用保存到栈里的形参里。
- 如果在对应的形参数组的位置没有实参,编译器会创建一个有零个元素的数组来使用。
关于参数数组,需要记住的重要一点是当数组在堆中被创建时,实参的值被复制到数组中。在这方面,它们像值参数。
- 如果数组是值类型,那么值被复制,实参不受方法内部的影响。
- 如果数组参数是引用类型,那么引用被复制,实参引用的对象可以受到方法内部的影响。
参数类型 | 修饰符 | 是否在声明中使用 | 是否在调用时使用 | 执行 |
---|---|---|---|---|
值 | 无 | 系统把实参的值复制到形参 | ||
引用 | ref | 是 | 是 | 形参是实参的别名 |
输出 | out | 是 | 是 | 形参是实参的别名 |
数组 | params | 是 | 否 | 允许传递可变数目的实参到方法 |
5.8 栈帧
当一个方法被调用时,在栈顶分配了一块内存用于保存一定数量与方法相关的数据项,这块内存叫方法的栈帧(stack frame)。
栈帧含有保存下列内容的内存:
- 返回地址——就是方法退出时在哪儿继续执行。
- 分配内存的参数——就是方法的值参数,如果有参数以及参数数组。
- 与方法调用相关的其他各种管理数据项。
5.9 递归
除了调用其他方法,方法还能调用自己,这称为递归(recursion)。
5.10 方法重载
一个类中可以有一个以上的方法拥有相同的名称,这叫做方法重载(method overload)。使用相同名称的每个方法必须有一个和其他方法不相同的签名(signature)。
- 方法的签名由下列信息组成,它们在方法声明的方法头中:
- 方法的名称;
- 参数的数目;
- 参数的数据类型和顺序;
- 参数修饰符。
- 返回类型不是签名的一部分,而认它是签名的一部分是一种常见的错误。
- 形参的名称也不是签名的一部分。