声明
本文内容来自微软 MVP solenovex 的视频教程——真会C#?- 第1-2章 简介和基础(完结),大致和第 10 课—— 变量和参数(入门) 对应。
本文主要包括以下内容:
- 变量
- 参数
变量
一个变量代表一个存储的位置,它的值是可以改变的。
变量可以是:
- 本地的。
- 参数(值类型,ref,out)。
- 字段。
- 数组的元素。
Stack 栈 和 Heap 堆
Stack 和 Heap 是变量和常量的所在地方。两者生命周期不同。
Stack 栈
- Stack(一块内存)存储本地变量和参数。
- 随着函数的进入和退出,Stack 也会随之增大和缩小。
static int Factorial (int x)
{
if (x == 0) return 1;
return x * Factorial (x-1);
}
Heap 堆
- Heap(一块内存),对象所在的地方(引用类型的实例)。
- 当新的对象被创建后,它就会被分配在 Heap 上,到该对象的一个引用被返回。
- 程序执行时,随着新对象的不断建立,Heap 会被慢慢的填满。运行时的 GC 会周期性的把对象从 Heap 上面释放出来,所以不会导致内存耗尽。
- 一旦一个对象不再被任何“存活”的东西引用,那么它就可以被释放了。
内存
值类型的实例(和对象的引用)会放在变量声明时所在的内存块里;如果该实例是一个 Class 的字段或数组的元素,那么它就放在 Heap 上。
GC
C# 里不可以显式的删除对象;无引用的对象会逐渐的被 GC 收集。
using System;
using System.Text;
class Test
{
static void Main()
{
StringBuilder ref1 = new StringBuilder ("object1");
Console.WriteLine (ref1);
// The StringBuilder referenced by ref1 is now eligible for GC.
StringBuilder ref2 = new StringBuilder ("object2");
StringBuilder ref3 = ref2;
// The StringBuilder referenced by ref2 is NOT yet eligible for GC.
Console.WriteLine (ref3); // object2
}
}
静态(Static)字段
在 Heap 上,它们会存活到应用程序域停止。
确定赋值 Definite Assignment
- 除非使用 unsafe,否则在 C# 里无法访问未初始化的内存。
- Definite Assignment 的三个含义:
- 本地变量在被读取之前必须被赋值。
- 当方法被调用的时候,函数的参数必须被提供(除非是可选参数)。
- 其他的变量(字段,数组元素)会被运行时自动初始化。
static void Main()
{
int x;
Console.WriteLine (x); // Compile-time error
}
默认值
- 所有类型的实例都有默认值。
- 预定义类型的默认值就是内存按位归零的结果。
可以通过 default 关键字来获取任何类型的默认值;自定义类型(Struct)的默认值就是该自定义类型定义的每个字段的默认值。
class Test
{
static int x;
static void Main() { Console.WriteLine (x); } // 0
}
decimal d = default (decimal);
Default Values.jpg
参数
一个方法可以多个参数(Parameter),参数(Parameter)定义了必须为该方法提供的参数(Arguments)。
static void Foo (int p)
{
p = p + 1; // Increment p by 1
Console.WriteLine (p); // Write p to screen
}
static void Main()
{
Foo (8); // Call Foo with an argument of 8
}
参数传递方式
参数传递的方式包括值类型和引用类型两种。无论是引用类型还是值类型的参数,都可以按值或引用传递。
按引用类型进行传递的含义,当你按引用传递 Arguments 的时候,相当于给现有变量的存储位置起了个别名,而不是创建了一个新的存储位置。
Passing arguments.jpg按值传递 Arguments
默认情况下,按值传递 Arguments;当传进方法时把 Arguments 的值复制了一份。
class Test
{
static void Foo (int p)
{
p = p + 1; // Increment p by 1
Console.WriteLine (p); // Write p to screen
}
static void Main()
{
int x = 8;
Foo (x); // Make a copy of x
Console.WriteLine (x); // x will still be 8
}
}
按值传递引用类型的 Arguments
复制的是引用,不是对象。
class Test
{
static void Foo (StringBuilder fooSB)
{
fooSB.Append ("test");
fooSB = null;
}
static void Main()
{
StringBuilder sb = new StringBuilder();
Foo (sb);
Console.WriteLine (sb.ToString()); // test
}
}
按引用传递 Arguments
ref
想要按引用传递,可以使用 ref 参数修饰符。
class Test
{
static void Foo (ref int p)
{
p = p + 1; // Increment p by 1
Console.WriteLine (p); // Write p to screen
}
static void Main()
{
int x = 8;
Foo (ref x); // Ask Foo to deal directly with x
Console.WriteLine (x); // x is now 9
}
}
out
- 和 ref 差不多,区别在,进入函数前不需要被赋值;离开函数前必须被赋值。
- 通常用来从方法返回多个值。
class Test
{
static void Split (string name, out string firstNames,
out string lastName)
{
int i = name.LastIndexOf (' ');
firstNames = name.Substring (0, i);
lastName = name.Substring (i + 1);
}
static void Main()
{
string a, b;
Split ("Stevie Ray Vaughan", out a, out b);
Console.WriteLine (a); // Stevie Ray
Console.WriteLine (b); // Vaughan
}
}
out 变量
从 C# 7.0 开始,调用方法时,可以使用 out 临时声明变量;当调用的方法有多个 out 参数时,你不需要其中一些 out 参数,可以使用下划线“_”来 discard(弃用)它们。
static void Main()
{
Split ("Stevie Ray Vaughan", out string a, out string b);
Console.WriteLine (a); // Stevie Ray
Console.WriteLine (b); // Vaughan
}
Split ("Stevie Ray Vaughan", out string a, out _); // Discard the 2nd param
Console.WriteLine (a);
params 修饰符
- 可以在方法的最后一个参数使用 params 参数修饰符。
- 可以接受任意数量的该类型的参数。
- 参数(parameters)类型必须是数组。
- 也可使用数组作为 arguments。
class Test
{
static int Sum (params int[] ints)
{
int sum = 0;
for (int i = 0; i < ints.Length; i++)
sum += ints[i]; // Increase sum by ints[i]
return sum;
}
static void Main()
{
int total = Sum (1, 2, 3, 4);
Console.WriteLine (total); // 10
}
}
可选参数
- 从 C# 4.0 开始,方法,构造函数,索引器都可以声明可选参数。
- 可选参数需要在声明的时候提供默认值。
- 调用时可以不写可选的 parameters。
- 往 public 方法里添加可选参数,若该方法被其他 Assembly 调用,那么两个 Assemblies 都需要重新编译,就和添加了一个必填参数是一样的。
- 可选参数的默认值必须是常量表达式或拥有无参数构造函数的值类型。
- 可选参数不可以使用 ref 和 out。
- 必填参数必须在可选参数前面(方法声明时和方法调用时),例外的是,params 的参数仍然在最后面。
void Foo (int x = 23) { Console.WriteLine (x); }
Foo(); // 23
命名参数
- 可以不按位置来区别 arguments。
- 使用名称来定位 arguments。
- 命名的 arguments 可以按任意顺序填写。
- Arguments 表达式被计算出的顺序和它们在调用的出现顺序一致。
- 可混合使用按位置参数和命名参数。
- 按位置参数必须在命名参数前。
- 可混合使用可选参数和命名参数。
void Foo (int x, int y) { Console.WriteLine (x + ", " + y); }
void Test()
{
Foo (x:1, y:2); // 1, 2
}
ref Locals
- C# 7.0 开始,可以定义一个本地变量,它引用了数组的一个元素或对象的一个字段。
- ref local 的目标必须是数组的元素,字段,本地变量。不可以是属性。
- 常用于微优化场景,通常与 ref returns 联合使用。
int[] numbers = { 0, 1, 2, 3, 4 };
ref int numRef = ref numbers [2];
numRef *= 10;
Console.WriteLine (numRef); // 20
Console.WriteLine (numbers [2]); // 20
ref returns
可以从方法返回 ref local,这就叫做 ref return。
static string X = "Old Value";
static ref string GetX() => ref X; // This method returns a ref
static void Main()
{
ref string xRef = ref GetX(); // Assign result to a ref local
xRef = "New Value";
Console.WriteLine (X); // New Value
}
var
- 隐式强类型本地变量。
- 声明和初始化变量通常一步完成,如果编译器能从初始化表达式推断出类型,就可以使用 var。
- 会降低代码可读性。
var x = "hello";
var y = new System.Text.StringBuilder();
var z = (float)Math.PI;
var x = 5;
x = "hello"; // Compile-time error; x is of type int
参考
Method Parameters (C# Reference)
网友评论