C#方法中的各类参数
居家隔离的第26天,还在持续的疫情着实让人担忧,看着每天新增的确认人数数字,也在为那些家庭祝福,每当想想那不是一个数字是一条条鲜活的生命时就格外沉重。利用闲在家里的时间巩固C#语言的一个难点。最近在温习刘铁锰老师教学视频《C#语言入门详解》加上翻看其他的电子图书巩固自己对一些难点知识的印象,好记性不如烂笔头,组织语言记录下来效果更佳。各种方法通过不同的逻辑和顺序组合在一起就形成了程序,常规都是带有参数的方法,参数可以分为以下几类:
- 传值参数
- 引用参数
- 输出参数
- 数组参数
- 可选参数
- 具名参数
- 扩展方法(this参数)
1、传值参数:声明时不带任何修饰符的形参是值形参,一个值形参对应一个局部变量,只是它的初始值来自该方法调用所提供的相对形参。形参是实参的副本,给形参赋值并不影响实参。针对参数的数据类型为值类型和引用类型分为两种情况进行讨论。
虚线以下是方法之内,虚线以上是方法之外。值参数实际上是方法内部的一个局部变量,是我们传进来的实参的一个副本,用实线框出来表明是副本关系,不会互相影响。
右边是说在方法体内对参数进行了赋值,参数获得了新值,方法外实际参数并不会改变。
通过观察实例的运行结果可以得到一致的结论,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; /// <summary>
/// 值参数也叫传值参数
/// 定义:声明时不带修饰符的形参是值形参。
/// 一个值形参对应了一个局部变量,只是它的初始值来自该方法调用所提供的相对实参。
/// </summary>
namespace BiliBiliVideoCSharpDemo.Paramter
{
/// <summary>
/// 数据类型为值类型的值参数
/// </summary>
public class ValueParamter
{
public void AddOne(int paramOne)
{
paramOne = paramOne + ;
Console.WriteLine("paramOne ="+paramOne);
}
}
} using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BiliBiliVideoCSharpDemo.Paramter; namespace BiliBiliVideoCSharpDemo
{
class Program
{
static void Main(string[] args)
{
#region 测试参数类型为值类型的传值参数示例
ValueParamter valueParamter = new ValueParamter();
int y = ;
valueParamter.AddOne(y);
Console.WriteLine(y);
#endregion
47
Console.ReadLine();
}
50 }
}
输出结果为:
paramOne =101
100
表明实参y还是100并未被修改,可以结合前面对于值类型的内存分析,方法AddOne调用时会在栈中分配一个新的局部变量(形参),变量初始化时存放的是实参一样的数值,变量和实参在栈中是两个地址。
实参(引用类型变量)存储所引用对象的地址,给形参(方法内参数,实参的副本)赋初始值时是把存储的地址赋值给形参。
右边我们在方法内部给形参赋新值,一般情况下为引用变量赋新值时赋值号的右边一般是new操作符的表达式。new操作符的作用就是根据数据类型创建对象,并且调用对象的实例构造器,然后再把实例在堆内存中的地址通过赋值符号交给我们的引用变量。结果就是实参和形参存储的是两个不一样的地址。
还是结合实例来观察结果是不是的确如此:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BiliBiliVideoCSharpDemo.Paramter; namespace BiliBiliVideoCSharpDemo
{
class Program
{
static void Main(string[] args)
{
#region 测试参数类型为引用类型的传值参数示例 方法中给引用类型对象赋新值
//方法内部给方法参数赋了新值(给对象赋新值一般就是使用new操作符),对方法参数的修改并不会影响传入进来的实参。
//根据输出的Hashcode就可以发现前后是两个对象。
Student tim = new Student();
tim.Name = "Tim"; NewObject(tim);
PrintStudent(tim);
#endregion Console.ReadLine();
} public static void NewObject(Student student)
{
student = new Student { Name = "Tom" };
PrintStudent(student);
}/// <summary>
/// 打印对象信息
/// </summary>
/// <param name="stu"></param>
public static void PrintStudent(Student stu)
{
Console.WriteLine(string.Format("Hascode:{0},Name:{1}", stu.GetHashCode(), stu.Name));
}
}
public class Student
{
public string Name { get; set; }
}
}
输出结果:HashCode值跟引用对象在堆上的内存地址相关,所以运行结果不一定和我的一致。
Hascode:46104728,Name:Tom
Hascode:12289376,Name:Tim
根据hasCode可以判断形参和实参不是同一个对象,也符合之前对于传值参数的定义。有同学可能疑问,如果不采用new操作重新给形参赋值,只是修改形参的属性值呢?结果是形参和实参的引用是同一个对象,属性值也一样,但是方法调用时栈中会创建一个实参的副本,里面存储的值和实参存储的值一样,实参存储的值是引用对象在堆上的内存地址,所以形参存储的也是引用对象在内存中的地址,修改对象属性时先通过引用对象的地址找到引用对象再修改对象的属性。作为一个方法而言,它的主要输出还是靠它的返回值,一般情况下我们把这种修改参数所引用的对象的值的操作叫做某个方法的副作用,不是方法的主作用,是方法顺带的作用,一般不会这样书写代码,并且C#为我们这种显式利用方法副作用的需求设计了引用参数。
2、引用参数::引用形参是用ref修饰符声明的形参。引用形参并不创建新的存储位置。相反,引用形参表示的存储位置恰好是在方法调用中作为实参给出的那个变量所表示的存储位置。
注意:当形参为引用形参时,方法调用中的对应实参必须由关键字ref并后接一个与形参类型相同的variablereference组成。变量在可以作为引用形参传递之前,必须先明确赋值。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BiliBiliVideoCSharpDemo
{
class Program
{
static void Main(string[] args)
{
#region 测试参数类型为引用类型的引用参数示例
Student stu = new Student() { Name = "RefObject" };
RefObject(ref stu);
PrintStudent(stu); Student stu2 = new Student() { Name = "Object" };
Object(stu2);
PrintStudent(stu2); //对比参数类型为引用类型的引用参数和参数类型为引用类型的传值参数,发现观察输出效果是一样的,
//但是实际上的机理不一样,引用参数并不会创建实参的副本,并不会创建新的变量存储在栈上,参数类型为引用类型的传值参数会创建指向实参地址的新变量
#endregion Console.ReadLine();
} public static void Object(Student stu)
{
stu.Name = "ObjectTwo";
PrintStudent(stu);
} public static void RefObject(ref Student stu)
{
stu.Name = "RefObjectTwo";
PrintStudent(stu);
} /// <summary>
/// 打印对象信息
/// </summary>
/// <param name="stu"></param>
public static void PrintStudent(Student stu)
{
Console.WriteLine(string.Format("Hascode:{0},Name:{1}", stu.GetHashCode(), stu.Name));
}
}
public class Student
{
public string Name { get; set; }
}
}
输出结果:
Hascode:43495525,Name:RefObjectTwo
Hascode:43495525,Name:RefObjectTwo
Hascode:55915408,Name:ObjectTwo
Hascode:55915408,Name:ObjectTwo
可以发现方法内的局部变量(形参)跟方法外的实参是相等的,在源码的注释中标注了参数数据类型为引用类型的传值参数和引用参数的差异。
3、输出参数:用out修饰符声明的形参是输出形参。类似于引用形参,输出形参不创建新的存储位置。相反,输出形参表示的存储位置恰是在该方法调用中作为实参给出的那个变量所表示的存储位置。输出变量用于除返回值外还需要输出的场景。
注意:1)当形参为输出形参时,方法调用中的相应实参必须由关键字out并后接-一个与形参类型相同的变量组成。变量在可以作为输出形参传递之前不一定需要明确赋值,但是在将变量作为输出形参传递的调用之后,该变量被认为是明确赋值的。
2)在方法内部,与局部变量相同,输出形参最初被认为是未赋值的,因而必须在使用它的值之前明确赋值。在方法返回之前,该方法的每个输出形参都必须明确赋值。
这里给出一个案例:
/// <summary>
/// 将输入内容转换为整型,转换失败返回false
/// </summary>
/// <param name="input">输入字符串内容</param>
/// <param name="result"返回值></param>
/// <returns>false-失败 true-成功</returns>
static bool ConvertToInt(string input,out int result)
{
try
{
result = Convert.ToInt32(input);
return true;
}
catch (Exception)
{
result = ;//这里必须添加输出参数的赋值语句,输出参数必须要在方法返回之前进行赋值操作,输出参数的应用场景是用于除返回值外还需要输出的情况。
return false;
}
} #region 测试输出参数示例 这里书写一个将输入内容转换为整型的方法,转换成功返回true否则返回false
//调用必须显式加上out修饰符
ConvertToInt("", out int result);
#endregion
4、数组参数:用params修饰符声明的形参是数组参数,注意:数组参数必须是形参列表的最后一个,调用时跟ref out修饰的不太一样,并不需要使用params,也不允许这样书写。直接看入参为数组类型参数的传值参数和数组参数的区别吧。你可能还会发现很多内置方法中使用了数组参数,例如String.Format方法就有一个object类型的数组参数。
/// <summary>
/// 使用数组参数简化数组类型入参的方法的调用方式
/// 方法声明时需要使用params修饰符修饰,并且必须是方法形参列表的最后一个参数
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
static int GetSumByParams(params int[] arr)
{
int result = ;
foreach (int item in arr)
{
result += item;
}
return result;
} /// <summary>
/// 求长整型数组的和
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
static int GetSum(int[] arr)
{
int result = ;
foreach (int item in arr)
{
result += item;
}
return result;
} #region 对比方法入参为数组使用数组参数和传统传值参数调用方式的区别
//传统传值参数需要先构建数组
int[] arr = new int[] { ,,,,};
Console.WriteLine(GetSum(arr)); Console.WriteLine("-----------");
//不需要先构建数组但是也可以使用数组
Console.WriteLine(GetSumByParams(,,,,));
Console.WriteLine(GetSumByParams(arr));
Console.WriteLine(String.Format("{0}+{1}={2}",,,));
Console.WriteLine(String.Format("{0}+{1}={2}", new object[] { , , }));
#endregion
5、具名参数:这实际上是一种方法调用方式的调整,方法声明跟传统传值参数并没有区别方法调用时以方法形参名+ : +参数数值传进方法的形式调用方法,好处就是参数的位置不受约束不必跟方法声明时参数顺序一致,还有就是方法调用时可以较容易分辨参数的含义是什么。接下来是案例时间:
/// <summary>
/// 跟别人打招呼
/// </summary>
/// <param name="myName">我的名字</param>
/// <param name="myAge">我的年龄</param>
static void GreetToPeople(string myName, int myAge,string peopleName)
{
Console.WriteLine(string.Format("Hello,{2}.my name is {0},I am {1} years old.",myName,myAge,peopleName));
} #region 具名参数的使用
//参数的位置不再约束
GreetToPeople(peopleName:"Bob", myAge: , myName: "Tom");
//允许只对部分形参使用具名的形式,但是必须放在所有非具名参数的后面 当然此时非具名参数必须跟方法形参对应
GreetToPeople("Powter", peopleName: "Bob", myAge: );
#endregion
6、可选参数:参数因为具有默认值而变得“可选”即可以不传入,要注意可选参数必须在方法的参数列表中非可选参数的后面。直接看案例:
static void SayHello(string Name = "Tom", int Age = )
{
Console.WriteLine("Hello,my name is"+Name+",i am "+Age+"years old.");
} #region 可选参数的使用
SayHello();
SayHello("Powter");
//SayHello(26); 这种写法是不允许的,因为会认为传进来的参数是第一个可选参数的数值,结果就出现类型不匹配,所以表明有多个可选参数调用时,给可选参数赋值时前面的可选参数必须赋值
//可是使用具名参数解决上一个问题
SayHello(Age: );
#endregion
7、扩展方法(this参数):由this修饰的参数,必须是方法参数列表中的第一个参数,并且方法必须是公共的、静态的,即被public static所修饰,必须包含在一个静态类中。扩展方法一般是对某个类(例如叫Double)进行扩展,所以建议静态类命名为 DoubleExtension 。
例如我们想对一个double类型的数值进行按精度舍入时,我们一般都是借用Math.Round(double value,int digits)方法实现,可是每次都这样就比较麻烦,要是能直接x.Round(int digits)就好了,使用扩展方法就可以实现功能。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace BiliBiliVideoCSharpDemo
{
public static class DoubleExtension
{
public static double Round(this double value, int digits)
{
double result = ;
result = Math.Round(value, digits);
return result;
}
}
} #region this参数的使用
double pi = 3.1415926;
Console.WriteLine(pi.Round());
#endregion
输入pi.编辑器智能提醒就可以看到多了一个Round方法,前面的图标跟正常方法不太一样,这就是扩展方法的图标。
这篇关于C#方法参数的博客到此就算结束了,主要给自己牢记这些概念。分享出来希望对读者有些帮助,有什么不妥之处欢迎留言讨论。
C#方法中的各类参数的更多相关文章
- 关于replace()方法中第二个参数的转义问题
如果你想通过Javascript代码在网页中呈现 \ 字符,则在JS代码中你必须输入两个反斜杠 \\,否则会报错.比如: var a = "\"; alert(a); //chro ...
- 4、处理方法中获取请求参数、请求头、Cookie及原生的servlet API等
1.请求参数和请求头 使用@RequestParam绑定请求参数,在处理方法的入参处使用该注解可以把请求参数传递给请求方法 —— value :参数名 —— required : 是否必须,默认为tr ...
- [改善Java代码]注意方法中传递的参数要求(replaceAll和replace的区别)
有这样一个简单的需求:写一个方法,实现从原始字符串中删除与之匹配的所有子字符串,比如"蓝蓝的天,白云飘"中,删除"白云飘",输出"蓝蓝的天," ...
- 143、Java内部类之访问方法中定义的参数或变量
01.代码如下: package TIANPAN; class Outer { // 外部类 private String msg = "Hello World !"; publi ...
- Java连载67-深入一维数组、main方法中的args参数详解
一.复习了一维数组,还复习了强制类型转换的注意点. package com.bjpowernode.java_learning; public class D67_1_GoDeepIntoArrays ...
- Java在方法中定义可变参数类型
学习目标: 掌握可变参数的应用 学习内容: 1.定义 在方法中传递数组有一种更简单的方式--方法的可变参数,其本质是一个语法糖,目的是让开发者写代码更简单. 2.语法 [修饰符] 返回值类型 方法名称 ...
- element-ui(或者说Vue的子组件)绑定的方法中传入自定义参数
比如el-upload中的 :on-success= fn,其实是给组件el-upload传递一个prop,这样写的话fn只能接受upload组件规定的参数,如果想自己传递父组件中的参数比如b,要写成 ...
- pandas中DataFrame对象to_csv()方法中的encoding参数
当使用pd.read_csv()方法读取csv格式文件的时候,常常会因为csv文件中带有中文字符而产生字符编码错误,造成读取文件错误,在这个时候,我们可以尝试将pd.read_csv()函数的enco ...
- C# 方法中的this参数
x 先看下面的代码: public static class StringExtension { public static void Foo(this string s) { Console.Wri ...
随机推荐
- generic
是什么 算法实现时保有待定类型的参数. 为什么 一份代码用于多个算法(当算法中只数个类型不同的时候) 可重新性 很多常用算法和容器数据结构都可以type-generic的方式实现 why not 许多 ...
- 了解人工智能?-百度AI
了解人工智能? 什么是人工智能? 由人创造的"智慧能力",同样具备智慧生物的能力 耳朵=倾听=麦克风=语音识别 ASR Automatic Speech Recognition 嘴 ...
- Word文档分节设置页码
在一篇论文中需要将摘要和目录作为一部分设置罗马数字页码,正文部分设置阿拉伯数字页码. 大致效果如下图所示: 这里面用到了分节符,步骤如下: 1 :点击开始菜单栏下 显示/隐藏编辑标记 2:点击插入菜单 ...
- React报错Failed prop type: Invalid prop `component` of type `object` supplied to `Route`, expected `function`
引言 最近在忙毕业设计,博客也很久没更新了,毕业设计使用vue做了一个校园寻物网站,现在开始学Raect,记录一下自己遇到问题,react-redux的connect方法使得组件与Redux建立了联系 ...
- 仅主机、NAT、桥接模式
三种模式区别: 桥接模式 :通过主机映射一个ip给虚拟机,只要主机可以访问外网.虚拟机也可以访问,两机可以相互通信. NAT模式:主机和虚拟机在同一个地址,原则上两者不能相互通信,但是通过修改NAT配 ...
- 13、FrameRely
Frame Relay 美国国家标准化协会(American National Standard Institute,简称ANSI)国际电信联盟远程通信标准化组 ITU-T 1.是由ITU和ANSI制 ...
- LUA解析json小demo
需要修改的json数据gui-config.json { "configs": [{ "server": "JP3.ISS.TF", &qu ...
- HDU_3038_并查集
http://acm.hdu.edu.cn/showproblem.php?pid=3038 并查集的应用,选择哪个点作为根结点都没关系,多了一个sum数组保存每个点到根节点的和,注意刚开始a减了1, ...
- POJ_1050_最大子矩阵
http://poj.org/problem?id=1050 这道题是最大子串的扩展,遍历过每一个子矩阵就好了,期间用了最大子串的方法. #include<iostream> #inclu ...
- Python中zip()函数的解释和可视化
zip()的作用 先看一下语法: zip(iter1 [,iter2 [...]]) -> zip object Python的内置help()模块提供了一个简短但又有些令人困惑的解释: 返回一 ...