数组与集合

学习了前面的 C# 三大特性,及接口,抽象类这些相对抽象的东西以后,是不是有点很累的感觉。具体的东西总是容易理解,因此我们在介绍前面抽象概念的时候,总是举的是具体的实例以加深理解。

本节内容相当具体,学起来也相当轻松。

1.数组

1.1 什么是数组?

数组是一种数据结构,包含同一个类型的多个元素。

1.2 数组的初始化

string[] mystringArray;

类型 + 方框号 数组名

1.3 数组初始化

我们知道数组是引用类型,所以需要给他分配堆上的内存。

1.myIntArray = new int[3];

2.myIntArray = new int[] { 1, 2, 3 };

3.int[] myIntArray = { 1, 2, 3 }; // 当使用这种方法对数组进行初始化时,只能在声明变量数组时使用,不能在声明数组之后使用。

1.4 数组的访问

数组在声明和初始化后,可以使用索引器进行访问,索引器总是以 0 开头,表示第一个元素。

//数组调用
string[] stringArray = new string[] { "aa", "bb", "cc" };
Console.WriteLine("stringValue=\"{0}\"", stringArray[0]);
Console.ReadLine();
//运行结果为:
//stringValue="aa"

1.5 数组类型

数组根据维度不同,分为矩阵数组 ( 一维数组,二维数组,多维数组 ) 和锯齿数组(也就是数组的数组) ,下面看一下他们的声明方式

//数组 声明
string[] stringArray = new string[] { "aa", "bb", "cc" };
//数组 访问
Console.WriteLine("一维数组第一个值为:{0}", stringArray[0]);
//运行结果为:一维数组第一个值为:aa


//二维数组 声明
string[,] stringArray2 = new string[,] { { "a1", "a2", "ac" }, { "b1", "b2", "b3" }, { "c1", "c2", "c3" } };
//二维数组访问
Console.WriteLine("二维数组的1维第1个值是:{0}", stringArray2[0, 0]);
//运行结果为:二维数组1维第1个值为:a1

//三维数组访问 声明
int[, ,] myIntArray3 = new int[,,] {
                { {1,1}, {11,11}, {111,111} }, 
                { {2,2}, {22,22}, {222,222} }, 
                { {3,3}, {33,33}, {333,333} }, 
                { {4,4}, {44,44}, {444,444} } 
                };
//三维数组访问 访问
Console.WriteLine("三维数组2维的2维的第2个值为:{0}", myIntArray3[1,1, 1]);
//运行结果为:三维数组2维的2维的第2个值为:22

//锯齿数组(又称数组的数组)
int[][] myIntArray4 = new int[3][];
myIntArray4[0] = new int[] { 1, 11, 111 };
myIntArray4[1] = new int[2] { 2, 22 };
myIntArray4[2] = new int[] { 3, 33, 333, 3333 };
Console.WriteLine("锯齿数组第3个数组的第2个值为:{0}", myIntArray4[2][1]);
//运行结果为:锯齿数组第3个数组的第2个值为:33
Console.ReadLine();

2.集合

2.1. 什么时集合 ?

广义的角度理解集合,就是一组东西聚集在一起。而 .NET 的集合的定义为: 在 .NET Framework 中,提供了用于数据存储和检索的专用类,这些类统称集合 。这些类提供对堆栈、队列、列表和哈希表的支持。大多数集合类实现相同的接口。

其中最常用的是 System.Collections 命名空间下的 ArrayList, 它是按需分配自动增加的实现 IList 接口的数组。它的默认初始容量为 0 ,因此集合的索引从 0 开始。

2.2. 数组和集合的区别:

在介绍集合前,先说一下集合和数组的区别,既然有了数组,为何 C# 还要设定一个集合呢?

区别如下:

数组有他的优点,就是在内存中连续存储,遍历比较方便,也可以很方便的修改元素。缺点就是需要指定数组变量大小,此外,两个元素间添加一个元素也比较麻烦。

而集合则不需要事先定义大号,可以根据需要自动分配大小。随意添加移除某一范围元素也比较方法。

2.3 示例:

还是以前面的动物家族为例,说明集合 ArrayList 的插入,添加,移除操作

/// <summary>
/// 动物类(父类 抽象类)
/// </summary>
abstract class Animal
{
    /// <summary>
    /// 名字
    /// 说明:类和子类可访问
    /// </summary>
    protected string name;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="name"></param>
    public Animal(string name)
    {
        this.name = name;
    }

    private int shoutNum = 3;
    public int ShoutNum
    {
        get { return shoutNum; }
        set { shoutNum = value; }
    }

    /// <summary>
    /// 名字(虚属性)
    /// </summary>
    public virtual string MyName
    {
        get { return this.name; }
    }

    /// <summary>
    /// 叫声,这个方法去掉虚方法,把循环写在这里
    /// </summary>
    public void Shout()
    {
        string result = "";
        for (int i = 0; i < ShoutNum; i++)
            result += getShoutSound() + "!";

        Console.WriteLine(MyName);
        Console.WriteLine(result);
    }

    /// <summary>
    /// 创建一个叫声的虚方法,子类重写
    /// </summary>
    /// <returns></returns>
    public virtual string getShoutSound()
    {
        return "";
    }
}
/// <summary>
/// 狗(子类)
/// </summary>
class Dog : Animal
{
    string myName;
    public Dog(string name)
        : base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:狗狗,我叫:" + this.name; }
    }
    /// <summary>
    /// 叫(重写父类方法)
    /// </summary>
    public override string getShoutSound()
    {
        return "汪!";
    }
}

/// <summary>
/// 猫(子类)
/// </summary>
class Cat : Animal
{
    string myName;
    public Cat(string name)
        : base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:猫咪,我叫:" + this.name; }
    }
    /// <summary>
    /// 叫(重写父类方法)
    /// </summary>
    public override string getShoutSound()
    {
        return "喵!";
    }
}

/// <summary>
/// 羊(子类)
/// </summary>
class Sheep : Animal
{
    string myName;
    public Sheep(string name)
        : base(name)
    {
        myName = name;
    }
    /// <summary>
    /// 名字(重写父类属性)
    /// </summary>
    public override string MyName
    {
        get { return "我是:羊羊,我叫:" + this.name; }
    }
    /// <summary>
    /// 叫(重写父类方法)
    /// </summary>
    public override string getShoutSound()
    {
        return "咩!";
    }
}

调用及返回结果: 增加、添加、移除实现示例如下:

//IList 添加、插入元素
IList animalList = new ArrayList();
Cat huaHua = new Cat("花花");
animalList.Insert(0, new Dog("旺财"));//Insert是在指定的索引处插入元素
animalList.Add(new Cat("阿狸"));//Add是在集合最后面插入元素
animalList.Insert(animalList.Count, new Sheep("慢羊羊"));
animalList.Add(huaHua);
//增加四个元素后的返回结果如下
Console.WriteLine("增加四个元素后的返回结果:");
foreach (Animal animal in animalList)
{
    animal.Shout();
}

//移除元素
//旺财和阿狸退出队列
animalList.RemoveAt(0);
animalList.RemoveAt(0);//注意这里当移除一个元素后,后面的元素会排到第一位了,移除第二个元素不能是 animalList.RemoveAt(1);
animalList.Remove(huaHua);//Remove是指移除指定的元素,注意这里不能animalList.Remove(new Cat("花花"));这样写查找不到,因为new以后又是另一个实例了(另一只同名的猫而已)

//移除元素后的返回结果如下:
Console.WriteLine("执行移除后的返回结果:");
foreach (Animal animal in animalList)
{
    animal.Shout();
}
Console.ReadLine();

返回结果:

2.4 集合 ArrayList 的缺点

集合 ArrayList 相比数组有这么多好处,但是他也是有很多缺点的

A.ArrayList 并非类型安全

ArrayList 不论什么类型都接受,实际是接受一个 object 类型。

比如如下操作:

ArrayList ar = new ArrayList();
ar.Add(111);
ar.Add("bbb");

我们使用 foreach 遍历的时候 foreach(int array in ar){} 那么遇到 ”bbb”则程度报错,因此我们说他是非安全类型。

B. 遍历 ArrayList 资源消耗大

因此类型的非安全,我们在使用 ArrayList 的时候,就意味着增加一个元素,就需要值类型转换为 Object 对象。遍历的时候,又需要将 Object 转为值类型。

就是装箱 (boxing, 指将值类型转换为引用类型 ) 和拆箱( unboxing, 指将引用类型转换为值类型)

由于装箱了拆箱频繁进行,需要大量计算,因此开销很大。如此说来还不如数组来的方便。我们只能说各有利弊,小伙伴们不要急, .net 的设计者在 2.0 以后的版本中为我们解决了上述后顾之忧,那就是下一节要说的泛型。

3.要点:

A.数组是一种数据结构,包含有同类型的多个元素

B.. 集合是 .net 中提供数据存储和检索的专用类

C.ArrayList 是按需分配自动增加的实现 IList 接口的数组

D.集合和数组各有优缺点,数组定义需要预先指定大小,而集合虽然不需要事先指定,但是存在类型安全和资源消耗过大的缺陷。

使用泛型可以解决上面的问题。