C#_02.13_基础四_.NET方法
一、方法概述:
方法是一块具有名称的代码。可以通过方法进行调用而在别的地方执行,也可以把数据传入方法并接受数据输出。
二、方法的结构:
方法头 AND 方法体
插入小知识,比较本地变量和字段:
# |
字段 |
本地变量 |
生命周期 |
实例被创建开始 |
从它在块中被声明开始 |
是否自动赋默认值 |
是 |
否 |
存储区域 |
实例字段是类成员,存储在堆里,无论是值类型还是引用类型 |
值类型:存储在栈里 |
是否类成员 |
是 |
不懂了,待问 |
三、var
首先变量实例的创建是使用的是变量创建表达式,使用变量表达式new+数据类型+();这个表达式会返回一个指向相应变量数据存储位置的索引。
由于返回的索引会包含创建的实例的类型,因此我们可以不使用如:
Int a=1;的形式来创建实例,而是使用var a=1;来创建实例。因此有必要补充var的用法:
1、只能用于本地变量的创建,不能用于字段;
2、只能在变量声明中包含初始化时使用;
3、一旦编译器推断出变量的类型,它就是不能更改的;
4、使用var在性能上是不存在影响的,亲测创建实例1000000次无影响。
四、块:
块已经很少用了,几乎也不用了,实际上方法就是一个具有名称的块,所以我们一般不使用块而是使用方法了。在方法中是可以使用块的,使用块的时候在快中的变量生命周期是从被创建到块结束。
如创建一个值类型的变量,当创建的时候这个值被存入栈中,而当块执行结束就会发生出栈操作。块的使用就是:{ 块体 }。So Easy吧!
五、本地常量
本地常量 |
1、在类型名称前面加上const关键字 |
2、存储的位置是和静态变量一样的,随着类的创建而创建,具有唯一性和不可更改性 |
|
3、可以使用null,但是不可以是对象的实例,因为对象的实例是在运行时才创建的,而不是在类创建的时候(编译时)就已经存在了的。 |
|
4、常量在生命周期仅仅在声明它的块中,如本地常量(eg:在方法中的)只在方法内可用。 |
除了本地常量还有成员常量,作用类似于静态变量,但是在内存中是没有存储位置的
六、方法的返回值:
方法可以有返回值也可以没有,不需要返回值的要用void来表明,有返回值的要指明返回值的类型。
当方法没有返回值的时候我们可以使用return在任意位置跳出函数。
当方法需要返回值的时候需要在方法执行的每一个结束的流程末尾添加上返回值,否则会报错。
七、方法的参数:
这个问题可以分为实参和形参,实参是用于初始化形参的。他们的类型一一对应。这个环节上个人感觉是很容易出现封箱和拆箱的。
参数的类别:
值参数(与值类型是完全不同的概念,只是名字像兄弟而已):
特点:把实参的值复制给形参的方式传递给形参。
解析:复制是这句话的重点。
如果参数是值类型,那么这个过程的意思是当我们调用方法的时候为形参在栈上面申请分配了内存空间,而把实参的值复制给形参进行传递。在方法中我们操作的形参虽然值和实参是一样的,甚至可能形参的变量名字也是一样的,但是事实上形参和实参确是两个不同的变量,在栈中各自都有自己的内存空间,只是值是一样的而已。而形参的生命周期是仅仅在方法中的,因此当方法执行结束形参就被回收了,而实参在这一个过程中的作用仅仅是被复制传值,在方法中的操作与实参是没有关系的。因此会出现很经典的一种情况是在方法中改变的值在方法之外并没有变化,要想变化就必须使用引用类型作为参数或者是使用引用参数。
如果是引用类型作为参数,那么在方法中修改了形参的值,实参中的值也会发生改变,为什么呢?因为引用类型是不一样的,回味创建引用类型实例的过程,如下:
因此,当实参向形参传递的过程发生的是实参把指向引用类型实例实际数据的引用复制给了形参,而引用类型的实际数据的存储位置是不变的,形参也没有在堆中再次申请内存空间进行存储实例的实际数据,要在堆中申请分配内存空间需要使用到new,因此在方法中形参和实参操纵的数据事实上事同一个。我们在方法中对这个引用类型的参数进行修改那么在方法之外他也是已经改变了的。
八、引用参数(与引用类型是完全不同的,只是名字像兄弟而已):与输出参数:
特点:引用参数是不会为形参在栈上申请分配内存空间的,而是把形参作为实参的一个别名,指向相同的内存地址。在方法中对形参进行操作实际上是就是对实参进行操作。
前面有说过在方法中进行生命的本地变量不会被赋予默认值,在使用的时候需要进行赋值才能够使用。但是确实有一个例外的,就是使用输出参数(与引用参数是不一样的)方法中使用out关键字是不需要在使用前堆变量进行赋值的,而是在方法结束之前必须对方法进行赋值。而使用ref必须在使用之前就对变量进行初始化。
九、数组参数:params 类型名[] 数组名;使用这个可以传递进入不确定数目的相同数据类型的参数。也可以传递一个数组作为参数进来。
参数总结:四中参数(值参数、引用参数、输出参数、数组参数)
参数类型 |
修饰符 |
声明时是否需要修饰符 |
调用时是否需要修饰符 |
值参数 |
无 |
||
引用参数 |
ref |
是 |
是 |
输出参数 |
out |
是 |
是 |
数组参数 |
params |
是 |
否 |
十、方法重载:
方法重载,什么样的方法是重载的呢?就是:
1、名称相同,
2、返回值相不相同不管,
3、参数的数目、参数的类型、参数的顺序、修饰符(如ref)不完全相同的方法。
方法的返回值与方法的是否重载是没有关系的。
如下图:
十一、命名参数:
这个是很少见的命名参数,使用方法很简单,方法的声明部分并没有什么不同,关键是在方法调用的时候不一样。
在调用方法的时候如果实参的位置和形参的位置严格一致,那这种调用方式就是使用位置参数,而命名参数方法是在调用的时候把参数变成:“形参名:实参值”的形式,如:
但是这个方法是存在一个弊端的,就是我们前面有提到的,定义重载方法的方式有一种是参数丶顺序不一样,那也算是一种重载,而使用命名参数之后它是会忽略掉位置信息的,是会出现报错的,如果我们太混蛋定义了仅仅是参数顺序不一致的重载方法的话。按照书本的说法使用命名参数增加了传递的信息,增强了代码的可读性,但是我反倒觉得这样子写变得繁琐了,不喜欢这样子的写法,建议只是了解。
十二、可选参数:
可选参数是一个很有用的传递参数的法子,使用方法如下:
当我们调用含有可选参数方法的时候需要注意的是,我们可以不对这个参数进行传值,那么在方法体中该参数就会被使用默认值。而在有多个可选参数的时候忽略可选参数只能从最后一个参数开始忽略而不能从任意顺序开始忽略。
十三、栈帧:
栈帧是什么呢?
栈帧:在调用方法的时候内存从栈的顶部开始分配,保存和方法关联的一些数据项,这块内存空间成为方法的栈帧。
栈帧包含的内容:
① 返回的地址,其实就是方法被调用之后下一条语句的地址;
② 为该方法的参数分配的内存,也就是方法的值参数,或者可能是参数数组;
③ 各种和方法调用相关的其他管理数据项。
在方法调用时整个栈帧都会压入栈;
在方法退出的时候整个栈帧都会从栈上弹出(栈展开)。
(该图片引自C#图解)
十四、递归方法:就是调用自己的方法,典型的是求阶乘的方法。。递归方法是很巧妙的应用。