424 lines
8.1 KiB
Markdown
424 lines
8.1 KiB
Markdown
|
|
# List
|
|||
|
|
概念:
|
|||
|
|
`List<T>` 是 C# 提供的==泛型集合类==
|
|||
|
|
本质上可以理解为:==一个长度可变的泛型数组==
|
|||
|
|
|
|||
|
|
它可以:
|
|||
|
|
1. 按下标访问元素
|
|||
|
|
2. 自动扩容
|
|||
|
|
3. 方便地增删查改
|
|||
|
|
4. 保证集合中元素类型统一
|
|||
|
|
|
|||
|
|
命名空间:
|
|||
|
|
```csharp
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
简单理解:
|
|||
|
|
数组长度固定
|
|||
|
|
`List<T>` 长度可以动态变化
|
|||
|
|
而且比 `ArrayList` 更安全,因为它有明确类型
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 通常用法
|
|||
|
|
1. 存储数量不固定的一组同类型数据
|
|||
|
|
2. 需要频繁添加、删除、遍历元素
|
|||
|
|
3. 作为项目里最常见的基础集合之一
|
|||
|
|
4. 替代早期非泛型集合 `ArrayList`
|
|||
|
|
|
|||
|
|
实际开发中:
|
|||
|
|
新项目里 `List<T>` 的使用频率非常高
|
|||
|
|
很多时候它就是“默认列表容器”
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 语法
|
|||
|
|
创建:
|
|||
|
|
```csharp
|
|||
|
|
List<int> list = new List<int>();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
创建并赋初值:
|
|||
|
|
```csharp
|
|||
|
|
List<string> names = new List<string>() { "小明", "小红", "小刚" };
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
添加:
|
|||
|
|
```csharp
|
|||
|
|
list.Add(10);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
访问:
|
|||
|
|
```csharp
|
|||
|
|
list[0]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
数量:
|
|||
|
|
```csharp
|
|||
|
|
list.Count
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 基础案例
|
|||
|
|
```csharp
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
|
|||
|
|
List<int> list = new List<int>();
|
|||
|
|
|
|||
|
|
list.Add(10);
|
|||
|
|
list.Add(20);
|
|||
|
|
list.Add(30);
|
|||
|
|
|
|||
|
|
Console.WriteLine(list[0]);
|
|||
|
|
Console.WriteLine(list[1]);
|
|||
|
|
Console.WriteLine(list[2]);
|
|||
|
|
Console.WriteLine(list.Count);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
输出:
|
|||
|
|
```text
|
|||
|
|
10
|
|||
|
|
20
|
|||
|
|
30
|
|||
|
|
3
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
理解:
|
|||
|
|
1. `List<int>` 里只能放 `int`
|
|||
|
|
2. 可以像数组一样通过下标访问
|
|||
|
|
3. `Count` 表示当前实际元素个数
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 和 ArrayList 的区别
|
|||
|
|
`ArrayList`:
|
|||
|
|
内部按 `object` 存储
|
|||
|
|
可以混合存很多类型
|
|||
|
|
取值时常常需要强制类型转换
|
|||
|
|
值类型放进去会发生装箱拆箱
|
|||
|
|
|
|||
|
|
`List<T>`:
|
|||
|
|
内部是泛型集合
|
|||
|
|
类型更明确
|
|||
|
|
编译期就能检查类型错误
|
|||
|
|
通常不会像 `ArrayList` 那样频繁发生装箱拆箱
|
|||
|
|
|
|||
|
|
对比表:
|
|||
|
|
|
|||
|
|
| 对比点 | `ArrayList` | `List<T>` |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 类型 | 非泛型 | 泛型 |
|
|||
|
|
| 存储 | `object` | 指定类型 `T` |
|
|||
|
|
| 类型安全 | 较弱 | 更强 |
|
|||
|
|
| 取值 | 常要强转 | 一般不用强转 |
|
|||
|
|
| 性能 | 值类型可能有装箱拆箱 | 通常更好 |
|
|||
|
|
| 推荐程度 | 主要用于学习旧集合 | 实际开发常用 |
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 增删查改
|
|||
|
|
增加:
|
|||
|
|
```csharp
|
|||
|
|
List<int> list = new List<int>();
|
|||
|
|
|
|||
|
|
list.Add(1);
|
|||
|
|
list.Add(2);
|
|||
|
|
list.Add(3);
|
|||
|
|
|
|||
|
|
list.Insert(1, 99);
|
|||
|
|
list.AddRange(new List<int> { 7, 8, 9 });
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
删除:
|
|||
|
|
```csharp
|
|||
|
|
list.Remove(2); // 删除第一个匹配到的元素
|
|||
|
|
list.RemoveAt(0); // 删除指定下标元素
|
|||
|
|
list.Clear(); // 清空整个列表
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
查找:
|
|||
|
|
```csharp
|
|||
|
|
bool hasValue = list.Contains(99);
|
|||
|
|
int index1 = list.IndexOf(99);
|
|||
|
|
int index2 = list.LastIndexOf(99);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
修改:
|
|||
|
|
```csharp
|
|||
|
|
list[0] = 100;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 遍历
|
|||
|
|
使用 `for`:
|
|||
|
|
```csharp
|
|||
|
|
List<string> names = new List<string>() { "A", "B", "C" };
|
|||
|
|
|
|||
|
|
for (int i = 0; i < names.Count; i++)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine(names[i]);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
使用 `foreach`:
|
|||
|
|
```csharp
|
|||
|
|
foreach (string item in names)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine(item);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
如果只是读取元素,`foreach` 写起来更方便
|
|||
|
|
如果需要下标,通常用 `for`
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# Count 和 Capacity
|
|||
|
|
`Count`:
|
|||
|
|
当前实际有多少个元素
|
|||
|
|
|
|||
|
|
`Capacity`:
|
|||
|
|
内部数组当前能容纳多少个元素
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
List<int> list = new List<int>();
|
|||
|
|
|
|||
|
|
Console.WriteLine(list.Count);
|
|||
|
|
Console.WriteLine(list.Capacity);
|
|||
|
|
|
|||
|
|
list.Add(10);
|
|||
|
|
list.Add(20);
|
|||
|
|
|
|||
|
|
Console.WriteLine(list.Count);
|
|||
|
|
Console.WriteLine(list.Capacity);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
理解:
|
|||
|
|
`List<T>` 会自动扩容
|
|||
|
|
所以 `Capacity` 往往大于等于 `Count`
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 常用成员
|
|||
|
|
```csharp
|
|||
|
|
List<int> list = new List<int>() { 3, 1, 2, 1 };
|
|||
|
|
|
|||
|
|
Console.WriteLine(list.Count);
|
|||
|
|
Console.WriteLine(list.Contains(2));
|
|||
|
|
Console.WriteLine(list.IndexOf(1));
|
|||
|
|
Console.WriteLine(list.LastIndexOf(1));
|
|||
|
|
|
|||
|
|
list.Sort();
|
|||
|
|
list.Reverse();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
常用方法:
|
|||
|
|
1. `Add(T item)`:添加一个元素
|
|||
|
|
2. `AddRange(IEnumerable<T>)`:添加一组元素
|
|||
|
|
3. `Insert(int index, T item)`:插入元素
|
|||
|
|
4. `Remove(T item)`:删除第一个匹配项
|
|||
|
|
5. `RemoveAt(int index)`:按下标删除
|
|||
|
|
6. `Clear()`:清空
|
|||
|
|
7. `Contains(T item)`:判断是否存在
|
|||
|
|
8. `IndexOf(T item)`:正向查找下标
|
|||
|
|
9. `LastIndexOf(T item)`:反向查找下标
|
|||
|
|
10. `Sort()`:排序
|
|||
|
|
11. `Reverse()`:反转
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 排序和反转
|
|||
|
|
排序:
|
|||
|
|
```csharp
|
|||
|
|
List<int> list = new List<int>() { 3, 5, 1, 2 };
|
|||
|
|
list.Sort();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
结果:
|
|||
|
|
```text
|
|||
|
|
1 2 3 5
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
反转:
|
|||
|
|
```csharp
|
|||
|
|
list.Reverse();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
结果:
|
|||
|
|
```text
|
|||
|
|
5 3 2 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
`Sort()` 默认按从小到大排序
|
|||
|
|
`Reverse()` 是把当前顺序直接反过来
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 插入和删除时要注意下标
|
|||
|
|
错误示例:
|
|||
|
|
```csharp
|
|||
|
|
List<int> list = new List<int>();
|
|||
|
|
list.Add(10);
|
|||
|
|
|
|||
|
|
// list[1] = 20;
|
|||
|
|
// list.RemoveAt(5);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
下标越界会报错
|
|||
|
|
|
|||
|
|
更稳的写法:
|
|||
|
|
```csharp
|
|||
|
|
if (list.Count > 0)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine(list[0]);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
`List<T>` 虽然长度可变
|
|||
|
|
但访问下标时仍然必须合法
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 泛型的好处
|
|||
|
|
```csharp
|
|||
|
|
List<int> numbers = new List<int>();
|
|||
|
|
numbers.Add(10);
|
|||
|
|
// numbers.Add("hello");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
说明:
|
|||
|
|
`List<int>` 只能存 `int`
|
|||
|
|
像 `"hello"` 这种类型不匹配的值,编译阶段就会报错
|
|||
|
|
|
|||
|
|
优点:
|
|||
|
|
1. 类型安全
|
|||
|
|
2. 减少强制类型转换
|
|||
|
|
3. 代码提示更清楚
|
|||
|
|
4. 更适合实际开发
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 基础案例:怪物列表
|
|||
|
|
```csharp
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
|
|||
|
|
class Program
|
|||
|
|
{
|
|||
|
|
static void Main(string[] args)
|
|||
|
|
{
|
|||
|
|
Monster monster1 = new Boss();
|
|||
|
|
Monster monster2 = new Goblin();
|
|||
|
|
Monster monster3 = new Boss();
|
|||
|
|
Monster monster4 = new Goblin();
|
|||
|
|
|
|||
|
|
for (int i = 0; i < Monster.Monsters.Count; i++)
|
|||
|
|
{
|
|||
|
|
Monster.Monsters[i].Atk();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
abstract class Monster
|
|||
|
|
{
|
|||
|
|
public static List<Monster> Monsters = new List<Monster>();
|
|||
|
|
|
|||
|
|
public Monster()
|
|||
|
|
{
|
|||
|
|
Monsters.Add(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public abstract void Atk();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class Boss : Monster
|
|||
|
|
{
|
|||
|
|
public override void Atk()
|
|||
|
|
{
|
|||
|
|
Console.WriteLine("Boss Atk");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class Goblin : Monster
|
|||
|
|
{
|
|||
|
|
public override void Atk()
|
|||
|
|
{
|
|||
|
|
Console.WriteLine("Goblin Atk");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
输出:
|
|||
|
|
```text
|
|||
|
|
Boss Atk
|
|||
|
|
Goblin Atk
|
|||
|
|
Boss Atk
|
|||
|
|
Goblin Atk
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
理解:
|
|||
|
|
1. `List<Monster>` 用来统一保存所有怪物对象
|
|||
|
|
2. 因为 `Boss` 和 `Goblin` 都继承 `Monster`
|
|||
|
|
3. 所以它们都能放进 `List<Monster>` 中
|
|||
|
|
4. 再通过遍历统一调用攻击方法
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 适合场景
|
|||
|
|
1. 玩家背包中的物品列表
|
|||
|
|
2. 敌人列表、角色列表、任务列表
|
|||
|
|
3. 聊天消息列表
|
|||
|
|
4. 成绩列表、订单列表、日志列表
|
|||
|
|
|
|||
|
|
一句话:
|
|||
|
|
只要你需要“数量不固定的一组同类型数据”
|
|||
|
|
通常都可以先考虑 `List<T>`
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 注意
|
|||
|
|
1. `List<T>` 在 `System.Collections.Generic` 命名空间中
|
|||
|
|
2. `List<T>` 是泛型集合,`T` 表示元素类型
|
|||
|
|
3. `Count` 是实际元素个数,不是容量
|
|||
|
|
4. `Capacity` 是当前容量,可能大于 `Count`
|
|||
|
|
5. `Add` 是追加到末尾
|
|||
|
|
6. `Insert` 是插入到指定位置
|
|||
|
|
7. `Remove` 删除的是第一个匹配项
|
|||
|
|
8. `RemoveAt` 删除的是指定下标元素
|
|||
|
|
9. `List<T>` 很常用,但按下标中间插入/删除很多次时会有移动开销
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 易错点
|
|||
|
|
1. `List<T>` 是类,不是数组,但用法上和数组有点像
|
|||
|
|
2. `Count` 不是 `Length`
|
|||
|
|
3. `Capacity` 不是当前元素数量
|
|||
|
|
4. `foreach` 遍历时一般不要直接改集合内容
|
|||
|
|
5. 下标访问、`Insert`、`RemoveAt` 都要注意越界
|
|||
|
|
|
|||
|
|
错误示例:
|
|||
|
|
```csharp
|
|||
|
|
List<int> list = new List<int>() { 10, 20, 30 };
|
|||
|
|
|
|||
|
|
// Console.WriteLine(list[3]);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
原因:
|
|||
|
|
下标从 `0` 开始
|
|||
|
|
上面这个列表只有 `0`、`1`、`2` 三个有效下标
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 一句话记忆
|
|||
|
|
==List<T> 是长度可变、类型安全、最常用的泛型列表集合。==
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
# 面试/复习时怎么说
|
|||
|
|
可以这样答:
|
|||
|
|
|
|||
|
|
“`List<T>` 是 C# 中非常常用的泛型集合,本质上可以理解成长度可变的数组。
|
|||
|
|
它支持按下标访问,也支持方便的增删查改,比如 `Add`、`Insert`、`Remove`、`RemoveAt`。
|
|||
|
|
相比 `ArrayList`,`List<T>` 类型更安全,通常不需要强制类型转换,所以在实际开发中更常用。”
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
引用:
|
|||
|
|
1. Microsoft Learn: [List<T> Class](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=net-9.0)
|
|||
|
|
2. Microsoft Learn: [System.Collections.Generic Namespace](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic?view=net-9.0)
|
|||
|
|
|
|||
|
|
#List
|
|||
|
|
#泛型集合
|
|||
|
|
#集合
|
|||
|
|
#进阶
|