5.方法(最全C#方法攻略)

目录

5.1 方法的结构

5.2 方法体内部的代码执行

5.3.1 类型推断和Var关键字

5.3.2 嵌套块中的本地变量

 5.4 本地常量

5.5 控制流

5.6 方法调用

5.7 返回值

5.8 返回语句和void 方法

5.9 参数

5.9.1 形参

5.9.2 实参

位置参数示例

5.10 值参数

5.11 引用参数

5.12 引用类型作为值参数和引用参数

5.13 输出参数

5.14 参数数组

5.14.1 方法调用 

5.14.2 用数组作为实参

 5.15 参数类型总结

5.16 方法的重载

5.17 命名参数

5.18 可选参数

5.19 栈帧

5.20 递归


5.1 方法的结构

方法是一块具有名称的代码。可以使用方法的名称从别的地方执行代码,也可以把数据传入方法并接受数据输出。

如前一章所属,方法是类的函数成员。方法有两个主要部分,如图5-1所示:方法头和方法体。

  1. 方法头指定方法的特征,包括:
  • 方法是否返回数据,如果返回,返回什么类型;
  • 方法的名称;
  • 那种类型的数据可以传递给方法或从方法返回,以及应如何处理这些数据。
  1. 方法体包含可执行代码的语句序列。执行过程从方法体的第一条语句开始,一直到整个方法结束。

下面的示例展示了方法头的形式。接下来阐述其中的每一部分。

 

例如,下面的代码展示了一个名称为MyMethord的简单方法,它多次轮流调用WriteLine方法。

 

 尽管前面几章都描述了类,但是还有另外一种用户定义的类型,叫做struct,我们会在第10章中介绍。本章中介绍的大多数有关方法的内容同样适用于struct方法。

5.2 方法体内部的代码执行

方法体是一个块,是大括号起的语句序列(参考第二章)。块可以包含以下项目:

  • 本地变量;
  • 控制流结构;
  • 方法调用;
  • 内嵌的块。

图5-2展示了方法体及其组成的示例。

5.3 本地变量

和第4章介绍的字段一样 ,本地变量也保存数据。字段通常保存和对象状态有关的数据,而创建本地变量经常是用于保存本地的临时的计算数据。表5-1对比了本地变量和实例字段的差别。

下面这行代码展示了本地变量生明的语法。可选的初始化语句由等号和用于初始化的值组成。

  • 本地变量的存在性和生存期仅限于它的块以及内嵌的块。
  1. 它从声明它的哪一点开始存在。
  2. 它再快完成执行时结束存在。
  • 可以在方法体内任意位置声明本地变量,但必须在使用它们前声明。

 下面的示例展示了两个本地变量的声明和使用。第一个是int类型,第二个是SomeClass类型变量。

5.3.1 类型推断和Var关键字

如果观察下面的代码,你会发现在声明的开始部分提供类型名时,你提供的时编译器能从初始化语句右边推断的信息。

  • 在第一个变量声明中,编译器能推断出15是int型。
  • 在第二个声明中,右边的对象创建表达式返回了一个MyExcellentClass类型的对象。

所以在这两种情况中,在声明的开始部分包括显示的类型名是多余的。

 

var关键字并不是特定类型变量的符号。它只是句法上的速记,在表示任何可以从初始化语句的右边推断出的类型。在第一个声明中,他是int的速记;在第二个声明中,他是MyExcellentClass的速记。前文中使用显示类型名的代码片段和使用var关键字的代码片段在语义上是等价的。

使用var关键字有一些重要条件:

  • 只能用于本地变量,不能用于字段;
  • 只能在变量声明中包含初始化时使用;
  • 只能在变量声明包含初始化时使用;
  • 一旦编译器推断出变量的类型,他就是固定且不能更改的。

5.3.2 嵌套块中的本地变量

方法体内部可以嵌套其他的块。

  • 可以有任意数量的块,并且它们既可以是顺序的也可以更深层嵌套的。块可以嵌套到任和级别。
  • 本地变量可以在嵌套块的内部声明,并且和所有的本地变量一样,它们的生存期和可见性仅限于声明它们的块及其内嵌的块。

图5-3阐述了两个本地变量的生存期,展示了代码和栈的状态。箭头标出了刚执行过的行。

  • 变量var1声明在方法体中,在嵌套块之前。
  • 变量var2声明在嵌套块内部。它从声明那一刻开始存在,直到声明它的那个块的尾部结束。
  • 当控制传出嵌套块时,它的本地变量从栈中弹出。

 

 5.4 本地常量

本地常量很想本地变量,只是一旦被初始化,它的值就不能改变了。如同本地变量,本地常量必须在块的内部。

常量的两个最重要的特征如下。

  • 常量在声明时必须初始化。
  • 常量在声明后不能改变。

常量的核心声明如下所示。语法与字段或变量的声明相同,除了下面内容。

  • 在类型之前增加关键字const。
  • 必须有初始化语句,初始化值必须在编译期决定,通常是一个预定义简单类型或由其组成的表达式。它还可以是null引用,但是不能是某对象的引用,因为对象的引用是在运行时决定的。

就像本地变量,本地常量声明在方法体或代码块里,在声明它的块结束的地方失效。列如,在下面的代码中,类型为内嵌类型double的本地常量在PI在方法DisplayRadii结束后失效。

 

5.5 控制流

方法包含了大部分组成程序行为的代码。剩余部分在其他的函数成员中,如属性和运算符。

术语控制流指的是程序从头到尾的执行流程。默认情况下,程序执行持续地从一条语句到下一条语句,控制流语句允许你改变执行的顺序。

在这一节,只会提及一些能用于代码的控制语句,第九章会详细阐述它们。

  • 选择语句 这些语句可以选择哪条语句或语句块来执行。
  1. if 有条件的执行一条语句;
  2. if...else 有条件地执行一组语句的的某一条。
  3. switch 有条件地执行一组语句中的某一条。
  • 迭代语句 这些语句可以在一个语句块上循化或迭代。
  1. for 循环-------在顶部测试;
  2. while 循环-------在顶部测试;
  3. do 循环-------在顶部测试;
  4. foreach 为每一组成员执行一次。
  • 跳转语句 这些语句可以让你从代码块或方法体内部的一个地方跳到另一个地方。
  1. break 跳出循环;
  2. continue 到当前循环的底部;
  3. goto 到另一个命名的语句;
  4. return 返回到调用方法继续执行。

例如,下面的方法展示了两个控制流语句,先别管那些细节。

5.6 方法调用

可以从方法体的内部调用其他方法。

  • 英文call(调用)方法和invoke方法是同义的。
  • 调用方法时要使用方法名并带上参数列表。参数列表将稍后讨论。

例如下面的类声明了一个名称为PrintDateAndTime的方法,在Main方法内会调用该方法。

 

图5-4阐明了调用方法时的动作顺序。

  1. 当前方法的执行在调用点被挂起。
  2. 控制转移到被调用方法的开始。
  3. 被调用方法执行直到完成。
  4. 控制回到发起调用的方法。

 

5.7 返回值

方法可以向调用代码返回一个值。返回值被插入到调用代码中发起调用的表达式所在的位置。 

  • 要返回值,方法必须在方法名前面声明一个返回类型。
  • 如果方法不返回值,它必须声明void返回类型。

下面的代码展示了两个方法声明。第一个返回int型值,第二个不返回值。

声明了返回类型的方法必须使用下面形式的返回语句从方法中返回一个值。返回语句包括关键字return以及其后面的表达式。每一条贯穿方法的路径都必须以一条这种形式的return语句结束。

 

 

5.8 返回语句和void 方法

在上一节,我们看到有返回值得方法必须包含返回语句。void方法不需要返回语句,当控制流到达方法体的关闭大括号时,控制返回到调用代码,并且没有值被插入到调用代码中。

不过,当特定条件符合的时候,我们通常会提前退出方法以简化程序逻辑。

  • 可以在任何时候使用下面的形式的返回语句退出方法,不带参数;

return;

  • 这种形式的返回语句只能用于用void声明的方法。

例如,下面的代码展示了一个名称为SomeMethord的void方法的声明。它可以在三个可能的地方返回到调用代码。前两个在if语句的分之内。if语句将在第九章阐述。最后一个方法体的结尾处。

 

下面的代码展示了一个带有一条返回语句的void方法示例,该方法只有当时间是下午的时候才能写出一条消息,如5-5所示,其过程如下。

  • 首先,方法获取当前日期和时间(现在不需要关注这些细节)。
  • 如果小时小于12(也就是在中午之前),那么执行return语句,不在屏幕上输出任何东西,直接把控制返回给调用方法。
  • 如果小时大于等于12,则跳过return语句,代码执行WriteLine语句,在屏幕上输出信息。

5.9 参数

迄今为止,你已经看到方法是可以被程序中很多地方调用的命名代码单元,它能把一个值返回给调用代码。返回一个值的确有用,但如果返回多个值呢?还有,能在方法开始执行时候把数据传入方法也会有用。参数就是允许做着两件事的特殊变量。

5.9.1 形参

形参是本地变量,它声明在方法的参数列表中,而不是在方法体中。

下面的方法头展示了参数声明的语法。它声明了两个形参:一个是int型,另一个是float型。

  • 因为形参是变量,所以它们有类型和名称,并能写入和读。
  • 和方法中的其他本地变量不同,参数在方法体的外面定义并在方法开始之前初始化(但有一种类型例外,称为输出参数,我们很快谈到它)。
  • 参数列表中可以有任意数目的形参声明,而且声明必须用逗号隔开。

形参在整个方法体内使用,在大部分地方就像其他本地变量一样。例如,下面的PrintSum方法的声明使用两个形参x和y,以及一个本地变量Sum,它们都是int型。

 

5.9.2 实参

当代码调用一个方法时,形参的值必须在方法的代码开始执行之前被初始化。

  • 用于初始化形参的表达式或变量称为实参(actual parameter,有时候也称为 argument)。
  • 实参位于方法调用的参数列表中。
  • 每一个实参必须与对应形参的类型相匹配,或是编译器必须能把实参隐式转换为那个类型。在第16章中我会解释类型转化的细节。

l例如,下面的代码展示了方法PrintSum的调用,他有两个int型的实参。

 

当方法被调用的时候,每个实参的值都被用于初始化相应的形参,方法体随后被执行。图5-6阐明了实参和形参的关系。

注意在之前那段实例代码以及图5-6中,实参的数量必须和形参的数量一致,并且每个实参的类型也必须和对应的形参类型一致。这种形式的参数叫做位置参数。我们稍后会看其他的一些选项,不过现在我们先来看看位置参数。

 

位置参数示例

在如下代码中,MyClass类声明了两个方法 -----一个方法接受两个整数并且返回他们的和,另一个方法接受了两个float并且返回它们的平均值。对于第二次调用,注意编译器把int值5和someInt隐式转换成了float类型。

 

5.10 值参数

参数有几种,各自以略微不同的方式从方法传入或传出数据。你到现在一直看到的·这种类型是默认的类型,称为值参数(value parameter)。

使用值参数,通过将实参的值复制到形参的方法把数据传递给方法。方法被调用时,系统做如下操作。

  •  在栈中为形参分配空间。
  • 将实参的值复制给总参。

值参数的实参不一定是变量。它可以是任何能计算成相应数据类型的表达式。例如,下面的代码展示了两个方法调用。在第一个方法调用中,实参是float类型的变量;第二个方法调用中,它是计算成float的表达式。

 例如,下面的代码展示了一个名称为MyMethord的方法,它有两个参数,一个MyClass型变量和一个int。

  • 方法为类的int类型字段和参数都加5.
  • 你可能还注意到MyMethord使用了修饰符static,我还没有解释过这个关键字,现在你可以忽略它,我会在第6章谈论静态方法。

图5-7说明了实参和形参在方法执行的不同阶段时的值,他表示以下3点。

  • 在方法开始时,系统在栈中为形参分配空间,并从实参复制值。
  1. 因为a1是引用类型,所以值被复制,产生一个独立的数据项。
  2. 因为a2是值类型,所以值被复制,产生了一个独立的数据项。
  • 在方法的结尾,f2和对象f1的子弹都被加上了5.
  1. 方法执行后,形参从栈中弹出。
  2. a2,值类型,它的值不受方法行为的影响。
  3. a1,引用类型,但它的值被方法的行为改变了。

 

5.11 引用参数

第二种参数类型是引用参数。

  • 使用引用参数时,必须在方法的声明和调用中都使用ref修饰符。
  • 实参必须是变量,在用作实参前必须被赋值。如果引用类型变量,可以赋值为一个引用或null。

例如,下面的代码阐明了引用参数和调用的语法: 

在之前的内容中我们已经认识到了,对于值参数,系统在栈上为形参分配内存,相反,引用参数具有以下特征。

  • 不会为形参在栈上分配内存。
  • 实际情况是,形参的参数名将作为实参变量的别名,指向相同的内存位置。

由于形参名和实参名的行为就好像指向相同的内存位置,所以在方法的执行过程中对形参作的任何改变在方法完成后依然有效(表现在实参变量上)。

 

 

图5-8阐明了在方法执行的不同阶段实参和形参的值。 

  • 在方法调用之前,将要被用作实参的变量a1和a2已经在栈里了。
  • 在方法的开始,形参名被设置为实参的别名。变量a1和f1引用相同的内存位置a2和f2引用相同的位置。
  • 在方法的结束位置,f2和f1的对象的字段都被加上了5.
  • 方法执行之后,形参的名称已经失效,但是值类型a2和引用类型a1所指向的对象的值都被方法内的行为改变了。

5.12 引用类型作为值参数和引用参数

在前几节中我们看到了,对于一个引用参数,不管是将其作为值参数传递还是作为引用参数传递,我们都可以在方法成员内部修改它的成员。不过我们并没有在方法内部设置形参本身。本节我们来看看那在方法内部设置引用类型形参时会发生什么。 

  •  将引用类型对象作为值参数传递 如果在方法内创建一个新对象并赋值给形参,将切断形参与实参之间的关联,并且在方法调用结束后,新对象也将不复存在。
  • 将引用类型对象作为引用参数传递 如果在方法内创建一个新对象并赋值给形参,在方法结束后该对象依然存在,并且是实参所引用的值。

下面代码展示了第一种情况-----将引用类型对象作为值参数传递:

 

图5-9阐明了上述代码的以下几点。

  • 在方法开始时,实参和形参都指向堆中相同的对象。
  • 在为对象成员赋值之后,它们仍指向堆中相同的对象。

  • 当方法分配新的对象并赋值给形参时,(方法外部的) 实参仍指向原始对象,而形参指向的是新对象。
  • 在方法调用之后,实参指向原始对象,形参和新对象都会消失。

下面的代码演示了将引用类型对象作为引用参数的情况。除了方法声明和方法调用时要使用ref关键字外,与上面的代码完全相投。

你肯定还记得,引用参数的行为就像是实参作为形参的别名。这样一来上面的代码就很好解释了。图5-10阐明了代码的以下几点。

  • 在方法调用时,形参和实参都指向堆中相同的对象。
  • 对成员值的修改会同时影响到形参和实参。
  • 当方法创建新的对象并赋值给形参时,形参和实参的引用都指向该新对象。
  • 在方法结束后,实参指向在方法内创建的新对象。

 

5.13 输出参数

输出参数用于从方法体内把数据传出到调用代码,它们的行为与引用参数非常类似。如同引用参数,输出参数有以下要求。

  • 必须在声明和调用中都使用修饰符。输出参数的修饰符是out而不是ref。
  • 和引用参数相似,实参必须是变量,而不能是其他类型的表达式。这是有道理的,因为方法需要内存位置保存返回值

例如,下面的代码声明了名称为MyMethod的方法,它带有单个输出参数。

与引用参数类似,输出参数的形参担当实参的别名。形参和实参都是同一块内存位置的名称。显然,方法内对形参的任何改变在方法执行完成之后通过实参变量都是可见的。

与引用参数不同,输出参数有以下要求。

  • 在方法内部,输出参数在能够被读取之前必须被赋值。这意味着参数的初始值是无关的,而且没有必要在方法调用之前为实参赋值。
  • 在方法返回之前,方法内部贯穿的任何可能路径都必须为所有输出进行一次赋值。

因为方法内的代码在读取输出变量之前必须对其写入,所以不可能使用输出参数把数据传入方法。事实上,如果方法中有任何执行路径试图在输出参数被方法赋值之前读取他,编译器就会产生一条错误信息。

 

图5-11阐明了在方法执行的不同阶段段中实参和形参的值。

  • 在方法调用之前,将要被用作实参a1和a2已经在栈里了。
  • 在方法的开始,形参的名称设置为实参的别名。你可以认为变量a1和f1指向的是相同的内存位置,也可以认为a2和f2指向的是相同的内存位置。a1和a2不在作用域之内,所以不能在MyMethod中访问。
  • 在方法内部,代码创建了一个MyClass类型的对象并把它赋值给f1.然后赋一个值 给f1的字段,也赋一个值 给f2,对f1和f2的赋值都是必须的,因为他们是输出参数。
  • 方法执行之后,形参的名称已经失效,但是引用类型的a1和值类型的a2的值都被方法的行为改变了。

5.14 参数数组

至此,本书所描述的参数类型都必须严格地一个实参对应一个形参。参数数组则不同,它允许零个或多个实参对应一个特殊的形参。参数数组的重点如下。

  • 在一个参数列表中只能有一个参数数组。
  • 如果有,他必须是列表的最后一个。
  • 由参数数组表示的所有参数都必须具有相同的类型。

声明一个参数数组必须做到的事如下。

  • 在数据类型前使用params修饰符。
  • 在数据类型后放置一组空的方括号。

下面的方法头展示了int型参数数组的声明语法。在这个示例中,形参inVals可以代表零个或多个int实参。

类型名后面的空方括号指明了参数是一个整数数组。在这里不必在意数组细节,它们将在第12章详细阐述。而现在,所有你需要了解的内容如下。

  • 数组是一组整齐的相同的数据项。
  • 数组使用一个数字索引进行访问。
  • 数组是一个引用类型,因此它所有的数据项都保存在堆中。

5.14.1 方法调用 

可以使用两种方式为参数数组提供实参。

  • 一个逗号分隔的该数据类型元素的列表。所有元素必须是方法声明中指定的类型。
  • 一个该数据类型元素的一堆数组。

请注意,在这些实例中,没有调用时使用params修饰符。参数数组中修饰符的使用与其他参数类型的模式并不相符。

  • 其他参数类型是一致的,要么都使用修饰符,要么都不使用修饰符。
  1. 值参数的声明和调用都不带修饰符。
  2. 引用参数和输出参数在两个地方都需要修饰符。
  • params修饰符的用法总结如下。
  1. 在声明中需要修饰符。
  2. 在调用中不允许有修饰符。

延伸式

参数数组方法调用的第一种形式有时被称为延伸式,这种形式在调用中使用分离的实参。例如,下面代码中的方法ListInsts的声明可以匹配它下面所有的方法调用,虽然它们有不同数目的实参。

在使用一个为参数数组分离实参的调用时,编译器做下面的事。

  • 接受实参列表,用它们在堆中创建并初始化一个数组。
  • 把数组的引用保存到栈中的形参里。
  • 如果在对应的形参数组的位置没有实参,编译器会创建一个有零个元素的数组来使用。

例如,下面的代码声明了一个名称为ListInts的方法,它带有一个参数数组。Main声明了3个整数并把他们传给了数组。

 

图5-12阐明了在方法执行的不同阶段实参和形参的值。

  • 方法调用之前,三个实参已经在栈里了。
  • 在方法的开始,三个实参被用于初始化堆中的数组,并且数组的引用被赋值给形参inVals。
  • 在方法内部,代码首先检查已确认数组引用不是null,然后处理数组,把每个元素乘以10并保存回去。
  • 方法执行之后,形参inVals失效。

关于参数数组,需要记住的重要一点是当数组在堆中被创建时,实参的值被复制到数组中在这方面,它们像值参数。

  • 如果数组参数是值类型,那么值类型被复制,实参不受方法内部影响。
  • 如果数组参数引用类型,那么引用类型被复制,实参引用的对象可以收到方法内部的影响。

 

5.14.2 用数组作为实参

也可以在方法调用之前创建并组装一个数组,把单一的数组变量作为实参传递。这种情况下编译器使用你的数组而不是重新创建一个。

例如,下面代码使用前一个示例中声明的方法ListIns.在这段代码中,Main创建一个数组并用数组变量而不是使用分离的整数作为实参。

 5.15 参数类型总结

因为有4种参数类型,有时很难记住它们的不同特征。表5-2对它们做了总结,使之更易于比较和对照。

5.16 方法的重载

一个类中可以有一个以上的方法拥有相同的名称,这叫做方法重载(method overload)。使用相同的名称的每一个方法必须有一个和其他方法不同的签名(signature)。

  • 方法的签名由下列信息组成,它们在方法声明的方法头中:
  1. 方法的名称;
  2. 参数的数量;
  3. 参数的类型和顺序;
  4. 参数的修饰符。
  • 返回类型不是签名的一部分,而我们往往认为是签名的一部分。
  • 请注意,形参的名称也不是签名的一部分。

下面的代码展示了一个非法的重载方法。两个方法仅返回类型和形参名不同,但它们仍有相同的签名,因为它们有相同的方法名,而参数的数目、类型和顺序也相同。编译器会对这段代码生成一条错误信息。 

5.17 命名参数

至今我们所有用到的参数都是位置参数,也就是说每一个实参的位置都必须与相应的形参位置一一对应。

此外,C#还允许我们使用命名参数(named parameter)。只要显示指定参数名字,就可以以任意顺序在方法调用中列出实参。细节如下

  • 方法的声明没有什么不一样。形参已经有名字了。
  • 不过在调用方法的时候,形参的名字后面跟着冒号和实际的蚕食值或者表达式,如下面的方法调用所示。在这里a、b、c是Calc方法3个形参的名字。

图5-13 在使用命名参数的时候,需要在方法调用中包含参数名字。而方法的声明不需要任何改变

在调用的时候,你可以即使用位置参数有使用命名参数,但如果这么做,所有位置参数必须先列出。例如,下面的代码演示了Calc方法的声明及其使用位置参数和命名参数不同组合的5种不同的调用方式

 

命名参试对于自描述的程序来说很有用,因为我们可以在方法调用的时候显示那个值赋给那个形参。例如,下面代码调用了两次GetCyLinderVolume,第二次调用具有更多的信息并且更不容易出错。

 

5.18 可选参数

C#还允许可选参数(optional parameter)。所谓可选参数就是我们可以在调用方法的时候包含这个参数,也可以省略它。

为了表明某个参数是可选的,你需要在方法声明的时候为参数提供默认值。指定默认值得语法和初始化本地变量的语法一样,如下面的代码的方法声明所示,在代码中,

  •  形参b设置成了默认值3;
  • 因此,如果在调用方法的时候只有一个参数,方法会使用3作为第二个参数的初始值。

对于可选参数的声明,我们需要知道如下几个重要事项。

  • 不是所有的参数类型都可以作为可选参数。图5-14列出了何时可以使用可选参数。
  1. 只要值类型的默认值在编译的时候可以确定,就可以使用值类型作为可选参数。
  2. 只有在默认值是null的时候,引用类型菜可以作为可选参数来使用。

 

  • 所有必填参数(required parameter)必须在可选参数声明之前声明。如果有params参数,必须在所有可选参数之后声明。图5-15演示了这种语法顺序。

 

在之前的示例中我们已经看到了,可以在方法调用的时候省略相应的实参从而认为可选参数使用默认值。但是在许多情况下,不能随意省略可选参数的组合,因为在很多情况下这么做会导致使用那些可选参数不明确,规则如下。

  • 你必须从可选参数列表的最后开始省略,一直到列表开头。
  • 也就是说,你可以省略最后一个可选参数,或是最后n个可选参数,但是不可以随意选择省略任意的可选参数,省略必须从最后开始。

 

 如果需要随意省略可选参数列表中的可选参数,而不是从列表的最后开始,那么必须使用可选参数的名字来消除赋值的歧义。在这种情况下,你需要结合利用命名参数和可选参数特性。下面的代码演示了位置参数。可选参数和命名参数的这种用法。

 

5.19 栈帧

至此,我们已经知道了局部变量和参数是位于栈上的,让我们再来深入讨论一下其组织。

在调用方法的时候,内存从栈的顶部开始分配,保存和方法关联的一些数据项。这块内存叫做方法的栈帧(stack frame)。

  •  栈帧包含的内容如下。
  1. 返回地址,也就是在方法退出的时候继续执行的位置。
  2. 这些参数分配的内存,也就是方法的值参数,或者还可能是参数数组(如果有的话)
  3. 各种和方法调用相关的其他管理数据项。
  • 在方法调用时,整个栈帧都会压如栈。
  • 在方法退出的时候,整个栈帧都会从栈上弹出,弹出栈帧有的时候也叫做栈展开(unwind)。

例如如下代码声明了3个方法。Main调用MethodA,MethodbB,创建了3个栈帧。在方法退出的时候,栈展开。

 

 

5.20 递归

除了调用其他方法,方法也可以调用自身。这叫做递归。

递归会产生很优雅的代码,比如下面计算阶乘数的方法就是如此。注意在本例的方法内部,方法使用比输入参数小1的实参调用自身。

 

调用方法自身的机制和调用其他方法其实完全一样的。都是为每一次方法调用把新的栈帧压如栈顶。

例如,在下面的代码中,Count方法比输入参数小1的值调用自身然后打印输入参数。随着递归也来越深,栈也越来越大。 

图5-17演示了这段代码。注意,如果输入值3,那么Count方法就有4个不同的独立栈帧。每一个都有其自己的输出参数值inVal。

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/2184.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【vm虚拟机】vmware虚拟机下载安装

vmware虚拟机下载安装🚩 vmware虚拟机下载🚩 安装虚拟机程序🚩 创建一个CentOS虚拟机🚩 异常情况🚩 vmware虚拟机下载 vmware官网下载地址 🚩 安装虚拟机程序 双击安装包exe程序,无脑下一步即…

来到CSDN的一些感想

之所以会写下今天这篇博客,是因为心中实在是有很多话想说!!! 认识我的人应该都知道,我是才来CSDN不久的,也可以很清楚地看见我的码龄,直到今天:清清楚楚地写着:134天&…

完美日记母公司再度携手中国妇基会,以“创美人生”助力女性成长

撰稿 | 多客 来源 | 贝多财经 当春时节,梦想花开。和煦的三月暖阳,唤醒的不止是满城春意,更有逸仙电商“创美人生”公益项目播撒的一份希望。 3月8日“国际妇女节”当日,为积极响应我国促进共同富裕的政策倡导,助力相…

C语言--自定义类型详解

目录结构体结构体的声明特殊的声明结构的自引用typedef的使用结构体变量的定义和初始化结构体的内存对齐为什么存在内存对齐?修改默认对齐数结构体传参位段位段的内存分配位段的跨平台问题枚举联合联合类型的定义联合在内存中开辟空间联合大小的计算结构体 结构体的…

Linux之磁盘分区、挂载

文章目录一、Linux分区●原理介绍●硬盘说明查看所有设备挂载情况挂载的经典案例二、磁盘情况查询基本语法应用实例磁盘情况-工作实用指令一、Linux分区 ●原理介绍 Linux来说无论有几个分区,分给哪一目录使用,它归根结底就只有一个根目录,…

可编程线性直流电源的特性有哪些?

可编程线性直流电源是一种高性能、高精度的电源设备,其主要特性包括以下几点:1、高稳定性:可编程线性直流电源具有极高的输出稳定性,能够保证输出电压、电流和功率的精度和稳定性。通常来说,稳定性能够达到0.01%或更高…

Linux的诞生过程

个人简介:云计算网络运维专业人员,了解运维知识,掌握TCP/IP协议,每天分享网络运维知识与技能。座右铭:海不辞水,故能成其大;山不辞石,故能成其高。个人主页:小李会科技的…

Android Lancet Aop 字节编码修复7.1系统Toast问题(WindowManager$BadTokenException)

近期在Bugly上出现7.1以下设备上出现大量BadTokenException: android.view.WindowManager$BadTokenExceptionUnable to add window -- token android.os.BinderProxy6c0415d is not valid; is your activity running?报错堆栈,如下所示: …

数据分析师CDA认证 Level Ⅰ笔记

**黑色字体部分为考纲,蓝色字体部分为笔记,仅供参考 PART 1 数据分析概念与职业操守 1、数据分析概念、方法论、角色 【领会】 数据分析基本概念(数据分析、数据挖掘、大数据) 数据分析目的及其意义 数据分析方法与流程 数据分析的…

【网络安全工程师】从零基础到进阶,看这一篇就够了

学前感言 1.这是一条需要坚持的道路,如果你只有三分钟的热情那么可以放弃往下看了。 2.多练多想,不要离开了教程什么都不会,最好看完教程自己独立完成技术方面的开发。 3.有问题多google,baidu…我们往往都遇不到好心的大神,谁…

【Leetcode】队列实现栈和栈实现队列

目录 一.【Leetcode225】队列实现栈 1.链接 2.题目再现 3.解法 二.【Leetcode232】栈实现队列 1.链接 2.题目再现 3.解法 一.【Leetcode225】队列实现栈 1.链接 队列实现栈 2.题目再现 3.解法 这道题给了我们两个队列,要求去实现栈; 首先&…

8大核心语句,带你深入python

人生苦短 我用python 又来给大家整点好东西啦~ 咱就直接开练噜!内含大量代码配合讲解 python 安装包资料:点击此处跳转文末名片获取 1. for - else 什么?不是 if 和 else 才是原配吗? No,你可能不知道, else 是个…

Cache的地址结构,tag到底与Cache什么关系,Cache容量与总容量,Cache行长,Cache字地址?

目录.Cache映射的问题一.Cache的三种映射重点:那么我说1.直接映射2.全相联映射3.组相联映射4.总结三种映射二.Cache的三个字眼(例题)1.Cache字地址多少位(字地址即按字编址)2.Cache容量与总容量3.Cache行长一.Cache的三种映射 重点&#xff…

C++ 类与对象

结构体与类:在C语言中结构体可以存储一些不同类型的数据,这个功能就很强大了,但是这些数据都是不安全的我们可以在主函数中随意修改它,在C中的类可以很好的解决这个问题。类就相当于C语言中的结构体一样,C结构体&#…

GC 垃圾回收机制

文章目录JVM 的内存模型对象存活?引用计数算法可达性分析算法垃圾收集标记-清除算法标记-复制算法标记-整理算法垃圾收集器垃圾收集器发展Serial / Serial OldParallel Scavenge / Parallel OldParNew / CMSG1ZGC扩展JVM 的内存模型 Java 虚拟机(Java V…

转速/线速度/角速度计算FC

工业应用中很多设备控制离不开转速、线速度的计算,这篇博客给大家汇总整理。张力控制的开环闭环方法中也离不开转速和线速度的计算,详细内容请参看下面的文章链接: PLC张力控制(开环闭环算法分析)_plc的收卷张力控制系统_RXXW_Dor的博客-CSDN博客里工业控制张力控制无处不…

按键修改阈值功能、报警功能、空气质量功能实现(STM32)

按键修改阈值功能 要使用按键,首先要定义按键。通过查阅资料,可知按键的引脚如图所示:按键1(S1)通过KEY0与PA0连接,按键2(S2)通过KEY1与PE2连接,按键3(S3&…

收到6家大厂offer,我把问烂了的《Java八股文》打造成3个文档。共1700页!!

前言大家好,最近有不少小伙伴在后台留言,近期的面试越来越难了,要背的八股文越来越多了,考察得越来越细,越来越底层,明摆着就是想让我们徒手造航母嘛!实在是太为难我们这些程序员了。这不&#…

【LINUX】初识文件系统

文章目录一、前言二、回顾C语言文件操作三、初识系统调用openreadwriteclose四、文件系统初识五、结语一、前言 二、回顾C语言文件操作 int main() {FILE* fp fopen("log.txt", "w");if (fp NULL){perror("fopen");}int cnt 0;fputs("…

spring2

1.Spring配置数据源1.1 数据源(连接池)的作用 数据源(连接池)是提高程序性能如出现的事先实例化数据源,初始化部分连接资源使用连接资源时从数据源中获取使用完毕后将连接资源归还给数据源常见的数据源(连接池):DBCP、C3P0、BoneC…