c#中的泛型与数组
一、固定数组:Array
有五种定义方法(主要记住第一种和第三种即可)
int[] num1 = new int[10];//指定数组的长度,声明的时候直接创建数组 int[] num2;//先声明一个数组变量 num2 = new int[10];//创建数组 int[] num3 = { 9, 8, 7, 6, 5, 4, 3, 2, 0, 1 };//直接赋值决定成员的个数 int[] num4 = new int[] { 1, 2, 4, 5, 6, 7 };//初始值来决定数组长度,类似于第三种 int[] num5 = new int[3] { 1, 2, 3 };//长度和所给的初始值一样 ,类似于第一种
foreach遍历数组与c#大小值常量
//隐式类型不允许这么的定义:var a = { 9, 8, 7 };无法判断后面的值是否为数组。 var a = new int[]{ 9, 8, 7, 6, 5, 4, 3, 2, 0, 1 };//var属于万能数据类型。可自动识别。 //c#给最大值和最小值的常量定义:能判断出数组里的值是否最大值。返回布尔值 int min = int.MaxValue;//最大值 int max = int.MinValue;//最小值 int sum = 0;//用给数来记录求和 //遍历数组foreach等价于for (int i = 0; i< a.Length; i++) foreach (int i in a) { if (i > max) max = i;//没有大括号只能写一句代码 if (i < min) min = i;//最小值:当值小于最大值,就得到最小值了 sum += i; } Console.WriteLine("a的最大值是:{0},最小值是:{1},和为:{2},平均值:{3}", max, min, sum, sum / a.Length);
冒泡排序
var a = new int[]{ 9, 8, 7, 6, 5, 4, 3, 2, 0, 1 };//var属于万能数据类型。可自动识别。 Console.Write("数组升序排列:"); for (int i = 0; i < a.Length - 1; i++) {//数组长度a.Length是从1开始,而数组下标从0开始索引值,所以长度需要-1才能得到当前下标值 for (int j = 0; j < a.Length - 1 - i; j++) { if (a[j] > a[j + 1]) { //交换顺序,升序排列 int temp = a[j];//数组[0]下标为第一个。 a[j] = a[j + 1]; a[j + 1] = temp;//给每个下标重新赋值,排序 } } } //遍历数组 for (int i = 0; i < a.Length; i++) { Console.Write(a[i] + (i == a.Length - 1 ? "" : ","));//括号里面使用了三目运算符,去掉了最后一个值后面的逗号 }
c#给数组排序定义了方法(一句代码简化了冒泡排序)
var a = new int[] { 9, 8, 7, 6, 5, 4, 2, 3, 0, 1 };//var属于万能数据类型。可自动识别。 Array.Sort(a);//升序,等价于上面的for循环升序排列 Console.Write("1、数组升序排列:"); for (int i = 0; i < a.Length; i++) { Console.Write(a[i] + (i == a.Length - 1 ? "" : ","));//三目运算符,【条件判断?满足就执行:不满足执行】类似于if判断 } Console.WriteLine();//换行方便代码显示效果好看而已 Array.Reverse(a);//反转,并非倒序,单独使用就是从最后一个往前排列,但是配合Sort方法升序后在反转就变倒序了 Console.Write("2、数组降序排列:"); foreach (int i in a) Console.Write(i + ",");//循环体里只有一句代码可以不用大括号
由此可见,数组(Array)的容量是固定的,数据类型也是固定的。会造成内存浪费,不清楚数组的长度,就会变得很麻烦。超过长度会报错,类型单一,针对缺点c#提出ArrayList对象来克服这些缺点。
二、动态数组:ArrayList
是一个值为object类型的集合,不需要指定它的长度,自动扩容,可以存放任意类型,只是每次用需要大量装箱和拆箱(不安全),效率低。
比如说我需要做数据排列,反而类型太乱,无法实现。效率低下:如果内部存放值类型会频繁的拆装箱操作,耗费资源。
using System.Collections;//ArrayList集合和Hashtable容器所在的命名空间。 ArrayList alc = new ArrayList(); alc.Add("dsaf"); //添加数据,赋值。 alc.Add(321); //不受数据类型限制 alc.Add("dsaf");//长度可任意改变,每次集合中包含个数超过了可包含个数时会自动向内存申请多一倍的空间。来保证集合长度一直够用。 //一些常用方法 alc[1] = 123;//修改数据 alc.Remove("dsaf");//通过值删除:只删除第一个匹配到值 alc.RemoveAt(0);//通过下标删除,删除后所有下标会向前移位。 alc.Insert(1, "qwe");//插入数据,只能插入现有长度下标,超出会报错,插入后原下标变为n+1下标往后排。 //alc.Sort();//升序排列,只有相同数据类型,才能排序,否则报错 alc.Reverse();//反转:可以用升序反转后变成倒叙的意思 alc.Contains("dsaf");//模糊查询,返回bool值,常用做判断。 alc.IndexOf("dsaf");//通过值索引到下标,找到后返回下标值,未找到返回 -1 foreach (var i in alc) Console.WriteLine(i +",");//由于是多种数据类型,因此用var来存储。
三、哈希表容器Hashtable:键值对。
哈希表容器Hashtable:和ArrayList集合是一样的,存放的都是obj类型,效率低需要大量装箱和拆箱,类型转换不安全。
using System.Collections;//ArrayList集合和Hashtable容器所在的命名空间, Hashtable ht = new Hashtable();//最原始的容器, ht.Add(3, 22);//以键值对的方式存储,由于装拆箱一系列问题导致数据类型的不安全。 ht.Add("s", "值"); ht[1] = "值,如果有1这个键就覆盖值,没有就添加。 上面的add是纯添加数据。"; int value = Convert.ToInt32(ht[3]);//非泛型键取值,需要拆箱:把obj类型转换为值类型 foreach (var a in ht.Keys)//通过键去值,通过值取键用Values { Console.WriteLine("键:{0},值:{1}", a, ht[a]); } Console.WriteLine("判断是否包含某个键:" + ht.ContainsKey("s")); ht.Clear();//删除所有数据 ht.Remove(3);//移除带3的键数据
注意:ArrayList和Hashtable都不是泛型类,都是存放的object类型,不需要指定它的长度自动扩容,可以存放任意类型,只是每次用需要大量装箱和拆箱(不安全),效率低。针对这个缺点就有了泛型。这两种已经淘汰,不建议使用。
泛型就是广泛的意思,任意类型,带<T>尖括号的类都是泛型,安全,不需要装拆箱,效率高。c#给我们提供【数组用:List<T>俗称泛型集合,键值对:用Dictionary<键,值>俗称字典】
四、字典Dictionary:俗称键值对。
泛型键值对也叫字典Dictionary<键,值>:长度可动态改变,有数据类型约束,不需要装拆箱,操作更安全。
Dictionary<int, string> dic = new Dictionary<int, string>(); dic.Add(1, "张三");//对应的是键,值 dic.Add(2, "李四");//键不能重复,值可以 dic[3] = "我是新来的"; //遍历键值对:KeyValuePair 是Dictionary 类中用于获取单个项的数据结构,用来获取键值对 foreach (KeyValuePair<int, string> kv in dic) { Console.WriteLine("{0}----->{1}", kv.Key, kv.Value); }
五、泛型List<T>
<任意类型>就是泛型:解决代码重用,类型安全,高效率。
因为ArrayList存在不安全类型与装箱拆箱的缺点,所以出现了泛型List<T>的概念。List类是ArrayList类的泛型等效类,它的大部分用法都与ArrayList相似.
因为List类也继承了IList接口。最关键的区别在于,int[]固定数组在定义时只是定义状态,而list已经是使用状态了,接口调用。
List<int> ilit = new List<int>(); //这种限制了指定数据类型只能是int ,不需要装箱和拆箱. ilit.Add(1); ilit.Add(2); ilit.AddRange(new int[] { 3, 4, 5 }); ilit.AddRange(ilit);//自己添加自己 //ilit.RemoveAt(0);//移除数据 int[] ii = ilit.ToArray();//集合转为数组。 ilit = ii.ToList();//数组转换为集合。 foreach (var a in ilit) { Console.WriteLine(a); }
泛型和数组效率比较,如果大量增删改用List泛型,如果是大量查询用固定数组Array会高效。不用考虑ArrayList了。
using System.Collections;//ArrayList集合和Hashtable容器所在的命名空间,不建议使用 DateTime begin = DateTime.Now; List<int> ilist = new List<int>();//list默认是泛型类:泛型是类型安全的,执行效率高,快 for (int i = 0; i < 10000000; i++) { ilist.Add(i); } Console.WriteLine("List泛型遍历完成用时:" + (DateTime.Now - begin).TotalMilliseconds + "毫秒"); DateTime begin1 = DateTime.Now; var a = new int[10000000];//Array固定数组遍历最快。 for (int i = 0; i < 10000000; i++) { a[i]=i; } Console.WriteLine("Array固定数组遍历完成用时:" + (DateTime.Now - begin1).TotalMilliseconds + "毫秒"); DateTime begin2 = DateTime.Now; ArrayList al = new ArrayList();//ArrayList动态数组,有个装拆箱的过程,执行效率会慢于泛型list,慢 for (int i = 0; i < 10000000; i++) { al.Add(i); } Console.WriteLine("ArrayList动态数组遍历完成用时:" + (DateTime.Now - begin2).TotalMilliseconds + "毫秒");
六、泛型方法
普通方法:每次调用都需要制定类型,不同参数,就要定义多个不同方法代码会越来越多
Console.WriteLine("得到int类型方法返回值:" + GetInt(1) + "\n得到string类型方法的返回值:" + GetStr("只能是字符串,int方法就只能是int类型的值,其他类型还需要继续定义。")); static int GetInt(int t) { return t; } static string GetStr(string t) { return t; }
上面2个普通方法归一成泛型方法,可解决代码的通用性
泛型方法:static M GetAny<M>(M m) where M:struct 泛型约束条件where
Console.WriteLine(GetAny(6) + GetAny(".泛型方法可以传任意类型:") + GetAny<double>(1.101) + GetAny(true)); static M GetAny<M>(M m) //where M:class //多约束;new() 必须放最后 { return m; }
泛型方法的约束 where :在方法后面加【where M:struct】说M必须值类型。关键字:struct 必须是值类型 class 必须是引用类型,new()必须有个无参构造函数
Fanxing c = new Fanxing("约束new()就必须要有一个有参构造函数。"); Console.WriteLine(Fanxing.GetT<Fanxing>(c));//表示必须实现某个接口而且必须具有无参构造的函数,如果没有继承接口就报错 public interface People { }//随便定义一个接口,interface定义接口关键字,泛型方法约束用l where T:约束一个接口 public class Fanxing: People//继承接口 { public Fanxing() { }//无参构造方法,每个类中默认有无参构造方法 public Fanxing(string str) { }//定义个有参构造做测试 public static T GetT<T>(T t) where T : People, new() { return t; } }
七、自定义泛型
做一个相当于List<T>数组的泛型类; List就是一个类名, add就是他的添加数据方法 ,同样自定义的 MyList<T> my = new MyList<T>();也是一样道理,写好方法就可以
自定义泛型数组没forecast遍历能力,需要实现至接口IEnumerable<T> :作用是里面封装了能让MyList支持Foreach遍历核心方法 常用的list里面一样也继承了这个接口
MyList<string> ml = new MyList<string>(); ml.Set("给自定义的泛型类做成list"); Console.WriteLine(ml.Get()); public class MyList<T> { private T t; //设置 public void Set(T _t) { t = _t; } //读取 public T Get() { return t; } }
做一个自定义数组
using System.Collections;//IEnumerable<T> 接口所在的命名空间 MyList<string> m = new MyList<string>(); //数组类型的添加 m.Add("直接调用自定义的添加方法:"); m.Add("张三"); for (int i = 6; i <= 30; i++) { m.Add("赵" + i); } //批量添加数据 //使用索引器设置和读取 m[0] = "李四"; m[5] = "王五"; Console.WriteLine(m[0] + m[1] + ",数组长度为:" + m.length); //让MyList支持Foreach遍历,必须要实现至IEnumerable<T>接口的GetEnumerator()方法 foreach (string item in m) { Console.WriteLine(item); } Console.ReadLine();//等待输入用来暂停 //**************************************************************************************************************************************** /// <summary> /// 自定义泛型类 /// </summary> /// <typeparam name="T">任意类型</typeparam> public class MyList<T> : IEnumerable<T> //接口里面封装了让MyList支持Foreach遍历核心两个方法 鼠标点击IEnumerable<T>实现接口自动生成 { private T[] tArray = new T[4];//定义泛型数组 ,长度4 public int length = 0;//存放数组长度 public void Add(T _t)//添加数据> { //动态给数组扩容:如果数组满了,需要扩容 if (length == tArray.Length) { //创建一个新的数组,每次扩大4 T[] newarray = new T[tArray.Length + 4]; //把以前的数组内容放进新数组里面 Array.Copy(tArray, newarray, tArray.Length);//Array.Copy(复制这个数组,粘贴到这个数组,被复制数组的长度int类型) //新的数组替换以前的数组 tArray = newarray; } tArray[length] = _t; length++; } /// <summary> /// 索引器:封装数组从0下标开始索引每个值,给属性设置和读取,只需要【get只读和set只写】两个访问器,实体类也一样:可读可写两个访问器都要有 /// </summary> /// <param name="index">索引下标:只有int型的索引器测试可读可写的,否则都是只读的get访问器</param> /// <returns></returns> public T this[int index] { get //只读:用来取值 { if (index >= length) throw new Exception("索引超出了最大范围"); //抛出异常 return tArray[index]; //在实体类中默认就是返回给当前字段,没有字段变化,所以不写返回值。set服务器的value也是一样默认当前字段,不需要写 } set //只写:用来设置一个值 { if (index >= length) throw new Exception("索引超出了最大范围"); //抛出异常 tArray[index] = value; //value是set访问器自带的关键字,作用:将外部值传递进来,并赋值给当前字段。 } } //让MyList支持Foreach遍历的核心方法 鼠标点击IEnumerable<T>实现接口自动生成的 public IEnumerator<T> GetEnumerator() { //throw new NotImplementedException();//自动生成的没用 for (int i = 0; i < length; i++) { yield return tArray[i]; //yield 表示 不是马上返回return 而是方法执行一次返回一次,这里必须要加 } } //和GetEnumerator一起生成的 IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } }
索引器的理解:封装数组的(我们希望把类封装成数组的形式来用就需要索引器)从0下标开始索引每个值,给属性设置和读取,只需要【get只读和set只写】两个访问器,实体类也一样:可读可写两个访问器都要有
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CSharp { class Demo2 { static void Main(string[] args) { //创建一个班级 ClassInfo c = new ClassInfo("t001"); //创建几个学生 StudentInfo s1 = new StudentInfo("t011021", "张三", "男", 18); StudentInfo s2 = new StudentInfo("t011022", "李四", "男", 18); StudentInfo s3 = new StudentInfo("t011023", "王五", "男", 18); //添加数据 c[0] = s1; c[1] = s2; c[2] = s3; c[1].SayHi(); c["t011023"].SayHi(); Console.ReadLine();//利用接收输入来暂停程序,避免程序一闪而过 } } class ClassInfo { public ClassInfo(string ClassName) { this.ClassName = ClassName; } public string ClassName { get; set; } //表示每个班级可以有10个学生 StudentInfo[] s = new StudentInfo[10]; /// <summary> /// 创建索引器,必须有上面的这一个数组,建立索引器才有意义 /// </summary> /// <param name="index"></param> /// <returns></returns> public StudentInfo this[int index] //只有int型的索引器才是可读可写的,否则都是get只读的,也就是【中括号的index必须是数字,否则就只能读,不可修改】 { get { return s[index]; } set { s[index] = value; } } /// <summary> /// 索引器是可以重载的 /// </summary> /// <param name="index"></param> /// <returns></returns> public StudentInfo this[string stuno] //只有int型的索引器才是可读可写的,否则都是get只读的 { get { foreach(StudentInfo stu in s) { if (stu.stoun == stuno) return stu; } return null; } } } /// <summary> /// 班级实体类 /// </summary> class StudentInfo { public StudentInfo(string stoun, string Name, string sex, int age) { this.stoun = stoun; this.Name = Name; this.sex = sex; this.age = age; } public StudentInfo() { } public string stoun { get; set; } public string Name { get; set; } public string sex { get; set; } public int age { get; set; } public void SayHi() { Console.WriteLine("班级:{0},姓名:{1},性别:{2},年龄:{3}",stoun,Name ,sex,age); } } }