数组
- 数组的定义
- Array、ArrayList、List的区别
- 数组的方法
数组的定义
数组是允许将多个数据项作为集合来处理的机制。CLR支持一维、多维和交错数组(即数组构成的数组)。所有数组类型都隐式地从System.Array抽象类派生,后者又派生自System.Object。这意味着数组始终是引用类型。
Array、ArrayList、List的区别
所有数组都隐式实现IEnumerable,ICollection,IList。
Array
数组在C#中最早出现的。在内存中是连续存储的,所以它的索引速度非常快,而且赋值与修改元素也很简单。但是数组存在一些不足的地方。在数组的两个数据间插入数据是很麻烦的,而且在声明数组的时候必须指定数组的长度,数组的长度过长,会造成内存浪费,过段会造成数据溢出的错误。如果在声明数组时我们不清楚数组的长度,就会变得很麻烦。
string[] s = new string[2] { "12","22"};
Array ss = new string[2] { "12", "22" };
在方法中声明局部变量来引用初始化好的数组时,可利用c#的“隐式类型的局部变量”功能来简化代码
string[] name = new[] { "jack", "tom"}; //隐式数组
var name = new[] { "jack", "tom", null }; //隐式类型的局部变量
var name = new[] { "jack", "tom", 123 }; //会报错,找不到隐式类型数组的最佳类型
string[] name = { "jack", "tom", null }; //初始化数组的额外语法糖
var name = new[] { "jack", "tom"}; //会报错,无法使用数组初始值设定项初始化隐式类型化的变量
上式中第三个报错原因是因为String和Int32的共同基类是Object,意味着编译器不得不创建Object引用的一个数组然后对123进行装箱,并让最后一个数组元素引用已装箱的、值为123的一个Int32。
针对数组的这些缺点,C#中最先提供了ArrayList对象来克服这些缺点。
ArrayList
ArrayList是命名空间System.Collections下的一部分,在使用该类时必须进行引用,同时继承了IList接口,提供了数据存储和检索。ArrayList对象的大小是按照其中存储的数据来动态扩充与收缩的。所以,在声明ArrayList对象时并不需要指定它的长度。
ArrayList ss = new ArrayList();
ss.Add(1);
ss.Add("qwe"); //是被允许的
我们从上面的例子看,在List中,我们不仅插入了字符串qwe,而且插入了数字1。这样在ArrayList中插入不同类型的数据是允许的。因为ArrayList会把所有插入其中的数据当作为Object类型来处理,在我们使用ArrayList处理数据时,很可能会报类型不匹配的错误,也就是ArrayList不是类型安全的。在存储或检索值类型时通常发生装箱和取消装箱操作,带来很大的性能耗损。
因为ArrayList存在不安全类型与装箱拆箱的缺点,所以出现了泛型的概念。
泛型List
List类是ArrayList类的泛型等效类,它的大部分用法都与ArrayList相似。最关键的区别在于,在声明List集合时,我们同时需要为其声明List集合内数据的对象类型。
List<string> qwe = new List<string>(); //必须声明类型
qwe.Add(1); //会报错,无法从"int"转换为"string"
数组的方法
多维数组
int[,] array1 = new int[,] { { 1, 2, 3 }, { -1, -2, -3 } };//定义一个2*3的二维int数组,并初始化
int[][] array2 = new int[][] //定义交错数组并初始化
{
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};
for (int i = 0; i < array2.Length; i++) //for循环遍历第一维
foreach (int j in array2[i]) //foreach循环遍历第二维
//foreach循环下,在C#3.0、C#4.0中输出五个9四个6两个22,c#5.0之后的版本与预期一样
Console.WriteLine(j);
数组排序
//定义数组
int[] arrSid = { 5, 3, 4, 2, 1 };
string[] arrSname = { "张三", "李四", "王五", "麻子", "淘气" };
Array.Sort(arrSid); //对数组排序,升序
foreach (int j in arrSid)
Console.WriteLine(j); //输出1,2,3,4,5
Array.Sort(arrSid); //对数组排序
Array.Reverse(arrSid); //翻转数组实现降序
foreach (int j in arrSid)
Console.WriteLine(j); //输出5,4,3,2,1
//输出原始数组:原始数组:张三(5)->李四(4)->王五(3)->麻子(2)->淘气(1)->
Console.WriteLine("原始数组:");
for (int i = 0; i < arrSid.Length; i++)
Console.Write("{0}({1})->", arrSname[i], arrSid[i]);
Console.WriteLine();
//根据学号关键字排序,左边是关键字,右边是对应的项
Array.Sort(arrSid, arrSname);
//并输出排序后的数组:淘气(1)->麻子(2)->王五(3)->李四(4)->张三(5)
Console.WriteLine("排序以后数组:");
for (int i = 0; i < arrSid.Length; i++)
Console.Write("{0}({1})->", arrSname[i], arrSid[i]);
数组复制
首先先对值类型数组进行复制
int[] ls = new int[2] { 1,2 };
int[] copy = new int[2];
//copy.Initialize(); //该方法通过调用值类型的默认构造函数,初始化值类型 System.Array 的每一个元素。
Console.WriteLine(copy[1]); //实际上没报错,虽然我没有初始化该数组。输出是0。
Array.Copy(ls, copy, 2);
copy[0] = 3;
Console.WriteLine(copy[0]); //输出3
Console.WriteLine(ls[0]); //输出1,值类型数组通过复制得到的数组地址已经完全不一样了,因此无法改变原型。
接着对引用类型进行复制
string[] ls = new string[2] { "1","2" };
string[] copy = new string[2];
Console.WriteLine(copy[1]); //居然没报错,理论上应该是null,然后返回未将对象引用到实例的错误,暂时无法理解
Array.Copy(ls, copy, 2);
copy[0] = "3";
Console.WriteLine(copy[0]); //输出3
Console.WriteLine(ls[0]); //输出1
引用类型数组通过复制得到的数组理论上只是地址,A改变了值,B应该也会改变值才对。实际上,这是由于string是特殊的引用类型,具体原因可以翻看我之前的博客string与StringBuilder。那么string类型看不出区别,我们看看其他引用类型
public class Student //先定义一个student类
{
public int num;
public string name;
public Student(int num,string name)
{
this.num = num;
this.name = name;
}
}
Student[] ls = new Student[2] { new Student(1, "张三"), new Student(2, "李四") };
Student[] copy = new Student[2];
Array.Copy(ls, copy, 2);
copy[0].num = 3;
copy[0].name = "王五";
Console.WriteLine(copy[0].name);//输出王五
Console.WriteLine(copy[0].num); //输出3
Console.WriteLine(ls[0].name); //输出王五
Console.WriteLine(ls[0].num); //输出3,可以看出Student类的复制只是浅表复制,共用一个地址。
Console.ReadKey();
Student[] copy2 = (Student[])ls.Clone(); //clone方法返回的是Object类,应该对他进行显式转换。
copy2[0].num = 4;
copy2[0].name = "赵六";
Console.WriteLine(copy[0].name);//输出赵六
Console.WriteLine(copy[0].num); //输出4
Console.WriteLine(ls[0].name); //输出赵六
Console.WriteLine(ls[0].num); //输出4,可以看出Copy与clone方法都是浅表复制,不同在于Copy是静态方法
//其次Clone()会创建一个新数组,Copy方法必须传递阶数相同且有足够元素的已有数组。
Console.ReadKey();
三维数组排序
List<Point3D> point3D = new List<Point3D>(){
new Point3D(1,5,3),
new Point3D(2,4,2),
new Point3D(3,3,4),
new Point3D(4,2,1),
new Point3D(5,1,5)
};
var list1 = point3D.OrderBy(p => p.X).Tolist();
foreach(Point3D p in list1)
{
Console.WriteLine($"X:{p.X} Y:{p.Y} Z:{p.Z}");
}
Console.WriteLine();
var list2 = point3D.OrderBy(p => p.Y).Tolist();
foreach(Point3D p in list2 )
{
Console.WriteLine($"X:{p.X} Y:{p.Y} Z:{p.Z}");
}
Console.WriteLine();
var list3 = point3D.OrderBy(p => p.Z).Tolist();
foreach(Point3D p in list3 )
{
Console.WriteLine($"X:{p.X} Y:{p.Y} Z:{p.Z}");
}
Console.ReadKey();