数组是最常用的数据结构,几乎在所有的编程语言中都会出现。在C#里用数组的话需要建立一个System.Array类型的对象,它是所有数组的抽象基类。
Array类提供了一组排序和查找的方法
另一个可以建立数组的替代品是ArrayList,ArrayList是可以按需要动态增长的数组。
在不能确定一个数组的大小的情况下,或在程序的生命周期内数组的大下需要改变时,ArrayList是个好的选择。
还有包括拷贝,克隆的内容,比较是否相等以及用Array和ArrayList的静态方法。
声明和初使化数组
数组声明时用下面的格式:
type[] array_name;
type就是数据类型,比如
string[] names;
第二行需要实例化这个数组(因为是System.Array类型)并指定数组的大小。代码如下:
names = new string[10];
这样就为10个字符串分配了内存。
也可以两句合在一起写:
string[] names = new string[10];
有时候你想把声明,实例化,指定数据三个步骤同时进行,那就这样子:
int[] numbers = new int[] {1,2,3,4,5};
这一串数字,叫做初使化列表,用花括号括起来,每个元素用逗号隔开。
当用这种方式声明数组时,不用指定数组大小,编译器会自动根据初使化列表的个数来指定。
设定和存取数组元素
数组元素的存储既可以直接存储,也可以调用SetValue方法,直接存储方法需要用索引指定数组内的位置,向下面这样:
Names[2] = "Raymond";
Sales[19] = 23123;
SetValue方法提供了一个更面向对象的做法去设置数组元素的值,此方法有两个参数,一个是索引数,另一个是值。
names.SetValue[2, "Raymond"];
sales.SetValue[19, 23123];
数组元素的取出既可以直接取得,也可以调用GetValue方法,GetValue用一个简单的参数:索引数
myName = names[2];
monthSales = sales.GetValue[19];
通过循环去取得数组内的每个元素是很常见的。程序员写循环时会经常犯一个错误:就是写死了循环的上限或调用取得上限的方法。
错误主要是因为数组的上限是动态的
for (int i = 0; i <= sales.GetUpperBound(0); i++)
totalSales = totalSales + sales[i];
检索数组元数据的方法和属性:
Length:返回数组元素的所有个数。
GetLength:返回数组元素一维内的个数。
Rank:返回维数
GetType:返回当前数组实例的类型
Length方法在统计多维数组内元素的个数时很有用。要不然就用GetUpperBound方法+1取这个值
因为Length方法返回的是数组内所有元素的个数,GetLength方法返回的是一维内数组的个数。
Rank属性可以在运行时改变数组的大小而不用担心丢失数据,这个在章节后面详细讨论。
GetType方法是当你确定不了数组类型时用来判定数据类型的方法,比如数组是入参。
下面的代码,会创建一个Type类型的变量,允许我们调用一个类方法:IsArray()来判定这个对象是否是数组,
如果是数组的话,那么就返回数组的数据类型
int[] numbers;
numbers = new int[] {0,1,2,3,4};
Type arrayType = numbers.GetType();
if (arrayType.IsArray)
Console.WriteLine("The array type is: {0}", arrayType);
else
Console.WriteLine("Not an array");
Console.Read();
GetType方法不仅返回数组的类型,也告诉我们数组其实是一个对象,下面是输出的代码:
The array type is: System.Int32[]
方括号说明这个对象是个数组,也注意一下当显示数组类型时我们使用的格式。
知道了这个,我们不能转换把这个类型转换成string去连接剩下的字符串
多维数组
目前我们的讨论都限于一维数组,C#里,可以用最多32维的数组,虽然3维以上的数组就不常用了(还混乱的很)。
多维数组是用指定了每维上限的数组声明的,2维的声明:
int[,] grades = new int[4,5];
声明一个4行5列的数组,2维数组一般用来模拟矩阵。
你也可以声明一个不指定每一维的上限的多维数组,用逗号分开就行了。
举个例子:
double[,] Sales;
声明一个2维数组,
double[,,] sales;
声明一个3维数组,当你声明数组不指定某维的上限时,用的时候要指定
多维数组也可以用初使化列表,看下面的代码:
Int[,] grades = new int[,] {{1, 82, 74, 89, 100},
{2, 93, 96, 85, 86},
{3, 83, 72, 95, 89},
{4, 91, 98, 79, 88}}
首先,注意数组的上限是没有指定的,当用初使化列表来初使化一个数组时,
不能指定该数组的上限。编译器会通过初使化列表计算每一维的上限。
初使化列表本身是一对花括号,每一行,每一个元素都用逗号分开。
取得多数组的元素和一维是差不多的,传统技术是这样的:
grade = Grades[2,2];
Grades[2,2] = 99
或者也可以用GetValue方法
grade = Grades.GetValue[0,2];
但不能用SetValue方法来设置多维数组的数据,因为这个方法只有两个参数。
在多维数组里计算所有元素是很常见的操作,虽然常常时是基于某一行或某一列的数据。
用Grades这个数组,如果数组的每一行是一条学生记录,我们可以计算年级里每个学生的平均分:
(代码如下)
int[,] grades = new int[,] {{1, 82, 74, 89, 100},
{2, 93, 96, 85, 86},
{3, 83, 72, 95, 89},
{4, 91, 98, 79, 88}};
int last_grade = grades.GetUpperBound(1);
double average = 0.0;
int total;
int last_student = grades.GetUpperBound(0);
for(int row = 0; row <= last_student; row++) {
total = 0;
for (int col = 0; col <= last_grade; col++)
total += grades[row, col];
average = total / last_grade;
Console.WriteLine("Average: " + average);
}
}
参数数组
很多方法的定义需要提供好几个参数,但是有时候你想写一个参数个数可选的方法。
你可以声明一个参数数组。参数数组是用关键字ParamArray定义参数列表的。
下面这个方法允许任意个数的参数,返回值是参数的个数。static int sumNums(params int[] nums) {
int sum = 0;
for(int i = 0; i <= nums.GetUpperBound(0); i++)
sum += nums[i];
return sum;
}
这个方法可以被以下代码调用:
total = sumNums(1, 2, 3);
total = sumNums(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
当你用参数数组定义一个方法时,参数数组必须放在此方法所有参数的最后,
这样编译器可以正确处理参数,否则,编译器不知道参数个数什么时候结束。
不规则数组
当你创建一个多维数组时,你一直创建的是每一行的元素个数相同的结构。打个比方,你看下面的代码:
int sales[,] = new int[12,30];//销售员每个月每天的情况
假设每行(每月)有同样的元素个数(天数),而实际上有的月31天,有的30天,有的29.
这样数据就有空位置,在这个数组里问题倒不大,但是一旦数据量大了以后就很浪费了
解决问题的方法是使用一个不规则数组来代替二维数组。
不规则数组是一个数组的每一行也是数组的数组。不规则数组的每一维是一维数组,叫它“不规则”是因为每一行的个数不同。
不规则数组的样子可能不是一个矩形,但是有不规则的边。
不规则数组用数组名后的两对括号声明。第一对括号指定行数,第二对是留空的。
int[][] jagged = new int[12][];
这句代码看上去有点怪,但是分开来理解就明白了。这个不规则数组有12个元素。
每个元素也是一个数组。初使化列表其实就是为了初使化每一行数据,说明每一行元素是有12个元素的数据,每个元素都初使成默认值。
一旦不规则数组声明,每一行的元素就可以指定指了,下面的代码就是设置值的:
jagged[0][0] = 23;
jagged[0][1] = 13;
. . .
jagged[7][5] = 45;
第一对括号指明行号,第二对括号指明这一行的某一个元素。
第一句取第一行的第一个元素,第二句取第一行的第二个元素。
第三句取第6行的第8个元素。
一个用不规则数组的例子,下面的程序创建一个叫做sales的数组,循环用来计算这两个月每周的平均销售情况。
using System;
class class1 {
static void Main[] {
int[] Jan = new int[31];
int[] Feb = new int[29];
int[][] sales = new int{Jan, Feb};
int month, day, total;
double average = 0.0;
sales[0][0] = 41;
sales[0][1] = 30;
sales[0][0] = 41;
sales[0][1] = 30;
sales[0][2] = 23;
sales[0][3] = 34;
sales[0][4] = 28;
sales[0][5] = 35;
sales[0][6] = 45;
sales[1][0] = 35;
sales[1][1] = 37;
sales[1][2] = 32;
sales[1][3] = 26;
sales[1][4] = 45;
sales[1][5] = 38;
sales[1][6] = 42;
for(month = 0; month <= 1; month++) {
total = 0;
for(day = 0; day <= 6; day++)
total += sales[month][day];
average = total / 7;
Console.WriteLine("Average sales for month: " +
month + ": " + average);
}
}
}
ArrayList类
在不知道数组大小的情况或运行时数组大小要改变的情况下,静态数组就不是那么有用了。
一个解决方是用一个空间不够用时可以自增长的数组类型,这个数组就是ArrayList,它也是System.Collections里的一部分。
ArrayList类的对象有一个Capacity属性用来存储数组大小,初使值是16,当数组的元素接近16个时,Capacity会加16,
当然同时数组空间也变大了。需要用ArrayList的情况是这样的:数组可能会变大或变小时,它比在标准数组里用ReDim更高效。
这个第一章也说过,ArrayList存储的是对象类型,如果需要强类型数组,你应该用标准数组或其他数据结构。
以下是些常用的方法或属性:
Add(): 向ArrayList添加一个元素.
AddRange(): 向ArrayList添加一组元素.
Capacity: 存储ArrayList可以容纳元素的个数
Clear(): 从ArrayList中移除一个元素.
Contains(): 判断是否有指定的元素存在于ArrayList.
CopyTo(): 复制ArrayList或是ArrayList的一部分到一个Array类.
Count: 返回ArrayList内的元素个数.
GetEnumerator(): 返回一个ArrayList的迭代器.
GetRange(): 返回ArrayList的一个子集.
IndexOf(): 返回第一个发现的指定元素的索引.
Insert(): 向ArrayList的指定位置插入一个元素.
InsertRange(): 向ArrayList的指定位置插入一组元素
Item(): 设置或取得某个指定位置的值.
Remove(): 移除第一个发现的指定元素.
RemoveAt(): 移除指定位置的元素.
Reverse(): 逆序.
Sort(): 按字母排序.
ToArray(): 复制所有元素到一个Array类.
TrimToSize(): 将Capacity值设置成与数组元素个数相等的值.
使用ArrayList类
ArrayList不像标准的数组。一般来说,添加元素用Add(),除非在特定位置添加,那就用Insert()。
这里,我们测试一下怎么使用ArrayList的其他方法。
首先声明一个对象ArrayList grades = new ArrayList();
注意这里用了构造函数,如果ArrayList没有用构造函数,这个ArrayList对象就不能使用。
用Add方法添加元素,这个方法只有一个参数,就是要添加到ArrayList的对象。
Add方法有返回值,返回的插入的位置,只是很少用而已。下面是例子:
grades.Add(100);
grades.Add(84);
int position;
position = grades.Add(77);
Console.WriteLine("The grade 77 was added at position:
" + position);
ArrayList对象可以用Foreach来遍历,ArrayList有一个内建的枚举器
int total = 0;
double average = 0.0;
foreach (Object grade in grades)
total += (int)grade;
average = total / grades.Count;
Console.WriteLine("The average grade is: " + average);
如果你要添加元素到特定位置,用Insert方法,有两个参数,位置和值
grades.Insert(1, 99);
grades.Insert(3, 80);
查看ArrayList当前的容量可以用Capacity属性,看数组个数可以用Count属性
Console.WriteLine("The current capacity of grades is:
" + grades.Capacity);
Console.WriteLine("The number of grades in grades is:
" + grades.Count);
这里有些其他的方法移除元素,如果你知道要移除的元素,但不知道位置,用Remove方法,只有一个参数,就是这个你要移除的对象。
如果这个对象存在于ArrayList中,会被移除,否则,什么都不做。就像是以下的代码。
if (grades.Contains(54))
grades.Remove(54)
else
Console.Write("Object not in ArrayList.");
如果知道要移除的对象的位置,用RemoveAt方法,这个方法有一个参数:对象的索引。
如果传入无效的索引则会异常。
grades.RemoveAt(2);
取得一个对象的索引用IndexOf方法,有一个参数,就是这个对象。返回这个对象的索引。如果不存在,返回-1
int pos;
pos = grades.IndexOf(70);
grades.RemoveAt(pos);
如果要添加对象集合的话,这组对象必须派生自ICollection,意思是说,这组对象可以存在于数组,Collection或者ArrayList里。
这里有两个不同的方法去添加一组对象到ArrayList。AddRange和InsertRange,AddRange添加元素到末尾,InsertRange方法添加到指定位置。
(代码如下)
结果如下
前面两个名字在ArrayList的开头是因为指定的索引是0.最后的那些名字在末尾是因为用的AaddRange。
还有两个常用的方法是ToArray和GetRange方法。GetRange方法返回ArrayList里的一组对象做为新的ArrayList。
ToArray方法复制所有元素到一个array元素
GetRange方法有两个参数:开始的索引和要取得的元素个数。
GetRange方法没有破坏性,只是取复本而已。下面是例子代码
ArrayList someNames = new ArrayList();
someNames = names.GetRange(2,4);
Console.WriteLine("someNames sub-ArrayList: ");
foreach (Object name in someNames)
Console.WriteLine(name);
总结:
数组是最常用的数据结构。几乎所有的编程语言都内置了数组类型。
很多应用程序里,数组是最简单最有效的数据结构,数组是很有用的。
.net有一个新的数组类型叫做ArrayList,基本上和数组差不多,但更强的是它可以重新设置容量。
ArrayList还有一些很有用的方法,比如插入,删除,以及查找。
因为C#不允许程序员像VB.net那样动态变更数组的大小,ArrayList在不知道数组大小的情况下是很有用的数据结构。