数组
1.同一类型和不同类型的多个对象
需要使用同一类型的多个对象则使用集合和数组。
Array类为数组元素排序和过滤提供了多个方法,使用枚举器,可以迭代数组所有元素。
2.简单数组
数组的声明
因为数组是引用类型,所以只要声明之后就会分配内存,以保存数组所有元素。使用new运算符,指定数组中的元素类型和数量来初始化数组的变量。
数组的声明:
int[] myArray;
数组的初始化
1.先定义后声明并指定数量
int[] myArray; myArray =new int[4];
2.声明并指定数量后直接初始化
int[] myArray=new int[4]{4,7,11,5};
3.声明后直接初始化
int[] myArray=new int[]{4,7,11,5,8,7};
4.直接定义初始化
int[] myArray={4,7,11,5,8,7};
访问数组元素
通过索引访问 myArray[0]访问的是数组的第一个元素。 myArray[myArray.Length-1]访问的是数组最后一个元素。 数组的第一个索引是从0开始的 循环中使用索引访问 for(int i=0;i<myArray.Length;i++) { Console.WriteLine(myArray[i]) ; } foreach(var item in myArray) { Console.WriteLine(item) ; }
使用引用类型
如果数组的元素是引用类型,则必须给数组每个元素分配内存,若使用了没有分配内存的元素。则会出现空指针的异常。
使用从0开始的索引器,可以为数组的每个元素分配内存:
myPersons[0] = new Person { FirstName = "Ayrton", LastName = "Senna" }; myPersons[1] = new Person { FirstName = "Michael", LastName = "Schumacher" };
3.多维数组
用一个整数做索引的是一维数组,用多个整数做索引的是多维数组。
二维数组:
int[,] twodim = new int[3, 3]; twodim[0, 0] = 1; twodim[0, 1] = 2; twodim[0, 2] = 3; twodim[1, 0] = 4; twodim[1, 1] = 5; twodim[1, 2] = 6; twodim[2, 0] = 7; twodim[2, 1] = 8; twodim[2, 2] = 9;
二维数组中的twodim[3,3],twodim[2,3],twodim[3,2]会发生运行时异常,索引最大值不能大于等于声明的数量,且不能小于0;
如果只赋了一部分索引的值。使用了没有赋值的索引会发生空指针的异常。比如说twodim[2,2]没有赋值的话,却使用了它则会报错。
int[,] twodim={ {1,2,3}, {4,5,6}, {7,8,9} };
二维数组的初始化器有点不同。大括号里面包含了多组小括号对。第一对小括号,代码索引0,小括号中的第一个值就是twodim[0,0]了
三维数组:
int[, ,] threedim = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } }, { { 9, 10 }, { 11, 12 } } };
Console.WriteLine(threedim[0, 1, 1]);
这个三维数组的threedim[0,0,0]等于1;threedim[2,0,2]等于10
4.锯齿数组
锯齿数组大小对应一个矩形。每一行都有不同的大小。
声明锯齿数组时,依次放置左右中括号。左括号为行数,右括号为空。
int[][] jagged = new int[3][]; jagged[0] = new int[2] { 1, 2 }; jagged[1] = new int[6] { 3, 4, 5, 6, 7, 8 }; jagged[2] = new int[3] { 9, 10, 11 };
迭代器数组中所有元素的代码可以放在嵌套的for循环中,外层的for循环中迭代每一行,
在内层的for循环中迭代一行中的每个元素:
for (int row = 0; row < jagged.Length; row++) { for (int element = 0; element < jagged[row].Length; element++) { Console.WriteLine( "row: {0}, element: {1}, value: {2}", row, element, jagged[row][element]); } }
该迭代结果显示了所有的行和每一行的各个元素:
row: 0, element: 0, value: 1 row: 0, element: 1, value: 2 row: 1, element: 0, value: 3 row: 1, element: 1, value: 4 row: 1, element: 2, value: 5 row: 1, element: 3, value: 6 row: 1, element: 4, value: 7 row: 1, element: 5, value: 8 row: 2, element: 0, value: 9 row: 2, element: 1, value: 10 row: 2, element: 2, value: 11
5.Array类
创建数组
Array类是抽象的,因此不能通过构造函数初始化。可以使用静态方法CreateInstance()方法创建数组。方法中需要传入数组的类型,创建好之后使用SetValue和GetValue来给这个数组添加和修改元素值。
Array intArray1 = Array.CreateInstance(typeof(int), 5); for (int i = 0; i < 5; i++) { intArray1.SetValue(33, i); } for (int i = 0; i < 5; i++) { Console.WriteLine(intArray1.GetValue(i)); }
还可以将数组强制转换为int数组。
int[] intArray2=(int[])intArray1
复制数组
因为数组是引用类型,所以将一个数组变量赋值给另一个数组变量,就得到2个引用地址相同的数组变量。从而复制数组。以下代码使用了克隆方法Clone方法进行了数组复制。
int[] intArray1={1,2}; int[] intArray2=(int[])intArray1.Clone();
如果数组中包含引用类型。则不复制元素,复制引用。则更改克隆后的对象会更改被克隆对象的值。
Person[] beatles = { new Person { FirstName="John", LastName="Lennon" }, new Person { FirstName="Paul", LastName="McCartney" } }; Person[] beatlesClone = (Person[])beatles.Clone();
除了Clone方法还可以使用Copy复制方法。复制数组的浅副本。只复制数据。
排序
Array类使用Quick算法对数组元素进行排序。需要使用Array类的Sort方法,因此需要实现IComparable接口。
string[] names = { "Christina Aguilera", "Shakira", "Beyonce", "Gwen Stefani" }; Array.Sort(names); foreach (string name in names) { Console.WriteLine(name); }
该应用程序的输出是排好序的数组:
Beyonce
Christina Aguilera
Gwen Stefani
Shakira
public class Person : IComparable<Person> { public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() { return String.Format("{0} {1}", FirstName, LastName); } public int CompareTo(Person other) { if (other == null) throw new ArgumentNullException("other"); int result = this.LastName.CompareTo(other.LastName); if (result == 0) { result = this.FirstName.CompareTo(other.FirstName); } return result; } }
现在可以按照姓氏对Person对象对应的数组排序
Person[] persons= new { new Person { FirstName="Damon", LastName="Hill" }, new Person { FirstName="Niki", LastName="Lauda" }, new Person { FirstName="Ayrton", LastName="Senna"}, new Person { FirstName="Graham", LastName="Hill" } };
Array.Sort(persons); foreach (Person p in persons) { Console.WriteLine(p); }
6.数组作为参数
数组可以作为方法参数进行传递,也可以作为方法返回类型,数组是引用类型。因此不用ref进行值传递。
数组协变
数组支持协变。因此数组可以声明为基类。方法参数中定义基类数组传入派生类数组进行传递。
数组协变只用于引用类型
在C#中多次提到协变和逆变,那么协变是什么逆变是什么呢。
“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。
“逆变”则是指能够使用派生程度更小的类型。
这个博客有了详细的介绍:http://www.cnblogs.com/qixuejia/p/4383068.html
ArraySegments<T>数组段
需要使用不同方法处理某个大型数组的不同部分。那么可以把数组部分复制到各个方法中。
上图中通过创建数组段,然后将第一个数组ar1拆分了一组到数组段,将第2个数组ar2拆分了一组到数组段。
第一个数组从ar1的开始位置引用了3个元素。第2个数组从ar2的第4个位置(索引值是3)引用了3个元素。最后通过SumOfSegments方法对这2个数组段进行了求和.
需要注意的是,ArraySegments<T>没有复制原数组,但是如果数组段中的数组元素改变了,会影响原数组的元素。
7.枚举
IEnumerator接口
foreach语句
我们编写的foreach语句:
IL生成后的语句:
C#会将foreach语句转换为IEnumerator接口,通过GetEnumerator()获得枚举器,在循环中只要MoveNext方法返回true,用Current属性访问数组或集合元素中的值。
yield语句
用于创建迭代器,yield return返回数组中的一个元素,并移到下一个元素。yield break用于停止迭代。
包含yield语句的属性和方法叫迭代块。编译器会生成一个yield的类型。包含状态机。生成后的yield类型相当于Enumerator类型。
8.元组
数组合并了相同类型的对象,元组合并了不同类型的对象。不同的Tuple类型支持不同数量的元素。
元组一共8个类型,元组最后一个类型是TRest需要传入一个元组。
9.结构比较
数组和元组都实现接口IStructuralEquatable和IStructuralComparable,不仅可以比较引用还可以比较内容。需要显示的实现这些内容。
IStructuralEquatable接口用于比较数组和元素是否有相同内容,IStructuralComparable接口用于排序。
结构比较
数组和元组都实现接口IStructuralEquatable和IStructuralComparable.这两个接口不仅可以比较引用,还可以比较内容.这些接口都是显示实现的,所以在使用时需要把数组和元组强制转换为这个接口.IStructuralEquatable接口用于比较两个元组或数组是否有相同的内容,IStructuralComparable接口用于给元组或数组排序.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace
{
class Program
{
static void Main(string[] args)
{
//创建两个Person项的数组.
//比较运算符!=返回true
//因为这其实是两个变量p1和p2引用的两个不同数组.
//因为array类没有重写带一个参数的Equals()放大,所以用"=="运算符
//比较引用会得到相同的结构,即这两个变量不相同
Person zhangsan = new Person { FirstName = "zhang", LastName = "san" };
Person[] p1 = {
new Person
{
FirstName="lisi"
} ,
zhangsan
};
Person[] p2 = {
new Person
{
FirstName="lisi"
} ,
zhangsan
};
if (p1!=p2)
{
Console.WriteLine("not the same reference"); ;
}
Console.ReadKey();
}
}
public class Person : IEquatable<Person>
{
public int ID { get; set; }
public string FirstName { set; get; }
public string LastName { set; get; }
public override string ToString()
{
return string.Format("{0},{1}{2}", ID, FirstName, LastName); ;
}
public override bool Equals(object obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return Equals(obj as Person);
}
public override int GetHashCode()
{
return ID.GetHashCode();
}
public bool Equals(Person other)
{
if (other == null)
{
throw new ArgumentNullException("other");
}
return this.ID == other.ID && this.FirstName == other.FirstName && this.LastName == other.LastName;
}
}
}
使用实现IEquatable接口的Person类.IEquatable接口定义了一个强类型化的Equals()方法,用来比较FirstName和LastName属性的值.
下面看看如何对元组执行相同的操作.这里创建了两个内容相同的元组实例:
var t1 = Tuple.Create<string, int>("zhangsan", 19);
var t2 = Tuple.Create<string, int>("zhangsan", 19);
//因为t1和t2引用了两个不同的对象,所以比较运算符"!="返回true
if (t1 != t2)
{
Console.WriteLine("not the same reference to the tuple");
}
//这会调用object.equals()方法比较元组的每一项,每一项都返回true
if (t1.Equals(t2))
{
Console.WriteLine("the same reference to the tuple");
}
Tuple<>类提供了两个Equals()方法:一个重写了object基类中的Equals()方法,并把object作为参数,第二个由IStructyralEqualityComparer接口定义,并把object和IequalityComparer作为参数.
还可以使用类TupleCOmparer创建一个自定义的UequalityComparer,这个类实现了IEqualityComparer接口的两个方法Equals()和GetHashCode():
public class TupleComparer : IEqualityComparer
{
public bool Equals(object x, object y)
{
return x.Equals(y);
}
public int GetHashCode(object obj)
{
return obj.GetHashCode();
}
}
实现IEqualityCOmparer接口的Equals()方法需要new修饰符或者隐式实现的接口,因为基类object也定义了带两个参数的静态的Equals()方法.
使用TupleComparer,给Tuple<T1,T2>类的Equals()方法传递一个新实例.Tuple类的Equals(0方法为要比较的每一项调用TupleComparer的Equals()方法.所以,对于Tuple<T1,T2>类,要调用两次TupleCOmparer,以检查所有项是否相等.