第六章 数组
1.简单数组
如果需要使用同一类型的多个对象,就可以使用数组。数组是一种数据结构,他可以包含同一类型的多个元素。
2.数组的声明
在声明数组时,应该先定义数组中的元素类型,其后是一对中括号和变量名,例如:
Int[] myArray
3.初始化
声明了数组,就必须为数组分配内存,以保存数组的所有元素。数组是引用类型,所以必须给他分配堆上的内存。为此,应使用new运算符,指定数组中的冤死的类型和数量来初始化数组变量。
myArray=new int[4];
注:指定了数组的大小之后,如果不复制数组中的所有元素,就不能重新设置数组的大小。如果事先不知道数组中应该包含多少元素,就可以使用集合。
还可以使用数组初始化器为数组的每个元素赋值。数组初始化器只能在声明时使用,不能在声明数组之后使用。
Int[] myArray =new int[4] {1,2,3,4};
如果使用花括号,还可以不指定数组的大小,因为编译器会自动统计元素的个数:
Int[] myArray =new int[] {1,2,3,4};
使用c#编译器还有一种更简化的形式:
Int[] myArray = {1,2,3,4};
4.访问数组元素
使用索引器访问
使用for语句迭代数组中的所有元素
使用foreach语句
5.使用引用类型
除了能声明预定义类型的数组,还可以声明自定义类型的数组。下面用Person类来说明,这个类有两个自动实现的属性,FirstName和LastName,以及从Object类重写的ToString方法:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return String.Format("{0} {1}",FirstName,LastName);
}
}
声明一个包含两个Person元素的数组:Person[] myPersons=newPerson[2],但是必须注意,如果数组中的元素是引用类型,就必须为每个数组元素分配内存,若使用了数组中未分配内存的元素,则会报空引用类型的异常。
6.Array类
Array类是抽象类,所以不能使用构造函数创建数组,但可以使用静态方法CreateInstance()来创建数组。如果事先不知道元素的类型,这个静态方法就非常有用,因为类型可以做贼Type对象传递给CreateInstance()方法。
Array intArray = Array.CreateInstance(typeof(int),5);
这个例子说明了如何创建一个大小为5的int型数组。可以使用SetValue()方法设置对应元素的值,使用GetValue()方法读取对应元素的值。
7.复制数组
因为数组是引用类型,所有经一个数组变量赋值给另一个数组,就会得到两个引用同一数组的变量。
8.枚举
在foreach语句中使用枚举,可以迭代集合中的元素,且无需知道集合中的元素个数。数组或集合实现带GetEumerator()方法的接口,GetEumerator()方法返回一个IEnumerator类型对象。
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
Q:为什么在foreach中不能修改item的值?
A:因为current 是只读的。
9.yield语句
对于yield关键字,首先看一下mdn的解释:
如果你在语句中使用 yield 关键字,则意味着它在其中出现的方法、运算符或 get 访问器是迭代器。 通过使用 yield 定义迭代器,可在实现自定义集合类型的 IEnumerable 和 IEnumerator 模式时无需其他显式类。(不需要 IEnumerable<int> list = new List<int>() 这样去new)
举个例子:
static void Main(string[] args)
{
foreach (int i in Power(2, 8, ""))
{
Console.Write("{0} ", i);
}
Console.ReadKey();
}
public static IEnumerable<int> Power(int number, int exponent, string s)
{
int result = 1;
for (int i = 0; i < exponent; i++)
{
result = result * number;
yield return result;
}
yield return 3;
yield return 4;
yield return 5;
}
首先看一下Power方法。该静态方法返回一个IEnumerablel<int>类型的参数。按照我们平常的做法。应该对数据执行一定操作,然后return一个IEnumerablel<int>类型的参数。我们把Power方法改造如下:
public static IEnumerable<int> Power(int number, int exponent, string s)
{
int result = 1;
//接口不能实例化,我们这儿new一个实现了IEnumerable接口的List
IEnumerable<int> example = new List<int>();
for (int i = 0; i < exponent; i++)
{
result = result * number;
(example as List<int>).Add(result);
}
return example;
}
这是我们平常的思路。但是这样做就有个问题。这儿要new一个List,或者任何实现了IEnumerable接口的类型。这样也太麻烦了吧。要知道IEnumerable是一个常用的返回类型。每次使用都要new一个LIst,或者其他实现了该接口的类型。与其使用其他类型,不如我们自己定制一个实现了IEnumerable接口专门用来返回IEnumerable类型的类型。我们自己定制也很麻烦。所以微软帮我们定制好了。这个类是什么,那就是yield关键字这个语法糖。
Yield中的特殊情况
但是我们在Power方法中写一些方法,编译器会如何处理?
static void Main(string[] args)
{
//这儿调用了方法。
var test = Power(2, 8, "");
Console.WriteLine("Begin to iterate the collection.");
//Display powers of 2 up to the exponent of 8:
foreach (int i in Power(2, 8, ""))
{
Console.Write("{0} ", i);
}
Console.ReadKey();
}
public static IEnumerable<int> Power(int number, int exponent, string s)
{
int result = 1;
if (string.IsNullOrEmpty(s))
{
//throw new Exception("这是一个异常");
Console.WriteLine("Begin to invoke GetItems() method");
}
for (int i = 0; i < exponent; i++)
{
result = result * number;
yield return result;
}
yield return 3;
yield return 4;
yield return 5;
}
按照我们的理解当我们 var test = Power(2, 8, "");的时候确实调用了Power方法。此时应该程序打印Console.WriteLine("Begin to invoke GetItems() method");然后继续执行 Console.WriteLine("Begin to iterate the collection.");方法。所以打印顺序应该是:
Begin to invoke GetItems() method
Begin to iterate the collection.
但是我们运行的时候却发现
我们的打印方法并没有出现在Power方法中,而是被封装进了实现枚举接口的类方法 private bool MoveNext()中。所以方法不会立即被执行,而是在我们使用数据的时候被执行。如果对此机制不了解,就容易出现另外一些意想不到的问题。例如在Power方法中添加一些验证程序,如果不符合条件就抛出一个异常。这样的异常检查不会被执行。只有我们使用数据的时候才会执行。这样就失去了检查数据的意义。