C#之方法
在C#中,方法是类的函数成员,方法由两个主要部分:
(1)方法头:指定了方法的特征,包括是否返回数据,如果返回,返回什么类型;方法的名称;哪种类型的数据可以传递给方法或从方法返回,以及如何处理这些数据.
(2)方法体:包含可执行代码的语句序列.
本地变量
与类的字段一样,本地变量也保存数据,字段通常保存和对象状态有关的数据,而创建本地变量经常是用于保存本地的或临时的计算数据.
实例字段 | 本地变量 | |
---|---|---|
生存期 | 从实例被创建时开始,到实例不再被访问时结束 | 从它在块中被声明开始,在块完成执行时结束 |
隐式初始化 | 初始化该类型的默认值 | 没有隐式初始化,如果变量在使用之前没有被赋值,编译器会产生一条错误信息 |
存储区域 | 由于实例是类成员,所以所有的字段都存储在堆里,无论它们是值类型还是引用类型 | 值类型:栈 引用类型:引用存储在栈,数据存储在堆. |
类型推断和var关键字
在本地变量声明中,可以使用var
关键字进行类型推断,比如
static void Main(){
var total=15;
var mec=new MyExcellentClass();
}
var
表示可以从初始化语句的右边推断出的类型,第一个声明中,它是int
的速记,第二个声明中,它是MyExcellentClass
的速记。需要注意的还有:只能在变量声明中包含初始化时使用,一旦编译器推断出变量的类型,它就是固定且不能更改的。
本地常量
常量最重要的两个特征,一是在声明时必须初始化,二是在声明后不能改变。
const Type Identifier=Value;
const 不是一个修饰符,而是核心声明中的一部分,它必须放在类型的前面。
参数
形参
形参是本地变量,它声明在方法的参数列表中,而不是在方法体中,它在整个方法体内使用。
如:
public void PrintSum(int x, float y) //形参声明
{
...
}
实参
当代码调用一个方法时,形参的值必须在方法的代码开始执行之前就被初始化。(有一种类型例外,称为输出参数)。用于初始化形参的表达式或变量称为实参,实参位于方法调用的参数列表中,每个实参必须与对应的形参的类型相匹配,或是编译器必须能够把实参隐式转换为那个类型。当方法被调用时,每个实参的值都被用于初始化相应的形参,方法体随后被执行。
- 位置参数
位置参数要求实参的数量必须与形参的数量一致,并且每个实参的类型与所对应的形参类型一致。
namespace LearningCSharp
{
class MyClass
{
public int Sum(int x,int y) //形参
{
return x + y;
}
public float Avg(float input1,float input2) //形参
{
return (input1 + input2) / 2;
}
}
class Program
{
static void Main()
{
MyClass myT = new MyClass();
int someInt = 6;
Console.WriteLine("Newflash:sum:{0} and {1} is {2}", 5, someInt, myT.Sum(5, someInt)); //调用方法中的实参
Console.WriteLine("Avg:{0} and {1} is {2}", 5, someInt, myT.Avg(5, someInt)); //调用方法中的实参,第二次调用编译器把int值隐式的转换为float值
}
}
}
值参数
使用值参数,通过将实参的值复制到形参的方法把数据传递给方法,方法被调用的时,系统在栈中为形参分配空间,将实参的值复制给形参,所以对于值类型来讲,值参数并不会改变其值,对于引用类型,值参数可能会改变其值。
值参数的实参不一定是变量,它可以是任何能计算成相应数据类型的表达式。
把变量用作实参之前,变量必须赋值(除非是输出参数,但输出参数虽然不要求实参必须赋值,但要求方法内的形参在被读取前,必须在方法内完成一次赋值),对于引用类型,变量可以被设置成为一个实际的引用或null。
需要注意的是:所谓值参数和值类型是两个概念,值类型指的是其值被记录在栈上,而值参数是将实参的值复制给形参(如果值参数类型是值类型,那么在栈上复制一个相同的值,如果值参数类型是引用类型,则在栈上把实参的引用复制给形参作引用)
namespace LearningCSharp
{
class MyClass
{
public int Val = 20;
}
class Program
{
static void MyMethod(MyClass f1,int f2)
{
f1.Val = f1.Val + 5;
f2 = f2 + 5;
Console.WriteLine("f1.Val:{0},f2:{1}", f1.Val, f2);
}
static void Main()
{
MyClass a1 = new MyClass();
int a2 = 10;
MyMethod(a1, a2); //对于值参数,实参必须先赋值
Console.WriteLine("f1.Val:{0},f2:{1}", a1.Val, a2);
}
}
}
在方法调用前,用作实参的a1,a2都在栈中分配了空间(必须);方法开始时,系统在栈中为形参分配空间,并从实参复制值,因为a1是引用类型,所以引用被复制,都引用堆中的同一个对象;在方法的结尾,f1与f2的字段都被加上了5,方法执行完毕,形参从栈中弹出,a2值类型不受方法的影响。
引用参数
使用引用参数时,必须在方法的声明和调用时都使用ref
修饰符,与值参数不同的是,实参必须是变量,在用作实参前必须被赋值,如果是引用类型变量,可以赋值为一个引用或null。
上图可以看到,对于引用参数来讲,实参必须是变量,而不能是表达式。
对于引用参数,系统不会为形参在栈上分配空间,而是形参的参数名作为实参变量的别名,指向相同的内存位置。这样对值类型实参来讲,函数就有可能改变其值。
namespace LearningCSharp
{
class MyClass
{
public int Val = 20;
}
class Program
{
static void MyMethod( ref MyClass f1,ref int f2)
{
f1.Val = f1.Val + 5;
f2 = f2 + 5;
Console.WriteLine("f1.Val:{0},f2:{1}", f1.Val, f2);
}
static void Main()
{
MyClass a1 = new MyClass();
int a2 = 10;
MyMethod(ref a1,ref a2 ); //对于应用参数,实参也必须先赋值
Console.WriteLine("f1.Val:{0},f2:{1}", a1.Val, a2);
}
}
}
在方法调用之前,将要被用作实参的变量a1,a2已经在栈里了;在方法的开始,形参名被设置为实参的别名,变量a1和f1引用相同的内存位置,a2和f2引用相同的内存位置;在方法结束位置,f2和f1的对象的字段都被加上了5.
引用类型作为值参数和引用参数,并且在方法内部创建一个新对象赋值给形参
- 将引用类型对象作为值参数传递,因为实参和形参的引用都在栈上,且是分配了不同的空间,如果在方法内创建一个新对象并赋值给形参,那么形参就引用了该新的对象,而实参的引用没变,所以将切断形参和实参的联系,并在方法调用后,形参被弹出栈,新对象也就不复存在。
namespace LearningCSharp
{
class MyClass
{
public int Val = 20;
}
class Program
{
static void MyMethod( MyClass f1)
{
f1.Val = 50;
Console.WriteLine($"After member assignment:{f1.Val}");
f1 = new MyClass();
Console.WriteLine($"After new object creation:{f1.Val}");
}
static void Main()
{
MyClass a1 = new MyClass();
Console.WriteLine($"Before method call:{a1.Val}");
MyMethod(a1 );
Console.WriteLine($"After method call:{a1.Val}");
}
}
}
- 将引用类型对象作为引用参数传递,因为实参和形参都是栈上同一个空间,互为彼此的别名,也引用了同一堆上的对象,若在方法内新创建一个新对象,并赋值给形参,那么形参和实参也都将引用该新对象,因此在方法结束后,该对象仍然存在,并且是实参所引用的值。
namespace LearningCSharp
{
class MyClass
{
public int Val = 20;
}
class Program
{
static void MyMethod(ref MyClass f1)
{
f1.Val = 50;
Console.WriteLine($"After member assignment:{f1.Val}");
f1 = new MyClass();
Console.WriteLine($"After new object creation:{f1.Val}");
}
static void Main()
{
MyClass a1 = new MyClass();
Console.WriteLine($"Before method call:{a1.Val}");
MyMethod(ref a1 );
Console.WriteLine($"After method call:{a1.Val}");
}
}
}
输出参数
输出参数必须在声明和调用中都使用out
修饰符,和引用参数类似,实参必须是变量,不能 是其他类型的表达式,输出参数的形参也担任了实参的别名,与引用参数不同的是:在方法内部,输出参数在读取之前必须被赋值,这意味着实参初始值的有无对输出参数没有影响;在方法返回之前,方法内部必须至少为输出参数赋值一次。
输出参数这个特点也是可以被理解的,它设计的目的就是输出一些参数,因此实参没必要进行初始化,但形参必须在方法内完成初始化,否则就算实参进行了初始化,在方法体内,形参没有初始化,方法内读取形参,仍然会报错。
namespace LearningCSharp
{
class MyClass
{
public int Val = 20;
}
class Program
{
static void MyMethod(out MyClass f1,out int f2)
{
f1 = new MyClass();
f1.Val = 25;
f2 = 15;
}
static void Main()
{
int a2 = 4;
MyClass a1 = null;
MyMethod(out a1, out a2);
Console.WriteLine($"{a1.Val},{a2}");
}
}
}
参数数组
参数数组允许零个或多个实参对应一个特殊的形参.需要关注的重点如下:
- 在一个参数列表中只能有一个参数数组.
- 如果有,它必须是列表中的最后一个
- 由参数数组表示的所有参数都必须具有相同的类型
声明一个参数数组的语法如下:
params int[] intVals
其中,params是修饰符,数据类型后需要放置一组空的方括号.
方法调用
可以使用两种方式为参数数组提供实参:
- 一个逗号分割的该数据类型元素的列表.所有元素必须是方法声明中指定的类型.
ListInts(10,20,30);
- 一个该数据类型元素的一维数组
int[] intArray={1,2,3};
ListInts(intArray);
注意到调用时没有使用params
修饰符.
namespace LearningCSharp
{
class MyClass
{
public void ListInts(params int[] inVals)
{
if ((inVals !=null) && (inVals.Length!=0))
for (int i = 0; i < inVals.Length; i++)
{
inVals[i] = inVals[i] * 10;
Console.WriteLine($"{inVals[i]}");
}
}
}
class Program
{
static void Main()
{
int first = 5, second = 6, third = 7;
MyClass mc = new MyClass();
mc.ListInts(first, second, third);
Console.WriteLine($"{first},{second},{third}");
}
}
}
用数组作为实参
namespace LearningCSharp
{
class MyClass
{
public void ListInts(params int[] inVals)
{
if ((inVals !=null) && (inVals.Length!=0))
for (int i = 0; i < inVals.Length; i++)
{
inVals[i] = inVals[i] * 10;
Console.WriteLine($"{inVals[i]}");
}
}
}
class Program
{
static void Main()
{
int[] myArr = { 4, 5, 6 };
MyClass mc = new MyClass();
mc.ListInts(myArr);
foreach (int x in myArr)
Console.WriteLine(x);
}
}
}
对比以上两个代码片段,第一个是通过调用分离的实参,第二个是通过调用数组:
当数组在堆中创建,实参的值被复制到数组中,如果数组参数是值类型,那么值被复制,实参不受方法影响;如果数组参数是引用类型,那么该引用被复制,实参的引用对象可以受到方法内部的影响.
方法重载
一个类中可以有一个以上的方法拥有相同的名称,这叫做方法重载.使用相同名称的没个方法必须有一个和其他方法不同的签名.所谓签名是:
方法的名称,参数的数目,参数的数据类型和顺序,参数的修饰符.
返回类型和形参名称不是签名的一部分.
命名参数
每个实参的位置都必须与相应的形参的位置一一对应的参数称为位置参数,除此之外,C#还允许我们使用命名参数,只要显式指定参数的名字,就可以以任意顺序在方法调用中列出实参.
命名参数在调用中采用形参名称:实参值
的方式.
namespace LearningCSharp
{
class MyClass
{
public int Calc(int a,int b,int c)
{
return (a + b) * c;
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
int result = mc.Calc(c: 2, a: 4, b: 3);
Console.WriteLine($"{result}");
}
}
}
可选参数
为表明某个参数是可选的,需要在方法声明的时候为参数提供默认值.
namespace LearningCSharp
{
class MyClass
{
public int Calc(int a,int b,int c=3)
{
return (a + b) * c;
}
}
class Program
{
static void Main()
{
MyClass mc = new MyClass();
int result = mc.Calc( a: 4, b: 3);
Console.WriteLine($"{result}");
}
}
}
不是所有的参数类型都可以作为可选参数,事实上,只有值参数的值类型和值参数的只允许null的默认值的引用类型才可以有可选参数.
所有必填参数必须在可选参数声明之前声明,如果有参数数组,必须在所有可选参数之后声明.
(intx,decimal y,...int op1=17,double op2=36,....params int[] intVals)
//必填参数.....可选参数.....params参数
C#之方法的更多相关文章
- javaSE27天复习总结
JAVA学习总结 2 第一天 2 1:计算机概述(了解) 2 (1)计算机 2 (2)计算机硬件 2 (3)计算机软件 2 (4)软件开发(理解) 2 (5) ...
- mapreduce多文件输出的两方法
mapreduce多文件输出的两方法 package duogemap; import java.io.IOException; import org.apache.hadoop.conf ...
- 【.net 深呼吸】细说CodeDom(6):方法参数
本文老周就给大伙伴们介绍一下方法参数代码的生成. 在开始之前,先补充一下上一篇烂文的内容.在上一篇文章中,老周检讨了 MemberAttributes 枚举的用法,老周此前误以为该枚举不能进行按位操作 ...
- IE6、7下html标签间存在空白符,导致渲染后占用多余空白位置的原因及解决方法
直接上图:原因:该div包含的内容是靠后台进行print操作,输出的.如果没有输出任何内容,浏览器会默认给该空白区域添加空白符.在IE6.7下,浏览器解析渲染时,会认为空白符也是占位置的,默认其具有字 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
- [C#] C# 基础回顾 - 匿名方法
C# 基础回顾 - 匿名方法 目录 简介 匿名方法的参数使用范围 委托示例 简介 在 C# 2.0 之前的版本中,我们创建委托的唯一形式 -- 命名方法. 而 C# 2.0 -- 引进了匿名方法,在 ...
- ArcGIS 10.0紧凑型切片读写方法
首先介绍一下ArcGIS10.0的缓存机制: 切片方案 切片方案包括缓存的比例级别.切片尺寸和切片原点.这些属性定义缓存边界的存在位置,在某些客户端中叠加缓存时匹配这些属性十分重要.图像格式和抗锯齿等 ...
- [BOT] 一种android中实现“圆角矩形”的方法
内容简介 文章介绍ImageView(方法也可以应用到其它View)圆角矩形(包括圆形)的一种实现方式,四个角可以分别指定为圆角.思路是利用"Xfermode + Path"来进行 ...
- JS 判断数据类型的三种方法
说到数据类型,我们先理一下JavaScript中常见的几种数据类型: 基本类型:string,number,boolean 特殊类型:undefined,null 引用类型:Object,Functi ...
- .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法
.NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...
随机推荐
- Eclipse各历史版本所需的最低JDK版本统计
Eclipse 版本名称 Version 发布时间 最低支持的jdk Kepler 4.3 2013.06 Java 6 Luna 4.4 2014.06.25 Java 7 Mars 4.5 201 ...
- Minecraft server.properties 参数含义 1.18.1,Java版
服务器搭建 参照: https://www.spigotmc.org/wiki/buildtools/#latest 参数含义 #Fri Feb 11 15:20:40 CST 2022 # 启用jm ...
- docker - [14] redis集群部署
本章节是在一个服务器上进行演示 一.准备工作 (1)创建redis集群使用的网络:redis-net docker network create redis-net --subnet 172.38.0 ...
- VMware16虚拟机安装激活教程
1.开始安装前需要准备好的软件 VMware-workstation-full-16--虚拟机软件(必要) 获取方式: 官方下载地址:https://www.vmware.com/cn/product ...
- 理解Rust引用及其生命周期标识(上)
写在前面 作为Rust开发者,你是否还没有完全理解引用及其生命周期?是否处于教程一看就会,但在实际开发过程中不知所措?本文将由浅入深,手把手教你彻底理解Rust引用与生命周期. 关于本文的理解门槛 本 ...
- Windows服务器等保审核安全设置
1.开启账户锁定策略 进入Windows服务器,快捷键"WIN+R"打开运行窗口.输入"gpedit.msc"并点击确定,依次点击"计算机配置&quo ...
- python 代码编写问题
1.解决控制台不输出问题 2.写代码写一些伪代码,即实现过程.步骤 3.再填充代码到伪代码 4.规则 正常变量 不太推荐使用下划线
- google浏览器删除token
测试登录时长,页面是否返回到首页 删除token
- [Vue warn]: Unknown custom element: did you register the component correctly?
前言 [Vue warn]: Unknown custom element: did you register the component correctly? For recursive compo ...
- crontab Do you want to retry the same edit? (y/n)
crontab: installing new crontab "/tmp/crontab.tEoCzO":2: bad day-of-month errors in cronta ...