C#基础之集合讲解
1 集合
集合(Collection
)类是专门用于数据存储和检索的类。这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类实现了相同的接口。
集合(Collection
)类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等等。这些类创建 Object
类的对象的集合。在 C# 中,Object
类是所有数据类型的基类。
1.1 数组
1.1.1 简介
数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。
声明数组变量并不是声明 number0、number1、...、number99 一个个单独的变量,而是声明一个就像 numbers 这样的变量,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来表示一个个单独的变量。数组中某个指定的元素是通过索引来访问的。
所有的数组都是由连续
的内存位置
组成的。最低的地址对应第一个元素,最高的地址对应最后一个元素。
1.1.2 声明使用
1.1.2.1 声明 & 初始化
在 C# 中声明一个数组,可以使用下面的语法:datatype[] arrayName;
其中,
datatype
:用于指定被存储在数组中的元素的类型。[ ]
指定数组的秩(维度),秩指定数组的大小。arrayName
指定数组的名称。
声明一个数组不会在内存中初始化数组。当初始化数组变量时,可以赋值给数组。数组是一个引用类型,所以需要使用 new
关键字来创建数组的实例。
double[] balance = new double[10];
1.1.2.2 赋值给数组
通过使用索引号赋值给一个单独的数组元素,比如:
double[] balance = new double[10];
balance[0] = 4500.0;
在声明数组的同时给数组赋值,比如:
double[] balance = { 2340.0, 4523.69, 3421.0};
也可以创建并初始化一个数组,比如:
int [] marks = new int[5] { 99, 98, 92, 97, 95};
在上述情况下,也可以省略数组的大小,比如:
int [] marks = new int[] { 99, 98, 92, 97, 95};
也可以赋值一个数组变量到另一个目标数组变量中。在这种情况下,目标和源会指向相同的内存位置:
int [] marks = new int[] { 99, 98, 92, 97, 95};
int[] score = marks;
当创建一个数组时,C#
编译器会根据数组类型隐式初始化每个数组元素为一个默认值。例如,int 数组的所有元素都会被初始化为 0。
1.1.2.3 访问数组元素
元素是通过带索引的数组名称来访问的。这是通过把元素的索引放置在数组名称后的方括号中来实现的。例如:double salary = balance[9];
下面是一个实例,使用上面提到的三个概念,即声明、赋值、访问数组:
using System;
namespace ArrayApplication
{
class MyArray
{
static void Main(string[] args)
{
int [] n = new int[10]; /* n 是一个带有 10 个整数的数组 */
int i,j;
/* 初始化数组 n 中的元素 */
for ( i = 0; i < 10; i++ )
{
n[ i ] = i + 100;
}
/* 输出每个数组元素的值 */
for (j = 0; j < 10; j++ )
{
Console.WriteLine("Element[{0}] = {1}", j, n[j]);
}
Console.ReadKey();
}
}
}
也可以使用一个 foreach 语句来遍历数组。
foreach (int j in n )
{
int i = j-100;
Console.WriteLine("Element[{0}] = {1}", i, j);
}
1.1.3 多维数组
1.1.3.1 声明
可以声明一个 string 变量的二维数组,如下:
string [,] names;
或者,可以声明一个 int 变量的三维数组,如下:
int [ , , ] m;
多维数组最简单的形式是二维数组。一个二维数组,在本质上,是一个一维数组的列表。
一个二维数组可以被认为是一个带有 x 行
和 y 列
的表格。下面是一个二维数组,包含 3 行和 4 列:
因此,数组中的每个元素是使用形式为 a[ i , j ] 的元素名称来标识的,其中 a 是数组名称,i 和 j 是唯一标识 a 中每个元素的下标。
1.1.3.2 初始化二维数组
多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组。
int [,] a = new int [3,4] {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
1.1.3.3 访问二维数组元素
二维数组中的元素是通过使用下标(即数组的行索引和列索引)来访问的。例如:
int val = a[2,3];
上面的语句将获取数组中第 3 行第 4 个元素,使用嵌套循环来处理二维数组:
using System;
namespace ArrayApplication
{
class MyArray
{
static void Main(string[] args)
{
/* 一个带有 5 行 2 列的数组 */
int[,] a = new int[5, 2] {{0,0}, {1,2}, {2,4}, {3,6}, {4,8} };
int i, j;
/* 输出数组中每个元素的值 */
for (i = 0; i < 5; i++)
{
for (j = 0; j < 2; j++)
{
Console.WriteLine("a[{0},{1}] = {2}", i, j, a[i,j]);
}
}
Console.ReadKey();
}
}
}
1.1.4 交错数组
交错数组是数组的数组,交错数组是一维数组
。
声明一个带有 int 值的交错数组 scores,如下所示:
int [][] scores;
声明一个数组不会在内存中创建数组。创建上面的数组:
int[][] scores = new int[5][];
for (int i = 0; i < scores.Length; i++)
{
scores[i] = new int[4];
}
初始化一个交错数组,如下所示:
int[][] scores = new int[2][]{new int[]{92,93,94},new int[]{85,66,87,88}};
其中,scores 是一个由两个整型数组组成的数组,其中 scores[0]
是一个带有 3 个整数的数组,scores[1]
是一个带有 4 个整数的数组。
实例
using System;
namespace ArrayApplication
{
class MyArray
{
static void Main(string[] args)
{
/* 一个由 5 个整型数组组成的交错数组 */
int[][] a = new int[][]{new int[]{0,0},new int[]{1,2},
new int[]{2,4},new int[]{ 3, 6 }, new int[]{ 4, 8 } };
int i, j;
/* 输出数组中每个元素的值 */
for (i = 0; i < 5; i++)
{
for (j = 0; j < 2; j++)
{
Console.WriteLine("a[{0}][{1}] = {2}", i, j, a[i][j]);
}
}
Console.ReadKey();
}
}
}
1.1.5 传递数组给函数
在 C# 中,可以传递数组作为函数的参数。通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
下面的实例演示了如何传递数组给函数:
using System;
namespace ArrayApplication
{
class MyArray
{
double getAverage(int[] arr, int size)
{
int i;
double avg;
int sum = 0;
for (i = 0; i < size; ++i)
{
sum += arr[i];
}
avg = (double)sum / size;
return avg;
}
static void Main(string[] args)
{
MyArray app = new MyArray();
/* 一个带有 5 个元素的 int 数组 */
int [] balance = new int[]{1000, 2, 3, 17, 50};
double avg;
/* 传递数组的指针作为参数 */
avg = app.getAverage(balance, 5 ) ;
/* 输出返回值 */
Console.WriteLine( "平均值是: {0} ", avg );
Console.ReadKey();
}
}
}
结果:
平均值是: 214.4
1.1.6 Array
1.1.6.1 简介
Array
类是 C# 中所有数组的基类,它是在 System
命名空间中定义,所有数组类型
(如 int[], string[], double[]
等)都继承自 Array
类。Array
类提供了各种用于数组的属性和方法。
特性:
- 类型安全:数组只能存储指定类型的元素,类型在声明时确定。
- 固定长度:数组的长度在创建后不可更改。
- 索引访问:数组中的元素通过索引访问,索引从 0 开始。
- 多维支持:C# 支持一维、多维和交错数组(数组的数组)。
1.1.6.2 属性
下表列出了 Array 类中一些最常用的属性:
属性名 | 说明 | 示例代码 | 输出 |
---|---|---|---|
Length | 获取数组中元素的总个数。 | int[] arr = {1, 2, 3}; int length = arr.Length; |
3 |
Rank | 获取数组的维数(即数组的维度) | int[,] matrix = new int[2, 3]; int rank = matrix.Rank; |
2 |
IsFixedSize | 判断数组的大小是否固定 | int[] arr = {1, 2, 3}; bool fixedSize = arr.IsFixedSize; |
true |
IsReadOnly | 判断数组是否为只读。 | int[] arr = {1, 2, 3}; bool readOnly = arr.IsReadOnly; |
false |
IsSynchronized | 判断数组是否线程安全 | int[] arr = {1, 2, 3}; bool sync = arr.IsSynchronized; |
false |
SyncRoot | 获取用于同步数组访问的对象,通常用于多线程操作 | int[] arr = {1, 2, 3}; object syncRoot = arr.SyncRoot; |
syncRoot |
1.1.6.3 方法
下表列出了 Array 类中一些最常用的方法:
方法 | 描述 |
---|---|
Clear | 根据元素的类型,设置数组中某个范围的元素为零、为 false 或者为 null。 |
Copy(Array, Array, Int32) | 从数组的第一个元素开始复制某个范围的元素到另一个数组的第一个元素位置。长度由一个 32 位整数指定。 |
CopyTo(Array, Int32) | 从当前的一维数组中复制所有的元素到一个指定的一维数组的指定索引位置。索引由一个 32 位整数指定 |
GetLength | 获取一个 32 位整数,该值表示指定维度的数组中的元素总数 |
GetLongLength | 获取一个 64 位整数,该值表示指定维度的数组中的元素总数 |
GetLowerBound | 获取数组中指定维度的下界 |
GetType | 获取当前实例的类型。从对象(Object)继承 |
GetUpperBound | 获取数组中指定维度的上界 |
GetValue(Int32) | 获取一维数组中指定位置的值。索引由一个 32 位整数指定 |
IndexOf(Array, Object) | 搜索指定的对象,返回整个一维数组中第一次出现的索引 |
Reverse(Array) | 逆转整个一维数组中元素的顺序 |
SetValue(Object, Int32) | 给一维数组中指定位置的元素设置值。索引由一个 32 位整数指定 |
Sort(Array) | 使用数组的每个元素的 IComparable 实现来排序整个一维数组中的元素 |
ToString | 返回一个表示当前对象的字符串。从对象(Object)继承 |
1.2 ArrayList
1.2.1 简介
动态数组(ArrayList
),它代表了可被单独索引的对象的有序集合。
它基本上可以替代一个数组。
与数组区别:
- 类型安全
- 数组(
Array
):数组是强类型的,意味着一旦数组被创建,其元素类型就固定了,不能存储不同类型的元素。例如,一个int[]数组只能存储整数。 ArrayList
:ArrayList是弱类型的,可以存储任何类型的对象(Object类型)。这意味着可以在ArrayList
中存储不同类型的元素,但在使用时需要进行类型转换,这可能导致性能损失和运行时错误。
- 数组(
- 大小固定性
- 数组(Array):数组的大小在创建时固定,之后不能改变。如果需要存储更多的元素,你需要创建一个新的数组并将旧数组的元素复制到新数组中。
ArrayList
:ArrayList
的大小是动态的,可以根据需要自动调整。这意味着可以随时向ArrayList中添加或删除元素,而不需要手动管理数组的大小。
- 性能
- 数组(Array):数组在内存中是连续存储的,因此访问速度非常快,特别适合需要频繁访问元素的场景。
ArrayList
:由于ArrayList
在添加或删除元素时可能需要调整内部数组的大小(如扩容或缩容),因此性能开销相对较大。此外,ArrayList存储的是对象引用,而不是值本身,这也会带来一些性能上的开销。
- 内存使用
- 数组(Array):数组的内存使用是高效的,因为它只存储元素本身(对于值类型)。
- ArrayList:由于ArrayList存储的是对象引用,因此需要额外的内存来存储这些引用。此外,当ArrayList扩容时,它通常会创建一个更大的内部数组,并复制现有元素,这也会导致额外的内存开销。
- 泛型支持
- 数组(Array):数组支持泛型,例如int[]、string[]等,这使得数组在类型安全和性能上都很有优势。
- ArrayList:ArrayList不支持泛型,因此在使用时需要进行类型转换,这降低了代码的可读性和安全性。为了解决这个问题,C#引入了 List
,它是ArrayList的泛型版本,提供了类似的动态数组功能,但支持类型安全。
- 集合初始化
- 数组(Array):数组可以在声明时直接初始化,例如int[] numbers = {1, 2, 3, 4, 5};。
- ArrayList:ArrayList需要在创建后使用Add方法逐个添加元素,或者通过构造函数和集合初始化器(在C# 3.0及更高版本中)进行初始化,例如ArrayList list = new ArrayList {1, 2, 3, 4, 5};。
1.2.2 方法和属性
下表列出了 ArrayList 类的一些常用的 属性:
属性 | 描述 |
---|---|
Capacity | 获取或设置 ArrayList 可以包含的元素个数 |
Count | 获取 ArrayList 中实际包含的元素个数 |
IsFixedSize | 获取一个值,表示 ArrayList 是否具有固定大小 |
IsReadOnly | 获取一个值,表示 ArrayList 是否只读 |
IsSynchronized | 获取一个值,表示访问 ArrayList 是否同步(线程安全) |
Item[Int32] | 获取或设置指定索引处的元素 |
SyncRoot | 获取一个对象用于同步访问 ArrayList |
下表列出了 ArrayList 类的一些常用的 方法:
方法名 | 描述 |
---|---|
public virtual int Add( object value ); | 在 ArrayList 的末尾添加一个对象 |
public virtual void AddRange( ICollection c ); | 在 ArrayList 的末尾添加 ICollection 的元素 |
public virtual void Clear(); | 从 ArrayList 中移除所有的元素 |
public virtual bool Contains( object item ); | 判断某个元素是否在 ArrayList 中 |
public virtual ArrayList GetRange( int index, int count ); | 返回一个 ArrayList,表示源 ArrayList 中元素的子集 |
public virtual int IndexOf(object); | 返回某个值在 ArrayList 中第一次出现的索引,索引从零开始 |
public virtual void Insert( int index, object value ); | 在 ArrayList 的指定索引处,插入一个元素 |
public virtual void InsertRange( int index, ICollection c ); | 在 ArrayList 的指定索引处,插入某个集合的元素 |
public virtual void Remove( object obj ); | 从 ArrayList 中移除第一次出现的指定对象 |
public virtual void RemoveAt( int index ); | 移除 ArrayList 的指定索引处的元素 |
public virtual void RemoveRange( int index, int count ); | 从 ArrayList 中移除某个范围的元素 |
public virtual void Reverse(); | 逆转 ArrayList 中元素的顺序 |
public virtual void SetRange( int index, ICollection c ); | 复制某个集合的元素到 ArrayList 中某个范围的元素上 |
public virtual void Sort(); | 对 ArrayList 中的元素进行排序 |
public virtual void TrimToSize(); | 设置容量为 ArrayList 中元素的实际个数 |
1.3 Hashtable
1.3.1 简介
哈希表(Hashtable
) 它使用键来访问集合中的元素。
使用键访问元素时,则使用哈希表,而且可以识别一个有用的键值。哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。
排序列表(SortedList) 它可以使用键和索引来访问列表中的项。
排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果使用索引访问各项,则它是一个动态数组(ArrayList),如果使用键访问各项,则它是一个哈希表(Hashtable)。集合中的各项总是按键值排序。
1.3.2 方法&属性
Hashtable 常用的 属性:
属性 | 描述 |
---|---|
Count | 获取 Hashtable 中包含的键值对个数 |
IsFixedSize | 获取一个值,表示 Hashtable 是否具有固定大小 |
IsReadOnly | 获取一个值,表示 Hashtable 是否只读 |
Item | 获取或设置与指定的键相关的值 |
Keys | 获取一个 ICollection,包含 Hashtable 中的键 |
Values | 获取一个 ICollection,包含 Hashtable 中的值 |
常用的 方法:
方法名 | 描述 |
---|---|
public virtual void Add( object key, object value ); | 向 Hashtable 添加一个带有指定的键和值的元素 |
public virtual void Clear(); | 从 Hashtable 中移除所有的元素 |
public virtual bool ContainsKey( object key ); | 判断 Hashtable 是否包含指定的键 |
public virtual bool ContainsValue( object value ); | 判断 Hashtable 是否包含指定的值 |
public virtual void Remove( object key ); | 从 Hashtable 中移除带有指定的键的元素 |
实例
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
Hashtable ht = new Hashtable();
ht.Add("001", "Zara Ali");
ht.Add("002", "Abida Rehman");
ht.Add("003", "Joe Holzner");
ht.Add("004", "Mausam Benazir Nur");
ht.Add("005", "M. Amlan");
ht.Add("006", "M. Arif");
ht.Add("007", "Ritesh Saikia");
if (ht.ContainsValue("Nuha Ali"))
{
Console.WriteLine("This student name is already in the list");
}
else
{
ht.Add("008", "Nuha Ali");
}
// 获取键的集合
ICollection key = ht.Keys;
foreach (string k in key)
{
Console.WriteLine(k + ": " + ht[k]);
}
Console.ReadKey();
}
}
}
1.4 Stack
1.4.1 简介
堆栈(Stack) 它代表了一个先进后出,后进先出
的对象集合。
当需要对各项进行后进先出的访问时,则使用堆栈。当在列表中添加一项,称为推入元素,当从列表中移除一项时,称为弹出元素。
1.4.2 方法&属性
常用的 属性:
属性 | 描述 |
---|---|
Count | 获取 Stack 中包含的元素个数 |
常用的 方法:
方法名 | 描述 |
---|---|
public virtual void Clear(); | 从 Stack 中移除所有的元素 |
public virtual bool Contains( object obj ); | 判断某个元素是否在 Stack 中 |
public virtual object Peek(); | 返回在 Stack 的顶部的对象,但不移除它 |
public virtual object Pop(); | 移除并返回在 Stack 的顶部的对象 |
public virtual void Push( object obj ); | 向 Stack 的顶部添加一个对象 |
public virtual object[] ToArray(); | 复制 Stack 到一个新的数组中 |
实例
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
Stack st = new Stack();
st.Push('A');
st.Push('M');
st.Push('G');
st.Push('W');
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
Console.WriteLine();
st.Push('V');
st.Push('H');
Console.WriteLine("The next poppable value in stack: {0}",
st.Peek());
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
Console.WriteLine();
Console.WriteLine("Removing values ");
st.Pop();
st.Pop();
st.Pop();
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
}
}
}
1.5 Queue
1.5.1 简介
队列(Queue
) 它代表了一个先进先出的对象集合。
当需要对各项进行先进先出的访问时,则使用队列。当在列表中添加一项,称为入队,从列表中移除一项时,称为出队。
1.5.2 方法&属性
常用的 属性:
属性 | 描述 |
---|---|
Count | 获取 Queue 中包含的元素个数 |
常用的 方法:
方法名 | 描述 |
---|---|
public virtual void Clear(); | 从 Queue 中移除所有的元素 |
public virtual bool Contains( object obj ); | 判断某个元素是否在 Queue 中 |
public virtual object Dequeue(); | 移除并返回在 Queue 的开头的对象 |
public virtual void Enqueue( object obj ); | 向 Queue 的末尾添加一个对象 |
public virtual object[] ToArray(); | 复制 Queue 到一个新的数组中 |
public virtual void TrimToSize(); | 设置容量为 Queue 中元素的实际个数 |
1.6 BitArray
1.6.1 简介
点阵列(BitArray) 它代表了一个使用值 1
和 0
来表示的二进制
数组。
BitArray
类管理一个紧凑型的位值数组,它使用布尔值来表示,其中 true
表示位是开启的(1
),false
表示位是关闭的(0)。
当需要存储位,但是事先不知道位数时,则使用点阵列。可以使用整型索引从点阵列集合中访问各项,索引从零开始。
1.6.2 方法&属性
常用的 属性:
属性 | 描述 |
---|---|
Count | 获取 BitArray 中包含的元素个数 |
IsReadOnly | 获取一个值,表示 BitArray 是否只读 |
Item | 获取或设置 BitArray 中指定位置的位的值 |
Length | 获取或设置 BitArray 中的元素个数 |
用的 方法:
方法名 | 描述 |
---|---|
public BitArray And( BitArray value ); | 对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位与操作 |
public bool Get( int index ); | 获取 BitArray 中指定位置的位的值 |
public BitArray Not(); | 把当前的 BitArray 中的位值反转,以便设置为 true 的元素变为 false,设置为 false 的元素变为 true |
public BitArray Or( BitArray value ); | 对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位或操作 |
public void Set( int index, bool value ); | 把 BitArray 中指定位置的位设置为指定的值 |
public void SetAll( bool value ); | 把 BitArray 中的所有位设置为指定的值 |
public BitArray Xor( BitArray value ); | 对当前的 BitArray 中的元素和指定的 BitArray 中的相对应的元素执行按位异或操作 |
实例
using System;
using System.Collections;
namespace CollectionsApplication
{
class Program
{
static void Main(string[] args)
{
// 创建两个大小为 8 的点阵列
BitArray ba1 = new BitArray(8);
BitArray ba2 = new BitArray(8);
byte[] a = { 60 };
byte[] b = { 13 };
// 把值 60 和 13 存储到点阵列中
ba1 = new BitArray(a);
ba2 = new BitArray(b);
// ba1 的内容
Console.WriteLine("Bit array ba1: 60");
for (int i = 0; i < ba1.Count; i++)
{
//格式化为至少6个字符宽度并左对齐(如果必要,右侧用空格填充)因为负号则左对齐
Console.Write("{0, -6} ", ba1[i]);
}
Console.WriteLine();
// ba2 的内容
Console.WriteLine("Bit array ba2: 13");
for (int i = 0; i < ba2.Count; i++)
{
Console.Write("{0, -6} ", ba2[i]);
}
Console.WriteLine();
BitArray ba3 = new BitArray(8);
ba3 = ba1.And(ba2);
// ba3 的内容
Console.WriteLine("Bit array ba3 after AND operation: 12");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
Console.WriteLine();
ba3 = ba1.Or(ba2);
// ba3 的内容
Console.WriteLine("Bit array ba3 after OR operation: 61");
for (int i = 0; i < ba3.Count; i++)
{
Console.Write("{0, -6} ", ba3[i]);
}
Console.WriteLine();
Console.ReadKey();
}
}
}