C# 集合

释放双眼,带上耳机,听听看~!

**集合(collection)**提供了一种结构化组织任意对象的方式,从广义的概念上讲,数组、枚举和结构等组合都是集合的一种表现,其内部的元素组织方式也都与集合的定义非常类似。但在C#中,集合这一专有名词特指System.Collections命名空间下的各种子类,数组、枚举和结构都不是System.Collections命名空间的成员。NET类库提供了丰富的集合数据类型,其种类之繁多甚至使许多人看得眼都花了,这些集合对象都具有各自的专用场合。不管怎么说,更多的选择也就意味着更高的灵活性,但同时也意味着更高的复杂性。因此,对集合各个类型的用途和使用条件具有适度的了解是完全必要的。

 

.NET集合定义

从.NET 的角度看,所谓的集合可以定义为一种对象,这种对象实现一个或者多个System.Collections.ICollection、 System.Collections.IDictionary和System.Collections.IList接口。这一定义把 System.Collections名称空间中的“内置”集合划分成了三种类别:

    *  有序集合:仅仅实现ICollection接口的集合,在通常情况下,其数据项目的插入顺序控制着从集合中取出对象的的顺序。 System.Collections.Stack和 System.Collections.Queue类都是ICollection集合的典型例子。
*  索引集合:实现Ilist的集合,其内容能经由从零开始的数字检索取出,就象数组一样。System.Collections.ArrayList对象是索引集合的一个例子。
*  键式集合:实现 IDictionary 接口的集合,其中包含了能被某些类型的键值检索的项目。IDictionary集合的内容通常按键值方式存储,可以用枚举的方式排序检索。 System.Collections.HashTable类实现了IDictionary 接口。

正如你看到的那样,给定集合的功能在很大程度上受到特定接口或其实现接口的控制。如果你对面向对象编程缺乏了解,那么你可能对上面说的这些话感到难以理解。不过你至少应该知道,以接口这种方式构造对象的功能不但造就了具有整套类似方法的对象族,而且还能让这些对象在必要的情况下可以当作同类,以OOP (面向对象编程)的术语来说,这就是大名鼎鼎的多态性技术。

 

System.Collections概述:

System.Collections 名称空间包含了在你的应用程序中可以用到的6种内建通用集合。另一些更为专业化的集合则归属于 System.Collections.Specialized,在某些情况下你会发现这些专用集合也是非常有用的。加上一些异常(exception)类,这些专业化集合在功能上和内建集合是类似的。

 

System.Collections.Generic概述:

System.Collections.Generic 命名空间包含定义泛型集合的接口和类,泛型集合允许用户创建强类型集合,它能提供比非泛型强类型集合更好的类型安全性和性能。

 

System.Collections命名空间下常用的集合类有:

  • ArrayList
  • Queue
  • Stack
  • BitArray
  • Hashtable
  • SortedList

System.Collections.Generic命名空间下常用的集合类有:

  • List<T>
  • Queue<T>
  • Stack<T>
  • LinkedList<T>
  • HashSet<T>
  • Dictionary<TKey, TValue>
  • SortedDictionary<TKey, TValue>
  • SortedList<TKey, TValue>
  • Lookup<TKey, TElement>

 

System.Collections命名空间中的接口

System.Collections命名空间中的几个接口提供了基本的集合功能

  • IEnumerable:公开枚举数,该枚举数支持在非泛型集合上进行简单迭代。简单的说就是实现 IEnumerable接口后可以支持用Microsoft Visual Basic 的 foreach 语义来进行迭代集合中的项。它只有一个方法 GetEnumerator(),该方法可以返回一个IEnumerator枚举数,通过它可以遍历集合。基本上所有的集合类都实现了这个方法。

  • ICollection:定义所有非泛型集合的大小、枚举数和同步方法。ICollection 接口是 System.Collections 命名空间中类的基接口,所有集合类都实现了这个接口。ICollection 接口继承于IEnumerable,扩展了IEnumerable。 IDictionary 和 IList 则是扩展 ICollection 的更为专用的接口。如果 IDictionary 接口和 IList 接口都不能满足所需集合的要求,则从 ICollection 接口派生新集合类以提高灵活性。

  • IList:表示可排序并且可以按照索引访问对象的非泛型集合(列表)。IList 是 ICollection 接口的子代,并且是所有非泛型列表的基接口,IList 实现有三种类别:只读、固定大小和可变大小。无法修改只读 IList。固定大小的 IList 不允许添加或移除元素,但允许修改现有元素。可变大小的 IList 允许添加、移除和修改元素。

  • IDictionary:IDictionary 可以称为字典、映射或者散列表,表示键/值对的非通用集合,即类似于IList但提供了可通过键值(而不是索引)访问的项列表。IDictionary是Icollection接口的子代,是键/值对的的集合的基接口,IDictionary 实现有三种类别:只读、固定大小、可变大小。无法修改只读 IDictionary 对象。固定大小的 IDictionary 对象不允许添加或移除元素,但允许修改现有元素。可变大小的 IDictionary 对象允许添加、移除和修改元素。C# 语言中的 foreach 语句需要集合中每个元素的类型。由于 IDictionary 对象的每个元素都是一个键/值对,因此元素类型既不是键的类型,也不是值的类型,而是 DictionaryEntry 类型。

 

System.Collections命名空间中常用集合类的使用

ArrayList

****实现的接口:IList、ICollections、IEnumerable

使用大小可按需动态增加的数组实现 IList 接口(大小可变的数组列表),ArrayList 的默认初始容量为 0。随着元素添加到 ArrayList 中,容量会根据需要通过重新分配自动增加。ArrayList 接受 空引用作为有效值并且允许重复的元素。

ArrayList把所有元素都当作object对象引用,因而在访问ArrayList的元素时要进行类型转换

优点:动态改变大小、灵活方便的插入和删除元素、可排序

    缺点:插入时性能不如数组、不是强类型的

 

示例

下面的代码示例演示如何创建并初始化 ArrayList 以及如何打印出其值。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
1using System;
2using System.Collections;
3public class SamplesArrayList  {
4
5   public static void Main()  {
6
7      // Creates and initializes a new ArrayList.
8      ArrayList myAL = new ArrayList();
9      myAL.Add(&quot;Hello&quot;);
10      myAL.Add(&quot;World&quot;);
11      myAL.Add(&quot;!&quot;);
12
13      // Displays the properties and values of the ArrayList.
14      Console.WriteLine( &quot;myAL&quot; );
15      Console.WriteLine( &quot;    Count:    {0}&quot;, myAL.Count );
16      Console.WriteLine( &quot;    Capacity: {0}&quot;, myAL.Capacity );
17      Console.Write( &quot;    Values:&quot; );
18      PrintValues( myAL );
19   }
20
21   public static void PrintValues( IEnumerable myList )  {
22      foreach ( Object obj in myList )
23         Console.Write( &quot;   {0}&quot;, obj );
24      Console.WriteLine();
25   }
26
27}
28
29
30/*
31This code produces output similar to the following:
32
33myAL
34    Count:    3
35    Capacity: f
36    Values:   Hello   World   !
37
38*/
39

 

Queue

****实现的接口:ICollection、IEnumerable

Queue是队列,先进先出的访问元素。可以调用Queque对象的GetEnumerator()方法,得到IEnumerator对象,来遍历队列中的各个元素。Queue 的默认初始容量为 32。向 Queue 添加元素时,将通过重新分配来根据需要自动增大容量。可通过调用 TrimToSize 来减少容量。Queue 接受 空引用(在 Visual Basic 中为 Nothing) 作为有效值并且允许重复的元素。

示例

下面的示例说明如何创建 Queue 并向其添加值,以及如何打印出其值。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1using System;
2 using System.Collections;
3 public class SamplesQueue  {
4
5    public static void Main()  {
6
7       // Creates and initializes a new Queue.
8       Queue myQ = new Queue();
9       myQ.Enqueue(&quot;Hello&quot;);
10       myQ.Enqueue(&quot;World&quot;);
11       myQ.Enqueue(&quot;!&quot;);
12
13       // Displays the properties and values of the Queue.
14       Console.WriteLine( &quot;myQ&quot; );
15       Console.WriteLine( &quot;\tCount:    {0}&quot;, myQ.Count );
16       Console.Write( &quot;\tValues:&quot; );
17       PrintValues( myQ );
18    }
19
20
21    public static void PrintValues( IEnumerable myCollection )  {
22       foreach ( Object obj in myCollection )
23          Console.Write( &quot;    {0}&quot;, obj );
24       Console.WriteLine();
25    }
26 }
27 /*
28 This code produces the following output.
29 T
30 myQ
31     Count:    3
32     Values:    Hello    World    !
33*/
34

 

Stack

****实现的接口:ICollection、IEnumerable

Stack是堆栈,先进后出的访问各个元素。

示例

下面的示例说明如何创建 Stack并向其添加值,以及如何打印出其值。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
1using System;
2using System.Collections;
3public class SamplesStack  {
4
5   public static void Main()  {
6
7      // Creates and initializes a new Stack.
8      Stack myStack = new Stack();
9      myStack.Push(&quot;Hello&quot;);
10      myStack.Push(&quot;World&quot;);
11      myStack.Push(&quot;!&quot;);
12
13      // Displays the properties and values of the Stack.
14      Console.WriteLine( &quot;myStack&quot; );
15      Console.WriteLine( &quot;\tCount:    {0}&quot;, myStack.Count );
16      Console.Write( &quot;\tValues:&quot; );
17      PrintValues( myStack );
18   }
19
20   public static void PrintValues( IEnumerable myCollection )  {
21      foreach ( Object obj in myCollection )
22         Console.Write( &quot;    {0}&quot;, obj );
23      Console.WriteLine();
24   }
25
26}
27
28
29/*
30This code produces the following output.
31
32myStack
33    Count:    3
34    Values:    !    World    Hello
35*/
36

 

HashTable

****实现接口:IDictionary、ICollection、IEnumerable

表示键/值对的集合,这些键/值对根据键的哈希代码进行组织。可以想HasTable中自由添加和删除元素,有些像ArrayList,但没有那么大的性能开销。

每个元素都是一个存储在 DictionaryEntry 对象中的键/值对。键不能为 空引用(在 Visual Basic 中为 Nothing),但值可以。

当把某个元素添加到 Hashtable 时,将根据键的哈希代码将该元素放入存储桶中。该键的后续查找将使用键的哈希代码只在一个特定存储桶中搜索,这将大大减少为查找一个元素所需的键比较的次数。

示例

下面的示例说明如何创建 Stack并向其添加值,以及如何打印出其值。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
1using System;
2using System.Collections;
3
4class Example
5{
6    public static void Main()
7    {
8        // Create a new hash table.
9        //
10        Hashtable openWith = new Hashtable();
11        
12        // Add some elements to the hash table. There are no
13        // duplicate keys, but some of the values are duplicates.
14        openWith.Add(&quot;txt&quot;, &quot;notepad.exe&quot;);
15        openWith.Add(&quot;bmp&quot;, &quot;paint.exe&quot;);
16        openWith.Add(&quot;dib&quot;, &quot;paint.exe&quot;);
17        openWith.Add(&quot;rtf&quot;, &quot;wordpad.exe&quot;);
18        
19        // The Add method throws an exception if the new key is
20        // already in the hash table.
21        try
22        {
23            openWith.Add(&quot;txt&quot;, &quot;winword.exe&quot;);
24        }
25        catch
26        {
27            Console.WriteLine(&quot;An element with Key = \&quot;txt\&quot; already exists.&quot;);
28        }
29
30        // The Item property is the default property, so you
31        // can omit its name when accessing elements.
32        Console.WriteLine(&quot;For key = \&quot;rtf\&quot;, value = {0}.&quot;, openWith[&quot;rtf&quot;]);
33        
34        // The default Item property can be used to change the value
35        // associated with a key.
36        openWith[&quot;rtf&quot;] = &quot;winword.exe&quot;;
37        Console.WriteLine(&quot;For key = \&quot;rtf\&quot;, value = {0}.&quot;, openWith[&quot;rtf&quot;]);
38        
39        // If a key does not exist, setting the default Item property
40        // for that key adds a new key/value pair.
41        openWith[&quot;doc&quot;] = &quot;winword.exe&quot;;
42
43        // The default Item property throws an exception if the requested
44        // key is not in the hash table.
45        try
46        {
47            Console.WriteLine(&quot;For key = \&quot;tif\&quot;, value = {0}.&quot;, openWith[&quot;tif&quot;]);
48        }
49        catch
50        {
51            Console.WriteLine(&quot;Key = \&quot;tif\&quot; is not found.&quot;);
52        }
53
54        // ContainsKey can be used to test keys before inserting
55        // them.
56        if (!openWith.ContainsKey(&quot;ht&quot;))
57        {
58            openWith.Add(&quot;ht&quot;, &quot;hypertrm.exe&quot;);
59            Console.WriteLine(&quot;Value added for key = \&quot;ht\&quot;: {0}&quot;, openWith[&quot;ht&quot;]);
60        }
61
62        // When you use foreach to enumerate hash table elements,
63        // the elements are retrieved as KeyValuePair objects.
64        Console.WriteLine();
65        foreach( DictionaryEntry de in openWith )
66        {
67            Console.WriteLine(&quot;Key = {0}, Value = {1}&quot;, de.Key, de.Value);
68        }
69
70        // To get the values alone, use the Values property.
71        ICollection valueColl = openWith.Values;
72        
73        // The elements of the ValueCollection are strongly typed
74        // with the type that was specified for hash table values.
75        Console.WriteLine();
76        foreach( string s in valueColl )
77        {
78            Console.WriteLine(&quot;Value = {0}&quot;, s);
79        }
80
81        // To get the keys alone, use the Keys property.
82        ICollection keyColl = openWith.Keys;
83        
84        // The elements of the KeyCollection are strongly typed
85        // with the type that was specified for hash table keys.
86        Console.WriteLine();
87        foreach( string s in keyColl )
88        {
89            Console.WriteLine(&quot;Key = {0}&quot;, s);
90        }
91
92        // Use the Remove method to remove a key/value pair.
93        Console.WriteLine(&quot;\nRemove(\&quot;doc\&quot;)&quot;);
94        openWith.Remove(&quot;doc&quot;);
95        
96        if (!openWith.ContainsKey(&quot;doc&quot;))
97        {
98            Console.WriteLine(&quot;Key \&quot;doc\&quot; is not found.&quot;);
99        }
100    }
101}
102
103/* This code example produces the following output:
104
105An element with Key = &quot;txt&quot; already exists.
106For key = &quot;rtf&quot;, value = wordpad.exe.
107For key = &quot;rtf&quot;, value = winword.exe.
108For key = &quot;tif&quot;, value = .
109Value added for key = &quot;ht&quot;: hypertrm.exe
110
111Key = dib, Value = paint.exe
112Key = txt, Value = notepad.exe
113Key = ht, Value = hypertrm.exe
114Key = bmp, Value = paint.exe
115Key = rtf, Value = winword.exe
116Key = doc, Value = winword.exe
117
118Value = paint.exe
119Value = notepad.exe
120Value = hypertrm.exe
121Value = paint.exe
122Value = winword.exe
123Value = winword.exe
124
125Key = dib
126Key = txt
127Key = ht
128Key = bmp
129Key = rtf
130Key = doc
131
132Remove(&quot;doc&quot;)
133Key &quot;doc&quot; is not found.
134 */
135

 

上面提到的几种集合类,他们都是通用的集合类,他们所接受的元素大都是Object类型,当对象放入

了集合之后,都失去了原有的类型信息-即这些通用集合类都不是强类型的。

 

比如: 


1
2
3
4
1 ArrayList list = new ArrayList();
2            list.Add(new Class1());
3           ((Class1)list[0]).function();  
4

list[0]无法直接调用Class1的方法function,因为在ArrayList中的各项都是System.Object类型的,但是可以通过数据类型转换实现(多态性)。

我们如何避免数据类型的转换呢?解决办法是使用强类型的集合类。

 

 

   定义强类型集合

****创建自己的强类型集合一种方式是手动实现需要的方法,但这较费时间,某些情况下也非常复杂。我们可以从System.Collections命名空间下的CollectionBase,DictionaryBase,ReadOnlyCollectionBase 类

中派生自己的集合,或者System.Collections.Specialized命名空间下的一些类可以满足要求,可以直接使用也可以继承。

System.Collections.CollectionBase这个抽象类提供了集合类的大量实现代码,这是推荐使用的方式。

这个类有接口ICollection、IEnumerable、Ilist,但值提供了一些必要的实现代码,主要是IList的Clear()、和RemoveAt()方法以及ICollection的Count属性。

 

C# 集合

 

 

   

CollectionBase还提供了两个受保护的属性List、InnerList,《c#入门经典》中的描述是:我们可以使用List和InnerList,其中通过List属性可以调用IList接口访问项目,InnerList则是用于存储项目的ArrayList对象。

 

C# 集合

 

示例:

有三个类Animal、Cow、Chicken,其中Cow、Chicken是抽象类Animal的派生类,我们要创建一个Animal类的集合类Animals来存储Animal对象。

Animal


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1public abstract class Animal
2   {
3      protected string name;
4
5      public string Name
6      {
7         get
8         {
9            return name;
10         }
11         set
12         {
13            name = value;
14         }
15      }
16
17      public Animal()
18      {
19         name = &quot;The animal with no name&quot;;
20      }
21
22      public Animal(string newName)
23      {
24         name = newName;
25      }
26
27      public void Feed()
28      {
29         Console.WriteLine(&quot;{0} has been fed.&quot;, name);
30      }
31   }
32

Cow


1
2
3
4
5
6
7
8
9
10
11
12
1public class Cow : Animal
2   {
3      public void Milk()
4      {
5         Console.WriteLine(&quot;{0} has been milked.&quot;, name);
6      }
7
8      public Cow(string newName) : base(newName)
9      {
10      }
11   }
12

Chicken


1
2
3
4
5
6
7
8
9
10
11
12
1public class Chicken : Animal
2   {
3      public void LayEgg()
4      {
5         Console.WriteLine(&quot;{0} has laid an egg.&quot;, name);
6      }
7
8      public Chicken(string newName) : base(newName)
9      {
10      }
11   }
12

 

存储Animal对象的集合类可以定义如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
1 public class Animals : CollectionBase
2   {
3      public void Add(Animal newAnimal)
4      {
5         List.Add(newAnimal);
6      }
7
8      public void Remove(Animal newAnimal)
9      {
10         List.Remove(newAnimal);
11      }
12    }
13

前面说过CollectionBase实现的接口只提供了IList的Clear()及RemoveAt()方法和ICollection的Count属性,想要实现向集合中添加及删除对象就需要自己来实现。我们实现了Add方法及Remove方法。

我们通过CollectionBase的List属性使用了IList接口中用于访问项的标准Add方法,并且限制了只能处理Animal类或派生于Animal的类,而前面介绍的ArrayList的实现代码可以处理任何对象不是强类型的。

这样我们就可以向Animals集合类中添加对象


1
2
3
4
1Animals animalCollection = new Animals();
2animalCollection.Add(new Cow(&quot;Jack&quot;));
3animalCollection.Add(new Chicken(&quot;Vera&quot;));
4

但是我们不能使用下面的代码

animalCollection[0].Feed();

要通过索引的方式来访问项,就需要使用索引符。

 

索引符(索引器)

****索引符是一个特殊类型的属性,可以把它添加到一个类中,以提供类似于数组的访问。

定义索引器的方式与定义属性有些类似,其一般形式如下:


1
2
3
4
5
6
7
1[修饰符] 数据类型 this[索引类型 index]  
2
3{  
4    get{//获得属性的代码}                                                  
5    set{ //设置属性的代码}  
6}
7

数据类型是将要存取的集合元素的类型。索引类型表示该索引器使用哪一种类型的索引来存取集合元素,可以是整数,可以是字符串。this表示操作本对象deep集合成员,可以简单理解成索引器的名字,因此索引器不能具有用户定义的名称。

示例:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1class Z  
2{  
3        //可容纳100个整数的整数集  
4        private long[] arr = new long[100];  
5        //声明索引器  
6        public long this[int index]  
7        {  
8            get
9            { //检查索引范围  
10                if (index &lt; 0 || index &gt;= 100)  
11                {  
12                    return 0;  
13                }  
14                else
15                {  
16                    return arr[index];  
17                }  
18            }  
19            set
20            {  
21                if (!(index &lt; 0 || index &gt;= 100))  
22                {  
23                    arr[index] = value;  
24                }  
25            }  
26   }  
27

这样我们就可以像访问数组一样访问类型Z中的arr数组


1
2
3
4
5
1Z  z=new  z();  
2z[0]=100;  
3z[1]=101;  
4Console.WriteLine(z[0]);
5

C#中并不将索引器的类型限制为整数。例如,可以对索引器使用字符串。通过搜索集合内的字符串并返回相应的值,可以实现此类的索引器。由于访问器可以被重载,字符串和整数版本可以共存。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1class DayCollection
2{  
3      string[] days={&quot;Sun&quot;,&quot;Mon&quot;,&quot;Tues&quot;,&quot;Wed&quot;,&quot;Thurs&quot;,&quot;Fri&quot;,&quot;Sat&quot;};  
4      private int GetDay(string testDay)  
5     {  
6        int i=0;  
7        foreach(string day in days)  
8          {  
9              if(day==testDay)  
10                      return i;  
11                      i++;  
12           }  
13         return -1;  
14      }  
15     public int this[string day]  
16      {  
17          get{return (GetDay(day))}  
18      }  
19}  
20  
21
22static void Main(string[] args)  
23{  
24     DayCollection week=new DayCollection();  
25     Console.WriteLine(&quot;Fri:{0}&quot;,week[&quot;Fri&quot;]);  
26     Console.WriteLine(&quot;ABC:{0}&quot;,week[&quot;ABC&quot;]);  
27}
28

结果:Fri:5

ABC:-1

接口中的索引器

在接口中也可以声明索引器,接口索引器与类索引器的区别有两个:一是接口索引器不使用修饰符;二是接口索引器只包含访问器get或set,没有实现语句。访问器的用途是指示索引器是可读写、只读还是只写的,如果是可读写的,访问器get或set均不能省略;如果只读的,省略set访问器;如果是只写的,省略get访问器。


1
2
3
4
5
6
7
1public interface IAddress  
2{  
3string this[int index]{get;set;}  
4string Address{get;set;}  
5string Answer();  
6}
7

 

通过索引访问自定义集合类

我们修改Animals类如下


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1public class Animals : CollectionBase
2   {
3      public void Add(Animal newAnimal)
4      {
5         List.Add(newAnimal);
6      }
7
8      public void Remove(Animal newAnimal)
9      {
10         List.Remove(newAnimal);
11      }
12
13      public Animal this[int animalIndex]
14      {
15         get
16         {
17            return (Animal)List[animalIndex];
18         }
19         set
20         {
21            List[animalIndex] = value;
22         }
23      }
24   }
25

 这样 Animais animaiCollection=new Animails(); 就可以通过animaiCollection[0]这种方式来访问了。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

Bootstrap 4 Flex(弹性)布局

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索