顺序容器----顺序容器操作,vector对象如何增长,额外的string操作,容器适配器
一、顺序容器操作
1、向顺序容器添加元素
向顺序容器(array除外)添加元素的操作:
操作 | 说明 |
c.push_back(t) | 在c的尾部创建一个值为t的元素。返回void |
c.emplace_back(args) | 在c的尾部创建一个由args创建的元素。返回void |
c.push_front(t) | 在c的头部创建一个值为t的元素。返回void |
c.emplace_front(args) | 在c的头部创建一个由args创建的元素。返回void |
c.insert(p, t) | 在迭代器p指向的元素之前创建一个值为t的元素。返回指向新添加的元素的迭代器 |
c.emplace(p, args) | 在迭代器p指向的元素之前创建一个由args创建的元素。返回指向新添加的元素的迭代器 |
c.insert(p, n, t) | 在迭代器p指向的元素之前插入n个值为t的元素。返回指向新添加的第一个元素的迭代器。若n为0,返回p |
c.insert(p, b, e) | 将迭代器b和e指定的范围内的元素插入到迭代器p指向的元素之前。b和e不能指向c中的元素。返回指向新添加的第一个元素的迭代器;若范围为空,返回p |
c.insert(p, items) | items是一个花括号包围的元素值列表。将这些给定值插入到迭代器p指向的元素之前。返回指向新添加的第一个元素的迭代器。若列表为空,则返回p |
forward_list有自己专有版本的insert和emplace。forward_list不支持push_back和emplace_back。vector和string不支持push_front和emplace_front。
向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效。
当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间,而这些策略直接影响性能。在一个vector或string的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。向一个vector或string添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。
1)使用push_back
push_back将一个元素追加到容器的尾部。除array和forward_list之外,每个顺序容器都支持push_back。
当我们用一个对象来初始化容器时,或是将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。
2)使用push_front
forward_list将一个元素插入到元素的头部。list、forward_list和deque支持push_front。
3)使用insert
insert允许我们在容器中任意位置插入0个或多个元素。vector、deque、list和string都支持insert。
4)使用emplace
新标准引入了三个新成员----emplace_front、emplace、emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应push_front、insert、push_back,允许我们将元素放置在容器头部,一个指定位置之前或容器尾部。
当我们调用一个emplace成员函数时,是将参数传递给元素类型的构造函数。emplace使用这些参数在容器管理的内存空间中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数相匹配。
2、访问元素
在顺序容器中访问元素的操作:
at和下标操作只适用于string、vector、deque和array。
back不适用于forward_list。
操作 | 函数 |
c.back() | 返回c中尾元素的引用。若c为空,函数行为未定义 |
c.front() | 返回c中首元素的引用。若c为空,函数行为未定义 |
c[n] | 返回c中下标为n的元素的引用,n是一个无符号整数,若n>=c.size(),则函数行为未定义 |
c.at() | 返回下标为n的元素的引用。如果下标越界,则抛出out_of_range异常 |
注:对一个空容器调用front和back是一种严重的程序设计错误。
1)访问成员函数返回的是引用
在容器中访问元素的成员函数(即front、back、下标和at)返回的都是引用。如果程序时一个const对象,则返回值是const的引用;如果程序不是const的,则返回值是普通引用。
#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <forward_list>
#include <array> int main()
{
std::vector<int> c = { , , };
if (!c.empty())
{
c.front() = ; // 修改第一个元素的值
auto &v = c.back();
v = ; // 修改最后一个元素的值
auto v2 = c.back(); // v2是最后一个元素的拷贝
v2 = ; // 最后一个元素未改变
for (auto iter = c.begin(); iter != c.end(); ++iter)
{
std::cout << (*iter) << " ";
}
std::cout << std::endl;
}
return ;
}
3、删除元素
顺序容器的删除操作:
这些操作会改变容器的大小,所以不适用于array。
forward_list有特殊版本额erase。
forward_list不支持pop_back;vector和string不支持pop_front。
操作 | 说明 |
c.pop_back() | 删除c中尾元素。若c为空,则函数行为未定义。函数返回void |
c.pop_front() | 删除c中首元素。若c为空,则函数行为未定义。函数返回void |
c.erase(p) | 删除迭代器p所指定的元素,返回一个指向被删除元素之后元素的迭代器,若p指向尾元素,则返回尾后迭代器。若p是尾后迭代器,则函数行为未定义 |
c.erase(b, e) | 删除迭代器b和e所指定范围内的元素[b, e)。返回一个指向最后一个被删除元素之后元素的迭代器(即e),若e本身就是尾后迭代器,则函数也返回尾后迭代器 |
c.clear() | 删除c中所有元素。返回void |
删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。指向vector和string中删除点之后位置的迭代器、引用和指针都会失效。
4、特殊的forward_list操作
在forward_list中插入或删除元素的操作:
操作 | 函数 |
lst.before_begin() | 返回指向链表首元素之前不存在的元素的迭代器。此迭代器不能解引用。cbefore_begin()返回一个const_iterator |
lst.cbefore_begin() | |
lst.insert_after(p, t) |
在迭代器p之后的位置插入元素。 t是一个对象,n是数量; b和e是表示范围的一对迭代器(b和e都不能指向lst内); items是一个花括号列表。 返回一个指向最后一个插入元素的迭代器。如果范围为空,则返回p。若p为尾后迭代器,则函数行为未定义 |
lst.insert_after(p, n, t) | |
lst.insert_after(p, b, e) | |
lst.insert_after(p, items) | |
lst.emplace_after(p, args) | 使用args在p指定的位置之后创建一个元素。返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义 |
lst.erase_after(p) | 删除p指向的位置之后的元素,或删除从b之后到e之间的元素(不包含e),若不存在这样的元素,则返回尾后迭代器。如果p指向lst的尾元素或者是一个尾后迭代器,则函数行为未定义 |
lst.erase_after(b, e) |
一个从forward_list中删除奇数的例子:
#include <iostream>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <forward_list>
#include <array> int main()
{
std::forward_list<int> lst = { , , , , , , , , , };
auto prev = lst.before_begin(); // lst的首前元素
auto curr = lst.begin(); // lst的首元素
while (curr != lst.end())
{
if (*curr % )
{
curr = lst.erase_after(prev); // 删除并移动curr
}
else
{
prev = curr;
++curr;
}
}
for (auto iter = lst.begin(); iter != lst.end(); ++iter)
{
std::cout << *iter << " ";
}
std::cout << std::endl;
return ;
}
5、改变容器大小
顺序容器的大小操作:
resize不适用于array。
操作 | 说明 |
c.resize(n) | 调整c的大小为n个元素。若n<c.size(),则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化 |
c.resize(n, t) | 调整c的大小为n个元素。任何新添加的元素都初始化为值t |
如果resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效。对vector、string或deque进行resize()可能导致迭代器、指针和引用失效。
6、容器操作可能使迭代器失效
在容器中添加元素和从容器中删除元素的操作可能会使指向容器元素的指针、引用和迭代器失效。
在向容器添加元素后:
a、如果容器是vector或string,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,则指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针和引用将会失效。
b、对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
c、对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
当我们从一个容器中删除元素后, 指向被删除元素的迭代器、指针和引用会失效。当我们删除一个元素后:
a、对于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
b、对于deque,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、引用和指针也会失效。如果删除deque的尾元素,则尾后迭代器也会失效,但其他迭代器、引用和指针有效;如果删除首元素,这些也不会受影响。
c、对于vector和string,指向被删除元素之前的迭代器、引用和指针仍有效。注意:当我们删除元素时,尾后迭代器总是失效的。
二、vector对象是如何增长的
为了支持快速访问,vector将元素连续存储----每个元素紧挨着前一个元素存储。
当不得不获取新的内存空间时,vector和string的实现通常会分配比新的空间的需求更大的内存空间。容器预留这些空间作为备用,可用来保存更多的新元素。这样,就不需要每次添加新元素都重新分配内存空间了。只要操作需求没有超过vector的容量,vector就不会重新分配空间。
1、管理容量的成员函数
容器大小管理操作:
shrink_to_fit只适用于vector、string和deque。
capacity和reserve只适用于vector和string。
操作 | 说明 |
c.shrink_to_fit() | 请将capacity()减少为与size()相同大小 |
c.capacity() | 不重新分配内存空间的话,c可以保存多少元素 |
c.reserve(n) | 分配至少能容纳n个元素的内存空间 |
reserve并不改变容器中元素的数量,它既能影响vector预先分配多大的内存空间。
只有当需要的内存空间超过当前容量时,reserve调用才会改变vector的容量。如果需求大小大于当前容量,reserve至少分配与需求一样大的空间(可能更大)。如果需求大小小于当前容量,reserve什么也不做。
调用该reserve永远也不会减少容器占用的内存空间。类似的,resize成员函数只改变容器中元素的数目,而不是容器的容量(即使用resize不会减少容器预留的内存空间)。
在新标准库中,我们可以调用shrink_to_fit来要求vector、string、deque退回不需要的内存空间。此函数指出我们不再需要多余的内存空间。但是具体的实现可以选择忽略此请求。也就是说,调用shrink_to_fit也并不保证一定退回内存空间。
2)capacity和size
容器的size是指它已经保存的元素的数目;而capacity则是在不分配新的内存空间的前提下它最多可以保存多少元素。
三、额外的string操作
1、构造string的其他方法
string类型还支持另外三个构造函数:
操作 | 说明 |
n、len2和pos2都是无符号值 | |
string s(p, n) | s是p指向的数组中的前n个字符的拷贝。此数组至少应该包含n个字符 |
string s(s2, pos2) | s是string s2从下标pos2开始的字符的拷贝。若pos2>s.size(),构造函数的行为未定义 |
string s(s2, pos2, len2) | s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。不管len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符 |
1)substr操作
子字符串操作:
操作 | 说明 |
s.substr(pos, n) | 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0。n的默认值为s.size()-ps,即拷贝从pos开始的所有字符 |
如果pos超过了string的大小,则substr函数抛出一个out_of_range异常。如果pos+n大于string的大小,则substr只拷贝到string的末尾。
2、改变string的其他方法
string类型除了支持顺序容器的赋值运算符以及assign、insert和erase操作外,还定义了额外的insert和erase版本。
修改string的其他操作:
操作 | 说明 |
s.insert(pos, args) | 在pos之前插入args指定的字符。pos可以是一个下标或迭代器。接受下标的版本返回一个指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器 |
s.erase(pos, len) | 删除从位置pos开始的len个字符。如果len被省略,则删除从pos开始至s末尾的所有字符。返回一个指向s的引用 |
s.assign(args) | 将s中的字符替换为args指定的字符。返回一个指向s的引用 |
s.append(args) | 将args追加到s。返回一个指向s的引用 |
s.replace(range, args) | 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。返回一个指向s的引用 |
args可以是下列形式之一;append和assign可以使用所有形式。 str不能与s相同,迭代器b和e不能指向s。 |
|
str | 字符串str |
str, pos, len | str中从pos开始最多len个字符 |
cp, len | 从cp指向的字符数组的前(最多)len个字符 |
cp | cp指向的以空字符结尾的字符数组 |
n, c | n个字符c |
b, e | 迭代器b和e指定的范围内的字符 |
初始化列表 | 花括号包围的,以逗号分隔的字符列表 |
replace和insert所允许的args形式依赖于range和pos是如何指定的:
replace | replace | insert | insert | args可以是 |
(pos, len, args) | (b, e, args) | (pos, args) | (iter, args) | |
是 | 是 | 是 | 否 | str |
是 | 否 | 是 | 否 | str, pos, len |
是 | 是 | 是 | 否 | cp, len |
是 | 是 | 否 | 否 | cp |
是 | 是 | 是 | 是 | n, c |
否 | 是 | 否 | 是 | b2, e2 |
否 | 是 | 否 | 是 | 初始化列表 |
3、string搜索操作
string搜索操作:
搜索操作返回指定字符出现的下标,如果未找到则返回npos。
操作 | 说明 |
s.find(args) | 查找s中args第一次出现的位置 |
s.rfind(args) | 查找s中args最后一次出现的位置 |
s.find_first_of(args) | 在s中查找args中任何一个字符第一次出现的位置 |
s.find_last_of(args) | 在s中查找args中任何一个字符最后一次出现的位置 |
s.find_first_not_of(args) | 在s中查找第一个不在args中的字符 |
s.find_last_not_of(args) | 在s中查找最后一个不在args中的字符 |
args必须是以下形式之一 | |
c, pos | 从s中位置pos开始查找字符c。pos默认为0 |
s2, pos | 从s中位置pos开始查找字符串s2,。pos默认为0 |
cp, pos | 从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos默认为0 |
cp, pos, n |
从s中位置pos开始查找指针cp指向的数组的前n个字符。pos和n无默认值 |
每个搜索操作都返回一个string::size_type值,表示匹配发生位置的下标。如果搜索失败,则返回一个名为string::npos的静态成员。标准库将string::npos定义为一个const string::size_type类型,并初始化为值-1.由于npos是一个unsigned类型,此初始值意味着npos等于任何string的最大的可能的大小。
4、compare函数
标准库string类型还提供了一组compare函数,根据s是等于、大于还是小于参数指定的字符串,s.compare()返回0、正数、负数。
s.compare()的几种参数形式:
参数形式 | 说明 |
s2 | 比较s和s2 |
pos1, n1, s2 | 将s中从pos1开始的n1个字符与s2进行比较 |
pos1, n1, s2, pos2, n2 | 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较 |
cp | 比较s与cp指向的以空字符结尾的字符数组 |
pos1, n1, cp | 将s中从pos1开始的n1个字符与cp指向的以空字符结尾的字符数组进行比较 |
pos1, n1, cp, n2 | 将s中从pos1开始的n1个字符与指针cp指向的地址开始的n2个字符进行比较 |
5、数值转换
string和数值之间的转换:
操作 | 说明 |
to_string(val) | 一组重载函数,返回数值val的string表示。val可以是任何算术类型。对每个浮点类型和int或更大的整型,都有相应版本的to_string。与往常一样,小整型会被提升 |
stoi(s, p, b) |
返回s的起始子串(表示子串)的数值,返回类型分别是int、long、unsigned long、long long、unsigned long long。 b表示转换所用的基数,默认值为10。p是size_t指针,用来保存s中第一个非数值字符的下标,p默认为0,即函数不保存下标 |
stol(s, p, b) | |
stoul(s, p, b) | |
stoll(s, p, b) | |
stoull(s, p, b) | |
stof(s, p) | 返回s的起始子串(表示浮点数内容)的数值,返回值类型分别是float、double或long double。参数p的作用与整数转换函数中一样 |
stod(s, p) | |
stold(s, p) |
四、容器适配器
标准库定义了三个顺序容器适配器:stack、queue、priority_queue。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器接受一种已有的容器类型,使其行为看起来像一种不同的行为。
所有容器适配器都支持的操作和类型:
操作或类型 | 说明 |
size_type | 一种类型,足以保存当前类型的最大对象的大小 |
value_type | 元素类型 |
container_type | 实现适配器的底层容器类型 |
A a; | 创建一个名为a的空适配器 |
A a(c); | 创建一个名为a的适配器,带有容器c的一个拷贝 |
关系运算符 |
每个适配器都支持所有关系运算符:==、!=、<、<=、>、>= 这些运算符返回底层容器的比较结果 |
a.empty() | 若a包含任何元素,返回false。否则返回true |
a.size() | 返回a中元素的数目 |
swap(a,b) | 交换a和b的内容,a和b具有相同类型,包括底层容器类型也必须相同 |
a.swap(b) |
1)定义一个适配器
默认情况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。我们可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。
#include <iostream>
#include <string>
#include <stack>
#include <queue> int main()
{
std::deque<int> deq = { , , };
std::stack<int> stk(deq); std::stack<int, std::vector<int>> stk1; // 在vector上实现的空栈
return ;
}
对于一个给定的适配器,可以使用哪些容器是有限制的。所有适配器都要求容器具有添加和删除元素的能力。因此,适配器不能勾键在array之上。类似的,我们也不能使用forward_list来构造适配器,因为所有适配器都要求容器具有添加、删除以及访问尾元素的能力。
2)栈适配器
栈适配器特有的操作:
栈默认基于deque实现,也可以在list或vector之上实现。
操作 | 说明 |
s.pop() | 删除栈顶元素,但不返回该元素值 |
s.push(item) | 创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或者由args构造 |
s.emplace(args) | |
s.top() | 返回栈顶元素,但不将元素弹出栈 |
每个容器适配器都基于底层容器类型的操作定义了自己的特殊操作。我们只可以使用适配器操作,而不能使用底层容器类型的操作。
3)队列适配器
queue和priority_queue适配器定义在queue头文件中。他们所支持的操作:
queue默认基于deque实现,也可以用list或vector实现。
priority_queue默认基于vector实现,也可以用deque实现。
操作 | 说明 |
q.pop() | 删除queue的首元素或priority_queue的最高优先级的元素,但不返回此元素 |
q.front() | 返回首元素或尾元素,但不删除此元素 |
q.back() | (只适用于queue) |
q.top() | 返回最高优先级元素,但不删除该元素(只适用于priority_queue) |
q.push(item) | 在queue末尾或priority_queue中恰当的位置创建一个元素,其值为item,或者由args构造 |
q.emplace(args) |
顺序容器----顺序容器操作,vector对象如何增长,额外的string操作,容器适配器的更多相关文章
- 容器大小的改变以及容器操作可能使迭代器失效、vector对象的容量变化
1 改变容器的大小 我们可以使用resize来增加或缩小容器,与往常一样,array不支持resize.如果当前大小大于所要求的大小,容器后面的元素会被删除:如果当前大小小于新大小,会将新元素添加到容 ...
- 用vector容器代替数组 ——使用数组初始化vector对象
在C++中,我们不能用数组直接初始化另一数组,而只能创建新的数组,然后显式的把原数组的元素逐个复制给新的数组. 按照C语言中的做法: const size_t arry_size=6; int int ...
- C++进阶 STL(2) 第二天 一元/二元函数对象、一元/二元谓词、stack容器、queue容器、list容器(双向链表)、set容器、对组、map容器
01 上次课程回顾 昨天讲了三个容器 string string是对char*进行的封装 vector 单口容器 动态数组 deque(双端队列) 函数对象/谓词: 一元函数对象: for_each ...
- httpServletRequest对象、filter、servlet、servlet容器、catalina、tomcat、以及web容器之间的关系
学习servlet的时候经常感到疑惑 HttpServletRequest是服务器创建的?还是servlet容器创建的? 过滤器是服务器创建的?还是servlet容器创建的? serlet容器和tom ...
- 用AS3清空容器下所有子显示对象
容器中的子显示对象分为两类: 处于显示列表中的子显示对象.被numChildren所记录的. 由容器graphics对象绘制出来的矢量图.这个矢量图不属于Shape类型,不在容器的显示列表中,不被nu ...
- 【2017-05-03】winform打印控件、事件对象和事件数据、MDI窗体容器
一.打印控件 第一步先把打印对象搞出来. - printDocument 打印对象(将要打印的内容放到该对象里,从该对象里取内容打印) 设置他的PrintPage事件(对于要打印的每一页触发一次 ...
- webApi core2 DI通过代码来获取容器里面已注入的对象
请求服务 来自 HttpContext 的一次 ASP.NET 请求中可用的服务通过 RequestServices 集合公开的. 请求服务将你配置的服务和请求描述为应用程序的一部分.当你的对象指定依 ...
- 实现现下列哪一种接口的对象,并不需要在web.xml文件内进行额外的设定,Servlet容器就能够回应该对象加入HTTP会话所发生的事件?(选择1项)
实现现下列哪一种接口的对象,并不需要在web.xml文件内进行额外的设定,Servlet容器就能够回应该对象加入HTTP会话所发生的事件?(选择1项) A.ServletContextListener ...
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
随机推荐
- activity 运行流程图
- CentOS7下搭建LAMP+FreeRadius+Daloradius Web管理
注意:本文所有命令均在root命令下执行. freeradius服务官网:http://freeradius.org/ daloradius Web管理页面官网:https://sourceforge ...
- 力扣(LeetCode)728. 自除数
自除数 是指可以被它包含的每一位数除尽的数. 例如,128 是一个自除数,因为 128 % 1 == 0,128 % 2 == 0,128 % 8 == 0. 还有,自除数不允许包含 0 . 给定上边 ...
- ubuntu vscode chrome 显示color emoji
win10 下vscode默认就可以显示color emoji, 真是亲儿子啊. 但linux下默认是显示黑白的. 绕了一些弯路之后,发现最简单的办法是: 1 下载google noto字体全集 ...
- Python3 操作Excel
首先说明一下 在处理大文件时,openpyxl 的性能不如 xlrd,xlwt等.所以可以读取的时候使用xlrd,写的时候用openpyxl. 今天遇到一个使用场景:excel存放的是一条条用例,包含 ...
- 第 5 章 网络 - 031 - none和host网络的适用场景
none和host网络的适用场景 Docker 安装时会自动在 host 上创建三个网络,用 docker network ls 命令查看: root@ubuntu:~# docker network ...
- 最大的矩形面积 Maximal Rectangle
2018-09-15 10:23:44 一.Largest Rectangle in Histogram 在求解最大的矩形面积之前,我们先讨论一条最大直方图面积的问题. 问题描述: 问题求解: 解法一 ...
- Linux 各种软件的安装-Apache + php 篇
mysql装好了,三剑客还差两个 apache就简单多了. yum -y install httpd # 删除默认欢迎页面 rm -f /etc/httpd/conf.d/welcome.conf s ...
- C# 读取word2003 并且显示在界面上的方法
1.新建一个windows窗体程序 2. 引入包WinWordControl.dll 3.添加引用 4.引入组件WinWordControl组件 5.主界面上加入按钮 ,opendialog, win ...
- 20165327 2017-2018-2《Java程序设计》课程总结
20165327 2017-2018-2<Java程序设计>课程总结 预备作业一:我期望的师生关系 预备作业二:学习基础和C语言基础调查 预备作业三:预备作业3 Linux安装及学习 第一 ...