数组与集合

通过数组(array)可以将一系列相关的数据统一处理,更有效地进行组织、管理和应用,提高操作效率。

C#数组与Array类

C#中的数组会映射为Array类的实例,也就是说,一方面C#中的数组是对象,另一方面可以使用Array类中定义的成员来操作数组。

下面的代码,我们创建包含了10个成员的数组,其成员类型为int。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[10];
            for (int i = 0; i < arr.Length; i++)
                arr[i] = i + 1;
            //
            for (int i = 0; i < arr.Length; i++)
                Console.WriteLine(arr[i]);
        }
    }
}

代码执行结果如下图。

请注意数组的声明方法,应使用成员类型加一对方括号,初始化时,使用new关键字,并在成员类型后的方括号中指定成员数量。访问数组成员时,使用数组对象,并使用一对方括号指定成员的索引值,请注意,索引值是从0开始的,也就是说,第一个成员索引为0,第二个成员索引为1,以此类推。

如果可以明确成员或成员数据没有规律,可以直接指定,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[] { 1,1,2,3,5,8};
            for (int i = 0; i < arr.Length; i++)
                Console.WriteLine(arr[i]);
        }
    }
}

代码执行结果如下图。

此外,除了使用索引访问数组,还可以使用foreach语句遍历所有成员,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[] { 1,1,2,3,5,8};
            foreach (int i in arr)
                Console.WriteLine(i);
        }
    }
}

foreach语句结构中,成员数组成员类型的变量i,并通过in关键字遍历arr中的所有成员。代码执行结果如下图。

实际应用中,我们还可以使用多维数组,如下面的代码,我们定义了一个二维数组。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int[,] matrix = new int[5,4];
            for(int row=0;row<matrix.GetLength(0);row++)
            {
                for(int col=0;col<matrix.GetLength(1);col++)
                {
                    Console.Write("({0},{1})",row,col);
                }
                Console.WriteLine();
            }
        }
    }
}

代码执行结果如下图。

本例中,使用int[,]声明成员为int类型的二维数组,如果需要更多维度的数组,加逗号(,)就行了。访问二维数组时,我们使用了两个嵌套的for语句结构,其中的循环控制变量应该是明确含义的名称,如代码中的row和col。Array中的GetLength()方法用于返回指定维度的的成员数量,请注意,这里的维度也是从0开始的。

前面的示例中已经使用Array对象中的Length属性和GetLength()方法,下面,我们再来了解一些Array类的常用成员。

设置和获取成员数据。除了使用索引值设置和获取成员,还可以使用多个重载版本的GetValue()和SetValue()方法。其中,GetValue()方法的参数分别是各个维度的索引,如获取一维数组中的第一个成员就使用GetValue(0),如果获取二维数组中第二组数据的第一个成员数据就使用GetValue(1,0)。SetValue()方法的第一个参数指定成员数据,第二参数开始指定各个维的索引,如设置一维数组的第一个成员数据为10就使用SetValue(10,0)。

IndexOf()和LastIndexOf()方法用于查询数组成员,并返回成员的索引值,如果没有找到则返回-1;它们的区别在于,IndexOf()查询成员第一次出现的位置,而LastIndexOf()查询成员最后一次出现的位置,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] arr = new string[] {"aaa","bbb","ccc","ddd","aaa","bbb" };
            Console.WriteLine(Array.IndexOf(arr,"aaa"));
            Console.WriteLine(Array.LastIndexOf(arr, "aaa"));
        }
    }
}

代码执行结果如下图。

查询成员时,还可以使用Exists()和Find()方法,它们的区别在于,Exists()方法返回bool类型的数据,表示成员是否存在,而Find()方法会返回找到的成员,如果没有找到匹配的成员,则返回成员类型的默认值。使用这两个方法时,需要指定查询成员的条件,可以使用委托或Lambda表达式设置方法的第二个参数,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] arr = new string[] {"Tom","Jerry","Maria","Jonh","Smith","Jack" };
            string result = Array.Find(arr, (string item) => {
                return item.Substring(0, 1) == "J";
            });
            Console.WriteLine(result);
        }
    }
}

代码中,我们使用Lambda表达式设置了查询条件,即查找第一个字母是“J”的单词,使用Find()方法只会找到第一个匹配的成员,如果需要全部满足条件的成员,可以使用FindAll()方法,下面的代码,我们使用一些委托方法设置查询条件。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] arr = new string[] {"Tom","Jerry","Maria","Jonh","Smith","Jack" };
            string[] result = Array.FindAll(arr, FindJ);
            foreach (string item in result)
                Console.WriteLine(item);
        }
        // 委托方法
        static bool FindJ(string item)
        {
            return item.Substring(0, 1) == "J";
        }
    }
}

代码执行结果如下图。

后面的课程中,还会详细讨论委托()与Lambda表达式的应用。此外,查询成员相关的方法还包括FindIndex()方法,用于返回第一个匹配成员的索引值;FindLast()方法返回最后一个匹配的成员;FindLastIndex()方法返回最后一个匹配成员的索引值。

需要将数组成员顺序反转时,可以使用Reverse()方法,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] arr = new string[] {"aaa","bbb","ccc","ddd" };
            Array.Reverse(arr);
            foreach (string s in arr)
                Console.WriteLine(s);
        }
    }
}

代码执行结果如下图。

Sort()方法可以对数组成员进行排序,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[] { 10, 9, 3, 5, 11, 6 };
            Array.Sort(arr);
            foreach (int n in arr)
                Console.WriteLine(n);
        }
    }
}

代码执行结果如下图。

ArrayList类与List<>泛型类

Array类中,虽然可以通过Resize()方法重新设置数组的尺寸,也就是成员的数量;但对于更多的修改成员的操作来讲,使用ArrayList类或List<>泛型类则更加高效,也更加灵活。

ArrayList类和List<>泛型类的应用方法比较相似,但ArrayList类的成员定义为object类型,操作时需要大量的类型转换操作,而List<>泛型类则可以确定成员的实际类型,操作起来效率更高,下面主要讨论List<>泛型类的应用,ArrayList类可以参考使用。

大量的集合操作资源定义在System.Collections和System.Collections.Generic命名空间,使用时应注意引用。

下面的代码,我们创建一个成员类型为string的List对象,并添加三个成员。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lst = new List<string>();
            lst.Add("aaa");
            lst.Add("bbb");
            lst.Add("ddd");
            foreach (string s in lst)
                Console.WriteLine(s);
        }
    }
}

代码执行结果如下图。

需要获取集合中成员的数量时,可以使用Count属性,如lst.Count。

向集合末尾添加成员时,除了可以使用Add()方法,还可以使用AddRange()方法;其中,Add()方法用于添加一个成员,而AddRange()方法则可以添加一组成员,下面的代码演示了这两个方法的应用。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lst1 = new List<string>();
            lst1.Add("aaa");
            //
            List<string> lst2 = new List<string>();
            lst2.Add("bbb");
            lst2.Add("ccc");
            lst1.AddRange(lst2);
            //
            foreach (string s in lst1)
                Console.WriteLine(s);
        }
    }
}

代码执行结果如下图。

向指定位置插入一个成员时可以使用Insert()方法,插入一组成员时则使用InsertRange()方法,下面的代码演示了这两个方法的应用。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lst1 = new List<string>();
            lst1.Add("aaa");
            lst1.Add("ddd");
            lst1.Insert(1, "bbb");
            //
            List<string> lst2 = new List<string>();
            lst2.Add("111");
            lst2.Add("222");
            lst1.InsertRange(2, lst2);
            //
            foreach (string s in lst1)
                Console.WriteLine(s);
        }
    }
}

代码执行结果如下图。

删除集合成员时,可以使用如下方法。

  • Clear()方法,删除所有成员。
  • RemoveAt(index)方法,删除指定索引位置的成员。
  • Remove(item)方法,删除指定的成员。
  • RemoveRange(index,length),从index位置删除成员,删除的数量由length指定。

另外一些常用的方法包括:

  • Sort()方法可默认的方式对成员进行排序。
  • Reverse()方法将成员的顺序反转。
  • ToArray()方法可以将集合转换为相应类型的数组。

下面的代码演示了这三个方法的应用。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> lst = new List<string>();
            lst.Add("bbb");
            lst.Add("aaa");
            lst.Add("ccc");
            lst.Sort();
            lst.Reverse();
            string[] arr = lst.ToArray();
            //
            foreach (string s in arr)
                Console.WriteLine(s);
        }
    }
}

代码执行结果如下图。

Hashtable类与Dictionary<>泛型类

Hashtable类和Dictionary<>泛型类用于处理的集合,其成员格式为“键(key)/值(value)”对应;其中,Hashtable中的键和值类型都为Object,而Dictionary<>泛型类则可以指定键和值的实际类型。下面,我们还是主要介绍Dictionary<>泛型类的应用。

下面的代码演示了Dictionary<>泛型类的基本应用方式。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string,string> planets = new Dictionary<string,string>();
            planets.Add("earth", "地球");
            planets.Add("mercury", "水星");
            planets.Add("venus", "金星");
            Console.WriteLine(planets.Count);
            //
            foreach (string pkey in planets.Keys)
                Console.WriteLine(pkey + " : " + planets[pkey]);
        }
    }
}

代码执行结果如下图。

本例,使用Add()方法添加了太阳系的三个地内行星信息;其中,使用Count属性获取成员数量;使用Keys属性获取集合中的所有键(Key),并通过foreach遍历所有成员,显示成员的值时,使用了以Key为索引的方式。

下面来看Dictionary<>泛型类中的一些常用成员。

  • Count属性获取集合中的成员数量。
  • Keys属性返回所有的键组成的集合。
  • Values属性返回所有的值组成的集合。
  • ContainsKey(k)方法,判断指定的键是否存在。
  • ContainsValue(v)方法,判断指定的值是否存在。
  • Add(k,v)方法,添加新的成员,参数指定键和值。
  • Clear()方法,删除所有成员。
  • Remove(key)方法,根据删除指定键的成员。
  • TryGetValue(k,out v)方法,尝试获取指定键的值,如果值存在则方法返回true,并通过参数二输出获取的值;如果指定的键不存在,则方法返回false,参数二输出的是值类型的默认值。

下面的代码,显示了TryGetValue()方法的使用。

C#
using System;
using System.Collections.Generic;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string,string> planets = new Dictionary<string,string>();
            planets.Add("earth", "地球");
            planets.Add("mercury", "水星");
            planets.Add("venus", "金星");
            string v;
            bool result = planets.TryGetValue("mars", out v);
            if (result)
                Console.WriteLine("mars : " + v);
            else
                Console.WriteLine("没有找到指定的键");
        }
    }
}

代码显示结果如下图。

Linq

LINQ(语言集成查询,Language Integrated Query)可以在C#代码中使用类似SQL语句的数据处理语言。

通过引用System.Linq命名空间,可以在集合中使用一些简单统计方法,如下面的代码。

C#
using System;
using System.Linq;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[] { 10, 9, 3, 5, 11, 6 };
            Console.WriteLine("最小值:{0}",arr.Min());
            Console.WriteLine("最大值:{0}", arr.Max());
            Console.WriteLine("和:{0}", arr.Sum());
            Console.WriteLine("平均数:{0}", arr.Average());
        }
    }
}

代码执行结果如下图。

下面,我们先定义一个表示商品基本信息的CMdseInfo类。

C#
using System.Collections.Generic;
public class CMdseInfo
{
    // 构造
    public CMdseInfo(string sName, string sCategory, decimal dPrice)
    {
        Name = sName;
        Category = sCategory;
        Price = dPrice;
    }
    //
     public string Name { get; set; }
    public string Category { get; set; }
    public decimal Price { get; set; }
    // 生成演示列表集合
    public static List<CMdseInfo> GetDemoList()
    {
        List<CMdseInfo> lst = new List<CMdseInfo>();
        lst.Add(new CMdseInfo("商品1", "类1", 10.0M));
        lst.Add(new CMdseInfo("商品2", "类1", 20.0M));
        lst.Add(new CMdseInfo("商品3", "类1", 30.0M));
        lst.Add(new CMdseInfo("商品4", "类2", 40.0M));
        lst.Add(new CMdseInfo("商品5", "类2", 50.0M));
        lst.Add(new CMdseInfo("商品6", "类3", 60.0M));
        return lst;
    }
}

CMdseInfo类中的GetDemoList()方法用于生成测试数据,稍后会使用。

下面的代码,我们查询分类为“类2”的商品。

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<CMdseInfo> lst = CMdseInfo.GetDemoList();
            var result =
                from mdse in lst
                where mdse.Category == "类1"
                select new { mdse.Name, mdse.Category, mdse.Price };
            // 显示查询结果
            foreach(var mdse in result)
            {
                Console.WriteLine("Name : {0}", mdse.Name);
                Console.WriteLine("Category : {0}", mdse.Category);
                Console.WriteLine("Price : {0:c2}", mdse.Price);
                Console.WriteLine();
            }
        }
    }
}

代码执行结果如下图。

查询语句中使用一些关键词,如:

  • from...in,类型foreach语句,使用一个对象表示查询的集合成员。
  • where,指定查询条件,多个条件可以使用&&、||等运算符进行组合。
  • select,选择返回的数据项,可以使用new关键组合为一个集合类型。如果返回单个数据项,可以直接书写,如select mdse.Name。

需要将查询结果排序时,可以使用orderby关键字来指定排序数组项;默认情况下会使用升序排列,需要降序排列时,需要在排序数组项后添加descending关键字,如下面的代码。

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<CMdseInfo> lst = CMdseInfo.GetDemoList();
            var result =
                from mdse in lst
                where mdse.Category == "类1"
                orderby mdse.Price descending
                select new { mdse.Name, mdse.Category, mdse.Price };
            // 显示查询结果
            foreach(var mdse in result)
            {
                Console.WriteLine("Name : {0}", mdse.Name);
                Console.WriteLine("Category : {0}", mdse.Category);
                Console.WriteLine("Price : {0:c2}", mdse.Price);
                Console.WriteLine();
            }
        }
    }
}

代码执行结果如下图。

下面的代码,我们通过分组功能统计不同分类(Category)各有多少种商品。

C#
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            List<CMdseInfo> lst = CMdseInfo.GetDemoList();
            var result =
                from mdse in lst
                group mdse.Category by mdse.Category into grp
                select grp;
            // 显示查询结果
            foreach(var item in result)
            {
                Console.WriteLine("商品分类:{0} , 商品数量:{1}",item.Key,item.Count());
                Console.WriteLine();
            }
        }
    }
}

代码执行结果如下图。

索引器(indexer)

在数组和集合中,我们可以使用整数或字符串作为索引来访问成员,这里就是使用对象的索引器实现的,实际工作中,在自定义的类型中,我们同样可以创建索引器,如下面的代码。

C#
/* 斐波那契数列(Fibonacci sequence) */
using System;
public class CFibonacci
{
    // 索引器
    public long this[int n]
    {
        get
        {
            return GetNumber(n);
        }
    }
    //
    public long GetNumber(int n)
    {
        if (n < 1)
            throw new Exception("斐波那契数列索引应从1开始");
        else if (n == 1 || n == 2)
            return 1;
        else
            return GetNumber(n - 1) + GetNumber(n - 2);
    }
}

代码中,使用this关键字和一对中括号定义了CFibonacci类的索引器;我们可以看到,索引器的定义与属性很相似,只不过是名称和参数的设置不同;相同的地方在于,索引器中同样使用get和set语句块分别创建数据的获取和设置代码,而本例中创建的是只读索引器。

此外,代码中的关键是GetNumber()方法,其功能是通过递归调用计算出斐波那契数列中某个位置的数据。实际上,这个方法虽然实现了代码的最少,但执行效率并不高,原因是计算GetNumber(n-2)的操作会执行两次,一次是计算某个位置前两位的数据,一次是计算某个位置前一位的数据,这样就造成了代码的重复执行。

下面的代码,我们重写CFibonacci类,并添加一个GetNumbers()方法,用于返回指定位数的数列。

C#
/* 斐波那契数列(Fibonacci sequence) */
using System;
public class CFibonacci
{
    // 索引器
    public long this[int n]
    {
        get
        {
            return GetNumber(n);
        }
    }
    //
    public long GetNumber(int n)
    {
        long[] nums = GetNumbers(n);
        return nums[n - 1];
    }
    //
    public long[] GetNumbers(int n)
    {
        if (n < 1)
        {
            throw new Exception("斐波那契数列索引应从1开始");
        }
        else
        {
            long[] result = new long[n];
            if (n >= 1) result[0] = 1;
            if (n >= 2) result[1] = 1;
            if (n >= 3)
            {
                for (int i = 2; i < n; i++)
                    result[i] = result[i - 1] + result[i - 2];
            }
            return result;
        }
    }
}

下面的代码,我们在Main()方法中测试CFibonacci类的应用。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            CFibonacci f = new CFibonacci();
            long[] nums = f.GetNumbers(10);
            for (int i = 0; i < nums.Length; i++)
                Console.WriteLine(nums[i]);
        }
    }
}

代码会显示斐波那契数列的前10个数据,执行结果如下图。

只想获取某一位置的数据时,可以使用索引器,如下面的代码。

C#
using System;
namespace HelloProject
{
    class Program
    {
        static void Main(string[] args)
        {
            CFibonacci f = new CFibonacci();
            Console.WriteLine(f[100]);
        }
    }
}

代码执行结果如下图。

本站内容均为原创作品,转载请注明出处,本页面网址为:http://caohuayu.com/chy/article/Article.aspx?code=cc002012