第九章:顺序容器
一.顺序容器概述
新标准库的容器性能几乎最优,所以现代C++程序应该使用标准库中的容器,而不是使用更原始的数据结构。
二.容器库概览
有些操作是所有容器都有的,有的是只有顺序容器有,有的只有关联容器有,有的只有一些特殊容器有。
将一个新容器创建为另一个容器的拷贝有两种方法,一种是直接拷贝,另一种是用迭代器范围进行初始化,第一种要求容器类型和容器中的元素类型都要相同,第二种只要求元素类型能够转化就行。
当定义一个array时,除了指定元素类型,还要指定容器大小。一个默认构造的array是非空的,里面的元素被默认初始化。array可以进行拷贝或对象赋值操作。
顺序容器(array除外)定义了一个名为assign的成员,允许我们从一个不同但相容的类型赋值。assign操作用参数指定的元素的拷贝替换左边容器中的所有元素。
使用swap需要注意三个事情:1.除array外,swap本身不交换元素,只是交换了数据结构,因此可以保证在常数时间内完成,且迭代器,指针,引用依旧指向原来的值,不过这些值已经属于另一个容器了;2.对于array swap会交换元素,那么之前的迭代器,指针和引用指向的是现在容器中的新值;3.string的迭代器,指针和引用会失效。
注意,赋值相关运算会导致指向左边容器内部的迭代器,引用和指针失效。
每个容器类型都支持相等运算符(==和!=),除了无序关联容器外的所有容器都支持关系运算符。两个容器要是相同类型且存储相同类型元素。且元素要支持对应的运算符。比较规则和string类似。
三.顺序容器操作
当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。
当使用push或insert的时候,我们是把对象拷贝到容器中,而我们使用emplace的时候,是将参数传入,然后直接在容器空间中构造对象,也就是说emplace的参数就对象构造函数的参数。
包括array在内的每个顺序容器都有一个front成员函数,除forward_list之外的所有顺序容器都有一个back成员函数,这两个函数返回的是首尾元素的引用,但注意如果用int或者auto之类的去接收这个函数的返回,接收的对象就不是一个引用了。更重要的是,在调用front或者back之前,要确认容器内有函数,否则是未定义的行为。
使用front,back,下标和at返回的都是引用,如果要用auto接收引用,则要加引用符号&。
因为forward_list的删除添加需要用到前一个个元素,所以它的添加和删除与其他的顺序容器不同,它没有insert,emplace,erase等操作,它的是insert_after之类的after操作,即操作后一个元素。
使用resize,如果新大小小于当前大小,后部的元素会被删除,如果新大小大于当前大小,新元素会添加到后部。
四.vector对象是如何增长的
reserve操作是分配至少能容纳n个元素的内存空间,但是元素数不会变,而resize是让对象有多少个元素。注意reserve和resize都不能减少容器的内存空间,reserve小于等于当前内存空间时什么也不做,resize只是调整元素个数。
五.额外的string操作
string有一个replace函数,先删除再插入。
string搜索操作返回一个string::size_type值,这是一个表示下标的无符号值,如果没有找到,则返回string::npos,这是一个值为-1的无符号static成员(所以用无符号数接收会很大,有符号数接收是-1)。
数值类型可以通过to_string转换为string类型,string可以通过sto函数转换为其他类型,只要string开头的第一个非空白符是可以转换的类型,例如±数字以及点,其他地方又诸如字母这种不规范的无所谓。
六.容器适配器
所谓适配器就是对容器进行一些包装以实现特定的功能。
顺序容器有stack,queue,priority_queue三个适配器。默认情况下stack和queue是基于deque实现的,priority_queue是基于vector实现的。
练习
部分答案参考:第9章答案
9.1
(a) vector。数量固定,没有其他额外开销,按字典序可先插入到vector再排序。如果要边插入边排序的话选择list,因为其在内部插入删除效率高。
(b) deque或者list。vector的头部删除效率很低,当然考虑到随机访问和开销的话deque会好一点。
(c) vector。需要频繁的随机访问。
9.2
list<deque<int>> a;
9.3
指向同一个容器的元素或最后一个元素后一个位置;end不在begin之前;begin可递增到end。
9.4
#include <vector>
#include <iostream>
using namespace std;
bool func(vector<int>::iterator begin, vector<int>::iterator end, int val) {
while (begin != end) {
if (*begin == val) {
return true;
}
++begin;
}
return false;
}
int main() {
vector<int> vec{1,2,3,4,5,6,7};
auto begin = vec.begin(), end = vec.end();
cout << func(begin, end, 7) << endl;
return 0;
}
9.5
#include <vector>
#include <iostream>
using namespace std;
vector<int>::iterator func(vector<int>::iterator begin, vector<int>::iterator end, int val) {
while (begin != end) {
if (*begin == val) {
return begin;
}
++begin;
}
return end;
}
int main() {
vector<int> vec{1,2,3,4,5,6,7};
auto begin = vec.begin(), end = vec.end();
if (func(begin, end, 7) == end) {
cout << "未找到值" << endl;
}
else {
cout << "找到值" << endl;
}
return 0;
}
9.6
与 vector 和 deque 不同,list 的迭代器不支持 < 运算,只支持递增、递减、== 以及 != 运算。
9.7
vector<int>::iterator
9.8
读取:list<string>::value_type
写入:list<string>::reference
9.9
begin返回iterator或者const_iterator,cbegin返回const_iterator,前者返回的迭代器根据情况可读取可写入,后者返回的迭代器只能读取
9.10
v1:vector<int>
v2:const vector<int> 整个vector都是常量,不能修改增删
it1:vector<int>::iterator
it2:vector<int>::const_iterator
it3:vector<int>::const_iterator
it4:vector<int>::const_iterator
9.11
vector<int> vec1; // 无值
vector<int> vec2(2); // 两个元素空间,无值
vector<int> vec3(2, 1); // 两个元素空间,都为1
vector<int> vec4{1,2}; vector<int> vec4 = {1, 2} // 两个元素,1和2
vector<int> vec5(vec4); vector<int> vec5 = vec4; // 同上
vector<int> vec6(vec5.begin(), vec5.end()); // 同上
9.12
前者需要接收的容器类型和容器中的元素类型与构造函数对应的容器完全一致;
后者只需要接收的容器中的元素类型能够转换为构造函数对应的容器中的类型。
9.13
#include <vector>
#include <list>
using namespace std;
int main() {
list<int> my_list = {1,2,3};
vector<int> my_vec = {4,5,6};
vector<double> my_vec2(my_list.begin(), my_list.end());
vector<double> my_vec3(my_vec.begin(), my_vec.end());
return 0;
}
9.14
#include <vector>
#include <list>
#include <string>
using namespace std;
int main() {
list<char*> my_list = {"hello", "world"};
vector<string> my_vec(my_list.begin(), my_list.end());
my_vec.assign(my_list.begin(), my_list.end());
return 0;
}
9.15
#include <vector>
#include <list>
#include <string>
#include <iostream>
using namespace std;
int main() {
vector<int> vec1 = {1,2,3};
vector<int> vec2 = {1,2,3};
cout << (vec1 == vec2) << endl;
return 0;
}
9.16
#include <vector>
#include <list>
#include <string>
#include <iostream>
using namespace std;
bool isEqual(const vector<int> &vec1, const vector<int> &vec2) {
if (vec1.size() != vec2.size()) {
return false;
}
for (int i = 0; i < vec1.size(); ++i) {
if (vec1[i] != vec2[i]) {
return false;
}
}
return true;
}
int main() {
vector<int> vec1 = {1,2,3};
vector<int> vec2 = {1,2,3};
cout << isEqual(vec1, vec2) << endl;
return 0;
}
9.17
1. 两个容器的类型及存储的元素类型要相同;
2. 容器存储的元素类型要支持 < 运算符
9.18
#include <deque>
#include <iostream>
#include <string>
using namespace std;
int main() {
string temp;
deque<string> my_que;
while (getline(cin, temp)) {
my_que.push_back(temp);
}
auto iter = my_que.begin();
for (auto iter = my_que.begin(); iter != my_que.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}
9.19
#include <list>
#include <iostream>
#include <string>
using namespace std;
int main() {
string temp;
list<string> my_que;
while (getline(cin, temp)) {
my_que.push_back(temp);
}
auto iter = my_que.begin();
for (auto iter = my_que.begin(); iter != my_que.end(); ++iter) {
cout << *iter << endl;
}
return 0;
}
9.20
#include <list>
#include <iostream>
#include <string>
#include <deque>
using namespace std;
int main() {
list<int> my_que{1,2,3,4,5,6,7};
deque<int> even, odd;
for (int i : my_que) {
if (i % 2 == 0) {
even.push_back(i);
}
else {
odd.push_back(i);
}
}
return 0;
}
9.21
整个循环的执行过程和结果与list并无区别,但是在vector的首部插入元素需要移动其他元素,所以时间复杂度会更高
9.22
vector插入后原来的迭代器会失效,修改如下:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> iv = {1, 1, 2, 1}; // int 的 vector
int some_val = 1;
vector<int>::iterator iter = iv.begin();
int org_size = iv.size(), new_ele = 0; // 原大小和新元素个数
// 每个循环步都重新计算 mid,保证正确指向 iv 原中央位置的元素
while (iter != (iv.begin() + org_size / 2 + new_ele))
if (*iter == some_val) {
iter = iv.insert(iter, 2 * some_val); // iter 指向新元素
++new_ele;
iter += 2; // 将 iter 推进到旧元素的下一个元素
} else {
++iter; // 指向后推进一个位置
}
// 用 begin() 获取 vector 首元素迭代器,遍历 vector 中的所有元素
for (iter = iv.begin(); iter != iv.end(); ++iter)
cout << *iter << endl;
return 0;
}
9.23
4个值都一样,都是第一个元素。
9.24
#include <list>
#include <iostream>
#include <string>
#include <deque>
#include <vector>
using namespace std;
int main() {
vector<int> nums;
// cout << nums.at(0) << endl; // 抛出异常
// cout << nums[0] << endl; // 段错误退出
// cout << nums.front() << endl; // 段错误退出
cout << *nums.begin() << endl; // 段错误退出
return 0;
}
9.25
相等,即使两个都为尾后迭代器,也不会发生什么
如果elem2为尾后迭代器而elme1不是,则删除elme1到最后的元素
9.26
#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 + sizeof(ia) / sizeof(ia[0])); // 将数据拷贝到 vector
il.assign(ia, ia + sizeof(ia) / sizeof(ia[0])); // 将数据拷贝到 list
vector<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
#include <list>
#include <iostream>
#include <string>
#include <deque>
#include <vector>
#include <forward_list>
using namespace std;
int main() {
forward_list<int> test{1,2,3,4,5,6};
auto prev = test.before_begin();
auto curr = test.begin();
while (curr != test.end()) {
if (*curr % 2 != 0) {
curr = test.erase_after(prev);
}
else {
prev = curr;
++curr;
}
}
for (auto i : test) {
cout << i << endl;
}
return 0;
}
9.28
#include <list>
#include <iostream>
#include <string>
#include <deque>
#include <vector>
#include <forward_list>
using namespace std;
void func(forward_list<string> test, string str1, string str2) {
auto prev = test.before_begin();
auto curr = test.begin();
while (curr != test.end()) {
if (*curr == str1) {
test.insert_after(curr, str2);
break;
}
else {
prev = curr;
++curr;
if (curr == test.end()) {
test.insert_after(prev, str2);
}
}
}
for (auto i : test) {
cout << i << endl;
}
}
int main() {
forward_list<string> test{"1", "2", "3"};
func(test, "4", "5");
return 0;
}
9.29
调用 vec.resize(100) 会向 vec 末尾添加 75 个元素,这些元素将进行值初始化。
接下来调用 vec.resize(10) 会将 vec 末尾的 90 个元素删除。
9.30
需要有默认构造函数。
9.31
对于list,其迭代器不能随意加减,只能+±-,所以需要++两次。对于forward_list,其插入和删除是after的,所以还需要一个前驱。
9.32
错误。编译器传递给函数的参数是从右自左的,所以传递的值没问题,但是iter已经++了,所以第一个参数就改变了,传递的位置就有问题了。
9.33
一直在同一个位置插入,陷入死循环。
9.34
首先循环没有花括号,死循环,其次就算有花括号但是如果向量中有偶数的话也还是会死循环。可以在if语句里面再加一个++iter。
9.35
size是已经存储的元素数目,capacity是可以存储的元素数目
9.36
不可能
9.37
list不是连续存储的不用capacity来表示和控制容量,而array长度不可变。
9.38
略
9.39
先预留1024个元素的空间,再把元素个数设为当前个数的1.5倍。
9.40
reserve和resize只能扩大容器对象的空间而不能减少,所以当resize后小于等于1024的时候,capacity还是1024。大于1024后容器可能会翻倍到能够存储当前size的大小。
9.41
int main() {
vector<char> test = {'1','2','3'};
string str(test.data(), test.size()); // .data()可以返回被vector元素的首地址使vector相当于数组来用
cout << str << endl;
return 0;
}
9.42
使用s.reserve(100)来先分配100个空间,再用push_back把输入的字符存入string
9.43
string func(string s, string oldVal, string newVal) {
auto begin = s.begin();
while (begin < (s.end() - oldVal.size() + 1)) {
if (string(begin, begin + oldVal.size()) == oldVal) {
begin = s.erase(begin, begin + oldVal.size());
int pos = begin - s.begin();
s.insert(pos, newVal);
begin = s.begin() + pos + newVal.size();
}
else {
++begin;
}
}
return s;
}
int main() {
cout << func("thr", "thr", "through") << endl;
return 0;
}
9.44
string func(string s, string oldVal, string newVal) {
auto begin = s.begin();
while (begin < (s.end() - oldVal.size() + 1)) {
if (string(begin, begin + oldVal.size()) == oldVal) {
int pos = begin - s.begin();
s.replace(begin, begin + oldVal.size(), newVal);
begin = s.begin() + pos + newVal.size();
}
else {
++begin;
}
}
return s;
}
int main() {
cout << func("dthrdd", "thr", "through") << endl;
return 0;
}
9.45
string func(string name, string pre, string after) {
name.insert(name.begin(), pre.begin(), pre.end());
name.append(after);
return name;
}
int main() {
cout << func("tang", "Mr.", "III") << endl;
return 0;
}
9.46
string func(string name, string pre, string after) {
name.insert(0, pre);
name.insert(name.length(), after);
return name;
}
int main() {
cout << func("tang", "Mr.", "III") << endl;
return 0;
}
9.47
int main() {
string str = "ab2c3d7R4E6", numbers = "0123456789";
string::size_type pos = 0;
while ((pos = str.find_first_of(numbers, pos)) != string::npos) {
cout << str[pos] << " ";
++pos;
}
cout << endl;
pos = 0;
while ((pos = str.find_first_not_of(numbers, pos)) != string::npos) {
cout << str[pos] << " ";
++pos;
}
cout << endl;
return 0;
}
9.48
string::npos
9.49
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
void find_longest_word(ifstream &in) {
string s, longest_word;
int max_length = 0;
while (in >> s) {
if (s.find_first_of("bdfghjklpqty") != string::npos)
continue;
cout << s << " ";
if (max_length < s.size()) {
max_length = s.size();
longest_word = s;
}
}
cout << endl << "最长字符串:" << longest_word << endl;
}
int main(int argc, char **argv) {
if (argc != 2) {
cerr << "请给出文件名" << endl;
return -1;
}
ifstream in(argv[1]);
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
find_longest_word(in);
return 0;
}
9.50
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
vector<string> isvec = {"123", "+456", "-789"};
vector<string> fsvec{"12.3", "-4.56", "+7.8e-2"};
int isum = 0;
for (vector<string>::iterator iiter = isvec.begin();
iiter != isvec.end(); ++iiter) {
isum += stoi(*iiter);
}
cout << "sum(int) = " << isum << endl;
float fsum = 0.0;
for (auto fiter = fsvec.begin(); fiter != fsvec.end(); ++fiter) {
fsum += stod(*fiter);
}
cout << "sum(float) = " << fsum << endl;
return 0;
}
9.51
// Exercise 9.51:
// Write a class that has three unsigned members representing year, month, and day.
// Write a constructor that takes a string representing a date. Your constructor should handle a
// variety of date formats, such as January 1, 1900, 1/1/1900, Jan 1, 1900, and so on.
#include <array>
#include <iostream>
#include <string>
class Date {
public:
explicit Date(const std::string &str = "");
void Print();
unsigned year = 1970;
unsigned month = 1;
unsigned day = 1;
private:
std::array<std::string, 12> month_names{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
unsigned MonthFromName(const std::string &str);
};
Date::Date(const std::string &str) {
if (str.empty()) return;
std::string delimiters{" ,/"}; // 分隔符 ' ' ',' 和 '/'
// month_day_delim_pos 表示月、日分割符在字符串中的下标索引,其它变量命名类似
std::string::size_type month_day_delim_pos = str.find_first_of(delimiters);
if (month_day_delim_pos == std::string::npos)
throw std::invalid_argument("This format is not supported now.");
month = MonthFromName(str.substr(0, month_day_delim_pos));
// auto 自动推断字符串搜索函数返回类型,也就是 string::size_type 类型
// 相关知识点可参考书 325 页 9.5.3 节开头部分
auto day_year_delim_pos = str.find_first_of(delimiters, month_day_delim_pos + 1);
auto day_len = day_year_delim_pos - month_day_delim_pos - 1;
day = std::stoi(str.substr(month_day_delim_pos + 1, day_len));
year = std::stoi(str.substr(day_year_delim_pos + 1));
}
void Date::Print() {
std::cout << year << "-" << month << "-" << day << "\n";
}
unsigned Date::MonthFromName(const std::string &str) {
if (str.empty()) return 0;
if (std::isdigit(str[0])) return std::stoi(str); // 若月份为数字
for (size_t i = 0; i != 12; ++i) { // 若月份为英文
if (str.find(month_names[i]) != std::string::npos) return i + 1;
}
return 0; // not found
}
int main() {
{ // default case
auto date = Date();
date.Print();
}
{ // case 0: January 1, 1900
auto date = Date("January 1, 1900");
date.Print();
}
{ // case 1: 1/1/1900
auto date = Date("1/1/1900");
date.Print();
}
{ // case 2: Jan 1, 1900
auto date = Date("Jan 1, 1900");
date.Print();
}
}
9.52
int main() {
string str = "this (is a) (test).";
char replace = '#';
stack<char> st;
for (char c : str) {
st.push(c);
if (c == ')') {
while (!st.empty() && st.top() != '(') {
st.pop();
}
st.pop();
st.push(replace);
}
}
string result;
while (!st.empty()) {
result.insert(result.begin(), st.top());
st.pop();
}
cout << result << endl;
return 0;
}