数组与集合
数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。数组是用引用类型,需要使用new开辟空间
1.1 一维数组
一维数组
【数组声明】
格式:
数据类型[ ] 数组名;
eg:
int[] int_array;
【数组索引/下标】
数组索引默认从0开始, 依次递增1,最大到[数组长度-1]。
【数组初始化】
数组是一个引用类型,声明一个数组不会在内存中初始化数组。当初始化数组变量时,需要使用 new 关键字来创建数组的实例,然后再进行初始化操作。
eg:
1、先声明后初始化(使用下标运算符[]进行初始化)
double[] balance = new double[5];
balance[0] = 4500.0;//索引初始化
balance[1] = 5000.0;//索引初始化
balance[2] = 6000.0;//索引初始化
balance[3] = 7000.0;//索引初始化
balance[4] = 8000.0;//索引初始化
2、声明的同时进行初始化(使用初始化表达式{}进行初始化)
float[] float_array = {1.1, 2.2, 3.3, 4.4, 5.5};
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; //score 与 marks 引用的内存位置相同
初始化的时候记住一点:只要后面有初始的值,长度就可以省略。数组长度初始化之后,不可以修改其长度。
当创建一个数组时,C# 编译器会根据数组类型隐式初始化每个数组元素为对应数据类型的默认值。例如,int 数组的所有元素都会被初始化为 0。
【数组访问】
1、单一访问(索引访问)
double salary = balance[9];
2、批量访问
using System;
namespace MSN
{
class MainClass
{
public static void Main(string[] args)
{
int[] balance = new int[10]; /* n 是一个带有 10 个整数的数组 */
int i, j;
/* 初始化数组 n 中的元素 */
for (i = 0; i < 10; i++)
{
balance[i] = i + 100;
}
/*For 批量化遍历*/
for (j = 0; j < 10; j++)
{
Console.WriteLine("balance[{0}] = {1}", j, balance[j]);
}
/*Foreach 批量化遍历*/
foreach (int ele in balance)
{
int index = ele - 100;
Console.WriteLine("balance[{0}] = {1}", index, ele);
}
}
}
}
数组使用非常广泛,可以直接根据下标访问对应的元素。使用for循环进行遍历。数组是引用类型,当两个数组相互赋值的时候,指向的是同一个底层数组。
1.2 二维数组
二维数组是一组具有相同数据类型且可重复的变量的集合。二维数组可看作是一维数组的嵌套:一维数组中的每一个元素都是一个数组。
【数组声明】
格式:
数据类型 [ , ] 数组名 ;
eg:
int [3,4] int_array; //必须指明维度
【数组初始化】
多维数组可以通过在括号内为每行指定值来进行初始化。下面是一个带有 3 行 4 列的数组。
int [,] a = new int [3,4] {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
【数组访问】
**1、单一访问(索引访问)**
二维数组中的元素可以通过使用下标(即数组的行索引和列索引)进行访问。
int val = a[2,3]; //访问第三维度下的第四个元素
2、批量访问
using System;
namespace MSN
{
class MainClass
{
public static void Main(string[] args)
{
/* 5 行 2 列的数组 */
int[,] int_array = { { 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("int_array[{0},{1}] = {2}", i, j, int_array[i, j]);
}
}
}
}
}
1.3 交错数组(扩展)
交错数组:交错数组是数组的数组。交错数组是一维数组。用的比较少。
该数组类似于我们C语言中的数组。它与上面讲解的二维数组的区别就在于它的每一个元素是一个长度可以不一样的数组。
Console.WriteLine("交错数组:交错数组是数组的数组。交错数组是一维数组。");
//一个由 5 个整型数组组成的交错数组
int[][] interleaved_Array1 = new int[5][] {
new int[] { 0,0},
new int[] { 1,2},
new int[] { 2,4},
new int[] { 3,6},
new int[] { 4,8}
};
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 2; j++)
{
Console.WriteLine("a[{0}][{1}]={2}", i, j, interleaved_Array1[i][j]);
}
}
Console.WriteLine();
int[] b1 = new int[3] { 1, 2, 3 }; //3个int类型数据的一维数组
int[] b2 = new int[5] { 4, 5, 6, 7, 8 }; //5个int类型数据的一维数组
//一个由不同大小个数的整型数组组成的交错数组
int[][] interleaved_Array2 = new int[2][] {
b1,
b2
};
Console.WriteLine("用for循环遍历数组的每个元素:");
for (int i = 0; i < interleaved_Array2.Length; i++)
{
if (i == 0)
{
Console.WriteLine("数组一:");
for (int j = 0; j < b1.Length; j++)
{
Console.WriteLine("interleaved_Array2[{0}][{1}]={2}", i, j, interleaved_Array2[i][j]);
}
}
else
{
Console.WriteLine("数组二:");
for (int j = 0; j < b2.Length; j++)
{
Console.WriteLine("interleaved_Array2[{0}][{1}]={2}", i, j, interleaved_Array2[i][j]);
}
}
}
Console.WriteLine();
Console.WriteLine("用foreach循环遍历数组的每个元素:");
foreach (int[] item in interleaved_Array2)
{
foreach (int elem in item)
{
Console.WriteLine("{0}", elem);
}
Console.WriteLine();
}
1.4 集合
C#中的集合(Collection)类是专门用于数据存储和检索的类。这些类提供了对栈(stack)、队列(queue)、列表(list)和哈希表(hash table)的支持。大多数集合类实现了相同的接口。
1.4.1 ArrayList
C#中的动态数组(ArrayList)代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序等操作。
数组与ArrayList对比如下:
优点:普通的数组在内存中是连续存储的、所以它的索引速度是非常快的、时间复杂度为O(1)、而且它的赋值/修改/获取元素也是非常简单的。
缺点
-
1、定义数组的时候需要指定数组的长度(过长会造成内存浪费、过短会导致程序异常System.IndexOutOfRangeException:“索引超出数组界限”)
-
2、插入和删除元素效率低、也比较麻烦。
在不清楚数组长度的时候、就很尴尬了。 所以C#提供了ArrayList了来处理这些问题。
下表列出了 ArrayList 类的一些常用的 属性:
下表列出了 ArrayList 类的一些常用的 方法:
联系代码如下所示:
eg:
// #endregion
using System;
using System.Collections;
namespace MSN
{
class MainClass
{
public static void Main(string[] args)
{
//实例化动态数组对象(用于存储string数据)
ArrayList stringArrayList = new ArrayList();
//1、添加元素
Console.WriteLine("Adding some numbers:");
stringArrayList.Add("Monday");
stringArrayList.Add("Tuesday");
stringArrayList.Add("Wednesday");
stringArrayList.Add("Thursday");
stringArrayList.Add("Friday");
stringArrayList.Add("Saturday");
stringArrayList.Add("Sunday");
//2、属性
Console.WriteLine(stringArrayList); //输出对象的类型
Console.WriteLine("Capacity: {0} ", stringArrayList.Capacity); //Capacity: 8
Console.WriteLine("Count: {0}", stringArrayList.Count); //Count: 7
Console.WriteLine("IsFixedSize:{0}", stringArrayList.IsFixedSize); //False
Console.WriteLine("IsReadOnly:{0}\n", stringArrayList.IsReadOnly); //False
//3、遍历容器中的元素
Console.Write("Content: ");
foreach (var item in stringArrayList)
{
Console.Write(item + " ");
}
Console.WriteLine();
//4、容器中元素逆置
Console.WriteLine("Reverse:");
stringArrayList.Reverse(); //容器的元素逆置
foreach (var item in stringArrayList)
{
Console.WriteLine(item);
}
Console.WriteLine();
//5、容器中元素排序
Console.Write("Sorted Content: ");
stringArrayList.Sort();
foreach (var item in stringArrayList)
{
Console.Write(item + " ");
}
Console.WriteLine();
//6、将子集合添加到stringList
ArrayList intList = new ArrayList() {
"星期一",
"星期二",
"星期三",
"星期四",
"星期五",
"星期六",
"星期日"
};
stringArrayList.AddRange(intList);
Console.WriteLine("New Content:");
foreach (var item in stringArrayList)
{
Console.WriteLine(item);
}
Console.WriteLine();
//7、容器中是否包含指定元素
bool isContain = stringArrayList.Contains("星期日");
Console.WriteLine("容器中是否包含元素“星期日”? {0}", isContain); //True
Console.WriteLine();
//8、截取指定位置范围内的子集合元素
ArrayList getIntList = new ArrayList();
getIntList = stringArrayList.GetRange(7, 7);
Console.WriteLine("getIntList容器中的元素:(获取子集合——指定索引位置开始往后count个元素)");
foreach (var item in getIntList)
{
Console.WriteLine(item);
}
Console.WriteLine();
//8、获取元素的索引位置
Console.WriteLine("IndexOf函数获取:“星期八”的具体位置:{0}",
stringArrayList.IndexOf("星期八")); //-1不存在
//9、指定索引位置处插入新元素
stringArrayList.Insert(0, "星期八");
Console.WriteLine("插入之后容器中的元素:");
foreach (var item in stringArrayList)
{
Console.WriteLine(item);
}
//10、清空容器元素
if (stringArrayList.Count == 0)
{
Console.WriteLine("ArrayList 中元素为空!");
}
else
{
stringArrayList.Clear();
}
}
}
}
1.4.2 Hashtable
C# 类实现了 IDictionary 接口,集合中的值都是以键值对的形式存取的。
C#中的 哈希表(Hashtable) 类代表了一系列基于键的哈希代码组织起来的键/值对。它使用键来访问集合中的元素。换句话说,在 Hashtable 中存放了两个数组,一个数组用于存放 key 值,一个数组用于存放 value 值。
Hashtable 类提供的构造方法有很多,最常用的是不含参数的构造方法,即通过如下代码来实例化 Hashtable 类。Hashtable 类中常用的属性和方法如下表所示。
下表列出了 Hashtable 类的一些常用的 属性:
下表列出了 Hashtable 类的一些常用的 方法:
练习代码如下,无非就是添加、查找、遍历、修改等操作。它与数组的区别在于它是属于键值结构。
eg:
using System;
using System.Collections;
namespace MSN
{
class MainClass
{
public static void Main(string[] args)
{
/*使用 Hashtable 集合实现图书信息的添加、查找以及遍历的操作。*/
Hashtable ht = new Hashtable();
//属性检测
Console.WriteLine("HashTable:{0}", ht);
Console.WriteLine("容器大小:{0}", ht.Count);
//1、添加元素(书的信息:key—编号,value——名称)
object key = 1;
object value = "计算机基础";
ht.Add(key, value);
ht.Add(2, "C语言程序开发");
ht.Add(4, "数据结构与算法分析");
ht.Add(3, "C#高级编程");
ht[4] = "Unity游戏开发"; //对于不存在的key添加一组元素;对于存在的直接修改Value
//2、移除
ht.Remove(1);//参数必须key的类型数据
Console.WriteLine("容器大小:{0}", ht.Count);
Console.WriteLine();
//3、通过编号检索图书是否存在
Console.Write("请输入要检索的图书编号:");
int bookId = Convert.ToInt32(Console.ReadLine());
bool flag = ht.ContainsKey(bookId);
if (flag)
{
Console.WriteLine("图书编码:{0} 图书名称:{1}", bookId, ht[bookId].ToString());
}
else
{
Console.WriteLine("图书信息不存在!");
}
Console.WriteLine();
//4、遍历HashTable中的每一组元素信息(通过DictionaryEntry字典实例)
Console.WriteLine("遍历HashTable中的每一组元素信息:");
foreach (DictionaryEntry dic in ht)
{
bookId = (int)dic.Key;
string bookName = dic.Value as string;
Console.WriteLine("图书编号:{0} 图书名称:{1}", bookId, bookName);
}
Console.WriteLine();
//5、遍历键集合
Console.WriteLine("遍历键集合:");
foreach (int item in ht.Keys)
{
Console.WriteLine("图书编号:{0}", item);
}
Console.WriteLine();
//6、遍历值集合
Console.WriteLine("遍历值集合:");
foreach (string item in ht.Values)
{
Console.WriteLine("图书名称:{0}", item);
}
Console.WriteLine();
//7、遍历哈希表中的键值
Console.WriteLine("枚举遍历遍历哈希表中的键值:");
IDictionaryEnumerator myEnumerator = ht.GetEnumerator();
while (myEnumerator.MoveNext()) //对集合中的元素下一个访问
{
Console.WriteLine("图书编号:{0} 图书名称:{1}", myEnumerator.Key, myEnumerator.Value);//ht[myEnumerator.Key]
}
Console.WriteLine();
//8、对HashTable按照键进行排序(但是本身的结构顺序不变)
ArrayList akeys = new ArrayList(ht.Keys);
akeys.Sort(); //按字母顺序进行排序
foreach (int item in akeys)
{
Console.WriteLine("图书编号:" + item + ": 图书名称:" + ht[item]); //排序后输出
}
Console.WriteLine();
//9、对HashTable按照值进行排序
ArrayList avalues = new ArrayList(ht.Values);
avalues.Sort(); //按字母顺序进行排序
foreach (string item in avalues)
{
Console.WriteLine("图书名称:" + item); //排序后输出
}
Console.WriteLine();
Console.WriteLine("遍历HashTable中的每一组元素信息:");
foreach (DictionaryEntry dic in ht)
{
int ID = (int)dic.Key;
string Name = dic.Value as string;
Console.WriteLine("图书编号:{0} 图书名称:{1}", ID, Name);
}
Console.WriteLine();
}
}
}
1.4.3 Stack
C#中的堆栈(Stack)代表了一个后进先出的对象集合。当您需要对各项进行后进先出的访问时,则使用堆栈。当您在列表中添加一项,称为推入元素,当您从列表中移除一项时,称为弹出元素。
使用代码如下:
using System;
using System.Collections;
namespace MSN
{
class MainClass
{
public static void Main(string[] args)
{
//堆栈(Stack)的使用
Stack st = new Stack();
//1、元素入栈
st.Push('H');
st.Push('e');
st.Push('l');
st.Push('l');
st.Push('o');
st.Push(' ');
st.Push('W');
st.Push('o');
st.Push('r');
st.Push('l');
st.Push('d');
//2、遍历stack容器
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
Console.WriteLine();
Console.WriteLine("\n通过枚举遍历容器中的元素");
IEnumerator itor; //枚举对象
itor = stack.GetEnumerator(); //获得Stack的枚举集合
while (itor.MoveNext())
{
Console.Write(itor.Current + " "); //d l r o W o l l e H
}
Console.WriteLine();
//3、将Stack中的元素复制到另一个数组中
Console.WriteLine("\n对数组中元素逆置后进行枚举遍历");
Array array = stack.ToArray();
foreach (char c in array)
{
Console.Write(c + " "); //H e l l o W o r l d
}
Console.WriteLine();
//将堆栈中的元素赋值到一个数组中,逆转后遍历数组元素
object[] greeting = st.ToArray();
foreach (object item in greeting.Reverse())
{
Console.Write(item + " "); //H e l l o W o r l d
}
Console.WriteLine();
//4、访问栈顶元素
Console.WriteLine("栈顶的元素为: {0}", st.Peek());
//5、元素出栈
Console.WriteLine("Removing values ");
Console.WriteLine("出栈的元素:{0}", st.Count); //11
int len = st.Count;
for (int i = 0; i < len; i++)
{
object obj = st.Pop(); //出栈
Console.Write((char)obj + " "); //d l r o W o l l e H
}
Console.WriteLine();
Console.WriteLine("Current stack: ");
foreach (char c in st)
{
Console.Write(c + " ");
}
Console.WriteLine();
//6、清空栈中所有的元素
if (st.Count == 0)
{
Console.WriteLine("栈中无元素,为空栈!");
}
else
{
stack.Clear(); // 否则清空栈
}
}
}
}
1.4.4 Queue
C#中的队列(Queue)类代表了一个先进先出的对象集合。当您需要对各项进行先进先出的访问时,则使用队列。当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队。
操作代码如下:
using System;
using System.Collections;
namespace MSN
{
class MainClass
{
public static void Main(string[] args)
{
//队列(Queue)的使用
Queue qe = new Queue();
Console.WriteLine(qe);
//1、入列
qe.Enqueue('H');
qe.Enqueue('e');
qe.Enqueue('l');
qe.Enqueue('l');
qe.Enqueue('o');
qe.Enqueue(' ');
qe.Enqueue('W');
qe.Enqueue('o');
qe.Enqueue('r');
qe.Enqueue('l');
qe.Enqueue('d');
//2、遍历队列元素
Console.WriteLine("Current queue: ");
foreach (char c in qe)
{
Console.Write(c + " "); //H e l l o W o r l d
}
Console.WriteLine();
//3、队列顶部的元素
char obj = (char)(qe.Peek());
Console.WriteLine("Peek:{0}", obj);
//4、将队列的元素复制到一个数组对象中
object[] greeting = qe.ToArray();
Console.WriteLine("Current arrays: ");
foreach (object item in greeting)
{
Console.Write(item + " "); //H e l l o W o r l d
}
Console.WriteLine();
/*
for (int i = 0; i < qArray.Length; i++)
{
Console.Write(qArray[i] + " ");
}
Console.WriteLine();
*/
//5、通过枚举来遍历队列中的元素
Console.WriteLine("通过枚举来遍历队列中的元素:");
IEnumerator itor = qe.GetEnumerator();
while (itor.MoveNext())
{
Console.Write(itor.Current + " ");
}
Console.WriteLine();
//6、队列克隆(产生原始队列的副本,托管内存不同)
Queue clone = qe.Clone() as Queue;
Console.WriteLine(clone);
foreach (char item in clone)
{
Console.Write(item + " ");
}
//7、移除队列中的元素
Console.WriteLine("Removing some values:");
char ch = (char)clone.Dequeue();
Console.WriteLine("clone 出队一个:{0}", ch);
Console.WriteLine("Removing some values after of Clone:");
foreach (char item in clone)
{
Console.Write(item + " ");
}
Console.WriteLine("队列顶部的元素:{0}", clone.Peek());
//8、检测指定元素是否存在于队列中
bool flag = clone.Contains('H');
if (flag)
{
Console.WriteLine("元素存在!");
}
else
{
Console.WriteLine("元素不存在!");
}
//9、清空——所有元素逐一出列
Console.WriteLine("clone的队列元素全部出对:");
int size = clone.Count;
while (size-- != 0)
{
object c = clone.Dequeue();
Console.Write(c + " ");
}
Console.WriteLine();
//9、清空队列——Clear方法
Console.WriteLine("qe :");
foreach (char item in qe)
{
Console.Write(item + " ");
}
Console.WriteLine();
//10、检测队列是否成功清除
if (qe.Count == 0)
{
Console.WriteLine("qe队列已经清空");
}
else
{
qe.Clear();
}
}
}
}
1.5 本章总结
- 常见的数组与集合的使用
- ArrayList和Hashtable存储的是任意类型,比较灵活,但是装箱拆箱资源浪费
1.6 习题
1:定义一个数组,长度为100,并且向其中随机插入1-100,并且不能重复
ArrayList myList=new ArrayList();
Random rnd = new Random();
while(myList.Count <100){
int num = rnd.Next(1,101);
if(!myList.Contains(num)){
myList.Add(num);
}
}
2:定义一个int[] 数组,进行逆序操作(不使用ArrayList)
for (int i = 0; i < num.Length / 2;i++ )
{
int temp =num[i]; //新建一个变量相互交换
num[i] = num[num.Length - i - 1];
num [num.Length - i - 1] = temp;//相互交换
}
foreach (var newnum in num)
{
Console.WriteLine(“逆序排列:” + newnum);
}
// Array.Reverse function
public string ReverseB(string text)
{
char[] charArray = text.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
3:实现数组的拷贝操作,将数组A里面的20个元素拷贝到数组B里面。
int[] a =new int[]{1,2,3,4,5}
int[] b = new int[a.Length*2];
for(int i=0;i<a.Length;i++){
b[i]=a[i];
}
4:实现冒泡排序
int[] nums = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
for (int i = 0; i < nums.Length - 1; i++)
{
for (int j = 0; j < nums.Length-1-i; j++)
{
if (nums[j] > nums[j + 1])
{
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
//打印数组
for (int i = 0; i < nums.Length; i++)
{
Console.WriteLine(nums[i]);
}