C#数据结构--数组和ArrayList

目录

本章目录:

[2.1 数组基本概念](#2.1 数组基本概念)

[2.1.1 数组的声明和初始化](#2.1.1 数组的声明和初始化)

[2.1.2 数组元素的设置和存取访问](#2.1.2 数组元素的设置和存取访问)

[2.1.4 多维数组](#2.1.4 多维数组)

[2.1.5 参数数组](#2.1.5 参数数组)

[2.2ArrayList 类](#2.2ArrayList 类)

[2.2.1ArrayList 类的成员](#2.2.1ArrayList 类的成员)

[2.2.2 应用 ArrayList 类](#2.2.2 应用 ArrayList 类)

数组和ArrayList之间的区别以及使用的场景

数组:

[ArrayList:(会造成装箱和拆箱操作 导致数据泄露)](#ArrayList:(会造成装箱和拆箱操作 导致数据泄露))

小结


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

C#语言中另外一种使用数组的有趣替换方式就是 ArrayList 类。ArrayList 是一种像要更多空间来动态生长的数组。对于无法精确知道数组最终大小的情况,或者对于程序生命周期内数组大小可能会发生一点变化的情况,用 ArrayList比用数组更合适。

本章将简要介绍 C#语言中使用数组的基本概念,然后继续展开更加深入的主题,这其中包括复制、克隆、相等判定,以及使用 Array 类和 ArrayList 类的静态方法。

本章目录:

  1. 数组基本概念
    1.1 数组的声明和初始化
    1.2 数组元素的设置和存取访问
    1.3 取回数组元数据的方法和属性
    1.4 多维数组
    1.5 参数数组

2. ArrayList 类
2.1 ArrayList 类的成员
2.2 应用 ArrayList 类
2.3 小结

数组和ArrayList之间的区别以及使用的场景

2.1 数组基本概念

数组是可索引的数据的集合。数据既可以是内置的类型,也可以是用户自定义的类型。事实上,把数组数据称为对象大概是最简便的方式。C#语言中的数组实际上就是对象本身,因为它们都来源于 System.Array 类。既然数组是System.Array 类的一个声明实例,所以在使用数组时也可以使用此类的所有方法和属性。

2.1.1 数组的声明和初始化

这里采用下列语法规则对数组进行声明:

type[ ] array-name;

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

string[ ] names;

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

names = new string[10];

必要时还可以把上述这两条语句合并成为一条语句:

string[ ] names = new string[10];

当想要在一条语句中对数组进行声明、例示以及赋值操作时都要花费时间。在 C#语言中可以采用初始化列表的方式来实现:

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

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

2.1.2 数组元素的设置和存取访问

存储数组元素既可以采用直接存取访问的方法也可以通过调用 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 循环来循环遍历数组是一种通用的方法。程序员在编写循环时常犯的错误即可能是写死循环的上限值(如果数组是动态的,那么这样做就是错误的,因为循环的上限可能会改变),也可能是每次循环重复时调用函数来存取循环的上限:

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

2.1.3 取回数组元数据的方法和属性

Array 类为取回数组元数据提供了几种属性:
Length:返回数组所有维数内元素的总数量。
GetLength:返回数组指定维数内元素的数量。
Rank:返回数组的维数。
GetType:返回当前数组实例的类型。
Length 方法对于计算多维数组中元素的数量以及返回数组中元素的准确编号都是很有用的。另外,还可以使用 GetUpperBound 方法,而且要对数值加一。
既然 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[]

这里的方括号说明对象是一个数组。还需要注意在显示数据类型的时候采用了一种格式。这里必须这么做,因为要把 Type 数据与显示的字符串的剩余部分相连接就不能把 Type 数据转变成为字符串。

2.1.4 多维数组

到目前为止的讨论只限于一维数组的情况。在 C#语言中,尽管数组多于三维的情况是非常少见的(而且也是非常容易使人混乱的),但是数组还是可以达到 32 维的。
通过提供数组每一维上限值的方式可以声明多维数组。二维数组的声明:

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

此语句声明了一个 4 行 5 列的数组。二维数组经常用来模拟矩阵。
声明多维数组也可以不指定维数的上限值。要想这样做就要用到逗号来明确数组的维数。例如,声明一个二维数组如下所示

double [ , ] Sales ;

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

double [ , , ] Sales ;

在声明不带维数上限的数组的时候,需要稍后对具有这类上限的数组重新确定维数:

sales = new double [4,5] ;

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

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

首先要注意这里没有指明数组的上限。当初始化带有初始化表的数组的时候,不用说明数组的上限。编译器会根据初始化表中数据计算出每一维的上限值。初始化表本身也像数组的每一行那样用大括号进行标记。数组行内的每一个元素则用逗号进行分割。
存取访问多维数组元素的方法类似于存取一维数组元素的方法。大家可以采用传统的数组存取访问方式,

grade = gGrades[2,2];
gGrades([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);
}

2.1.5 参数数组

大多数的方法定义要求一套提供给方法的参数的数目,但是想要编写一个允许可选参数数目的方法定义是需要时间的。用一种称为参数数组的构造就可以做到。
通过使用关键字 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);

当用参数数组定义方法的时候,为了使编译器能够正确处理参数列表,需要在参数列表的最后提供参数数组的参数。否则,编译器无法知道参数数组元素的截止位置以及方法其他参数的起始位置。

2.2ArrayList 类

当无法提前知道数组的大小或者在程序运行期间数组的大小可能会发生改变的时候,静态数组就不是很适用了。这类问题的一种解决方案就是当数组超出存储空间的时使用能够自动调整自身大小的数组类型。这种数组被称为是 ArrayList。 它是.NET 框架库中 System.Collections 命名空间的内容。
ArrayList 对象拥有可存储数组大小尺寸的 Capacity 属性。该属性的初始值为 16。当 ArrayList 中元素的数量达到此界限值时,Capacity 属性就会为 ArrayList 的存储空间另外增加 16 个元素。在数组内元素数量有可能扩大或缩小的情况下使用 ArrayList 会比用带标准数组的 ReDim Preserver 更加有效。

2.2.1ArrayList 类的成员

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

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

2.2.2 应用 ArrayList 类

ArrayList 的使用不同于标准数组。除非事出有因要把数据项添加到特殊位置上,否则通常情况下使用 Add 方法只是向 ArrayList 添加数据项,而对于上述特殊情况就要采用 Insert 方法来进行操作了。本节会讨论如何使用这些操作及 ArrayList 类的其他成员。
首先要做的事情就是如下所示那样声明 ArrayList:

ArrayList grades = new ArrayList();

注意此声明中使用到了构造器。如果 ArrayList 没有声明使用构造器,那么在后续程序语句里就无法获得对象。
用 Add 方法把对象添加给 ArrayList。此方法会取走一个参数,即添加给 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);

用 For Each 循环可以把 ArrayList 中的对象显示出来。ArrayList 有一个内置计数器用来记录循环遍历 ArrayList 内所有对象的次数,而且是每次一个。下面这段代码就说明了对 ArrayLsit 使用 For Each 循环的方法:

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

如果需要在 ArrayList 某个特殊位置上添加元素,则可以采用 Insert 方法。此方法会取走两个参数:插入元素的索引,以及要插入的元素。下面这段代码为了保持 ArrayList 内对象的次序而在指定位置上插入了两个成绩:

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

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

Console.WriteLine("The current capacity of grades is:" + grades.Capacity);
Console.WriteLine("The number of grades in grades is:" + grades.Count);

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

if (grades.Contains(54))
grades.Remove(54)
else
Console.Write("Object not in 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);
}

}
此程序输出是:

Dvid
Michael
Mike
Bernica
Beata
Raymond
Jennifer
Terrill
Donnie
Mayo
Clayton
Alisa

因为指定的索引为 0,所以是在 ArrayList 开始处添加了前两个名字。而后面的几个名字由于使用了 AddRange
方法而被添加到了末尾处。
许多程序员还找到了另外两种非常有用的方法ToArray方法和GetRange方法。GetRange方法会返回来自ArrayList
的对象的范围作为另外一个 ArrayList。而 ToArray 方法则会把 ArrayList 的所有元素复制给一个数组。首先来看一看
GetRange 方法。
GetRange 方法会取走两个参数:起始索引以及要从 ArrayList 找回的元素数量。GetRange 方法没有破坏性,因为这只是把对象从原始 ArrayList 复制给新的 ArrayList。下面这个实例采用和上述相同的程序来说明此方法的工作原理:

ArrayList someNames = new ArrayList();
someNames = names.GetRange(2, 4);
Console.WriteLine("someNames sub-ArrayList: ");
foreach (Object name in someNames)
Console.WriteLine(name);

这个程序段的输出是:

Mike
Bernica
Beata
Raymond

ToArray 方法允许把 ArrayList 的内容轻松传递给一个标准数组。采用 ToArray 方法的主要原因就是由于用户需要
更快的数组存取访问速度。
ToArray 方法不带参数,但是会把 ArrayList 的元素返回给数组。下面这个例子就说明了此方法的使用原理:

Object[] arrNames;
arrNames = names.ToArray();
Console.WriteLine("Names from an array: ");
for(int i = 0; i <= arrNames.GetUpperBound(0); i++)
Console.WriteLine(arrNames[i]);

这段代码的后半部分证明了确实把来自 ArrayList 的元素存储到了数组 arrNames 里面。

数组和ArrayList之间的区别以及使用的场景

数组:

是线性结构的,是连续的内存空间

优点:可以随时访问数组中的数据

缺点:不能动态的修改数组的内存

适用于场景:常用于用index访问数据元素

ArrayList:(会造成装箱和拆箱操作 导致数据泄露)

是链表,并不需要连续的内存来存储数据,通过指针指向下一组数据的方式,将零散的内存链接起来

优点:内存可扩容,更适合链表的插入和删除操作

缺点:在遍历查询的操作上 ,有很高的时间复杂度

场景:对于数据频繁插入和删除操作

小结

数组是计算机编程中最常采用的数据结构。即使不是全部也是绝大多数的编程语言都会提供一些内置数组类型。对许多应用程序而言,数组是最容易实现的数据结构,也是最有效率的数据结构。

数组对于需要直接存取访问数据集合"偏远"元素的情况是非常有用的。
.NET 框架介绍了一种被称为 ArrayList 的新的数组类型。

ArrayList 具有数组的许多特征,但是在某些方面它比数组更强大,这是因为 ArrayLsit 可以在结构容量已满的情况下我调整自身的大小。

ArrayList 还有几种对执行插入、删除以及查找操作很有用的方法。既然 C#语言不允许程序员像在 VB.NET Shop 中那样动态地调整数组的大小,所以在无法提前知道要存储的数据项数量的情况下 ArrayList 就是一种非常有用的数据结构了。

相关推荐
程序媛小果2 分钟前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林8 分钟前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨22 分钟前
El表达式和JSTL
java·el
JSU_曾是此间年少35 分钟前
数据结构——线性表与链表
数据结构·c++·算法
sjsjs1142 分钟前
【数据结构-合法括号字符串】【hard】【拼多多面试题】力扣32. 最长有效括号
数据结构·leetcode
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
blammmp2 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵2 小时前
设计模式-工厂设计模式
java·开发语言·设计模式