C#方法参数传递
今日份主要内容:
- 构造函数执行顺序
- 析构函数
- 方法参数传递:值传递,引用传递
- ref、out、in关键字
- 递归
- 可变参数params
- foreach语句
构造函数执行顺序
子类中调用父类的方法
-
构造函数什么时候执行?
实例化的时候执行,new Cat();
-
调用子类无参构造函数的时候,会默认调用父类无参构造函数。通过调用子类 有参构造函数,同样默认会调用父类无参构造函数。
-
那如何调用父类的有参构造函数呢?
在子类的有参构造函数里可以使用 : base(参数),明确调用父类有参构造函数。
public class Animal : Cat
public Animal():base()//子类默认调用基类的无参数构造函数。相当于:base()。
{
Console.WriteLine("子类Animal的无参构造函数");
}
public Animal(int color, string name) : base(color, name)
{
Console.WriteLine("子类Animal的有参构造函数");
}
//base充当了基类的实例
构造函数的执行顺序:构造函数在执行时,先进入子类构造函数,再进入父类构造函数,依次向上,直到0bject对象;
出来的时候,先把Object对象的构造函数执行完,再回到父类。父类执行完,再回到子类,依次向下,直到最终的子类,形成了U型结构。
参考:构造函数 - C# | Microsoft Learn
C#中子类调用父类的实现方法 - phpStudy (xp.cn)
析构函数
- GC在回收类的实例时,和终结器(析构函数)有关系。析构函数用来释放对象。
- 因为在C#释放对象一般是自动,不建议手动。所以析构函数不常用。
- 定义的格式: ~+类名,不能有修饰符。只有一个析构函数。开发者无法调用它,一般是GC调用。
参考:终结器 - C# | Microsoft Learn
方法参数传递
方法参数可以通过值传递、引用传递和输出参数传递。
static void Main(string[] args)
{
// 方法参数传递时有两种传递方式:1。值传递 2。引用传递
Class1 cls = new Class1();
// a,b实参,即实际参数,使用方法时,实参传递到形参中。
int a = 1;
int b = 2;
// 值传递特点:默认情况,实参传递到形参中,方法内修改形参后,不会影响实参。
// 值传递,默认实参向形参传递过去的是值。
cls.Add(a, b);
Console.WriteLine($"a:{a},b:{b}");
Console.WriteLine("---------------------");
// 字符串虽然是引用类型,但字符串比较特殊,在实参向形参传递参数时,和值类型的默认效果一样。即方法内修改形参不会影响实参。
string s1 = "value1";
string s2 = "value2";
cls.Add(s1, s2);
Console.WriteLine($"s1:{s1},s2:{s2}");
Console.WriteLine("---------------------");
// 引用传递特点:默认情况,方法内修改了形参(修改实例指向的地址),会影响实参。
// 如果修改了某个实例的相关属性(即没有修改地址,只修改堆上某个空间中的值),不会影响实参。
// 引用传递,默认实参向形参传递是地址。画图理解
Person p1 = new Person() { Id = 1, Name = "name1" };
Person p2 = new Person() { Id = 2, Name = "name2" };
cls.Add(p1, p2);
Console.WriteLine($"p1:{p1},p2:{p2}");
Console.ReadKey();
}
public class Class1
{
// x,y形参,即形式参数,占个位置,方法使用时,实参传递到形参中。
public void Add(int x, int y)
{
// 由于值类型在栈上分配空间,值也在栈上,和堆没有关系。
// 所以在方法内修改了栈上的值,不会影响实参。
x = 20;
y = 20;
Console.WriteLine($"x:{x},y{y}");
}
public void Add(string x, string y)
{
// x,y重新指向堆上的某个空间,形参s1,s2的在堆上的空间没有变。
x = "hello";
y = "world";
Console.WriteLine($"x:{x},y:{y}");
}
public void Add(Person x, Person y)
{
// 修改了某个实例的相关属性
x.Id = 10;
x.Name = "ABC";
// 修改实例指向的地址
y = new Person() { Id = 11, Name = "DEF" };
Console.WriteLine($"x:{x},y:{y}");
}
}
ref、out、in关键字
ref:表示引用传递。通过这个关键字,方法可以直接修改引用所指向的原始变量的值。
out:主要用于从方法中输出一个值。调用方法前,变量可以不初始化,但在方法内必须给它赋值。
in:从 C# 7.2 开始引入,它确保参数在方法内不会被修改,提供了一种明确表达参数只读意图的方式。
public class Class1
{
public void Method1(int a, bool flag)
{
a = 100;
flag = true;
Console.WriteLine($"值类型修改后的值:a={a},flag={flag}");
}
// ref==reference引用,指针,地址 ref是输入参数
// 虽然形参的数据类型是值类型,但添加ref关键字,实参向形参传递的不再变量值,是变量地址。
// 向ref形参传递参数时,实参必须初始化。
// ref 传入的变量必须初始化,但是在内部可改可不改
public void Method2(ref int a, ref bool flag)
{
a = 100;
flag = true;
Console.WriteLine($"值类型修改后的值:a={a},flag={flag}");
}
// out输出参数
// 方法的返回值只能有一个,通过return返回。
// 方法输出的结果可以有多个,return只是返回结果的一种方式,而输出参数out是另外一种方式。
// 输出参数在方法内必须赋初值。即:out 传入的变量不用初始化,但是在内部必须修改该值
// 向out形参传递参数时,实参不必初始化。
// 可变参数,默认值,后面不可以跟必选参数,out没有这个限定。
public int Method3(int a, out string x, out long y, int z)
{
x = "hello world";
y = 200;
return 100;
}
// in和out最大不同,in传递过来的参数,不能修改。而out必须修改。ref想改就改,不想改不改。
// in传递过来的参数是只读的。
public string Method4(int a, in string x, in long y, int z)
{
//x = "hello world"; // 报错
//y = 200;
return x + 100 + y;
}
}
static void Main(string[] args)
{
Class1 cls = new Class1();
//向ref形参传递参数时,实参必须初始化。
int x = 1;
bool f = false;
cls.Method1(x, f);
Console.WriteLine($"x={x},f={f}");
Console.WriteLine("-----------------");
cls.Method2(ref x, ref f);
Console.WriteLine($"x={x},f={f}");
Console.WriteLine("-----------------");
//向out形参传递参数时,实参不必初始化。
string xx = "abc";
long yy = 100;
int result = cls.Method3(0, out xx, out yy, 0);
Console.WriteLine($"xx={xx},yy={yy}");
Console.WriteLine($"返回值={result}");
Console.WriteLine("-----------------");
// 向in形参传递参数时,必须初始化。由于方法不让修改in形参,所以实参不受影响
string xxx = "abc";
long yyy = 100;
string result1 = cls.Method4(0, in xxx, in yyy, 0);
Console.WriteLine($"xxx={xxx},yyy={yyy}");
Console.WriteLine($"返回值={result1}");
Console.ReadKey();
}
昨天作业(新知识:递归,foreach,for和foreach区别,可变参数params)
递归
递归:函数的自调用。函数内部再调用函数自身,就形成递归。形成了循环。
C# 循环,递归,迭代,可枚举的都含有循环。数组就带有迭代器。
递归必须要有return或者break,跳出循环。
public static int Factorial(int n)
{
/*if (n == 0)
return 1;
else
return n * Factorial(n - 1);*/
return n == 0 ? 1 : n * Factorial(n - 1);
}
可变参数params
关键字params,parameters 参数,表示可变参数,即参数个数不固定。
可变参数后面不能加上必填参数,可变参数只能有一个。
class Program
{
static void PrintParams(params int[] numbers)
{
foreach (var num in numbers)
{
Console.Write(num + " ");
}
Console.WriteLine();
}
static void Main()
{
PrintParams(1, 2, 3);
PrintParams(4, 5, 6, 7);
Console.WriteLine(Math.Sum1(new float[] { 1,2}));
}
}
public static float Sum1(float[] nums = null)// 把参数传递一个数组,参数个数也可以不固定,但不包括0,除非带有默认值null。为null,后面的不调用。
{
if (nums == null) return 0;
float sum = 0;
for (int i = 0; i < nums?.Length; i++)
{
sum += nums[i];
}
return sum;
}
可变参数具有以下一些重要意义:
-
灵活性:它允许方法接受不同数量的参数,使方法在使用时更加灵活和通用,而无需针对不同数量的参数定义多个方法版本。
-
简洁性:减少了代码的重复和复杂性,开发人员可以更简洁地处理参数数量不确定的情况。
-
易用性:对于调用者来说,传递参数变得更加方便,无需特意去构建特定结构的参数集合。
-
适应性:能更好地适应不同的场景和需求,使得方法可以根据实际情况处理各种数量的输入。
foreach语句
类似于for语句,foreach专门用来循环对象的,循环用来遍历数组、集合等的,但是只能用于遍历形式,不能改变循环目标。
-
for循环在循环中是可以更改循环目标的。
-
foreach的循环性能比较高。
-
foreach的应用范围没有for的广。
-
字符串也可以用foreach循环,它实现了可枚举的接口。
-
for中可以拿循环对象的索引,而foreach不能拿循环对象的索引。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异