STL入门:像搭积木一样轻松编程”

C++ STL入门:像搭积木一样轻松编程

大家好~今天我们要解锁C++编程里的“神器”——STL(标准模板库)。很多同学刚学C++时,都会有一个烦恼:想存一组数据,要自己写数组;想排序,要自己写循环;想查找,还要自己一个个比对,又麻烦又容易出错。

而STL,就相当于给我们准备了一整套 “现成的积木” :不用你自己动手做积木(写基础功能),直接拿过来拼一拼,就能快速完成复杂的编程任务。比如,要存1000个学生的成绩,不用纠结数组开多大,STL的 vector 会帮你自动扩容;要给这1000个成绩排序,不用写几十行排序代码,STL的 sort 函数一句话就能搞定。


一、先搞懂:STL到底是什么?

其实STL一点都不神秘,我们可以把它想象成一个 “程序员专属工具箱” 。这个工具箱里有三类核心“工具”,分工明确、配合默契,能帮我们解决几乎所有“数据处理”相关的问题。

先看一张表格,快速get三类工具的作用,后续我们逐一拆解:

STL核心组件 作用(通俗版) 生活类比
容器(Containers) 用来“装数据”的容器,相当于数据的“家” 书包、抽屉、快递盒、书架
算法(Algorithms) 用来“操作数据”的工具,比如排序、查找、修改 计算器、整理书包的方法、查找书本的技巧
迭代器(Iterators) 用来“访问容器里的数据”,相当于“手” 你的手指(用来翻书包、拿书本)

核心逻辑:用「容器」装数据,用「迭代器」拿数据,用「算法」处理数据——三者配合,搞定所有数据操作!

举个最直观的例子:你想整理书包里的书本(数据)。书包就是「容器」,用来装书本;你的手就是「迭代器」,用来拿出、放回书本;你把书本按大小排序的方法,就是「算法」。STL做的事情,就是把“书包、手、排序方法”都给你准备好了,你直接用就行。


二、4个常用容器:前置汇总表(必看)

容器是STL的核心,入门阶段我们重点掌握4个最常用的容器:vector(动态数组)、queue(队列)、stack(栈)、map(键值对映射)。为了让大家先有整体认知,先给大家整理好「容器用法、特点、易错点、注意事项」汇总表,后续我们逐一讲解每个容器的细节和代码示例:

容器类型 核心用法(常用方法) 核心特点 易错点(新手必避) 注意事项
vector(动态数组) 1. 创建:vector<类型> 名称;
2. 添加:push_back(元素)
3. 访问:[]at()
4. 遍历:普通for/范围for/迭代器
5. 清空:clear()
6. 大小:size()
✅ 动态扩容
✅ 随机访问高效
❌ 中间插入/删除低效
❌ 用[]访问越界
❌ 误以为push_back()插入到任意位置
❌ 清空后用[]访问元素
1. 需包含头文件 #include <vector>
2. 优先用at()访问(越界会报错)
3. 频繁中间插入建议换list
queue(队列) 1. 创建:queue<类型> 名称;
2. 入队:push(元素)
3. 出队:pop()
4. 访问队首:front()
5. 访问队尾:back()
6. 大小:size()、判空:empty()
✅ 先进先出(FIFO)
✅ 队首/队尾访问高效
❌ 不能随机访问
❌ 用[]访问队列元素
❌ 队空时调用front()/back()
❌ 出队后误以为元素还存在
1. 需包含头文件 #include <queue>
2. pop()只出队,不返回元素
3. 遍历只能通过出队实现(会清空队列)
stack(栈) 1. 创建:stack<类型> 名称;
2. 压栈:push(元素)
3. 出栈:pop()
4. 访问栈顶:top()
5. 大小:size()、判空:empty()
✅ 后进先出(LIFO)
✅ 栈顶操作高效
❌ 不能访问栈底/中间元素
❌ 用[]访问栈元素
❌ 栈空时调用top()
❌ 混淆push/pop的顺序
1. 需包含头文件 #include <stack>
2. pop()只出栈,不返回元素
3. 适合只需要操作末尾元素的场景
map(键值对) 1. 创建:map<键类型,值类型> 名称;
2. 添加:map[键] = 值
3. 访问:map[键] 或 迭代器
4. 遍历:范围for/迭代器
5. 删除:erase(键)
6. 大小:size()
✅ 键唯一,自动排序
✅ 按键查找高效
❌ 不能随机访问
❌ 用不存在的键访问(会自动创建键,值为默认)
❌ 重复添加相同的键(会覆盖原有值)
❌ 误以为map按插入顺序排序
1. 需包含头文件 #include <map>
2. 查找不存在的键用find()更安全
3. 键可以是string、int等,需保证可比较

提醒:这张表是“总览”,大家先有个印象,后续学习每个容器时,对照这张表理解,会更轻松~


三、重点学习:4个最常用的“容器”

前面我们已经通过汇总表了解了4个容器的核心信息,接下来我们逐一拆解每个容器,让大家真正学会使用。

3.1 vector:可以“自动扩容”的神奇书包

我们先从 vector 开始,因为它是STL里最常用、最灵活的容器,相当于一个 “可以自动变大的书包” 。平时我们用普通数组,比如 int arr[10]; ,只能装10个数据,装多了就会“装不下”;但 vector 不一样,你往里面装多少数据,它就自动扩多大,不用你提前考虑“书包够不够大”。

生活类比:你有一个神奇的书包,刚开始只能装5本书,当你装第6本时,书包自动变大,能装10本;装第11本时,又自动变大,能装20本——永远不用担心装不下。

vector核心用法

首先要记住:使用 vector ,必须先包含头文件 #include <vector> ,还要加上 using namespace std;

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
#include <vector>
#include <iostream>
using namespace std;

int main() {
// 1. 创建一个vector(空书包),用来装整数
vector<int> bag; // <int> 表示这个书包只能装整数,也可以装其他类型(比如string)

// 2. 往书包里装东西(尾部添加)
bag.push_back(10); // 装10
bag.push_back(20); // 装20
bag.push_back(30); // 装30
bag.push_back(40); // 装40

// 3. 查看书包里有多少东西
cout << "书包里有 " << bag.size() << " 个数据" << endl; // 输出:4

// 4. 查看书包里的第n个东西(和数组一样用[]访问,索引从0开始)
cout << "书包里第2个数据是:" << bag[1] << endl; // 输出:20(索引1对应第2个)

// 5. 遍历书包里的所有东西(两种方法,推荐第二种)
// 方法1:普通for循环(和数组一样)
cout << "方法1遍历:";
for (int i = 0; i < bag.size(); i++) {
cout << bag[i] << " "; // 输出:10 20 30 40
}
cout << endl;

// 方法2:范围for循环(STL专属,更简单)
cout << "方法2遍历:";
for (int num : bag) { // 把bag里的每个数据,依次赋值给num
cout << num << " "; // 输出:10 20 30 40
}
cout << endl;

// 6. 清空书包(清空所有数据)
bag.clear();
cout << "清空后,书包里有 " << bag.size() << " 个数据" << endl; // 输出:0

return 0;
}

vector核心特点(对照前置汇总表,加深理解)

动态扩容:不用提前指定大小,push_back() 自动添加,满了就扩容。
随机访问:和数组一样用 [] 访问,比如 bag[0]bag[1],速度极快。
尾部操作高效push_back()(添加)、pop_back()(删除尾部数据)都很高效。
中间插入/删除低效:就像在书包中间插一本书,要把后面的书都往后挪,麻烦。

适用场景:存储需要频繁访问、尾部添加数据的场景(比如学生成绩列表、商品价格列表)。


3.2 queue:只能“排队”的食堂窗口(先进先出)

queue 翻译过来是“队列”,它的特点是 「先进先出」——就像食堂打饭的队伍,先排队的人先打饭,后排队的人后打饭,不能插队,也不能从队伍中间拿人。

生活类比:你去食堂打饭,队伍是 queue,你排到队尾(push),前面的人打完饭离开(pop),你只能看到队首的人(front),看不到后面的人;也不能中途插到队伍中间,更不能从队伍中间走掉。

queue核心用法

使用 queue,需要包含头文件 #include <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
#include <queue>
#include <iostream>
using namespace std;

int main() {
// 1. 创建一个queue(空队伍),用来装整数
queue<int> canteen;

// 2. 往队尾加人(排队)
canteen.push(1); // 第一个人,编号1
canteen.push(2); // 第二个人,编号2
canteen.push(3); // 第三个人,编号3

// 3. 查看队伍里有多少人
cout << "队伍里有 " << canteen.size() << " 个人" << endl; // 输出:3

// 4. 查看队首的人(第一个打饭的人)
cout << "队首的人是:" << canteen.front() << endl; // 输出:1

// 5. 队首的人打饭离开(删除队首元素)
canteen.pop();
cout << "第一个人离开后,队首的人是:" << canteen.front() << endl; // 输出:2

// 6. 遍历队列(注意:queue不能用[]访问,只能一个个出队遍历)
cout << "队伍里的人依次离开:";
while (!canteen.empty()) { // 只要队伍不为空
cout << canteen.front() << " "; // 输出队首的人
canteen.pop(); // 队首的人离开
}
cout << endl; // 输出:2 3

return 0;
}

queue核心特点(对照前置汇总表,加深理解)

先进先出(FIFO):先 push 的先 pop,不能插队。
只能访问队首和队尾front()(队首)、back()(队尾),不能访问中间元素。
不能随机访问:不能用 [] 访问任意位置的元素,也不能遍历中间元素。

适用场景:需要“排队”处理的场景(比如任务队列、消息队列、模拟排队场景)。


3.3 stack:只能“叠盘子”的货架(后进先出)

stack 翻译过来是“栈”,它的特点是 「后进先出」——就像一叠盘子,你最后放上去的盘子,最先拿下来;最下面的盘子,最后才能拿下来。

生活类比:你把盘子一个个叠在货架上,叠的时候从下往上叠(push),拿的时候从上往下拿(pop),永远只能拿到最上面的那个盘子(top),不能直接拿中间或最下面的盘子。

stack核心用法

使用 stack,需要包含头文件 #include <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
#include <stack>
#include <iostream>
using namespace std;

int main() {
// 1. 创建一个stack(空货架),用来装整数
stack<int> plates;

// 2. 往货架上叠盘子(压栈)
plates.push(10); // 第一个盘子(最下面)
plates.push(20); // 第二个盘子(中间)
plates.push(30); // 第三个盘子(最上面)

// 3. 查看货架上有多少个盘子
cout << "货架上有 " << plates.size() << " 个盘子" << endl;
// 输出:3

// 4. 查看最上面的盘子(栈顶)
cout << "最上面的盘子是:" << plates.top() << endl;
// 输出:30

// 5. 拿走最上面的盘子(出栈)
plates.pop();
cout << "拿走最上面的盘子后,最上面的是:" << plates.top() << endl; // 输出:20

// 6. 遍历栈(和queue一样,只能一个个出栈遍历)
cout << "盘子依次被拿走:";
while (!plates.empty()) { // 只要货架不为空
cout << plates.top() << " "; // 输出最上面的盘子
plates.pop(); // 拿走最上面的盘子
}
cout << endl; // 输出:20 10

return 0;
}

stack核心特点(对照前置汇总表,加深理解)

后进先出(LIFO):最后 push 的先 pop,先 push 的后 pop
只能访问栈顶top(),不能访问中间或栈底元素。
不能随机访问:不能用 [] 访问,也不能遍历中间元素。

适用场景:需要“后进先出”的场景(比如括号匹配、函数调用栈、撤销操作)。


3.4 map:一本“智能字典”(键值对映射)

map 是最实用的“关联式容器”,它的核心是 「键值对」——就像一本字典,“单词”是 「键(key)」,“解释”是 「值(value)」,你通过“单词”(键),能快速找到“解释”(值),而且每个单词(键)只能有一个解释(值)。

生活类比:你的手机通讯录,姓名是 「键」,电话号码是 「值」;你输入姓名(键),就能快速找到对应的电话号码(值),不会有两个相同的姓名(键)对应不同的号码(值)。

map核心用法

使用 map,需要包含头文件 #include <map>,而且 map 的键和值可以是不同的类型(比如键是 string,值是 int)。

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
#include <map>
#include <string>
#include <iostream>
using namespace std;

int main() {
// 1. 创建一个map(空字典),键是string(姓名),值是int(电话号码)
map<string, int> phoneBook;

// 2. 往字典里添加键值对(存通讯录)
phoneBook["张三"] = 13812345678;
phoneBook["李四"] = 13987654321;
phoneBook["王五"] = 13711112222;

// 3. 查看字典里有多少个键值对
cout << "通讯录里有 " << phoneBook.size() << " 个人" << endl; // 输出:3

// 4. 通过键查找值(查电话号码)
cout << "张三的电话号码是:" << phoneBook["张三"] << endl; // 输出:13812345678

// 5. 遍历字典(两种方法,推荐第一种)
// 方法1:范围for循环(遍历所有键值对)
cout << "通讯录所有信息:" << endl;
for (auto& pair : phoneBook) { // pair是键值对,pair.first是键,pair.second是值
cout << "姓名:" << pair.first << ",电话:" << pair.second << endl;
}
// 输出:
// 姓名:李四,电话:13987654321
// 姓名:王五,电话:13711112222
// 姓名:张三,电话:13812345678(map会自动按键排序)

// 方法2:迭代器遍历(后续讲迭代器时再详细说)
cout << "用迭代器遍历:" << endl;
for (map<string, int>::iterator it = phoneBook.begin(); it != phoneBook.end(); it++) {
cout << "姓名:" << it->first << ",电话:" << it->second << endl;
}

// 6. 删除某个键值对(删除通讯录中的人)
phoneBook.erase("李四");
cout << "删除李四后,通讯录有 " << phoneBook.size() << " 个人" << endl; // 输出:2

return 0;
}

map核心特点(对照前置汇总表,加深理解)

键值对映射:通过键快速查找值,时间效率高。
键唯一:每个键只能对应一个值,不能重复(比如不能有两个“张三”)。
自动排序map 会自动按“键”的顺序排序(比如字符串按字母顺序)。
不能随机访问:不能用 [] 访问任意位置,只能通过键查找。

适用场景:需要“键值对应”、快速查找的场景(比如通讯录、学生成绩查询、统计数据)。


四、迭代器:访问容器的“万能手”

前面我们讲了容器,也讲了如何遍历容器,但有一个问题:vector 可以用 [] 访问,queuestack 不能;vector 可以用范围for循环,那如果我们想更灵活地访问容器(比如只遍历前半部分、在遍历中修改数据),该怎么办?

这时候,**「迭代器」**就派上用场了。迭代器相当于 “访问容器的万能手” ,不管是什么容器,都能用迭代器来访问、遍历,用法统一,非常方便。

生活类比:迭代器就像你的手指,不管是书包(vector)、队伍(queue)、货架(stack),还是字典(map),你都能用手指去触摸、拿取里面的东西——只是不同容器,手指能做的动作不一样(比如 queue 只能摸队首,stack 只能摸栈顶)。

迭代器核心用法(入门)

迭代器的用法很简单,核心就是3步:获取迭代器 → 移动迭代器 → 访问迭代器指向的数据。我们以 vector 为例,讲解迭代器的基本用法(其他容器用法类似):

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
#include <vector>
#include <iostream>
using namespace std;

int main() {
vector<int> bag = {10, 20, 30, 40, 50};

// 1. 获取迭代器(begin()是容器第一个元素,end()是容器最后一个元素的“后面”)
vector<int>::iterator it = bag.begin(); // it指向bag的第一个元素(10)

// 2. 移动迭代器(++it 移动到下一个元素,--it 移动到上一个元素)
it++; // 移动到第二个元素(20)
cout << "当前迭代器指向:" << *it << endl; // *it 访问迭代器指向的数据,输出:20

it += 2; // 移动2步,到第四个元素(40)
cout << "移动2步后指向:" << *it << endl; // 输出:40

// 3. 用迭代器遍历容器(最通用的遍历方式)
cout << "用迭代器遍历容器:";
for (vector<int>::iterator it = bag.begin(); it != bag.end(); it++) {
cout << *it << " "; // 输出:10 20 30 40 50
}
cout << endl;

return 0;
}

迭代器小总结(入门)

✅ 所有容器都有 begin()end(),用来获取迭代器。
✅ 用 *it 访问迭代器指向的数据(类似指针的用法)。
✅ 不同容器的迭代器功能不同:vector 的迭代器可以随意移动(++--+=),queuestack 的迭代器只能访问队首/栈顶(其实它们没有迭代器,只能通过 front/top 访问)。


五、算法:STL的“超级工具”

前面我们学了容器(装数据)和迭代器(拿数据),现在学最后一个核心组件——算法(处理数据)。STL的算法是“现成的工具”,不用你自己写代码,一句话就能完成排序、查找、求和等复杂操作。

生活类比:算法就像你手机里的“快捷功能”——不用你手动操作,点一下就能完成任务(比如一键排序照片、一键查找文件)。

入门阶段,我们掌握3个最常用的算法就够了:sort(排序)、find(查找)、accumulate(求和)。

5.1 sort:一键排序(最常用!)

sort 是STL里最常用的算法,用来给容器中的数据排序,默认是升序(从小到大),也可以自定义排序规则(比如降序)。使用 sort,需要包含头文件 #include <algorithm>,用法是:sort(容器.begin(), 容器.end())

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
#include <vector>
#include <algorithm> // 包含sort头文件
#include <iostream>
using namespace std;

int main() {
vector<int> scores = {85, 92, 78, 95, 88};

// 1. 默认升序排序(从小到大)
sort(scores.begin(), scores.end());
cout << "升序排序后:";
for (int score : scores) {
cout << score << " "; // 输出:78 85 88 92 95
}
cout << endl;

// 2. 自定义降序排序(从大到小)
sort(scores.begin(), scores.end(), [](int a, int b) {
return a > b; // a > b 表示降序,a < b 表示升序
});
cout << "降序排序后:";
for (int score : scores) {
cout << score << " "; // 输出:95 92 88 85 78
}
cout << endl;

return 0;
}

5.2 find:一键查找

find 算法用来在容器中查找指定的数据,找到后返回指向该数据的迭代器,找不到则返回容器的 end()

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
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;

int main() {
vector<string> names = {"张三", "李四", "王五", "赵六"};

// 查找“王五”
auto it = find(names.begin(), names.end(), "王五");

if (it != names.end()) {
cout << "找到了:" << *it << endl; // 输出:找到了:王五
} else {
cout << "没找到" << endl;
}

// 查找“孙七”
auto it2 = find(names.begin(), names.end(), "孙七");
if (it2 != names.end()) {
cout << "找到了:" << *it2 << endl;
} else {
cout << "没找到" << endl; // 输出:没找到
}

return 0;
}

5.3 accumulate:一键求和

accumulate 算法用来计算容器中所有数据的和,需要包含头文件 #include <numeric>,用法是:accumulate(容器.begin(), 容器.end(), 初始值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>
#include <numeric> // 包含accumulate头文件
#include <iostream>
using namespace std;

int main() {
vector<int> scores = {85, 92, 78, 95, 88};

// 求和,初始值为0(如果是小数,初始值可以设为0.0)
int sum = accumulate(scores.begin(), scores.end(), 0);
cout << "成绩总和:" << sum << endl; // 输出:438

// 计算平均分
double average = (double)sum / scores.size();
cout << "成绩平均分:" << average << endl; // 输出:87.6

return 0;
}

六、综合练习:用STL做一个“学生成绩管理小工具”

学完了容器、迭代器、算法,我们来做一个综合实战,把所学知识串起来——实现一个简单的学生成绩管理工具,包含3个功能:添加成绩、排序成绩、查询成绩

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
#include <vector>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;

// 定义学生结构体(存储姓名和成绩)
struct Student {
string name; // 姓名
int score; // 成绩
};

int main() {
vector<Student> students; // 用vector存储学生信息
int choice;

do {
// 菜单
cout << "\n=== 学生成绩管理工具 ===" << endl;
cout << "1. 添加学生成绩" << endl;
cout << "2. 按成绩降序排序" << endl;
cout << "3. 查询学生成绩" << endl;
cout << "0. 退出" << endl;
cout << "请选择:";
cin >> choice;

switch (choice) {
case 1: {
// 1. 添加学生成绩
Student s;
cout << "请输入学生姓名:";
cin >> s.name;
cout << "请输入学生成绩:";
cin >> s.score;
students.push_back(s);
cout << "添加成功!" << endl;
break;
}
case 2: {
// 2. 按成绩降序排序(自定义排序规则)
sort(students.begin(), students.end(), [](Student a, Student b) {
return a.score > b.score; // 按成绩降序
});
cout << "排序成功!排序结果:" << endl;
for (auto& s : students) {
cout << "姓名:" << s.name << ",成绩:" << s.score << endl;
}
break;
}
case 3: {
// 3. 查询学生成绩
string name;
cout << "请输入要查询的学生姓名:";
cin >> name;
// 用find_if查找(需要自定义查找规则,因为是结构体)
auto it = find_if(students.begin(), students.end(), [&](Student s) {
return s.name == name;
});
if (it != students.end()) {
cout << name << "的成绩是:" << it->score << endl;
} else {
cout << "未找到该学生!" << endl;
}
break;
}
case 0:
cout << "退出程序,再见!" << endl;
break;
default:
cout << "输入错误,请重新选择!" << endl;
}
} while (choice != 0);

return 0;
}

运行效果:你可以添加多个学生的成绩,排序后查看排名,也可以查询某个学生的成绩。


七、核心总结+入门避坑指南

7.1 核心知识点总结

  1. STL三大组件:容器(装数据)、迭代器(拿数据)、算法(处理数据)。
  2. 4个常用容器(对照前置汇总表复习):
    • vector:动态数组,随机访问方便,适合存列表数据。
    • queue:先进先出,适合排队场景。
    • stack:后进先出,适合栈操作场景。
    • map:键值对映射,适合快速查找场景。
  3. 3个常用算法
    • sort(排序)
    • find(查找)
    • accumulate(求和)
  4. 核心思想:不用重复造轮子,用好STL,提升编程效率,减少错误。

7.2 入门避坑指南(必看)

忘记包含头文件:用 vector 要加 <vector>,用 sort 要加 <algorithm>,否则会报错。
用错容器:比如需要随机访问,却用了 queue;需要快速查找,却用了 vector
混淆迭代器的 begin()end()end() 不是最后一个元素,而是最后一个元素的“后面”,遍历的时候要写 it != end()
在容器为空时调用 front()back()top():会导致未定义行为,务必先用 empty() 检查。
map 时,通过 [] 访问不存在的键:会自动创建该键,可能不是你想要的。建议用 find() 来检查是否存在。

建议:先把 vectorsort 用熟,这两个是STL里最常用的,80%的入门情况都能搞定。


好了,同学们,STL的入门内容就到这里!是不是觉得STL其实并不神秘?它就像一套现成的工具,你只需要知道每个工具是干什么的、怎么用,就能轻松解决编程中的数据处理问题。赶快打开你的编译器,动手敲一敲上面的代码,亲自感受一下STL的威力吧!


STL入门:像搭积木一样轻松编程”
https://www.psnow.sbs/2026/03/03/STL入门:像搭积木一样轻松编程”/
作者
Psnow
发布于
2026年3月3日
许可协议