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
|
||
#泛型集合
|
||
#集合
|
||
#进阶
|