《C++ Primer》学习笔记/习题答案 总目录
——————————————————————————————————————————————————————
对于下面的程序任务,vector
、deque
和 list
哪种容器最为适合?解释你的选择的理由。如果没有哪一种容器优于其他容器,也请解释理由。
解:
(a)“按字典序插入到容器中” 意味着进行插入排序操作,从而需要在容器内部频繁进行插入操作,vector
在尾部之外的位置插入和删除元素很慢,deque
在头尾之外的位置插入和删除元素很慢,而 list
在任何位置插入、删除速度都很快。因此,这个任务选择 list
更为适合。当然,如果不是必须边读取单词边插入到容器中,可以使用 vector
,将读入的单词依次追加到尾部,读取完毕后,调用标准库到排序算法将单词重排为字典序。
(b)由于需要在头、尾分别进行插入、删除操作,因此将 vector
排除在外,deque
和 list
都可以达到很好的性能。如果还需要频繁进行随机访问,则 deque
更好。
(c)由于整数占用空间很小,且快速的排序算法需频繁随机访问元素,将 list
排除在外。由于无须在头部进行插入、删除操作,因此使用 vector
即可,无须使用 deque
。
定义一个 list
对象,其元素类型是 int
的 deque
。
解:
list<deque<int>> a;
构成迭代器范围的迭代器有何限制?
解:
begin
和 end
必须指向同一个容器中的元素,或者是容器最后一个元素之后的位置;begin
反复进行递增操作,可保证到达 end
,即 end
不在 begin
之前。编写函数,接受一对指向 vector
的迭代器和一个 int
值。在两个迭代器指定的范围中查找给定的值,返回一个布尔值来指出是否找到。
解:
#include
#include
using namespace std;
bool search_vec(vector<int>::iterator beg,
vector<int>::iterator end, int val){
for (; beg != end; beg++){
if (*beg == val)
return true;
}
return false;
}
int main(){
vector<int> ilist = { 1, 2, 3, 4, 5, 6, 7 };
cout << search_vec(ilist.begin(), ilist.end(), 3) << endl;
cout << search_vec(ilist.begin(), ilist.end(), 8) << endl;
system("pause");
return 0;
}
重写上一题的函数,返回一个迭代器指向找到的元素。注意,程序必须处理未找到给定值的情况。
解:
#include
#include
using namespace std;
vector<int>::iterator search_vec(vector<int>::iterator beg,
vector<int>::iterator end, int val){
for (; beg != end; beg++){
if (*beg == val)
return beg;
}
return end;
}
int main(){
vector<int> ilist = { 1, 2, 3, 4, 5, 6, 7 };
cout << search_vec(ilist.begin(), ilist.end(), 3) - ilist.begin() << endl;
cout << search_vec(ilist.begin(), ilist.end(), 8) - ilist.begin() << endl;
system("pause");
return 0;
}
下面的程序有何错误?你应该如何修改它?
list<int> lst1;
list<int>::iterator iter1 = lst1.begin(),
iter2 = lst1.end();
while (iter1 < iter2) /* ... */
解:
与 vector
和 deque
不同,list
的迭代器不支持 <
运算,只支持递增、递减、=
以及 !=
运算。
原因在于这几种数据结构实现上的不同:vector
和 deque
将元素在内存中连续保存,而 list
则是将元素以链表方式存储,因此前者可以方便地实现迭代器的大小比较(类似指针的大小比较)来体现元素的前后关系。而在 list
中,两个指针的大小关系与它们指向的元素的前后关系并不一定是吻合的,实现 <
运算将会非常困难和低效。
为了索引 int
的 vector
中的元素,应该使用什么类型?
解:
使用迭代器类型 vector
来索引 int
的 vector
中的元素。
为了读取 string
的 list
中的元素,应该使用什么类型?如果写入 list
,又应该使用什么类型?
解:
为了读取 string
的 list
中的元素,应使用 list
,因为 value_type
表示元素类型。
为了写入数据,需要(非常量)引用类型,因此应使用 list
。
begin
和 cbegin
两个函数有什么不同?
解:
cbegin
是C++新标准引入的,用来与 auto
结合使用。它返回指向容器第一个元素的 const
迭代器,可以用来只读地访问容器元素,但不能对容器元素进行修改。因此,当不需要写访问时,应该使用 cbegin
。
begin
则是被重载过的,有两个版本:其中一个是 const
成员函数,也返回 const
迭代器;另一个则返回普通迭代器,可以对容器元素进行修改。
下面4个对象分别是什么类型?
vector<int> v1;
const vector<int> v2;
auto it1 = v1.begin(), it2 = v2.begin();
auto it3 = v1.cbegin(), it4 = v2.cbegin();
解:
v1
是 int
的 vector
类型,我们可以修改 v1
的内容,包括添加、删除元素及修改元素值等操作。
v2
是 int
的常量 vector
类型,其内容不能修改,添加、删除元素及修改元素值等均不允许。
begin
与 auto
结合使用时,会根据调用对象的类型来决定迭代器的类型,因此 it1
是普通迭代器,可对容器元素进行读写访问,而 it2
是 const
迭代器,不能对容器元素进行写访问。
而 cbegin
则不管调用对象是什么类型,始终返回 const
迭代器,因此 it3
和 it4
都是 const
迭代器。
对6种创建和初始化 vector
对象的方法,每一种都给出一个实例。解释每个 vector
包含什么值。
解:
vector<int> vec; // 0
vector<int> vec(10); // 10个0
vector<int> vec(10, 1); // 10个1
vector<int> vec{ 1, 2, 3, 4, 5 }; // 1, 2, 3, 4, 5
vector<int> vec(other_vec); // 拷贝 other_vec 的元素
vector<int> vec(other_vec.begin(), other_vec.end()); // 拷贝 other_vec 的元素
(1)vector
size
返回0,表明容器中尚未有元素;capacity
返回0,意味着尚未分配存储空间。这种初始化方式适合于元素个数和值未知,需要在程序运行中动态添加的情况。
(2)vector
ilist
必须与 ilist2
类型相同,即也是 int
的 vector
类型,ilist2
将具有与 ilist
相同的容量和元素。
vector
(3)vector
列表中的元素类型必须与 ilist
的元素类型相容,在本例中必须是与整型相容的数值类型。对于整型,会直接拷贝其值,对于其他类型则需进行类型转换(如3.0转换为3)。这种初始化方式适合元素数量和值预先可知的情况。
vector
(4)vector
范围中的元素类型必须与 ilist3
的元素类型相容,在本例中 ilist3
被初始化为 {3, 4, 5, 6}
。注意,由于只要求范围中元素类型与待初始化的容器的元素类型相容,因此,迭代器来自于不同类型的容器是可能的,例如,用一个 double
的 list
的范围来初始化 ilist3
是可行的。另外,由于构造函数只是读取范围中的元素并进行拷贝,因此使用普通迭代器还是 const
迭代器来指出范围并无区别。这种初始化方法特别适合于获取一个序列的子序列。
(5)vector
每个元素进行缺省的值初始化,对于 int
,也就是被赋值为0,因此 ilist4
被初始化为包含7个0。当程序运行初期元素大致数量可预知,而元素的值需动态获取时,可采用这种初始化方式。
(6)vector
。
对于接受一个容器创建其拷贝的构造函数,和接受两个迭代器创建拷贝的构造函数,解释它们的不同。
解:
接受一个已有容器的构造函数会拷贝此容器中的所有元素,这样,初始化完成后,我们得到此容器的一个一模一样的拷贝。当我们确实需要一个容器的完整拷贝时,这种初始化方式非常方便。
但当我们不需要已有容器中的全部元素,而只是想拷贝其中一部分元素时,可使用接受两个迭代器的构造函数。传递给它要拷贝的范围的起始和尾后位置的迭代器,即可令新容器对象包含所需范围中元素的拷贝。
如何从一个 list
初始化一个 vector
?从一个 vector
又该如何创建?编写代码验证你的答案。
解:
由于 list
与 vector
是不同的容器类型,因此无法采用容器拷贝初始化方式。但前者的元素类型是 int
,与后者的元素类型 double
是相容的,因此可以采用范围初始化方式来构造一个 vector
,令它的元素值与 list
完全相同。对 vector
也是这样的思路。
#include
#include
#include
using namespace std;
int main()
{
list<int> ilist = { 1, 2, 3, 4, 5, 6, 7 };
vector<int> ivec = { 7, 6, 5, 4, 3, 2, 1 };
// 容器类型不同,不能使用拷贝初始化
//vector ivec(ilist);
// 元素类型相容,因此可采用范围初始化
vector<double> dvec(ilist.begin(), ilist.end());
// 容器类型不同,不能使用拷贝初始化
// vector dvecl(ivec);
// 元素类型相容,因此可采用范围初始化
vector<double> dvecl(ivec.begin(), ivec.end());
cout << dvec.capacity() << " " << dvec.size() << " "
<< dvec[0] << " " << dvec[dvec.size() - 1] << endl;
cout << dvecl.capacity() << " " << dvecl.size() << " "
<< dvecl[0] << " " << dvecl[dvecl.size() - 1] << endl;
system("pause");
return 0;
}
编写程序,将一个 list
中的 char *
指针元素赋值给一个 vector
中的 string
。
解:
由于 list
与 vector
是不同类型的容器,因此无法采用赋值运算符 =
来进行元素赋值。但 char*
可以转换为 string
,因此可以采用范围赋值方式来实现本题要求。
#include
#include
#include
#include
using namespace std;
int main()
{
list<char*> slist = { "hello", "world", "!" };
vector<string> svec;
// 容器类型不同,不能直接赋值
// svec = slist;
// 元素类型相容,可采用范围初始化
svec.assign(slist.begin(), slist.end());
cout << svec.capacity() << " " << svec.size() << " " <<
svec[0] << " " << svec[svec.size() - 1] << endl;
system("pause");
return 0;
}
编写程序,判定两个 vector
是否相等。
解:
标准库容器支持关系运算符,比较两个 vector
是否相等使用 ==
运算符即可。当两个 vector
包含相同个数的元素,且对位元素都相等时,判定两个 vector
相等,否则不等。
两个 vector
的 capacity
不会影响相等性判定,因此,当下面程序中 ivec1
在添加、删除元素导致扩容后,仍然与 ivec
相等。
#include
#include
#include
using namespace std;
int main()
{
vector<int> ivec = { 1, 2, 3, 4, 5, 6, 7 };
vector<int> ivec1 = { 1, 2, 3, 4, 5, 6, 7 };
vector<int> ivec2 = { 1, 2, 3, 4, 5 };
vector<int> ivec3 = { 1, 2, 3, 4, 5, 6, 8 };
vector<int> ivec4 = { 1, 2, 3, 4, 5, 7, 6 };
cout << (ivec == ivec1) << endl;
cout << (ivec == ivec2) << endl;
cout << (ivec == ivec3) << endl;
cout << (ivec == ivec4) << endl;
ivec.push_back(8);
ivec1.pop_back();
cout << ivec.capacity() << " " << ivec.size() << endl;
cout << (ivec == ivec1) << endl;
system("pause");
return 0;
}
重写上一题的程序,比较一个 list
中的元素和一个 vector
中的元素。
解:
两个容器相等的充分条件是包含相同个数的元素,且对位元素的值都相等。因此,首先判断两个容器是否包含相同个数的元素,若不等,则两个容器也不等。否则,遍历两个容器中的元素,两两比较对位元素的值,若有元素不相等,则容器不等。否则,两个容器相等。
std::list<int> li{ 1, 2, 3, 4, 5 };
std::vector<int> vec2{ 1, 2, 3, 4, 5 };
std::vector<int> vec3{ 1, 2, 3, 4 };
std::cout << (std::vector<int>(li.begin(), li.end()) == vec2 ? "true" : "false") << std::endl;
std::cout << (std::vector<int>(li.begin(), li.end()) == vec3 ? "true" : "false") << std::endl;
假定 c1
和 c2
是两个容器,下面的比较操作有何限制?
解:
if (c1 < c2)
首先,容器类型必须相同,元素类型也必须相同。
其次,元素类型必须支持 <
运算符。
编写程序,从标准输入读取 string
序列,存入一个 deque
中。编写一个循环,用迭代器打印 deque
中的元素。
解:
#include
#include
#include
using namespace std;
int main()
{
deque<string> sd;
string word;
while (cin >> word)
{
sd.push_back(word);
}
for (auto si = sd.cbegin(); si != sd.cend(); si++){
cout << *si << endl;
}
system("pause");
return 0;
}
重写上一题的程序,用 list
替代 deque
。列出程序要做出哪些改变。
解:
对 list
来说,在任何位置添加新元素都有很好的性能,遍历操作也能高效完成,因此程序与上一题并无太大差异。
#include
#include
#include
using namespace std;
int main()
{
list<string> sl;
string word;
while (cin >> word)
{
sl.push_back(word);
}
for (auto si = sl.cbegin(); si != sl.cend(); si++){
cout << *si << endl;
}
system("pause");
return 0;
}
编写程序,从一个 list
拷贝元素到两个 deque
中。值为偶数的所有元素都拷贝到一个 deque
中,而奇数值元素都拷贝到另一个 deque
中。
解:
#include
#include
#include
#include
using namespace std;
int main()
{
list<int> ilist = { 1, 2, 3, 4, 5, 6, 7, 8 };
deque<int> odd_d, even_d;
for (auto iter = ilist.cbegin(); iter != ilist.cend();iter++){
if (*iter & 1){
odd_d.push_back(*iter);
}
else{
even_d.push_back(*iter);
}
}
for (auto iter = odd_d.cbegin(); iter != odd_d.cend(); iter++){
cout << *iter << " ";
}
cout << endl;
for (auto iter = even_d.cbegin(); iter != even_d.cend(); iter++){
cout << *iter << " ";
}
cout << endl;
system("pause");
return 0;
}
如果我们将第308页中使用 insert
返回值将元素添加到 list
中的循环程序改写为将元素插入到 vector
中,分析循环将如何工作。
解:
在循环之前,vector
为空,此时将 iter
初始化为 vector
首位置,与初始化为尾后位置效果是一样的。循环中第一次调用 insert
会将读取的第一个 string
插入到 iter
指向位置之前的位置,即,令新元素成为 vector
的首元素,而 insert
的返回指向此元素的迭代器,我们将它赋予 iter
,从而使得 iter
始终指向 vector
的首元素。接下来的每个循环步均是如此,将新 string
插入到 vector
首元素之前的位置,成为新的首元素,并使 iter
始终指向 vector
首。这样,string
在 vector
排列的顺序将与它们的输入顺序恰好相反。整个循环执行的过程和最后的结果都与 list
版本没有什么区别。
但要注意,在 list
首元素之前插入新元素性能很好,但对于 vector
,这样的操作需要移动所有现有元素,导致性能很差。
#include
#include
#include
using namespace std;
int main()
{
vector<string> svec;
string word;
auto iter = svec.begin();
while (cin >> word)
{
svec.insert(iter, word);
}
for (auto iter = svec.cbegin(); iter != svec.cend(); iter++){
cout << *iter << endl;
}
system("pause");
return 0;
}
假定 iv
是一个 int
的 vector
,下面的程序存在什么错误?你将如何修改?
解:
vector<int>::iterator iter = iv.begin(),
mid = iv.begin() + iv.size() / 2;
while (iter != mid)
if (*iter == some_val)
iv.insert(iter, 2 * some_val);
解:
循环中未对 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+newele
都能正确指向 iv
原来的中央元素。
#include
#include
#include
using namespace std;
int main()
{
vector<int> iv = { 1, 1, 2, 1 };
int some_val = 1;
vector<int>::iterator iter = iv.begin();
int org_size = iv.size(), new_ele = 0;
while (iter != (iv.begin() + org_size / 2 + new_ele))
{
if (*iter == some_val){
iter = iv.insert(iter, 2 * some_val);
new_ele++;
iter++; iter++;
}
else{
iter++;
}
}
for (iter = iv.begin(); iter != iv.end(); iter++){
cout << *iter << endl;
}
system("pause");
return 0;
}
在本节第一个程序中,若 c.size()
为1,则 val
、val2
、val3
和 val4
的值会是什么?
解:
4个变量的值会一样,都等于容器中唯一一个元素的值。
编写程序,分别使用 at
、下标运算符、front
和 begin
提取一个 vector
中的第一个元素。在一个空 vector
上测试你的程序。
解:
下面的程序会异常终止。因为 vector
为空,此时用 at
访问容器的第一个元素会抛出一个 out_of_range
异常,而此程序未捕获异常,因此程序会因异常退出。正确的编程方式是,捕获可能的 out of range
异常,进行相应的处理。
但对于后三种获取容器首元素的方法,当容器为空时,不会抛出 out_of_range
异常,而是导致程序直接退出(注释掉前几条语句即可看到后面语句的执行效果)。因此,正确的编程方式是,在采用这几种获取容器的方法时,检查下标的合法性(对 front
和 begin
只需检查容器是否为空),确定没有问题后再获取元素。当然这种方法对 at
也适用。
#include
#include
using namespace std;
int main()
{
vector<int> iv;
cout << iv.at(0);
cout << iv[0];
cout << iv.front();
cout << *(iv.begin());
system("pause");
return 0;
}
对于第312页中删除一个范围内的元素的程序,如果 elem1
与 elem2
相等会发生什么?如果 elem2
是尾后迭代器,或者 elem1
和 elem2
皆为尾后迭代器,又会发生什么?
解:
如果两个迭代器 elem1
和 elem2
相等,则什么也不会发生,容器保持不变。哪怕两个迭代器是指向尾后位置(例如 end()+1
),也是如此,程序也不会出错。因此 elem1
和 elem2
都是尾后迭代器时,容器保持不变。
如果 elem2
为尾后迭代器,elem1
指向之前的合法位置,则会删除从 elem1
开始直至容器末尾的所有元素。
使用下面代码定义的 ia
,将 ia
拷贝到一个 vector
和一个 list
中。是用单迭代器版本的 erase
从 list
中删除奇数元素,从 vector
中删除偶数元素。
int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 };
解:
当从 vector
中删除元素时,会导致删除点之后位置的迭代器、引用和指针失效。而 erase
返回的迭代器指向删除元素之后的位置。因此,将 erase
返回的迭代器赋予 iiv
,使其正确向前推进。且尾后位置每个循环步中都用 end
重新获得,保证其有效。
对于 list
,删除操作并不会令迭代器失效,但上述方法仍然是适用的。
#include
#include
#include
#include
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);
il.assign(ia, ia + 11);
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;
system("pause");
return 0;
}
编写程序,查找并删除 forward_list
中的奇数元素。
解:
关键点是理解 forward_list
其实是单向链表数据结构,只有前驱节点指向后继节点的指针,而没有反向的指针。因此,在 forward_list
中可以高效地从前驱转到后继,但无法从后继转到前驱。而当我们删除一个元素后,应该调整被删元素的前驱指针指向被删元素的后继,起到将该元素从链表中删除的效果。
因此,在 forward_list
中插入、删除元素既需要该元素的迭代器,也需要前驱迭代器。为此,forward_list
提供了 before_begin
来获取首元素之前位置的迭代器,且插入、删除都是 after
形式,即删除(插入)给定迭代器的后继。
#include
#include
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;
system("pause");
return 0;
}
编写函数,接受一个 forward_list
和两个 string
共三个参数。函数应在链表中查找第一个 string
,并将第二个 string
插入到紧接着第一个 string
之后的位置。若第一个 string
未在链表中,则将第二个 string
插入到链表末尾。
解:
与删除相同的是,forwardlist
的插入操作也是在给定元素之后。不同的是,插入一个新元素后,只需将其后继修改为给定元素的后继,然后修改给定元素的后继为新元素即可,不需要前驱迭代器参与。但对于本题,当第一个 string
不在链表中时,要将第二个 string
插入到链表末尾。因此仍然需要维护前驱迭代器,当遍历完链表时,prev
指向尾元素,curr
指向尾后位置。若第一个 string
不在链表中,此时只需将第二个 string
插入到 prev
之后即可。
总体来说,单向链表由于其数据结构上的局限,为实现正确插入、删除操作带来了困难。标准库的 forward_list
容器为我们提供了一些特性,虽然(与其他容器相比)我们仍需维护一些额外的迭代器,但已经比直接用指针来实现链表的插入、删除方便了许多。
#include
#include
#include
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);
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.begin(); curr != sflst.end(); curr++){
cout << *curr << " ";
}
cout << endl;
test_and_insert(sflst, "!", "?");
for (auto curr = sflst.begin(); curr != sflst.end(); curr++){
cout << *curr << " ";
}
cout << endl;
test_and_insert(sflst, "Bye", "再见");
for (auto curr = sflst.begin(); curr != sflst.end(); curr++){
cout << *curr << " ";
}
cout << endl;
system("pause");
return 0;
}
假定 vec
包含25个元素,那么 vec.resize(100)
会做什么?如果接下来调用 vec.resize(10)
会做什么?
解:
调用 vec.resize(100)
会向 vec
末尾添加75个元素,这些元素将进行值初始化。
接下来调用 vec.resize(10)
会将 vec
末尾的90个元素删除。
接受单个参数的 resize
版本对元素类型有什么限制(如果有的话)?
解:
对于元素是类类型,则单参数 resize
版本要求该类型必须提供一个默认构造函数。
第316页中删除偶数值元素并复制奇数值元素的程序不能用于 list
或 forward_list
。为什么?修改程序,使之也能用于这些类型。
解:
list
和 forward_list
与其他容器的一个不同是,迭代器不支持加减运算,究其原因,链表中元素并非在内存中连续存储,因此无法通过地址的加减在元素间远距离移动。因此,应多次调用++来实现与迭代器加法相同的效果。
#include
#include
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;
system("pause");
return 0;
}
对于 forward_list
,由于是单向链表结构,删除元素时,需将前驱指针调整为指向下一个节点,因此需维护 前驱
、后继
两个迭代器。
#include
#include
using namespace std;
int main()
{
forward_list<int> iflst = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
auto prev = iflst.before_begin();
auto curr = iflst.begin();
while (curr != iflst.end())
{
if (*curr & 1){
curr = iflst.insert_after(curr, *curr);
prev = curr;
curr++;
}
else{
curr = iflst.erase_after(prev);
}
}
for (curr = iflst.begin(); curr != iflst.end(); curr++){
cout << *curr << " ";
}
cout << endl;
system("pause");
return 0;
}
在第316页的程序中,向下面语句这样调用 insert
是否合法?如果不合法,为什么?
iter = vi.insert(iter, *iter++);
解:
很多编译器对实参求值、向形参传递的处理顺序是由右至左的。这意味着,编译器在编译上述代码时,首先对 *iter++
求值,传递给 insert
第二个形参,此时 iter
已指向当前奇数的下一个元素,因此传递给 insert
的第一个参数的迭代器指向的是错误位置,程序执行会发生混乱,最终崩溃。
因此,若将代码改为 iter=vi.insert(iter++, *iter);
,或是使用由左至右求值、传递参数的编译器,代码的运行结果是正确的。当然,这样的代码在逻辑上是毫无道理的。
在本节最后一个例子中,如果不将 insert
的结果赋予 begin
,将会发生什么?编写程序,去掉此赋值语句,验证你的答案。
解:
向 vector
中插入新元素后,原有迭代器都会失效。因此,不将 insert()
返回的迭代器赋予 begin
,会使 begin
失效。继续使用 begin
会导致程序崩溃。对此程序,保存尾后迭代器和不向 begin
赋值两个错误存在其一,程序都会崩溃。
假定 vi
是一个保存 int
的容器,其中有偶数值也有奇数值,分析下面循环的行为,然后编写程序验证你的分析是否正确。
iter = vi.begin();
while (iter != vi.end())
if (*iter % 2)
iter = vi.insert(iter, *iter);
++iter;
解:
此段代码的第一个错误是忘记使用花括号,使得 ++iter
变成循环后的第一条语句,而非所期望的循环体的最后一条语句。因此,除非容器为空,否则程序会陷入死循环:
if
语句真分支不会被执行,iter
保持不变。循环继续执行,真分支仍然不会执行,iter
继续保持不变,如此陷入死循环。insert
语句被调用,将该值插入到首元素之前,并将返回的迭代器(指向新插入元素)赋予 iter
,因此 iter
指向新首元素。继续执行循环,会继续将首元素复制到容器首位置,并令 iter
指向它,如此陷入死循环。示例(粗体代表迭代器位置):
…
解释一个 vector
的 capacity
和 size
有何区别。
解:
capacity
返回已经为 vector
分配了多大内存空间(单位是元素大小),也就是在不分配新空间的情况下,容器可以保存多少个元素。
而 size
则返回容器当前已经保存了多少个元素。
一个容器的 capacity
可能小于它的 size
吗?
解:
由上一题解答可知,这是不可能的。
为什么 list
或 array
没有 capacity
成员函数?
解:
list
是链表,当有新元素加入时,会从内存空间中分配一个新节点保存它;当从链表中删除元素时,该节点占用的内存空间会被立刻释放。因此,一个链表占用的内存空间总是与它当前保存的元素所需空间相等(换句话说,capacity
总是等于 size
)。
而 array
是固定大小数组,内存一次性分配,大小不变,不会变化。
因此它们均不需要 capacity
。
编写程序,探究在你的标准实现中,vector
是如何增长的。
解:
#include
#include
#include
using namespace std;
int main()
{
vector<int> vi;
int s, c;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
for (s = vi.size(), c = vi.size(); s <= c; s++){
vi.push_back(1);
}
cout << "capacity: " << vi.capacity() << " size: " << vi.size() << endl;
system("pause");
return 0;
}
解释下面程序片段做了什么:
vector<string> svec;
svec.reserve(1024);
string word;
while (cin >> word)
svec.push_back(word);
svec.resize(svec.size() + svec.size() / 2);
解:
首先,reserse
为 svec
分配了1024个元素(字符串)的空间。
随后,循环会不断读入字符串,添加到 svec
末尾,直至遇到文件结束符。这个过程中,如果读入的字符串数量不多于1024,则 svec
的容量(capacity
)保持不变,不会分配新的内存空间。否则,会按一定规则分配更大的内存空间,并进行字符串的移动。
接下来,resize
将向 svec
末尾添加当前字符串数量一半那么多的新字符串,它们的值都是空串。若空间不够,会分配足够容纳这些新字符串的内存空间。
如果上一题的程序读入了256个词,在 resize
之后容器的 capacity
可能是多少?如果读入了512个、1000个、或1048个呢?
解:
resize
之后容器的 capacity
将是384。resize
之后容器的 capacity
将是768。resize
之后容器的 capacity
将是2048。resize
之后容器的 capacity
将是2048。编写程序,从一个 vector
初始化一个 string
。
解:
vector
提供了 data
成员函数,返回其内存空间的首地址。将此返回值作为 string
的构造函数的第一个参数,将 vector
的 size
返回值作为第二个参数,即可获取 vector
中的数据,将其看作一个字符数组来初始化 string
。
#include
#include
#include
using namespace std;
int main()
{
vector<char> vc = { 'H', 'E', 'L', 'L', 'O' };
string s(vc.data(), vc.size());
cout << s << endl;
system("pause");
return 0;
}
假定你希望每次读取一个字符存入一个
string
中,而且知道最少需要读取100个字符,应该如何提高程序的性能?
解:
由于知道至少读取100个字符,因此可以用 reserve
先为 string
分配100个字符的空间,然后逐个读取字符,用 push_back
添加到 string
末尾。
#include
#include
#include
using namespace std;
void input_string(string &s){
s.reserve(100);
char c;
while (cin >> c)
{
s.push_back(c);
}
}
int main()
{
string s;
input_string(s);
cout << s << endl;
system("pause");
return 0;
}
编写一个函数,接受三个 string
参数是 s
、oldVal
和 newVal
。使用迭代器及 insert
和 erase
函数将 s
中所有 oldVal
替换为 newVal
。测试你的程序,用它替换通用的简写形式,如,将"tho"替换为"though",将"thru"替换为"through"。
解:
由于要求使用迭代器,因此使用如下算法:
iter
遍历字符串 s
。注意,对于 s
末尾少于 oldVal
长度的部分,己不可能与 oldVal
相等,因此无须检查。s
中字符是否与 oldVal
中的字符都相等。iter2==oldVal.end
而退出,表明 s
中 iter
开始的子串与 oldVal
相等。则调用 erase
将此子串删除,接着用一个循环将 newVal
复制到当前位置。由于 insert
将新字符插入到当前位置之前,并返回指向新字符的迭代器,因此,逆序插入 newVal
字符即可。最后将 iter
移动到新插入内容之后,继续遍历 s
。iter
开始的子串与 oldVal
不等,递增 iter
,继续遍历 s
。#include
#include
#include
using namespace std;
void replace_string(string &s, const string &oldVal, const string &newVal)
{
auto l = oldVal.size();
if (!l){
return;
}
auto iter = s.begin();
while (iter <= s.end() - 1)
{
auto iter1 = iter;
auto iter2 = oldVal.begin();
while (iter2 != oldVal.end() && *iter1 == *iter2)
{
iter1++;
iter2++;
}
if (iter2 == oldVal.end()){
iter = s.erase(iter, iter1);
if (newVal.size()){
iter2 = newVal.end();
do{
iter2--;
iter = s.insert(iter, *iter2);
} while (iter2 > newVal.begin());
}
iter += newVal.size();
}
else iter++;
}
}
int main()
{
string s = "tho thru tho!";
replace_string(s, "thru", "through");
cout << s << endl;
replace_string(s, "tho", "though");
cout << s << endl;
replace_string(s, "through", "");
cout << s << endl;
system("pause");
return 0;
}
重写上一题的函数,这次使用一个下标和 replace
。
解:
由于可以使用下标和 replace
,因此可以更为简单地实现上一题的目标。通过 find
成员函数(只支持下标参数)即可找到 s
中与 oldVal
相同的子串,接着用 replace
即可将找到的子串替换为新内容。可以看到,使用下标而不是迭代器,通常可以更简单地实现字符串操作。
#include
#include
#include
using namespace std;
void replace_string(string &s, const string &oldVal, const string &newVal)
{
int p = 0;
while ((p = s.find(oldVal, p)) != string::npos)
{
s.replace(p, oldVal.size(), newVal);
p += newVal.size();
}
}
int main()
{
string s = "tho thru tho!";
replace_string(s, "thru", "through");
cout << s << endl;
replace_string(s, "tho", "though");
cout << s << endl;
replace_string(s, "through", "");
cout << s << endl;
system("pause");
return 0;
}
编写一个函数,接受一个表示名字的 string
参数和两个分别表示前缀(如"Mr.“或"Mrs.”)和后缀(如"Jr.“或"III”)的字符串。使用迭代器及 insert
和 append
函数将前缀和后缀添加到给定的名字中,将生成的新 string
返回。
解:
通过 insert
插入到首位置之前,即可实现前缀插入。通过 append
即可实现将后缀追加到字符串末尾。
#include
#include
#include
using namespace std;
void name_string(string &name, const string &prefix, const string &suffix)
{
name.insert(name.begin(), 1, ' ');
name.insert(name.begin(), prefix.begin(), prefix.end());
name.append(" ");
name.append(suffix.begin(), suffix.end());
}
int main()
{
string s = "James Bond";
name_string(s, "Mr.", "II");
cout << s << endl;
s = "M";
name_string(s, "Mrs.", "III");
cout << s << endl;
system("pause");
return 0;
}
重写上一题的函数,这次使用位置和长度来管理 string
,并只使用 insert
。
解:
#include
#include
#include
using namespace std;
void name_string(string &name, const string &prefix, const string &suffix)
{
name.insert(0, " ");
name.insert(0, prefix);
name.insert(name.size(), " ");
name.insert(name.size(), suffix);
}
int main()
{
string s = "James Bond";
name_string(s, "Mr.", "II");
cout << s << endl;
s = "M";
name_string(s, "Mrs.", "III");
cout << s << endl;
system("pause");
return 0;
}
编写程序,首先查找 string
"ab2c3d7R4E6"中每个数字字符,然后查找其中每个字母字符。编写两个版本的程序,第一个要使用 find_first_of
,第二个要使用 find_first_not_of
。
解:
find_first_of
在字符串中查找给定字符集合中任一字符首次出现的位置。若查找数字字符,则“给定字符集合”应包含所有10个数字;若查找字母,则要包含所有大小写字母—abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOQRSTUVWXYZ
。
#include
#include
#include
using namespace std;
void find_char(string &s, const string &chars)
{
cout << "在" << s << "中查找" << chars << "中字符" << endl;
string::size_type pos = 0;
while ((pos=s.find_first_of(chars, pos))!=string::npos)
{
cout << "pos: " << pos << ",char: " << s[pos] << endl;
pos++;
}
}
int main()
{
string s = "ab2c3d7R4E6";
cout << "查找所有数字" << endl;
find_char(s, "0123456789");
cout << endl << "查找所有字母" << endl;
find_char(s, "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
system("pause");
return 0;
}
find_first_not_of
查找第一个不在给定字符集合中出现的字符,若用它查找某类字符首次出现的位置,则应使用补集。若查找数字字符,则“给定字符集合”应包含10个数字之外的所有字符—abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL MNOPQRSTUVWXYZ
;若查找字母,则要包含所有非字母字符。
注意,这一设定仅对此问题要查找的字符串有效—它只包含字母和数字。因此,字母和数字互为补集。若字符串包含任意 ASCII
字符,可以想见,正确的“补集”可能非常冗长。
#include
#include
#include
using namespace std;
void find_not_char(string &s, const string &chars)
{
cout << "在" << s << "中查找不在" << chars << "中字符" << endl;
string::size_type pos = 0;
while ((pos=s.find_first_not_of(chars, pos))!=string::npos)
{
cout << "pos: " << pos << ",char: " << s[pos] << endl;
pos++;
}
}
int main()
{
string s = "ab2c3d7R4E6";
cout << "查找所有数字" << endl;
find_not_char(s, "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
cout << endl << "查找所有字母" << endl;
find_not_char(s, "0123456789");
system("pause");
return 0;
}
假定 name
和 numbers
的定义如325页所示,numbers.find(name)
返回什么?
解:
s.find(args)
查找 s
中 args
第一次出现的位置,即第一个与 args
匹配的字符串的位置。args
是作为一个字符串整体在 s
中查找,而非一个字符集合在 s
中查找其中字符。因此,对325页给定的 name
和 numbers
值,在 numbers
中不存在与 name
匹配的字符串,find
会返回 npos
。
如果一个字母延伸到中线之上,如 d
或 f
,则称其有上出头部分(ascender
)。如果一个字母延伸到中线之下,如 p
或 g
,则称其有下出头部分(descender
)。编写程序,读入一个单词文件,输出最长的既不包含上出头部分,也不包含下出头部分的单词。
解:
查找既不包含上出头字母,也不包含下出头字母的单词,等价于“排除包含上出头字母或下出头字母的单词”。因此,用 find_first_of
在单词中查找上出头字母或下出头字母是否出现。若出现(返回一个合法位置,而非 npos
)则丢弃此单词,继续检查下一个单词。否则,表明单词符合要求,检查它是否比之前的最长合法单词更长,若是,记录其长度和内容。文件读取完毕后,输出最长的合乎要求的单词。
#include
#include
#include
#include // file I/O support
#include // support for exit()
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()
{
char filename[20];
cout << "Enter name of data file: ";
cin.getline(filename, 20);
ifstream inFile; // object for handling file input
inFile.open(filename); // associate inFile with a file
if (!inFile.is_open()) // failed to open file
{
cout << "Could not open the file " << filename << endl;
cout << "Program terminating.\n";
// cin.get(); // keep window open
exit(EXIT_FAILURE);
}
find_longest_word(inFile);
inFile.close(); // finished with the file
system("pause");
return 0;
}
编写程序处理一个 vector
,其元素都表示整型值。计算 vector
中所有元素之和。修改程序,使之计算表示浮点值的 string
之和。
解:
标准库提供了将字符串转换为整型函数 stoi
。如果希望转换为不同整型类型,如长整型、无符号整型等,标准库也都提供了相应的版本。
#include
#include
#include
using namespace std;
int main()
{
vector<string> vs = { "123", "+456", "-789" };
int sum = 0;
for (auto iter = vs.begin(); iter != vs.end(); iter++){
sum += stoi(*iter);
}
cout << "和:" << sum << endl;
system("pause");
return 0;
}
标准库也提供了将字符串转换为浮点数的函数,其中 stof
是转换为单精度浮点数。简单修改上面的程序即可实现本题的第二问。
#include
#include
#include
using namespace std;
int main()
{
vector<string> vs = { "12.3", "-4.56", "+7.8e-2" };
float sum = 0;
for (auto iter = vs.begin(); iter != vs.end(); iter++){
sum += stof(*iter);
}
cout << "和:" << sum << endl;
system("pause");
return 0;
}
注意,当给定的字符串不能转换为数值时(不是所需类型数值的合法表示),这些转换函数会抛出 invalid_argument
异常;如果表示的值超出类型所能表达的范围,则抛出一个 out_of_range
异常。
设计一个类,它有三个 unsigned
成员,分别表示年、月和日。为其编写构造函数,接受一个表示日期的 string
参数。你的构造函数应该能处理不同的数据格式,如January 1,1900、1/1/1990、Jan 1 1900 等。
解:
在头文件中定义了 date
类。构造函数 date(string &ds)
从字符串中解析出年、月、日的值,大致步骤如下:
stoi
提取月份值,若月份值不合法,抛出异常,否则转到步骤6。ds
开始的子串与月份简称进行比较,若均不等,抛出异常(若与简称不等,则不可能与全称相等)。i
个月简称相等,且下一个字符是合法间隔符,返回月份值。stoi
提取日期值和年份值,如需要,检查间隔符合法性。此程序已经较为复杂,但显然离“完美”还差很远,只能解析3种格式,且进行了很多简化。程序中已经给出了几种格式错误,读者可尝试构造其他可能的格式错误。并尝试补充程序,支持其他格式,如“2006年7月12日”。
data.h
:
#ifndef DATE_H_INCLUDED
#define DATE_H_INCLUDED
#include
#include
#include
using namespace std;
class date{
public:
friend ostream& operator<<(ostream&, const date&);
date() = default;
date(string &ds);
unsigned y() const { return year; }
unsigned m() const { return month; }
unsigned d() const { return day; }
private:
unsigned year, month, day;
};
// 月份全称
const string month_name[] = { "January", "February", "March",
"April", "May", "June", "July", "August", "September",
"October", "November", "December" };
// 月份简写
const string month_abbr[] = { "Jan", "Feb", "Mar", "Apr", "May",
"Jun", "Jul", "Aug", "Sept", "oct", "Nov", "Dec" };
// 每月天数
const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
inline int get_month(string &ds, int &end_of_month){
int i, j;
for (i = 0; i < 12; i++){
// 检查每个字符是否与月份简写相等
for (j = 0; j < month_abbr[i].size(); j++)
if (ds[j] != month_abbr[i][j]) //不是此月简写
break;
if (j == month_abbr[i].size()) // 与简写匹配
break;
}
if (i == 12) // 与所有月份名都不相同
throw invalid_argument("不是合法月份名");
if (ds[j] == ' '){ //空白符,仅是月份简写
end_of_month = j + 1;
return i + 1;
}
for (; j < month_name[i].size(); j++)
if (ds[j] != month_name[i][j])
break;
if (j == month_name[i].size() && ds[j] == ' '){ //月份全称
end_of_month = j + 1;
return i + 1;
}
throw invalid_argument("不是合法月份名");
}
inline int get_day(string&ds, int month, int &p){
size_t q;
int day = stoi(ds.substr(p), &q); // 从p开始的部分转换为日期值
if (day<1 || day>days[month])
throw invalid_argument("不是合法日期值");
p += q;//移动到日期值之后
return day;
}
inline int get_year(string &ds, int &p){
size_t q;
int year = stoi(ds.substr(p), &q); // 从p开始的部分转换为年
if (p + q<ds.size())
throw invalid_argument("非法结尾内容");
return year;
}
date::date(string &ds){
int p;
size_t q;
if ((p = ds.find_first_of("0123456789")) == string::npos)
throw invalid_argument("没有数字,非法日期");
if (p>0){ // 月份名格式
month = get_month(ds, p);
day = get_day(ds, month, p);
if (ds[p] != ' ' && ds[p] != ',')
throw invalid_argument("非法间隔符");
p++;
year = get_year(ds, p);
}
else{ // 月份值格式
month = stoi(ds, &q);
p = q;
if (month<1 || month >12)
throw invalid_argument("不是合法月份值");
if (ds[p++] != '/')
throw invalid_argument("非法间隔符");
day = get_day(ds, month, p);
if (ds[p++] != '/')
throw invalid_argument("非法间隔符");
year = get_year(ds, p);
}
}
ostream & operator<<(ostream& out, const date& d){
out << d.y() << "年" << d.m() << "月" << d.d() << "日" << endl;
return out;
}
#endif // DATE_H_INCLUDED
main.cpp
:
#include
#include
#include "data.h"
using namespace std;
int main()
{
string dates[] = { "Jan 1, 2014", "February 1 2014", "3/1/2014", "3 1 2014"
//"Jcn 1,2014",
//"Janvary 1,2014",
//"Jan 32,2014",
//"Jan 1/2014",
};
try{
for (auto ds : dates){
date dl(ds);
cout << dl;
}
}
catch (invalid_argument e){
cout << e.what() << endl;
}
system("pause");
return 0;
}
使用 stack
处理括号化的表达式。当你看到一个左括号,将其记录下来。当你在一个左括号之后看到一个右括号,从 stack
中 pop
对象,直至遇到左括号,将左括号也一起弹出栈。然后将一个值(括号内的运算结果)push
到栈中,表示一个括号化的(子)表达式已经处理完毕,被其运算结果所替代。
解:
如下所示,本题的解答已经较为复杂,但这已是将问题大幅简化之后的解答了。我们假定表达式中只有加、减两种运算,由于所有运算优先级相同,无须进行复杂的优先级判断。由于加、减运算都是左结合,因此,遇到一个数值,直接与之前的运算数进行它们中间的运算即可。唯一的例外是括号,它将改变计算次序——括号里的运算将先于括号前的运算执行。不过,我们可以将它看作“阻断”了括号前的表达式,将括号内的部分看作一个独立的表达式优先处理,计算结果作为一个运算数参与之前的运算即可。
表达式解析的逻辑大致如下:
v
。v
是第一个运算数,直接压栈即可。v
前必须是一个运算符,再之前是另一个运算数 v'
,从栈顶弹出这两项,将计算结果压栈即可;否则,就抛出一个“缺少运算符”异常。v
,若栈空或栈顶不是左括号,仍抛出“未匹配右括号”异常;否则弹出左括号,把 v
作为新运算数,执行1中的逻辑。值得注意的是,为了在栈中保存括号、运算符和运算数三类对象,程序中定义了枚举类型 obj_type
。栈中每个对象都保存了类型 t
和数值 v
(如果 t
是 VAL
的话)。
#include
#include
#include
#include
#include
using namespace std;
//表示栈中对象的不同类型
enum obj_type{ LP, RP, ADD, SUB, VAL };
struct obj{
obj(obj_type type, double val = 0){ t = type; v = val; }
obj_type t;
double v;
};
inline void skipws(string &exp, size_t &p){
p = exp.find_first_not_of(" ", p);
}
inline void new_val(stack<obj> &so, double v){
if (so.empty() || so.top().t == LP){ //空栈或左括号
so.push(obj(VAL, v));
//cout << "push" << v << endl;
}
else if(so.top().t == ADD || so.top().t == SUB){
//之前是运算符
obj_type type = so.top().t;
so.pop();
/*
if(type == ADD)
cout << "pop+" << endl;
else
cout << "pop-" << endl;
*/
// cout << "pop" << so.top().v << endl;
// 执行加减法
if (type == ADD)
v += so.top().v;
else
v = so.top().v - v;
so.pop();
so.push(obj(VAL, v)); //运算结果压栈
//cout <<"push"<< v<< endl;
}
else
throw invalid_argument("缺少运算符");
}
int main()
{
stack<obj> so;
string exp;
size_t p = 0, q;
double v;
cout << "请输入表达式:";
getline(cin, exp);
while (p < exp.size()){
skipws(exp, p); //跳过空格
if (exp[p] == '('){ //左括号直接压栈
so.push(obj(LP));
p++;
//cout <<"push LP"<< endl;
}
else if (exp[p] == '+' || exp[p] == '-'){
//新运算符
if (so.empty() || so.top().t != VAL)
//空栈或之前不是运算数
throw invalid_argument("缺少运算数");
if (exp[p] == '+') //运算符压栈
so.push(obj(ADD));
else
so.push(obj(SUB));
p++;
//cout <<"push"<< exp[p-1]<< endl;
}
else if (exp[p] == ')'){ //右括号
p++;
if (so.empty()) //之前无配对的左括号
throw invalid_argument("未匹配右括号");
if (so.top().t == LP) //一对括号之间无内容
throw invalid_argument("空括号");
if (so.top().t == VAL){ //正确:括号内运算结果
v = so.top().v;
so.pop();
//cout <<"pop"<
if (so.empty() || so.top().t != LP)
throw invalid_argument("未匹配右括号");
so.pop();
//cout <<"pop LP"<
new_val(so, v); //与新运算数逻辑一致
}
else
//栈顶必定是运算符
throw invalid_argument("缺少运算数");
}
else{ //应该是运算数
v = stod(exp.substr(p), &q);
p += q;
new_val(so, v);
}
}
if (so.size() != 1 || so.top().t != VAL)
throw invalid_argument("非法表达式");
cout << so.top().v << endl;
system("pause");
return 0;
}
如果想要更多的资源,欢迎关注 @我是管小亮,文字强迫症MAX~
回复【福利】即可获取我为你准备的大礼,包括C++,编程四大件,NLP,深度学习等等的资料。
想看更多文(段)章(子),欢迎关注微信公众号「程序员管小亮」~