C#中的 Array和ArrayList
发布日期:2021-05-15 00:46:43 浏览次数:28 分类:精选文章

本文共 11733 字,大约阅读时间需要 39 分钟。

C#中的 Array和ArrayList

大家好,我是苏州程序大白,讲讲上个文章提到的Array。内容有点多。我这里会持续更新,希望大家关注我、支持我,谢谢大家。不废话了下面我们开始。

Array和ArrayList

数组是最通用的数据结构, 它出现在几乎所有的编程语言里. 在C#中使用数组包括创建System. Array类型的数组对象, 它是所有数组的抽象基类. Array类提供了一套方法, 这些方法执行的诸如排序和查找归工作在历史上需要程序员手工完成。

C#中数组的另外一种使用方式就是使用ArrayList类. ArrayList是一种可以动态增加长度的数组, 对于无法精确知道数组最终大小的情况, 或者对于程序生命周期内数 组大小可能会发生一点变化的情况, 用ArrayList比用Array更合适。
本章将简要介绍C#中使用数组的基本概念, 然后继续展开更加深入的主题, 这其中包括复制、克隆、相等比较, 以及使用Array类和ArrayList类的静态方法。

数组基本概念

数组是可索引的数据的集合. 数据既可以是内置的类型, 也可以是用户自定义的类型. C#中的数组一种对象, 因为它们都来源于System. Array类, 是System. Array类的一个声明实例, 所以在使用数组时也可以使用此类的所有方法和属性.

数组的声明和初始化

对数组进行声明的语法规则是:类型[]数组名称

这里的类型就是数组元素的数据类型. 下面是一个实例:

string[ ] names;

接下来一行需要实例化数组(既然它是System. Array类型的一个对象), 还需要确定数组的大小. 下面这行就实例化了刚声明的name数组, 并且预留了五个字符串的内 存空间:

names = new string[10];

声明与初始化可以合并成为一条语句:

string[ ] names = new string[10];

你还可以在一条语句中对数组进行声明、实例化并赋值. 在C#中可以采用初始化列表的方式来实现:

int[ ] numbers = new int[ ] {   1, 2, 3, 4, 5};

上述这个数的列表被称为是初始化列表. 它用一对大括号作为界定符, 并且每个元素之间用逗号进行分割. 当用这种方法来声明数组时, 不需要指定元素的个数. 编译器会通过初始列 表中数据项的数量来推断出此数据。

设置和访问数组元素

存储数组元素既可以采用直接存取访问的方法也可以通过调用Array类的SetValue方法.

直接存取方式通过赋值语句左侧的索引来引用数组位置:

nNames[2] = "Raymond"; sSales[19] = 23123;

而SetValue方法则提供了一种更加面向对象的方法来为数组元素赋值. 该方法需要两个参数, 一个代表要设置给索引位置元素的值, 另一个代表索引。

names.SetValue("Raymond", 2); sales.SetValue(23123, 9);

数组元素的访问既可以通过直接存取的方式也可以通过调用GetValue方法的方式. GetValue方法需要一个代表索引的参数。

myName = names[2]; monthSales = sales.GetValue([19)];

通常为了访问每一个数组元素, 会使用For循环遍历数组. 程序员在编写循环时常犯的错误即可能是写死循环的上限值(如果数组是动态的, 那么这样做就是错误的, 因为循环的上限可能会改变), 可以调用数组的GetUpperBound方法设置动态的循环上限:

(for( int i = 0; i <= sales.GetUpperBound(0); i++)    totalSales = totalSales + sales[i];

获取数组元数据的方法和属性

Array类为取回数组元数据提供了几种属性:

• Length:返回数组所有维度内元素的总数量.
• GetLength:返回数组指定维度内元素的数量.
• Rank:返回数组的维度数.
• GetType:返回当前数组实例的类型.
Length方法对于计算多维数组中元素的数量很有用.
既然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();

代码的输出是:Thearraytypeis:System. Int32[]

输出内容中的方括号说明对象是一个数组。

多维数组

目前为止只讨论过一维数组的情况. 在C#中, 尽管数组多于三维的情况是非常少见(而且也是非常容易使人混乱的), 不过如果你有需要, 最多可以创建32维的数组. 通过提供数组每一维上限值的方式可以声明多维数组. 二维数组的声明:

int [ , ] grades = new int [4,5] ;

此语句声明了一个4行5列的数组. 二维数组经常用来模拟矩阵.

声明多维数组也可以不指定维数的上限值. 要想这样做就要用到逗号来明确数组的维数. 例如, 声明一个二维数组如下所示:

double [ , ] Sales ;

再比如声明一个三维数组:

double [ , , ] Sales ;

对多维数组可以用初始化表进行初始化操作. 请看下面这条语句:

Iint[,] grades = new int[,] {    {   1, 82, 74, 89, 100}, {   2, 93, 96, 85, 86}, {   3, 83, 72, 95, 89}, {   4, 91, 98, 79, 88} };

首先要注意这里没有指明数组的上限. 当初始化带有初始化表的数组的时候, 不用说明数组 的上限. 编译器会根据初始化表中数据计算出每一维的上限值. 初始化表本身也像数组的每 一行那样用大括号进行标记. 数组行内的每一个元素则用逗号进行分割.

访问多维数组元素的方法类似于访问一维数组元素的方法. 可以采用传统的数组访问方式:

int grade = grades[2, 2];grades[2, 2] = 99;

也可以采用Array类的方法:

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); }

参数数组

大多数的方法调用需要若干参数, 有时你可能需要每次传给方法的参数数量能够发生变化. 使用称为参数数组的方式就可以做到.

通过使用关键字params方法定义的参数列表中指明参数数组. 下面的方法定义允许提供任意数量的数字作为参数, 并且方法会返回所有数字的总和:

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];

这个数组假设每行(即月数)都有相同的元素(即天数)数量, 但是大家知道某些月有30天, 而某些月是31天, 还有一个月是29天. 因而, 这个刚刚声明的数组会有元素位置始终得不到使用. 对于这个数组而言, 这不是太大的问题, 但是对于更加庞大的数组而言, 就需要减少不必要的元素, 避免大量浪费的空间.

解决这个问题的方法是用锯齿状数组代替二维数组. 锯齿状数的每一行都是一个一维数组. “锯齿”数组的就是指的数组每一行元素的数量都可能不同. 锯齿状数组每一行排列在一起的图形不是矩形, 而是具有锯齿边缘的图形.
锯齿状数组的声明需要通过在数组变量名后放置两个方括号的方式来完成. 初始化锯齿数组时, 第一个方括号说明了数组的行数, 这为存储在每行内的一维数组标记了位置. 第二组方括号则是空白的. 就像下列这样:

int[][] jagged = new int[12][];

这条语句看上去很奇怪, 但是把它分解后就一目了然了. jagged是12个元素的数组, 其中的每个元素都是一个整数数组. 初始锯齿数组实际上就是对数组的每个组成数组进行初始化.

一旦声明了锯齿状的数组, 就可以分别对各自行数组的元素进行赋值操作了. 下面这段代码对jaggedArray进行了赋值操作:

//不为每个单独的数组初始化是无法进行赋值的!jagged[0] = new int[1];jagged[1] = new int[2];. . . jagged[11] = new int[12];//上面是我补充的每个数组的初始化参考, 下面开始是之前写的原文的代码jagged[0][0] = 23;jagged[0][1] = 13; . . . jagged[7][5] = 45;

第一组方括号说明了行编号, 而第二组方括号则表明了行数组的元素. 第一条语句访问第一个数组的第一个元素, 接着第二条语句访问第一个数组的第二个元素, 而第三条语句访问的则是第八个数组的第六个元素.

为了做一个使用锯齿状数组的实例, 下边这段程序创建了一个名为sales的数组(用来跟踪两个月内每星期的销售情况), 并且把每个月第一个周的销售额赋值给数组, 然后计算出每月第一周的平均销售额:

using System; class class1 {        static void Main()[]     {            //一月31天        int[] Jan = new int[31];        //二月29天        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][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. 它是. NET框架库中System. Collections命名空间的内容。

ArrayList对象拥有可存储数组大小尺寸的Capacity属性. 该属性的初始值为16. 当ArrayList中元素的数量达到此界限值时, Capacity属性就会为ArrayList的存储空间另外增加16个元素. 在数组内元素数量有可能扩大或缩小的情况下使用ArrayList会比用带标准数组的ReDimPreserver更加有效(实际上C#中因为有了ArrayList, 所以没有ReDim函数, VB中才有)。
就像第1章讨论过的那样, ArrayList用Object类型来存储对象. 如果需要强类型的数组, 就应该采用标准数组或者其他一些数据结构。

ArrayList类的成员

ArrayList类包含几种用于ArrayList的方法和属性. 下面这个列表就是最常用到的一些方法和属性:

• Add():向ArrayList添加一个元素.
• AddRange():在ArrayList末尾处添加一批元素.
• Capacity:存储ArrayList所能包含的元素的数量.
• Clear():从ArrayList中移除全部元素.
• Contains():确定制定的对象是否在ArrayList内.
• CopyTo():把ArrayList或其中的某一段复制给一个数组.
• Count:返回ArrayList中当前元素的数量.
• GetEnumerator():返回迭代ArrayList的计数器.
• GetRange():返回ArrayList的子集作为ArrayList.
• IndexOf():返回指定数据项首次出现的索引.
• Insert():在ArrayList的指定索引处插入一个元素.
• InsertRange():从ArrayList指定索引处开始插入群集的元素.
• Item():在指定索引处获取或者设置一个元素.
• Remove():移除指定数据项的首次出现.
• RemoveAt():在指定索引处移除一个元素.
• Reverse():对ArrayList中元素的顺序进行反转.
• Sort():对ArrayList中的元素按照阿拉伯字母表顺序进行排序.
• ToArray():把ArrayList的元素复制给一个数组.
• TrimToSize():为ArrayList中元素数量设置ArrayList的容量.

应用ArrayList类

ArrayList的使用不同于标准数组. 通常情况应该只使用Add方法向ArrayList的末尾添加元素. 如果出现特殊情况要把数据项添加到指定位置上, 就要采用Insert方法来进行操作了. 本节会讨论如何使用这些操作及ArrayList类的其他成员.

首先要做的事情就是如下所示那样声明ArrayList:

ArrayList grades = new ArrayList();

注意此声明中使用到了构造函数. 如果声明ArrayList时没有使用构造函数, 那么在后续程序语句里就无法获得ArrayList对象.

Add方法会将参数作为新增元素添加给ArrayList. Add方法也会返回一个整数用来说明ArrayList中被添加元素的位置, 下面是一些实例:

grades.Add(100); grades.Add(84); int position; position = grades.Add(77); Console.WriteLine("The grade 77 was added at position:" + position);

用foreach循环可以遍历ArrayList. ArrayList有一个内置计数器用来管理对ArrayList元素的遍历, 每次计数+1. 下面这段代码说明了对ArrayLsit使用foreach循环的方法:

int total = 0; double average = 0.0; foreach (Object grade in grades)     total += (int)grade; average = total / grades.Count; Console.WriteLine("平均分数是: " + average);

Insert方法可在ArrayList某个特殊位置上添加元素. 此方法需要两个参数:插入位置索引, 以及要插入的元素. 下面这段代码在指定位置上插入了两个成绩:

grades.Insert(1, 99); grades.Insert(3, 80);

通过调用Capacity属性可以检查ArrayList当前的容量, 而通过调用Count属性可以确定ArrayList中元素的数量:

Console.WriteLine("当前总共可以容纳的成绩数:" + grades.Capacity); Console.WriteLine("当前已经容纳的成绩数:" + grades.Count);

这里有几种从ArrayList中移除数据项的方法. 如果知道要移除的数据项, 但不确定它所处的位置, 可以采用Remove方法. 此方法的参数代表要从ArrayList中移除的对象. 如果ArrayList内有这个对象, 就可以把它移除掉. 如果此对象不在ArrayList内, 那就什么结果也不会发生. 当使用像Remove这样的方法时, 典型做法是把方法放置在if-else语句内进行调用, 并且使用诸如Contains这样的方法来验证对象确实存在ArrayList内. 下面是一个代码段实例:

if (grades.Contains(54))     grades.Remove(54) else     Console.Write("没有在ArrayList中找到对象");

如果知道所要移除数据项的索引, 那么可以使用RemoveAt方法. 此方法的参数代表要移除对象的索引. 如果你传递给方法一个无效的索引, 则会导致产生异常报错. 此方法的工作形式如下所示:

grades.RemoveAt(2)

通过调用IndexOf方法可以确定ArrayList中某个对象的位置. 方法返回参数在ArrayList内的位置. 如果参数对象不在ArrayList内, 方法就会返回-1. 下面这段代码把IndexOf方法与RemoveAt方法结合在一起使用:

int pos; pos = grades.IndexOf(70); grades.RemoveAt(pos);

了向ArrayList中添加单独的对象, 还可以添加一批多个对象. 对象必须存储在派生自ICollection的数据类型里面. 也就是要把对象存储在数组里, 或存储在Collection类里, 甚至是存储到另一个ArrayList里面.

有两种不同的方法可以用来给ArrayList添加一批对象. 它们是AddRange方法和InsertRange方法. AddRange方法会把对象的范围添加到ArrayList的末尾处, 而InsertRange方法则会把范围添加到ArrayList内指定的位置上.
下面这段程序说明了如何使用这两种方法:

using System; using System.Collections; class class1 {        static void Main()     {            ArrayList names = new ArrayList();         names.Add("Mike");         names.Add("Beata");         names.Add("Raymond");         names.Add("Bernica");         names.Add("Jennifer");         Console.WriteLine("The original list of names: ");         foreach (Object name in names)             Console.WriteLine(name);         Console.WriteLine();         string[] newNames = new string[] {    "前面的David", "前面的Michael" };         ArrayList moreNames = new ArrayList();         moreNames.Add("末尾的Terrill");         moreNames.Add("末尾的Donnie");         moreNames.Add("末尾的Mayo");         moreNames.Add("末尾的Clayton");         moreNames.Add("末尾的的Alisa");         names.InsertRange(0, newNames);         names.AddRange(moreNames);         Console.WriteLine("The new list of names: ");         foreach (Object name in names)             Console.WriteLine(name);     } }

代码输出如下:

在这里插入图片描述
因为InsertRange指定的索引为0, 所以是在ArrayList开始处添加了前两个名字. 而后面的几个名字由于使用AddRange方法而被添加到了末尾处.
许多程序员还找到了另外两种非常有用的方法, ToArray方法和GetRange方法. GetRange方法会返回某个ArrayList的对象指定的范围的元素作为另外一个ArrayList. 而ToArray方法则会把ArrayList的所有元素转化为一个数组. 首先来看一看GetRange方法.
GetRange方法需要两个参数:起始索引以及要返回的元素数量. GetRange不会改变原ArrayList的数据, 这只是把对象从原始ArrayList复制给新的ArrayList. 下面的例子采用和上述相同的程序来说明此方法的工作原理:

ArrayList names = new ArrayList();names.Add("名字0");names.Add("名字1");names.Add("名字2");names.Add("名字3");names.Add("名字4");ArrayList someNames = new ArrayList();someNames = names.GetRange(1, 3);Console.WriteLine("从位置1开始取出3个元素的新ArrayList :");foreach (Object name in someNames)    Console.WriteLine(name);

代码运行结果如下:

在这里插入图片描述
ToArray方法允许把ArrayList的内容轻松传递给一个数组. 采用ToArray方法的常见原因就是由于用户需要更快的数据访问速度. (也就是说数组的访问性能快于ArrayList)
ToArray方法不带参数, 会把ArrayList的元素转换为一个新的数组并返回. 下面这个例子就说明了此方法的使用原理:

ArrayList names = new ArrayList();names.Add("名字0");names.Add("名字1");names.Add("名字2");names.Add("名字3");names.Add("名字4");Object[] arrNames;arrNames = names.ToArray();Console.WriteLine("ArrayList.ToArray方法转换出来的数组: ");for (int i = 0; i <= arrNames.GetUpperBound(0); i++)    Console.WriteLine(arrNames[i]);

以上代码运行效果如下:

.在这里插入图片描述
本文小结
数组是计算机编程中最常采用的数据结构. 几乎所有编程语言都会提供一些内置数组类型. 对许多应用程序而言, 数组是最容易实现的数据结构, 也是最有效率的 数据结构. 数组在需要直接访问的数据在集合中位置很“偏远”时非常有用。
. NET框架介绍了一种被称为ArrayList的新的数组类型. ArrayList具有数组的许多特征, 但是在某些方面它比数组更强大, 这是因为ArrayLsit可以在容量已满的情况下我调整自身的大小. ArrayList还有几种对执行插入、删除以及查找操作很有用的方法. 既然C#语言不允许程序员像在VB. NET中那样动态地调整数组的大小, 所以在无法提前知道要存储的数据项数量的情况下ArrayList就是一种非常有用的数据结构了。

关注苏州程序大白,持续更新技术分享。谢谢大家支持

上一篇:流体运动估计光流算法研究
下一篇:C#中的群集, 泛型和计时类

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2025年04月17日 05时36分49秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章