《C++ Primer》第9章 顺序容器

9.3节顺序容器操作习题答案

练习9.18:编写程序,从标准输入读取string序列,存入一个deque中。编写一个循环,用迭代器打印deque中的元素。

【出题思路】

本题练习向容器中添加元素,继续练习遍历容器中的元素。重点:不同容器在不同位置添加元素的性能是有差异的。

【解答】

对deque来说,在首尾位置添加新元素性能最佳,在中间位置插入新元素性能会很差。对遍历操作,可高效完成。

#include <iostream>
#include <deque>
#include <string>using namespace std;int main()
{deque<string> sd;       //string的dequestring word;while(cin >> word)      //读取字符串,直至遇到文件结束符{sd.push_back(word);}cout << "word===" << word << endl;cout << "sd.size====" << sd.size() << endl;//用cbegin()获取deque首元素迭代器,遍历deque中所有元素for(auto si = sd.cbegin(); si != sd.cend(); si++)cout << *si << endl;return 0;
}

运行结果:

练习9.19:重写上题的程序,用list替代deque。列出程序要做出哪些改变。

【出题思路】

练习不同容器的添加操作的异同。

【解答】

对list来说,在任何位置添加新元素都有很好的性能,遍历操作也能高效完成,因此程序与上一题并无太大差异。

#include <iostream>
#include <list>
#include <string>using namespace std;int main()
{list<string> sd;       //string的dequestring word;while(cin >> word)      //读取字符串,直至遇到文件结束符{sd.push_back(word);}//用cbegin()获取deque首元素迭代器,遍历deque中所有元素for(auto si = sd.cbegin(); si != sd.cend(); ++si)cout << *si << endl;cout << "hello world" << endl;return 0;
}

运行结果:

练习9.20:编写程序,从一个list<int>拷贝元素到两个deque中。值为偶数的所有元素都拷贝到一个deque中,而奇数值元素都拷贝到另一个deque中。

【出题思路】

这是一个很简单的数据处理问题的练习。读者可练习多个容器间数据的处理、拷贝。

【解答】

通过遍历list<int>,可检查其中每个元素的奇偶性,并用push_back分别添加到目的deque的末尾。程序中用位与运算检查元素最低位的值,若为1,表明是奇数,否则即为偶数。

#include <iostream>
#include <deque>
#include <list>using namespace std;int main()
{list<int> ilist = {1, 2, 3, 4, 5, 6, 7, 8};//初始化整数listdeque<int> odd_d, even_d;//遍历整数listfor(auto iter = ilist.cbegin(); iter != ilist.cend(); ++iter){if(*iter & 1)//查看最低位,1:奇数,0:偶数odd_d.push_back(*iter);elseeven_d.push_back(*iter);}cout << "奇数值有:";for(auto iter = odd_d.cbegin(); iter != odd_d.cend(); ++iter)cout << *iter << " ";cout << endl;cout << "偶数值有:";for(auto iter = even_d.cbegin(); iter != even_d.cend(); ++iter)cout << *iter << " ";cout << endl;return 0;
}

运行结果:

练习9.21:如果我们将第308页中使用insert返回值将元素添加到list中的循环程序改写为将元素插入到vector中,分析循环将如何工作。

【出题思路】

本题练习用insert向容器中添加元素的方法。理解这是最通用的方法,可以实现push_back和push_front这些特殊插入操作的效果。

【解答】

在循环之前,vector为空,此时将iter初始化为vector首位置,与初始化为尾后位置效果是一样的。循环中第一次调用insert会将读取的第一个string插入到iter指向位置之前的位置,即,令新元素成为vector的首元素。而insert的返回指向此元素的迭代器,我们将它赋予iter,从而使得iter始终指向vector的首元素。接下来的每个循环步均是如此,将新string插入到vector首元素之前的位置,成为新的首元素,并使iter始终指向vector首。这样,string在vector排列的顺序将与它们的输入顺序恰好相反。整个循环执行的过程和最后的结果都与list版本没有什么区别。但要注意,在list首元素之前插入新元素性能很好,但对于vector,这样的操作需要移动所有现有元素,导致性能很差。

#include <iostream>
#include <vector>
#include <string>using namespace std;int main()
{vector<string> svec;//string的vectorstring word;auto iter = svec.begin();//获取vector首位置迭代器while(cin >> word)      //读取字符串,直至遇到文件结束符{//cout << "iter old======" << *iter << endl;iter = svec.insert(iter, word);//更新iter的位置,指向的新的元素cout << "iter new======" << *iter << endl;}cout << "svec.size======" << svec.size() << endl;//用cbegin()获取vector首元素迭代器,遍历vector中所有元素for(auto iter = svec.cbegin(); iter != svec.cend(); ++iter){cout << "*iter=====" << *iter << endl;}cout << endl;return 0;
}

运行结果:

练习9.22:假定iv是一个int的vector,下面的程序存在什么错误?你将如何修改?

vector<int>::iterator iter = iv.begin(), mid = iv.begin() + iv.size() / 2;
while(iter != mid)if(*iter == som_val)iv.insert(iter, 2 * some_val);

【出题思路】

首先,理解容器插入操作的副作用——向一个vector、string或deque插入元素会使现有指向容器的迭代器、引用和指针失效。其次,练习如何利用insert返回的迭代器,使得在向容器插入元素后,仍能正确在容器中进行遍历。

【解答】

循环中未对iter进行递增操作,iter无法向中点推进。其次,即使加入了iter++语句,由于向iv插入元素后,iter已经失效,iter++也不能起到将迭代器向前推进一个元素的作用。修改方法如下:

首先,将insert返回的迭代器赋予iter,这样,iter将指向新插入的元素y。我们知道,insert将y插入到iter原来指向的元素x之前的位置,因此,接下来我们需要进行两次iter++才能将iter推进到x之后的位置。其次,insert()也会使mid失效,因此,只正确设置iter仍不能令循环在正确的时候结束,我们还需设置mid使之指向iv原来的中央元素。在未插入任何新元素之前,此位置是iv.begin()+iv.size()/2,我们将此时的iv.size()的值记录在变量org_size中。然后在循环过程中统计新插入的元素的个数new_ele,则在任何时候,iv.begin()+org_size/2+new_ele都能正确指向iv原来的中央元素。

#include <iostream>
#include <vector>using namespace std;int main()
{vector<int> ivec = {1, 1, 2, 1};//int的vectorint some_val = 1;vector<int>::iterator iter = ivec.begin();int org_size = ivec.size(), new_ele = 0;//原大小和新素个数//每个循环步都重新计算“mid”,保证正确指向ivec中原中央元素while(iter != (ivec.begin() + org_size / 2) + new_ele){if(*iter == some_val){iter = ivec.insert(iter, 2 * some_val);//iter指向新元素new_ele++;iter++;iter++;//将iter推进到旧元素的下一个位置}elseiter++;//简单推进iter}//用begin()犯获取vector首元素迭代器,遍历vector中的所有元素for(iter = ivec.begin(); iter != ivec.end(); ++iter){cout << "*iter============" << *iter << endl;}return 0;
}

运行结果:

练习9.23:在本节第一个程序(第309页)中,若c.size()为1,则val、val2、val3和val4的值会是什么?

【出题思路】

理解获取容器首、尾元素的不同方法。

【解答】

4个变量的值会一样,都等于容器中唯一一个元素的值。

练习9.24:编写程序,分别使用at、下标运算符、front和begin提取一个vector中的第一个元素。在一个空vector上测试你的程序。

【出题思路】

练习获取容器首元素的不同方法,以及如何安全访问容器元素。

【解答】

下面的程序会异常终止。因为vector为空,此时用at访问容器的第一个元素会抛出一个out_of_range异常,而此程序未捕获异常,因此程序会因异常退出。正确的编程方式是,捕获可能的out_of_range异常,进行相应的处理。但对于后三种获取容器首元素的方法,当容器为空时,不会抛出out_of_range异常,而是导致程序直接退出(注释掉前几条语句即可看到后面语句的执行效果)。因此,正确的编程方式是,在采用这几种获取容器的方法时,检查下标的合法性(对front和begin只需检查容器是否为空),确定没有问题后再获取元素。当然这种方法对at也适用。

#include <iostream>
#include <vector>using namespace std;int main()
{vector<int> iv;     //int的vectorcout << iv.at(0) << endl;cout << iv[0] << endl;cout << iv.front() << endl;cout << *(iv.begin()) << endl;return 0;
}

练习9.25:对于第312页中删除一个范围内的元素的程序,如果elem1与elem2相等会发生什么?如果elem2是尾后迭代器,或者elem1和elem2皆为尾后迭代器,又会发生什么?

【出题思路】

理解范围删除操作的两个迭代器参数如何决定删除操作的结果。

【解答】

如果两个迭代器elem1和elem2相等,则什么也不会发生,容器保持不变。哪怕两个迭代器是指向尾后位置(例如end()+1),也是如此,程序也不会出错。因此elem1和elem2都是尾后迭代器时,容器保持不变。如果elem2为尾后迭代器,elem1指向之前的合法位置,则会删除从elem1开始直至容器末尾的所有元素。

练习9.26:使用下面代码定义的ia,将ia拷贝到一个vector和一个list中。使用单迭代器版本的erase从list中删除奇数元素,从vector中删除偶数元素。

int ia[] = {0,1,1,2,3,5,8,21,55,89}

【出题思路】练习删除指定位置元素的操作,理解操作对迭代器的影响。

【解答】

当从vector中删除元素时,会导致删除点之后位置的迭代器、引用和指针失效。而erase返回的迭代器指向删除元素之后的位置。因此,将erase返回的迭代器赋予iiv,使其正确向前推进。且尾后位置每个循环步中都用end重新获得,保证其有效。对于list,删除操作并不会令迭代器失效,但上述方法仍然是适用的。

#include <iostream>
#include <vector>
#include <list>using namespace std;int main()
{int ia[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89};vector<int> iv;list<int> il;iv.assign(ia, ia + 11);  //将数据拷贝到vectoril.assign(ia, ia + 11);  //将数据拷贝到listvector<int>::iterator iiv = iv.begin();while(iiv != iv.end()){if(!*iiv & 1)                   //偶数{iiv = iv.erase(iiv);        //删除偶数,返回下一个位置迭代器}else{++iiv;                      //推进到下一人位置}}list<int>::iterator iil = il.begin();while(iil != il.end()){if(*iil & 1)                    //奇数{iil = il.erase(iil);        //删除奇数,返回下一个位置迭代器}else{++iil;                      //推进到下一人位置}}for(iiv = iv.begin(); iiv != iv.end(); ++iiv)cout << *iiv << " ";cout << endl;for(iil = il.begin(); iil != il.end(); ++iil)cout << *iil << " ";cout << endl;return 0;
}

运行结果:

练习9.27:编写程序,查找并删除forward_list<int>中的奇数元素。

【出题思路】

练习forward_list特殊的删除操作。

【解答】

关键点是理解forward_list其实是单向链表数据结构,只有前驱节点指向后继节点的指针,而没有反向的指针。因此,在forward_list中可以高效地从前驱转到后继,但无法从后继转到前驱。而当我们删除一个元素后,应该调整被删元素的前驱指针指向被删元素的后继,起到将该元素从链表中删除的效果。因此,在forward_list中插入、删除元素既需要该元素的迭代器,也需要前驱迭代器。为此,forward_list提供了before_begin来获取首元素之前位置的迭代器,且插入、删除都是_after形式,即,删除(插入)给定迭代器的后继。

#include <iostream>
#include <forward_list>using namespace std;int main()
{forward_list<int> iflst = {1, 2, 3, 4, 5, 6, 7, 8};auto prev = iflst.before_begin();           //前骊元素auto curr = iflst.begin();                  //当前元素while(curr != iflst.end()){if(*curr & 1)                           //奇数{curr = iflst.erase_after(prev);     //删除,移动到下一个元素}else{prev = curr;                        //前驱和当前迭代器都向前推进++curr;}}for(curr = iflst.begin(); curr != iflst.end(); ++curr)cout << *curr << " ";cout << endl;return 0;
}

运行结果:

练习9.28:编写函数,接受一个forward_list<string>和两个string共三个参数。函数应在链表中查找第一个string,并将第二个string插入到紧接着第一个string之后的位置。若第一个string未在链表中,则将第二个string插入到链表末尾。

【出题思路】

练习forward_list特殊的添加操作。

【解答】

与删除相同的是,forward_list的插入操作也是在给定元素之后。不同的是,插入一个新元素后,只需将其后继修改为给定元素的后继,然后修改给定元素的后继为新元素即可,不需要前驱迭代器参与。但对于本题,当第一个string不在链表中时,要将第二个string插入到链表末尾。因此仍然需要维护前驱迭代器,当遍历完链表时,“前驱”指向尾元素,“当前”指向尾后位置。若第一个string不在链表中,此时只需将第二个string插入到“前驱”之后即可。总体来说,单向链表由于其数据结构上的局限,为实现正确插入、删除操作带来了困难。标准库的forward_list容器为我们提供了一些特性,虽然(与其他容器相比)我们仍需维护一些额外的迭代器,但已经比直接用指针来实现链表的插入、删除方便了许多。

#include <iostream>
#include <forward_list>
#include <string>using namespace std;void test_and_insert(forward_list<string> &sflst, const string &s1, const string &s2)
{auto prev = sflst.before_begin();               //前驱元素auto curr = sflst.begin();                      //当前元素bool inserted = false;while(curr != sflst.end()){if(*curr == s1)                             //找到给定字符串{curr = sflst.insert_after(curr, s2);    //插入新字符串,curr指向它inserted = true;}prev = curr;                                //当驱迭代器向前推进++curr;                                     //当前迭代器向前推进}if(!inserted)sflst.insert_after(prev, s2);               //未找到给定字符串,插入尾后
}int main()
{forward_list<string> sflst = {"Hello", "!", "world", "!"};test_and_insert(sflst, "Hello", "你好");for(auto curr = sflst.cbegin(); curr != sflst.cend(); ++curr)cout << *curr << " ";cout << endl;test_and_insert(sflst, "!", "?");for(auto curr = sflst.cbegin(); curr != sflst.cend(); ++curr)cout << *curr << " ";cout << endl;test_and_insert(sflst, "Bye", "再见");for(auto curr = sflst.cbegin(); curr != sflst.cend(); ++curr)cout << *curr << " ";cout << endl;return 0;
}

运行结果:

练习9.29:假定vec包含25个元素,那么vec.resize(100)会做什么?如果接下来调用vec.resize(10)会做什么?

【出题思路】

本题练习改变容器大小的操作。

【解答】

调用vec.resize(100)会向vec末尾添加75个元素,这些元素将进行值初始化。接下来调用vec.resize(10)会将vec末尾的90个元素删除。

练习9.31:第316页中删除偶数值元素并复制奇数值元素的程序不能用于list或forward_list。为什么?修改程序,使之也能用于这些类型。

【出题思路】

本题继续练习list和forward_list的插入、删除操作,理解与其他容器的不同,理解对迭代器的影响。

【解答】

list和forward_list与其他容器的一个不同是,迭代器不支持加减运算,究其原因,链表中元素并非在内存中连续存储,因此无法通过地址的加减在元素间远距离移动。因此,应多次调用++来实现与迭代器加法相同的效果。

#include <iostream>
#include <list>using namespace std;int main()
{list<int> ilst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};auto curr = ilst.begin();                       //首节点while(curr != ilst.end()){if(*curr & 1)                               //奇数{curr = ilst.insert(curr, *curr);        //插入到当前元素之前++curr;++curr;                                 //移动到下一个元素}else                                        //偶数{curr = ilst.erase(curr);                //删除,指向下一元素}}for(curr = ilst.begin(); curr != ilst.end(); ++curr)cout << *curr << " ";cout << endl;return 0;
}

运行结果:

练习9.32:在第316页的程序中,像下面语句这样调用insert是否合法?如果不合法,为什么?

 iter = vi.insert(iter, *iter++);

【出题思路】

本题复习实参与形参的关系,进一步熟悉迭代器的处理对容器操作的关键作用。

【解答】

很多编译器(例如作者所使用的tdm-gcc)对实参求值、向形参传递的处理顺序是由右至左的。这意味着,编译器在编译上述代码时,首先对*iter++求值,传递给insert第二个形参,此时iter已指向当前奇数的下一个元素,因此传递给insert的第一个参数的迭代器指向的是错误位置,程序执行会发生混乱,最终崩溃。因此,若将代码改为iter = vi.insert(iter++, *iter);,或是使用由左至右求值、传递参数的编译器,代码的运行结果是正确的。当然,这样的代码在逻辑上是毫无道理的。

练习9.33:在本节最后一个例子中,如果不将insert的结果赋予begin,将会发生什么?编写程序,去掉此赋值语句,验证你的答案。

【出题思路】

进一步理解容器插入、删除操作会使迭代器失效。

【解答】

向vector中插入新元素后,原有迭代器都会失效。因此,不将insert()返回的迭代器赋予begin,会使begin失效。继续使用begin会导致程序崩溃。对此程序,保存尾后迭代器和不向begin赋值两个错误存在其一,程序都会崩溃。

练习9.34:假定vi是一个保存int的容器,其中有偶数值也有奇数值,分析下面循环的行为,然后编写程序验证你的分析是否正确。

iter = vi.begin();
while(iter != vi.end())if(*iter % 2)iter = vi.insert(iter, *iter);++iter;

【出题思路】

继续熟悉容器插入、删除操作与迭代器的关系,以及编程中容易出现的错误。

【解答】

此段代码的第一个错误是忘记使用花括号,使得++iter变成循环后的第一条语句,而非所期望的循环体的最后一条语句。因此,除非容器为空,否则程序会陷入死循环:1.若容器的第一个元素是偶数,布尔表达式为假,if语句真分支不会被执行,iter保持不变。循环继续执行,真分支仍然不会执行,iter继续保持不变,如此陷入死循环。

2.若容器的第一个元素是奇数,insert语句被调用,将该值插入到首元素之前,并将返回的迭代器(指向新插入元素)赋予iter,因此iter指向新首元素。继续执行循环,会继续将首元素复制到容器首位置,并令iter指向它,如此陷入死循环。示例(粗体代表迭代器位置):

初始:{1,2,3,4,5,6,7,8,9}

第一步:{1,1,2,3,4,5,6,7,8,9}

第二步:{1,1,1,2,3,4,5,6,7,8,9}

......

下面的程序可展示程序执行效果。其中,我们在循环体最后加入了一个循环,打印容器中所有元素,即可观察程序执行效果。这是一种简单的程序调试方法。cout>> tmp是为了让程序暂停,程序员有时间观察输出,需要继续执行程序时,随意输入一个字符串即可。

#include <iostream>
#include <vector>
#include <string>using namespace std;int main()
{vector<int> vi = {1, 2, 3, 4, 5, 6, 7, 8, 9};auto iter = vi .begin();string tmp;while(iter != vi.end()){if(*iter % 2)iter = vi.insert(iter, *iter);for(auto begin = vi.begin(); begin != vi.end(); ++begin)cout << *begin << " ";cout << endl;cin >> tmp;}++iter;return 0;
}

运行结果:

当我们将++iter放入循环体后,程序仍然是错误的,除非容器为空或仅包含偶数,否则程序仍然会陷入死循环。原因是,当遍历到奇数时,执行insert将该值插入到旧元素之前,将返回指向新元素的迭代器赋予iter,再递增iter,此时iter将指向旧元素。继续执行循环仍会重复这几个步骤,程序陷入死循环。正确的程序应该是将++iter移入循环体,再增加一个++iter,令iter指向奇数之后的元素。示例(粗体代表迭代器位置):

初始:{0,1,2,3,4,5,6,7,8,9}

第一步:{0,1,1,1,2,3,4,5,6,7,8,9}

第二步:{0,1,1,1,1,1,2,3,4,5,6,7,8,9}

.......

#include <iostream>
#include <vector>
#include <string>using namespace std;int main()
{vector<int> vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};auto iter = vi.begin();string tmp;while(iter != vi.end()){if(*iter % 2)iter = vi.insert(iter, *iter);++iter;for(auto begin = vi.begin(); begin != vi.end(); ++begin)cout << *begin << " ";cout << endl;cin >> tmp;}return 0;
}

运行结果:

《C++ Primer》第9章 9.3节习题答案相关推荐

  1. 《C++ Primer》第14章 14.3节习题答案

    <C++ Primer>第14章 操作重载与类型转换 14.3节  算术和关系运算符  习题答案 练习14.13:你认为Sales_data类还应该支持哪些其他算术运算符(参见表4.1,第 ...

  2. 《C++ Primer》第15章 15.4节习题答案

    <C++ Primer>第15章 面向对象程序设计 15.4节 抽象基类 习题答案 练习15.15:定义你自己的Disc_quote和Bulk_quote. [出题思路]本题练习实现不同折 ...

  3. 《C++ Primer》第15章 15.2节习题答案

    <C++ Primer>第15章 面向对象程序设计 本章介绍了面向对象程序设计的两个重要概念:继承和动态绑定,包括: □●继承.基类.派生类的基本概念. □●虚函数和虚基类. □●继承中的 ...

  4. 《C++ Primer》第13章 13.5节习题答案

    <C++ Primer>第13章 拷贝控制 13.5节 动态内存管理类 习题答案 练习13.39:编写你自己版本的StrVec,包括自己版本的reserve.capacity(参见9.4节 ...

  5. 《C++ Primer》第12章 12.3节习题答案

    <C++ Primer>第12章 动态内存 12.3节使用标准库:文本查询程序 习题答案 练习12.27:TextQuery和QueryResult类只使用了我们已经介绍过的语言和标准库特 ...

  6. 《C++ Primer》第5章 5.2节习题答案

    <C++ Primer>第5章 语句 5.2节 语句作用域 练习5.4:说明下列例子的含义,如果存在问题,试着修改它. (a)while(string::iterator iter != ...

  7. C++ Primer - 5th Edition - 书中源代码 - 课后习题答案

    C++ Primer - 5th Edition - 书中源代码 - 课后习题答案 C++ Primer - 5th Edition - 书中源代码 - 课后习题答案 1. C++ Primer, 5 ...

  8. matlab第三章题目,第3章 MATLAB矩阵处理习题答案

    <第3章 MATLAB矩阵处理习题答案>由会员分享,可在线阅读,更多相关<第3章 MATLAB矩阵处理习题答案(3页珍藏版)>请在人人文库网上搜索. 1.第3章 MATLAB矩 ...

  9. 浙大版《C语言程序设计》第四版(何钦铭颜晖) 第4章 循环结构 课后习题答案

    浙大版<C语言程序设计>第四版(何钦铭颜晖) 第4章 循环结构 课后习题答案 你也可以上程序咖(https://meta.chengxuka.com),打开大学幕题板块,不但有答案,讲解, ...

最新文章

  1. linux 保存编译log,(转)Linux下编译安装log4cxx
  2. 键盘鼠标录制哪个好用_好看好用还不贵的那种键盘鼠标真的有吗?这次还真让我碰到了...
  3. idea下快速创建SpringBoot项目
  4. 简述angular中constant和$filter的用法
  5. 全志A33-串口的使用
  6. joo工作流_不要错过使用jOOλ或jOOQ编写Java 8 SQL单行代码的机会
  7. 数据分析工具篇——HQL原理及函数逻辑
  8. 如果你觉得 Git 很迷惑人,那么这份小抄正是为你准备的!
  9. iOSMVVM(Model-View(View/ViewController) -ViewModel ) 设计模式
  10. vue js中解决二进制转图片显示问题
  11. 关于SQL语句中分号的问题
  12. linux入门、常用命令
  13. 电视盒子cpu天梯图 电视盒子CPU性能天梯图2022
  14. E71(S60 3rd)通话录音软件 -终极录音- 的用法
  15. STM32F7 I2S接口 WM8978 边播放 边录音
  16. 通过hutool工具包实现将数据库中的全量数据导出,一张表一个sheet页,包含目录页,目录页和sheet页之间可以互相跳转
  17. Matplotlib坐标轴格式
  18. 软件工程(1) CSDN花神生涯
  19. 计算机应用格式工厂部分教案,格式工厂教学案.doc
  20. 快看影视大全隐私政策

热门文章

  1. 在少儿编程中使用easygui来实现交互(1)——msgbox
  2. 创造与魔法怎么自建服务器,创造与魔法如何搭建出梦想中的房屋
  3. IDEA设置类文件模板@Auther@Date等注释信息——idea笔记
  4. 关于Linux中批量配置SSH免密的一些笔记
  5. 分库分表和 NewSQL 到底怎么选?
  6. oracle 触发器抛出错误,oracle 触发器编译错误,求解
  7. php的swoole教程,PHP + Swoole2.0 初体验(swoole入门教程)
  8. 一期Go群问答-并发控制-数据竞争-错误与异常
  9. 51单片机 简易秒表计时器(100秒) 小数点后四位
  10. 《鲸鱼安慰了大海》精选篇章