C#高级编程9-第6章 数组

数组


1.同一类型和不同类型的多个对象

    需要使用同一类型的多个对象则使用集合和数组。

    Array类为数组元素排序和过滤提供了多个方法,使用枚举器,可以迭代数组所有元素。

2.简单数组
数组的声明

因为数组是引用类型,所以只要声明之后就会分配内存,以保存数组所有元素。使用new运算符,指定数组中的元素类型和数量来初始化数组的变量。

数组的声明:

int[] myArray; 
数组的初始化
1.先定义后声明并指定数量
int[] myArray;
myArray =new int[4];

2.声明并指定数量后直接初始化
int[] myArray=new int[4]{4,7,11,5};

3.声明后直接初始化
int[] myArray=new int[]{4,7,11,5,8,7};

4.直接定义初始化
int[] myArray={4,7,11,5,8,7};
访问数组元素
通过索引访问

myArray[0]访问的是数组的第一个元素。
myArray[myArray.Length-1]访问的是数组最后一个元素。
数组的第一个索引是从0开始的

循环中使用索引访问
for(int i=0;i<myArray.Length;i++)
{
     Console.WriteLine(myArray[i])  ;
}
foreach(var item in myArray)
{
     Console.WriteLine(item)  ;
}
使用引用类型

如果数组的元素是引用类型,则必须给数组每个元素分配内存,若使用了没有分配内存的元素。则会出现空指针的异常。

使用从0开始的索引器,可以为数组的每个元素分配内存:

myPersons[0] = new Person { FirstName = "Ayrton", LastName = "Senna" };
myPersons[1] = new Person { FirstName = "Michael", LastName = "Schumacher" };

 

 

3.多维数组

用一个整数做索引的是一维数组,用多个整数做索引的是多维数组。

二维数组:

int[,] twodim = new int[3, 3];
      twodim[0, 0] = 1;
      twodim[0, 1] = 2;
      twodim[0, 2] = 3;
      twodim[1, 0] = 4;
      twodim[1, 1] = 5;
      twodim[1, 2] = 6;
      twodim[2, 0] = 7;
      twodim[2, 1] = 8;
      twodim[2, 2] = 9;

二维数组中的twodim[3,3],twodim[2,3],twodim[3,2]会发生运行时异常,索引最大值不能大于等于声明的数量,且不能小于0;

如果只赋了一部分索引的值。使用了没有赋值的索引会发生空指针的异常。比如说twodim[2,2]没有赋值的话,却使用了它则会报错。

int[,] twodim={
     {1,2,3},
     {4,5,6},
     {7,8,9}  
};

二维数组的初始化器有点不同。大括号里面包含了多组小括号对。第一对小括号,代码索引0,小括号中的第一个值就是twodim[0,0]了

三维数组:

int[, ,] threedim = {
                    { { 1, 2 }, { 3, 4 } },
                    { { 5, 6 }, { 7, 8 } },
                    { { 9, 10 }, { 11, 12 } }
                   };
Console.WriteLine(threedim[0, 1, 1]);

这个三维数组的threedim[0,0,0]等于1;threedim[2,0,2]等于10

4.锯齿数组

锯齿数组大小对应一个矩形。每一行都有不同的大小。

声明锯齿数组时,依次放置左右中括号。左括号为行数,右括号为空。

 int[][] jagged = new int[3][];
 jagged[0] = new int[2] { 1, 2 };
 jagged[1] = new int[6] { 3, 4, 5, 6, 7, 8 };
 jagged[2] = new int[3] { 9, 10, 11 };

  迭代器数组中所有元素的代码可以放在嵌套的for循环中,外层的for循环中迭代每一行,

在内层的for循环中迭代一行中的每个元素:

for (int row = 0; row < jagged.Length; row++)
      {
        for (int element = 0;
           element < jagged[row].Length; element++)
        {
          Console.WriteLine(
             "row: {0}, element: {1}, value: {2}",
             row, element, jagged[row][element]);
        }
      }

  该迭代结果显示了所有的行和每一行的各个元素:

row: 0, element: 0, value: 1
row: 0, element: 1, value: 2
row: 1, element: 0, value: 3
row: 1, element: 1, value: 4
row: 1, element: 2, value: 5
row: 1, element: 3, value: 6
row: 1, element: 4, value: 7
row: 1, element: 5, value: 8
row: 2, element: 0, value: 9
row: 2, element: 1, value: 10
row: 2, element: 2, value: 11
5.Array类
创建数组

Array类是抽象的,因此不能通过构造函数初始化。可以使用静态方法CreateInstance()方法创建数组。方法中需要传入数组的类型,创建好之后使用SetValue和GetValue来给这个数组添加和修改元素值。

Array intArray1 = Array.CreateInstance(typeof(int), 5);
      for (int i = 0; i < 5; i++)
      {
        intArray1.SetValue(33, i);
      }

      for (int i = 0; i < 5; i++)
      {
        Console.WriteLine(intArray1.GetValue(i));
      }

还可以将数组强制转换为int数组。

int[] intArray2=(int[])intArray1
复制数组

因为数组是引用类型,所以将一个数组变量赋值给另一个数组变量,就得到2个引用地址相同的数组变量。从而复制数组。以下代码使用了克隆方法Clone方法进行了数组复制。

int[] intArray1={1,2};
int[] intArray2=(int[])intArray1.Clone();

如果数组中包含引用类型。则不复制元素,复制引用。则更改克隆后的对象会更改被克隆对象的值。

Person[] beatles = {
                     new Person { FirstName="John", LastName="Lennon" },
                     new Person { FirstName="Paul", LastName="McCartney" }
                   };

Person[] beatlesClone = (Person[])beatles.Clone();

除了Clone方法还可以使用Copy复制方法。复制数组的浅副本。只复制数据。

排序

Array类使用Quick算法对数组元素进行排序。需要使用Array类的Sort方法,因此需要实现IComparable接口。

 string[] names = {
                   "Christina Aguilera",
                   "Shakira",
                   "Beyonce",
                   "Gwen Stefani"
                 };

Array.Sort(names);

foreach (string name in names)
{
   Console.WriteLine(name);
}

该应用程序的输出是排好序的数组:

Beyonce
Christina Aguilera
Gwen Stefani
Shakira

 public class Person : IComparable<Person>
    {
        public string FirstName { get; set; }

        public string LastName { get; set; }

        public override string ToString()
        {
            return String.Format("{0} {1}",
                  FirstName, LastName);
        }

        public int CompareTo(Person other)
        {
            if (other == null) throw new ArgumentNullException("other");

            int result = this.LastName.CompareTo(other.LastName);
            if (result == 0)
            {
                result = this.FirstName.CompareTo(other.FirstName);
            }

            return result;
        }
 
    }
View Code

 现在可以按照姓氏对Person对象对应的数组排序

Person[] persons= new  {
 new Person { FirstName="Damon", LastName="Hill" },
 new Person { FirstName="Niki", LastName="Lauda" },
 new Person { FirstName="Ayrton", LastName="Senna"},
 new Person { FirstName="Graham", LastName="Hill" }
             };
Array.Sort(persons);
foreach (Person p in persons)
{
   Console.WriteLine(p);
}

 

 

 

 

6.数组作为参数

数组可以作为方法参数进行传递,也可以作为方法返回类型,数组是引用类型。因此不用ref进行值传递。

数组协变

数组支持协变。因此数组可以声明为基类。方法参数中定义基类数组传入派生类数组进行传递。

数组协变只用于引用类型

 

在C#中多次提到协变和逆变,那么协变是什么逆变是什么呢。

“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。 

“逆变”则是指能够使用派生程度更小的类型。

这个博客有了详细的介绍:http://www.cnblogs.com/qixuejia/p/4383068.html

 

ArraySegments<T>数组段

需要使用不同方法处理某个大型数组的不同部分。那么可以把数组部分复制到各个方法中。

 上图中通过创建数组段,然后将第一个数组ar1拆分了一组到数组段,将第2个数组ar2拆分了一组到数组段。

第一个数组从ar1的开始位置引用了3个元素。第2个数组从ar2的第4个位置(索引值是3)引用了3个元素。最后通过SumOfSegments方法对这2个数组段进行了求和.

需要注意的是,ArraySegments<T>没有复制原数组,但是如果数组段中的数组元素改变了,会影响原数组的元素。

7.枚举
IEnumerator接口

foreach语句

我们编写的foreach语句:

IL生成后的语句:

C#会将foreach语句转换为IEnumerator接口,通过GetEnumerator()获得枚举器,在循环中只要MoveNext方法返回true,用Current属性访问数组或集合元素中的值。

yield语句

用于创建迭代器,yield return返回数组中的一个元素,并移到下一个元素。yield break用于停止迭代。

包含yield语句的属性和方法叫迭代块。编译器会生成一个yield的类型。包含状态机。生成后的yield类型相当于Enumerator类型。

 

 

8.元组

数组合并了相同类型的对象,元组合并了不同类型的对象。不同的Tuple类型支持不同数量的元素。

元组一共8个类型,元组最后一个类型是TRest需要传入一个元组。

9.结构比较

数组和元组都实现接口IStructuralEquatable和IStructuralComparable,不仅可以比较引用还可以比较内容。需要显示的实现这些内容。

IStructuralEquatable接口用于比较数组和元素是否有相同内容,IStructuralComparable接口用于排序。

 

结构比较

数组和元组都实现接口IStructuralEquatable和IStructuralComparable.这两个接口不仅可以比较引用,还可以比较内容.这些接口都是显示实现的,所以在使用时需要把数组和元组强制转换为这个接口.IStructuralEquatable接口用于比较两个元组或数组是否有相同的内容,IStructuralComparable接口用于给元组或数组排序.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

 

namespace

{

    class Program

    {

        static void Main(string[] args)

        {

           //创建两个Person项的数组.

            //比较运算符!=返回true

            //因为这其实是两个变量p1和p2引用的两个不同数组.

            //因为array类没有重写带一个参数的Equals()放大,所以用"=="运算符

            //比较引用会得到相同的结构,即这两个变量不相同

            Person zhangsan = new Person { FirstName = "zhang", LastName = "san" };

            Person[] p1 = {

                              new Person

                                {

                                    FirstName="lisi"

                                } ,

            zhangsan

                          };

            Person[] p2 = {

                              new Person

                                {

                                    FirstName="lisi"

                                } ,

            zhangsan

                          };

            if (p1!=p2)

            {

                Console.WriteLine("not the same reference"); ;

            }

            Console.ReadKey();

        }

    }

    public class Person : IEquatable<Person>

    {

        public int ID { get; set; }

        public string FirstName { set; get; }

        public string LastName { set; get; }

 

        public override string ToString()

        {

            return string.Format("{0},{1}{2}", ID, FirstName, LastName); ;

        }

        public override bool Equals(object obj)

        {

            if (obj == null)

            {

                throw new ArgumentNullException("obj");

            }

            return Equals(obj as Person);

        }

        public override int GetHashCode()

        {

            return ID.GetHashCode();

        }

        public bool Equals(Person other)

        {

            if (other == null)

            {

                throw new ArgumentNullException("other");

            }

            return this.ID == other.ID && this.FirstName == other.FirstName && this.LastName == other.LastName;

        }

    }

}

 

使用实现IEquatable接口的Person类.IEquatable接口定义了一个强类型化的Equals()方法,用来比较FirstName和LastName属性的值.

 

下面看看如何对元组执行相同的操作.这里创建了两个内容相同的元组实例:

 var t1 = Tuple.Create<string, int>("zhangsan", 19);

            var t2 = Tuple.Create<string, int>("zhangsan", 19);

            //因为t1和t2引用了两个不同的对象,所以比较运算符"!="返回true

            if (t1 != t2)

            {

                Console.WriteLine("not the same reference to the tuple");

            }

            //这会调用object.equals()方法比较元组的每一项,每一项都返回true

            if (t1.Equals(t2))

            {

                Console.WriteLine("the same reference to the tuple");

            }

Tuple<>类提供了两个Equals()方法:一个重写了object基类中的Equals()方法,并把object作为参数,第二个由IStructyralEqualityComparer接口定义,并把object和IequalityComparer作为参数.

 

还可以使用类TupleCOmparer创建一个自定义的UequalityComparer,这个类实现了IEqualityComparer接口的两个方法Equals()和GetHashCode():

 public class TupleComparer : IEqualityComparer

    {

        public bool Equals(object x, object y)

        {

            return x.Equals(y);

        }

 

        public int GetHashCode(object obj)

        {

            return obj.GetHashCode();

        }

}

实现IEqualityCOmparer接口的Equals()方法需要new修饰符或者隐式实现的接口,因为基类object也定义了带两个参数的静态的Equals()方法.

 

使用TupleComparer,给Tuple<T1,T2>类的Equals()方法传递一个新实例.Tuple类的Equals(0方法为要比较的每一项调用TupleComparer的Equals()方法.所以,对于Tuple<T1,T2>类,要调用两次TupleCOmparer,以检查所有项是否相等.

转载于:https://www.cnblogs.com/licin/p/6829980.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值