java程序设计基础 第5版

java程序设计基础 第5版你实现的 operator 没问题 但仅仅是一个特例 因为 ps rhs ps 会调用 std string 的 operator 这个实现恰好没问题 但是 ps 如果不是 std string 而是别的类型 比如某个浅拷贝实现的 operator 的类型 那不就出问题了 而 C Primer 的实现不会调用 ps 对应类型 operator 所以不会出问题 总结

大家好,我是讯享网,很高兴认识大家。



你实现的 operator=没问题,但仅仅是一个特例。因为*ps_ = *rhs.ps_会调用std::string的operator=,这个实现恰好没问题。

但是,ps_如果不是std::string而是别的类型,比如某个浅拷贝实现的operator=的类型。那不就出问题了。

而《C++ Primer》的实现不会调用*ps_对应类型operator=,所以不会出问题。

总结,你的实现没有通用性,有没有问题完全依赖*ps_对应类型operator=的实现。

练习1.20:在网站informit.com/title/0321上,第一章的代码目录中包含了头文件Sales_item.h。将它拷贝到你自己的工作目录中。用它编写一个程序,读取一组书籍销售记录,将每条记录打印到标准输出上。

java程序设计基础 第5版答:我使用Visual Studio2019进行C++编程,在网站http://www.informit.com/title/0中的下载选项下面下载Microsoft pre-C++ 11 compilers,其中的文件夹1中含有Salesitem.h,将其拷贝放在.pp的同级目录下。在可执行文件C++Primer第一章.exe的同级目录下建一个文本文档book_sales用于文件重定向,文档中输入几条书籍销售记录从而免去程序运行时繁琐的输入。在可执行文件的路径下输入cmd回车如图1-1所示:

图1-1

输入执行程序并将执行结果存入sales_book2文本中,如图1-2所示:

1-2

部分程序代码如下:

#include<iostream> #include"Sales_item.h" int main() { Sales_item item;//为类Sales_item 创建一个对象item while (true) { std::cin >> item;//读取书籍销售记录 if (item.isbn()=="")//如果读取的isbn号为空,表示文件读取完毕,结束while循环 { return 0; } else { std::cout << item << std::endl;//打印输出 } } return 0; }
讯享网

练习1.21:编写程序,读取两个ISBN相同的Sales_item对象,输出它们的和。

答:在可执行文件的同级目录下新建一个文本add_item,在其中输入所需的数据,然后运行程序,如图1-3所示:

图1-3

部分程序代码如下:

讯享网#include<iostream> #include"Sales_item.h" int main() { Sales_item item1, item2; std::cin >> item1 >> item2; std::cout << item1 + item2 << std::endl; return 0; }

练习1.22:编写程序,读取多个具有相同ISBN的销售记录,输出所有记录的和。

答:在可执行文件下使用文本add_item2,在其中输入ISBN号相同的销售记录,然后执行程序,如图1-4所示:

图1-4

部分程序代码如下:

#include<iostream> #include"Sales_item.h" int main() { Sales_item item1, item2; std::cin >> item1; while (true) { std::cin >> item2; if (item2.isbn() == "") { std::cout << item1 << std::endl; return 0; } else { item1 += item2; } } return 0; }

练习2.27:下面的哪些初始化是合法的?请说明原因。

讯享网(a) int i=-1,&r=0; (b)int *const p2=&r2; (c) const int i=-1, &r=0; (d)const int *const p3=&i2; (e) const int *p1=&i2; (f)const int &const r2; (g) const int i2=i,&r=i;

答:(a)非法,引用r必须绑定到一个同类型的对象而不是一个数。

(b)合法,常量指针p2将一直指向变量r2。

(c)合法,初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。

(d)合法,p3是一个指向常量的常量指针。

(e)合法,p1是一个指向常量的指针,它可以改变指向,但不能通过它改变指向的常量的值。

(f)非法,引用必须初始化。

(g)合法,i2的初始化可以是任意复杂的表达式,r是一个int常量类型的引用,它的对象可以是int变量。

练习2.28:说明下面的这些定义是什么意思,挑出其中不合法的。

(a) int i,*const cp; (b) int *p1,*const p2; (c) const int ic,&r=ic; (d)const int *const p3; (e) const int *p;

答:(a)创建int型常量i和指向指针的int型常量cp;非法,必须初始化常量cp;

(b)创建int型指针p1和指向常量的int型常量指针p2;非法,必须初始化常量p2;

(c)创建int型的常量ic和常量引用r;非法,int型常量必须初始化;

(d) 创建一个指向常量的int型常量指针p3;非法,p3必须初始化;

(e)创建一个指向常量的int型指针p。

修改后的代码:

#include<iostream> int main() { //(a) int i=0, * const cp=&i; //(b) int* p1=&i,* const p2=p1; //(c) const int ic=0, & r = ic; //(d) const int* const p3=p1; //(e) const int* p; return 0; }

练习2.29:假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。

(a) i = ic; (b) p1=p3; (c)p1=&ic; (d) p3=&ic; (e) p2=p1; (f)ic=*p3;

答:(a)合法

(b) 不合法,p1是指针变量,此表达式存在非法通过p1去修改p3的值;

(c)不合法,p1是指针变量,此表达式存在非法通过p1去修改ic的值;

(d) 不合法,不能修改常量p3的值;

(e) 不合法,不能修改常量p2的值;

(f) 不合法,不能修改常量ic的值;

代码调试如下:

#include<iostream> int main() { //(a) int i = 0, * const cp = &i; //(b) int* p1 = &i, * const p2 = p1; //(c) const int ic = 0, & r = ic; //(d) const int* const p3 = p1; //(e) const int* p; i = ic; /* * p1 = p3; //error C2440: “=”: 无法从“const int *const ”转换为“int *” * p1 = &ic;// error C2440: “=”: 无法从“const int *”转换为“int *” * p3 = &ic;//error C3892: “p3”: 不能给常量赋值 * p2 = p1; // error C3892: “p2”: 不能给常量赋值 * ic = *p3;//error C3892 : “ic”: 不能给常量赋值 */ return 0; }

练习3.2:编写一段程序从标准输入中一次读入一整行,然后修改该程序使其一次读入一个词。

答:

从标准输入中一次读入一整行的程序代码:

#include<iostream> #include <string> using std::cin; using std::cout; using std::string; using std::endl; int main() { string str; while (getline(cin, str)) //从标准输入值一次读入一整行 { if (!str.empty()) cout << str << endl; } }

从标准输入中一次读入一整行的运行结果如图3-1所示:

图3-1

一次读入一个词的程序代码:

#include<iostream> #include <string> using std::cin; using std::cout; using std::string; using std::endl; int main() { while (cin>>str) //从标准输入值一次读入一个词 { if (!str.empty()) cout << str << endl; } }

程序运行结果如图3-2所示:

图3-2

练习3.3:请说明string类的输入运算符和getline函数分别是如何处理空白字符的。

答:在执行读取操作时,string类的输入运算符会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,知道遇到下一处空白为止。而getling只要一遇到换行符就读取结束操作并返回结果,哪怕输入的一开始就是换行符也是如此,如果输入真的一开始就是换行符,那么所得结果是个空string,对于空白字符串,getline依然可以读取。

练习3.4:编写一段程序读入两个字符串,比较其是否相等并输出结果。如果不相等,输出较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较长的那个字符串。

答:

读入两个字符串,比较其是否相等并输出结果。如果不相等,输出较大的那个字符串。程序代码如下:

#include<iostream> #include<string> using std::cin; using std::cout; using std::endl; using std::string; int main() { string str1, str2; while (getline(cin, str1)) { getline(cin, str2); if (str1 == str2) cout << "两个字符窜相等" << endl; else if (str1 > str2) cout << "较大的字符串是:" << str1 << endl; else cout << "较大的字符串是:" << str2 << endl; } return 0; }

读入两个字符串,比较其是否相等并输出结果。如果不相等,输出较大的那个字符串。程序运行结果如图3-3所示:

图3-3

比较输入的两个字符串是否等长,如果不等长,输出长度较长的那个字符串。程序代码如下:

#include<iostream> #include<string> using std::cin; using std::cout; using std::endl; using std::string; int main() { //比较输入的两个字符串是否等长,如果不等长,输出长度较长的那个字符串 while (getline(cin, str1)) { getline(cin, str2); if (str1.size() == str2.size()) cout << "两个字符窜等长" << endl; else if (str1.size() > str2.size()) cout << "较长的字符串是:" << str1 << endl; else cout << "较长的字符串是:" << str2 << endl; } return 0; }

比较输入的两个字符串是否等长,如果不等长,输出长度较长的那个字符串。程序运行结果如如3-4所示:

图3-4

练习3.5:编写一段程序从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分隔开来。

答:编写一段程序从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串,程序代码如下:

#include<iostream> #include<string> using std::cin; using std::cout; using std::endl; using std::string; int main() { string str1,str2; while (getline(cin,str1)) { if (!str1.empty()) { str2 = str2 + str1; } else { if (str2.empty()) { cout << "没有字符串!" << endl; return 0; } else { cout << str2 << endl; return 0; } } } return 0; }

程序运行结果如图3-5所示:

图3-5

修改上述程序,用空格把输入的多个字符串分隔开来。程序如下:

#include<iostream> #include<string> using std::cin; using std::cout; using std::endl; using std::string; int main() { string str1,str2; bool first_str = true; while (getline(cin,str1)) { if (!str1.empty()) { if (first_str == true) { str2 = str1; first_str = false; } else { str2 = str2 + " "+str1; } } else { if (str2.empty()) { cout << "没有字符串!" << endl; return 0; } else { cout << str2 << endl; return 0; } } } return 0; }

修改上述程序,用空格把输入的多个字符串分隔开来。程序运行如图3-6所示:

图3-6

练习3.6:编写一段程序,使用范围for语句将字符串内的所有字符用X代替。

答:程序如下:

#include<iostream> #include<string> using std::cout; using std::cin; using std::string; using std::endl; int main() { string str("hello world!"); for (auto& c:str) c = 'X'; cout << str << endl; return 0; }

程序运行结果如图3-1所示:

图3-1

练习3.7:就上题完成的程序而言,如果将循环控制变量的类型设为char将发生什么?先估计一下结果,然后实际编程进行验证。

答:就上题完成的程序而言,如果将循环控制变量的类型设为char,将不能成功替换字符串中的字符,因为for(char c:str)实现的是将str的一个字符拷贝给c,此时替换c的值并不对str中的字符起作用。因此我们需要使用char &类型的变量c,改变c的值即改变绑定的字符的值。

程序运行结果如图3-2所示:

图3-2

练习3.8:分别用while循环和传统的for循环重写第一题的程序,你觉得哪种形式更好?为什么?

答:我觉得没有优劣之分,程序员能自然的写出来,阅读者能够都能够轻松理解代码的意义就可以。

while循环实现的代码如下:

#include<iostream> #include<string> using std::cout; using std::cin; using std::string; using std::endl; int main() { string str("hello world!"); decltype(str.size()) index = 0; while (index<str.size()) { str[index] = 'X'; index++; } cout << str << endl; return 0; }

传统的for循环实现的代码如下:

#include<iostream>

#include<string> using std::cout; using std::cin; using std::string; using std::endl; int main() { string str("hello world!"); //传统for循环实现将str中的字符转换成‘X'; for (decltype(str.size()) index = 0; index < str.size(); index++) { str[index] = 'X'; } cout << str << endl; return 0; }

练习3.9:下面的程序有何作用?它合法吗?如果不合法,为什么?

string s; cout<<s[0]<<endl;

答:创建了一个空字符串,然后输出字符串的第一个字符。我认为它不合法,即使编译器通过了并且能够运行,因为编译器没有去检查下标索引是否超出范围,这可能会导致未知的错误,编写程序时要避免此种情况。

练习3.10:编写一段程序,读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩余部分。

答:程序代码如下:

#include<iostream> #include<string> using std::cout; using std::cin; using std::string; using std::endl; int main() { string s1,s2; while (getline(cin, s1))//读取一行字符串 { if (s1.empty())//忽略空字符串 continue; else { for (auto c : s1) if (!ispunct(c)) s2 = s2 + c;//将非标点符号字符存入字符串s2 s1 = s2; //将s2覆盖s1 cout << "去除字符串后:" << s1 << endl; s1 = ""; s2 = ""; } } return 0; }

程序运行结果如下图3-3所示:

图3-3

练习3.11:下面的范围for语句合法吗?如果合法,c的类型是什么?

const string s="Keep out!"; for(auto &c:s) {/........https://www.zhihu.com/topic/}

答:合法,c是指向const char类型的引用。

练习3.12:下列vector对象的定义有不正确的吗?如果有,请指出来。对于正确的,描述其执行结果;对于不正确的,说明其错误的原因。

(a) vector< vector<int> > ivec;

(b) vector<string> svec = ivec;

(c) vector<string> svec( 10, "null" );

答:(a) 正确,该向量的元素是vector对象,元素是空的。

(b) 错误,sevc是string对象,而ivec是vector对象。

(c) 正确,sevc包含10个重复的元素"null"。

练习3.13:下列的vector对象各包含多少个元素?这些元素的值分别是多少?

答:

 vector<int> v1; //包含0各元素 vector<int> v2(10); //包含10个元素,每个元素默认值:10 vector<int> v3(10, 42); //包含10个元素,每个元素的值是:42 vector<int> v4{ 10 }; //包含一个元素,元素的值:10 vector<int> v5{ 10,42 }; //包含两个元素,元素的值分别是:10、42 vector<string> v6{ 10 }; //包含10个元素,每个元素的值为:"" vector<string> v7{10,"hi"}; //包含10个元素,每个元素的值是:"hi"

练习3.14:编写一段程序,用cin读入一组整数并把它们存入一个vector对象。

答:程序代码如下:

#include<iostream> #include<string> #include<vector> using std::vector; using std::cout; using std::cin; using std::string; using std::endl; int main() { vector <int> ivec; int a; cout << "请输入一组整数:"; while (cin >> a) { ivec.push_back(a); if (cin.get() == '\n') { //ivec.push_back(a); if (ivec.empty()) continue; else { cout << "输出整数:"; for (auto c : ivec) cout << c << " "; cout << endl; ivec.clear(); cout << "请继续输入一组整数:" ; } } } return 0; }

程序运行结果如图3-1所示:

图3-1

练习3.15:改写上题的程序,不过这次读入的是字符串。

答:程序代码如下:

#include<iostream> #include<string> #include<vector> using std::vector; using std::cout; using std::cin; using std::string; using std::endl; int main() { vector <string> ivec; string a; cout << "请输入一组字符串:"<<endl; while (cin >> a) { ivec.push_back(a); if (cin.get() == '\n') { //ivec.push_back(a); if (ivec.empty()) continue; else { cout << "输出字符串:" << endl; for (auto c : ivec) cout << c << endl; ivec.clear(); cout << "请继续输入一组字符串:" << endl; } } } return 0; }

程序运行结果如图3-2所示:

图3-2

练习3.16:编写一段程序,把练习3.13中vector对象的容量和具体内容输出出来。检验你之前的回答是否正确,如果不对,回过头重新学习3.3.1节直到弄明白错在何处为止。

答:程序代码如下:

#include<iostream> #include<string> #include<vector> using std::vector; using std::cout; using std::cin; using std::string; using std::endl; void PrintResult(vector<int> v,int n) { if (v.empty()) { cout << "v"<<n<<"为空,包含0个元素!" << endl; } else { cout << "v"<<n<<"包含的容量为" << v.size() << ", 它们的内容为:"; for (auto c : v) { cout << c << " "; } cout << endl; } } void PrintResult(vector<string> v, int n) { if (v.empty()) { cout << "v" << n << "为空,包含0个元素!" << endl; } else { cout << "v" << n << "包含的容量为" << v.size() << ", 它们的内容为:"; for (auto c : v) { cout << c << " "; } cout << endl; } } int main() { vector<int> v1; //包含0个元素 vector<int> v2(10); //包含10个元素,每个元素默认值:0 vector<int> v3(10, 42); //包含10个元素,每个元素的值是:42 vector<int> v4{ 10 }; //包含一个元素,元素的值:10 vector<int> v5{ 10,42 }; //包含两个元素,元素的值分别是:10、42 vector<string> v6{ 10 }; //包含10个元素,每个元素的值为:"" vector<string> v7{10,"hi"}; //包含10个元素,每个元素的值是:"hi" PrintResult(v1, 1); PrintResult(v2, 2); PrintResult(v3, 3); PrintResult(v4, 4); PrintResult(v5, 5); PrintResult(v6, 6); PrintResult(v7, 7); return 0; }

程序运行结果如图3-1所示:

图3-1

练习3.17:从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改写成大写形式。输出改变后的结果,每个词占一行。

答:程序代码如下:

#include<iostream> #include<string> #include<vector> using std::vector; using std::cout; using std::cin; using std::string; using std::endl; int main() { vector<string> str; string s; while (cin >> s) { str.push_back(s); for (auto& c : str[str.size() - 1]) c = toupper(c); } for (auto& c : str) cout << c << endl; return 0; }

程序运行结果如图3-2所示:

图3-2

练习3.18:下面的程序合法吗?如果不合法,你准备如何修改?

 vector<int> ivec; ivec[0] = 42;

答:不合法,修改为:

vector<int> ivec; ivec.push_back(42);

练习3.19:如果想定义一个含有10个元素的vector对象,所有元素的值都是42,请列举出3种不同的实现方法。哪种方法更好呢?为什么?

答:程序中的方法一更方便,因为程序员容易书写。程序如下:

#include<iostream> #include<vector> #include<string> using std::vector; using std::cin; using std::string; using std::cout; using std::endl; /* * 定义含有10个元素的vector对象, * 所有元素的值都是42 */ void printResult(vector<int> ivec, int n) { cout << "ivec"<<n<<"="; for (auto c : ivec) cout << c << ","; cout << endl; } int main() { //方法1 vector<int> ivec1(10,42); //方法2 vector<int> ivec2{ 42,42,42,42,42,42,42,42,42,42 }; //方法3 vector<int> ivec3(10); for (auto& c : ivec3) c = 42; printResult(ivec1, 1); printResult(ivec2, 2); printResult(ivec3, 3); return 0; }

程序输出结果如图3-3所示:

图3-3

练习3.20:读入一组整数并把它们存入一个vector对象,将每对相邻整数的和输出出来。改写程序,这次要求先输出第一个和最后一个元素的和,接着输出第二个和倒数第2个元素的和,依次类推。

答:程序代码如下:

#include<iostream> #include<vector> #include<string> using std::vector; using std::cin; using std::string; using std::cout; using std::endl; int main() { vector<int> ivec; int n=0; while (n < 10) { ivec.push_back(n); n++; } cout << "需要处理的一组整数:"; for (auto c : ivec) cout << c << " "; cout << endl; //输出每两对整数的和 cout << "输出每相邻两对整数的和:"; for (decltype(ivec.size()) index = 0; index < ivec.size() - 1; index=index+2) { cout << ivec[index] + ivec[index + 1] << " "; } cout << endl; //输出第一个和最后一个元素的和,接着输出第二个和倒数第2个元素的和,依次类推。 cout << "依次输出首尾两对整数的和:"; for (decltype(ivec.size()) index = 0; index < ivec.size()/2; index++) { cout << ivec[index] + ivec[ivec.size()-index-1] << " "; } cout << endl; return 0; }

程序运行结果如图3-4所示:

图3-4

练习3.30:指出下面代码中的索引错误。

 constexpr size_t array_size = 10; int ia[array_size]; for (size_t ix = 1; ix <= array_size; ++ix) ia[ix] = ix;

答:数组越界,ia[10]已经越界。

练习3.31:编写一段程序,定义一个含有10个int的数组,令每个元素的值就是其下标值。

答:程序代码如下:

#include<iostream> #include<string> using namespace std; int main() { constexpr size_t array_size = 10; int ia[array_size]; //r (size_t ix = 1; ix <= array_size; ++ix) for (size_t ix = 0; ix < array_size; ++ix) ia[ix] = ix; for (size_t ix = 0; ix < array_size; ++ix) cout << ia[ix] << " "; return 0; }

程序运行结果如图3-1所示:

图3-1

练习3.32:将上一题刚刚创建的数组拷贝给另一个数组。利用vector重写程序,实现类似的功能。

答:将上一题刚刚创建的数组拷贝给另一个数组程序代码如下:

#include<iostream> #include<string> using namespace std; int main() { constexpr size_t array_size = 10; int ia[array_size]; //r (size_t ix = 1; ix <= array_size; ++ix) for (size_t ix = 0; ix < array_size; ++ix) ia[ix] = ix; cout << "ia="; for (size_t ix = 0; ix < array_size; ++ix) cout << ia[ix] << " "; cout << endl; //数组拷贝 int ia2[array_size]; for (size_t ix = 0; ix < array_size; ix++) { ia2[ix] = ia[ix]; } cout << "ia2="; for (size_t ix = 0; ix < array_size; ix++) cout << ia2[ix] << " "; cout << endl; return 0; }

利用vector重写程序,实现类似的功能,修改代码如下:

#include<iostream> #include<string> #include<vector> using namespace std; int main() { //利用vector重写程序,实现类似的功能 vector<int> ivec(10,0),ivec2(10,0); for (int i = 0; i < ivec.size(); i++) { ivec[i] += i; } //拷贝 ivec2 = ivec; //输出ivec2的元素 for (auto c : ivec2) cout << c << " "; cout << endl; return 0; }

练习3.33:对于104页的程序来所,如果不初始化scores将会发生什么?

答:如果scores不初始化,编译器不会报错,但输出结果是一个未知的数。

#include<iostream> #include<vector> using namespace std; int main() { //unsigned scores[11];//未初始化 unsigned scores[11] = {};//初始化score的11个元素都为0 unsigned grade[14]= { 42,65,95,100,39,67,95,76,88,76,83,92,76,93 }; for (size_t i=0; i < sizeof(grade)/sizeof(grade[0]); i++) if (grade[i] <= 100) ++scores[grade[i] / 10]; for (auto c : scores) cout << c << " "; return 0; }

练习3.34:假定p1和p2指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的?

p1+=p2-p1;

答:该程序的含义是先计算p2-p1的位置差x,然后p1移动x个位置指向新的元素。

当p1移动x个位置,但该位置不是数组表示范围或数组尾元素的下一个位置,,该程序非法,虽然编译器依然通过。

练习3.35:编写一段程序,利用指针将数组中的元素置为0 ;

答:程序代码如下:

#include<iostream> #include<iterator> using namespace std; int main() { constexpr size_t max = 10; int arr[max] = { 0,1,2,3,4,5,6,7,8,9 }; int* pbeg = begin(arr); int* pend = end(arr); while(pbeg != pend) { *pbeg = 0; pbeg++; } for (pbeg = begin(arr); pbeg != pend; pbeg++) cout << *pbeg << " "; cout << endl; return 0; }

程序测试结果如图3-1所示:

图3-1

练习3.36:编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector对象是否相等。

答:编写一段程序,比较两个数组是否相等,代码如下:

#include<iostream> #include<iterator> using namespace std; int main() { constexpr size_t max = 10; int arr[max] = { 0,1,2,3,4,5,6,7,8,9 }; int arr2[max] = {}; int* pbeg = begin(arr); int* pend = end(arr); int* pbeg2 = begin(arr2); int* pend2 = end(arr2); while (pbeg != pend) { if (*pbeg != *pbeg2) { cout << "两个数组不相等" << endl; break; } else { pbeg++; pbeg2++; } } return 0; }

程序测试结果如图3-2所示:

图3-2

再写一段程序,比较两个vector对象是否相等。程序代码如下:

#include<iostream> #include<iterator> #include<vector> using namespace std; int main() { /*constexpr size_t max = 10; int arr[max] = { 0,1,2,3,4,5,6,7,8,9 }; int arr2[max] = {}; int* pbeg = begin(arr); int* pend = end(arr); int* pbeg2 = begin(arr2); int* pend2 = end(arr2); while (pbeg != pend) { if (*pbeg != *pbeg2) { cout << "两个数组不相等" << endl; break; } else { pbeg++; pbeg2++; } }*/ //再写一段程序,比较两个vector对象是否相等。 vector<int> ivec1(10,1); vector<int> ivec2(11, 1); if (ivec1 == ivec2) cout << "两个vector相等!" << endl; else cout << "两个vector不相等!" << endl; return 0; }

程序测试结果如图3-3所示:

图3-3

练习3.37:下面的程序是何含义,程序的输出结果是什么?

 const char ca[] = { 'h','e','l','l','o'}; const char* cp = ca; while (*cp) { cout << *cp << endl; ++cp; }

答:程序定义了一个const char 型的数组ca[],然后定义一个const char*型的指针指向数组ca的第一个元素,接着遍历数组并且输出数组的内容。因为ca数组没有添加字符'\0';在输出完'h','e','l','l','o'之后,指针继续指向下一个内存,输出内存上相应的字符,直到找到'\0'为止。

练习3.38:在本节中我们提到,将两个指针相加不但是非法的,而且也没什么意义。请问为什么两个指针相加没什么意义?

答:因为两个指针相加相当于两个内存相加,所以没有意义。

练习3.39:编写一段程序,比较两个string对象。再编写一段程序,比较两个C风格字符串的内容。

答:编写一段程序,比较两个string对象,程序代码如下:

#include<iostream> #include<string> using namespace std; int main() { const string str1 = "hello world!"; const string str2 = "hello world!"; if (str1 == str2) cout << "str1==str2" << endl; else cout << "str1!=str2" << endl; return 0; }

再编写一段程序,比较两个C风格字符串的内容。程序代码如下:

 const char ca1[] = "hello world"; const char ca2[] = "hello world"; if(strcmp(ca1,ca2)==0) cout<<"ca1==ca2"<<endl; else cout<<"ca1!ca2!"<<endl;

3.40:编写一段程序,定义两个字符数组并用字串字面值初始化它们;接着再定义一个字符数组存放前两个数组连接后的结果。使用strcpy何strcat把前面两个数组的内容拷贝到第三个数组中。

答:程序代码如下:

#include<iostream> #include<string> using namespace std; int main() { constexpr size_t max = 100; const char ca1[] = "hello world!"; const char ca2[] = "I'm fine! Thanks!"; char ca3[max] = {}; strcpy(ca3, ca1); strcat(ca3, ca2); cout << ca3; return 0; }

程序测试结果如图3-1所示:

图3-1

练习3.43:编写3个不同版本的程序,令其均能输出ia的元素。版本1使用范围for语句管理迭代过程;版本2和版本3都使用普通的for语句,其中版本2要求用下标运算符,版本3要求用指针。此外,在所有的3个版本的程序中都要直接写出数据类型,而不能使用类型别名、auto关键字或decltype关键字。

答:程序代码如下:

#include<iostream> using namespace std; //编写3个不同版本的程序,令其均能输出ia的元素。 int main() { int ia[3][4] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} }; //版本1 cout << "版本1" << ":" << endl; for (int (&i)[4]: ia) { for (int j : i) cout << j << " "; cout << endl; } cout << endl; //版本2 cout << "版本2" << ":" << endl; for (size_t i=0; i < 3; i++) { for (size_t j=0; j < 4; j++) cout << ia[i][j] << " "; cout << endl; } cout << endl; //版本3 cout << "版本3" << ":" << endl; for (int(*p)[4] = ia; p!=ia+3; p++) { for (int* q = *p; q != *p + 4; q++) cout << *q << " "; cout << endl; } return 0; }

程序测试结果如图3-1所示:

图3-1

练习3.44:改写上一个练习中的程序,使用类型别名来代替循环控制变量的类型。

答:程序代码如下:

#include<iostream> using namespace std; //编写3个不同版本的程序,令其均能输出ia的元素。 int main() { int ia[3][4] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} }; using int_array = int[4];//类型别名 //版本1 cout << "版本1" << ":" << endl; for (int_array &i: ia) { for (int j : i) cout << j << " "; cout << endl; } cout << endl; //版本2 cout << "版本2" << ":" << endl; for (size_t i = 0; i < 3; i++) { for (size_t j = 0; j < 4; j++) cout << ia[i][j] << " "; cout << endl; } cout << endl; //版本3 cout << "版本3" << ":" << endl; for (int_array *p = ia; p != ia + 3; p++) { for (int* q = *p; q != *p + 4; q++) cout << *q << " "; cout << endl; } return 0; }

练习3.35:再一次改写程序,这次使用auto关键字。

答:程序代码如下:

#include<iostream> using namespace std; //编写3个不同版本的程序,令其均能输出ia的元素。 int main() { int ia[3][4] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} }; //版本1 cout << "版本1" << ":" << endl; for (auto &i : ia) { for (auto j : i) cout << j << " "; cout << endl; } cout << endl; //版本2 cout << "版本2" << ":" << endl; for (auto i = 0; i < 3; i++) { for (auto j = 0; j < 4; j++) cout << ia[i][j] << " "; cout << endl; } cout << endl; //版本3 cout << "版本3" << ":" << endl; for (auto* p = ia; p != ia + 3; p++) { for (auto* q = *p; q != *p + 4; q++) cout << *q << " "; cout << endl; } return 0; }

练习4.36:假设i是int类型,d是double类型,书写表达式i*=d使其执行整数类型的乘法而非浮点类型的乘法。

答:程序代码如下:

#include<iostream> #include<vector> #include<string> using namespace std; int main() { int i = 3; double d = 5.5; i *= static_cast<int>(d); cout << i<< endl; return 0; }

程序测试结果如图4-1所示:

图4-1

练习4.37:用命名的强制类型转换改下列旧式的转换语句。

int i; double d; const string *ps; char *pc; void *pv; (a)pv=(void*) ps; (b)i = int(*pc) (c)pv=&d; (d)pc=(char*) pv;

答:

(a) pv = static_cast<void*>(const_cast<string*>(ps));

(b) i = static_cast<int>(*pc);

(c) pv = static_cast<void*>(&d);

(d) pc = reinterpret_cast<char*>(pv);

练习4.38:说明下面这条表达式的含义。

double slope = static_cast<double>(j/i);

答:将j/i的值强制转换成double类型,然后拷贝给slope。

练习5.9:编写一段程序,使用一系列if语句统计从cin读入的文本中有多少元音字母

答:程序代码如下:

#include<iostream> #include<vector> using namespace std; int main() { unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; char ch; while (cin >> ch) { if (ch == 'a') ++aCnt; else if (ch == 'b') ++eCnt; else if (ch == 'i') ++iCnt; else if (ch == 'o') ++oCnt; else if (ch == 'u') ++uCnt; } cout << "Number of vowel a: \t" << aCnt << '\n' << "Number of vowel e: \t" << eCnt << '\n' << "Number of vowel i: \t" << iCnt << '\n' << "Number of vowel o: \t" << oCnt << '\n' << "Number of vowel u: \t" << uCnt << endl; return 0; }

测试文本5.9中存入的是测试代码(将要被统计),程序测试结果如图5-1所示:

图5-1

练习5.10:我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计大写形式,也就是说,新程序遇到'a'和'A'都应该递增aCnt的值,以此类推。

答:程序代码如下:

#include<iostream> #include<vector> using namespace std; int main() { unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; char ch; while (cin >> ch) { switch(ch) { case 'a': case 'A': ++aCnt; break; case 'e': case 'E': ++eCnt; break; case 'i': case 'I': ++iCnt; break; case 'o': case 'O': ++oCnt; break; case 'u': case 'U': ++uCnt; break; } } cout << "Number of vowel a: \t" << aCnt << '\n' << "Number of vowel e: \t" << eCnt << '\n' << "Number of vowel i: \t" << iCnt << '\n' << "Number of vowel o: \t" << oCnt << '\n' << "Number of vowel u: \t" << uCnt << endl; return 0; }

测试文本5.10中存入的是测试代码(将要被统计),程序测试结果如图5-2所示:

图5-2

练习5.11:修改统计元音字母的程序,使其也能统计空格、制表符和换行符的数量。

答:程序代码如下:

#include<iostream> #include<vector> using namespace std; int main() { unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0,spaceCnt=0,tabCnt=0,newlineCnt=0; char ch; //注意:cin>>会自动过滤掉不可见字符(如空格 回车 tab等)。若不想过滤掉空白字符,可以用noskipws流进行控制。 while (cin >> noskipws>>ch) { switch (ch) { case 'a': case 'A': ++aCnt; break; case 'e': case 'E': ++eCnt; break; case 'i': case 'I': ++iCnt; break; case 'o': case 'O': ++oCnt; break; case 'u': case 'U': ++uCnt; break; case ' ': ++spaceCnt; break; case '\t': ++tabCnt; break; case '\n': ++newlineCnt; break; default: break; } } cout << "Number of vowel a: \t" << aCnt << '\n' << "Number of vowel e: \t" << eCnt << '\n' << "Number of vowel i: \t" << iCnt << '\n' << "Number of vowel o: \t" << oCnt << '\n' << "Number of vowel u: \t" << uCnt << '\n' << "Number of vowel space: \t" << spaceCnt << '\n' << "Number of vowel tab: \t" << tabCnt << '\n' << "Number of vowel newline: \t" << newlineCnt << endl; return 0; }

测试文本5.11中存入的是测试代码(将要被统计),程序测试结果如图5-3所示:

图5-3

练习5.12:修改能统计元音字母的程序,使其能统计以下含有两个字符的字符序列的数量:ff、fl和fi。

答:程序代码如下:

#include<iostream> #include<vector> using namespace std; int main() { unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, spaceCnt = 0, tabCnt = 0, newlineCnt = 0, ffCnt = 0, flCnt = 0, fiCnt = 0; char ch; //注意:cin>>会自动过滤掉不可见字符(如空格 回车 tab等)。若不想过滤掉空白字符,可以用noskipws流进行控制。 while (cin >> noskipws >> ch) { switch (ch) { case 'a': case 'A': ++aCnt; break; case 'e': case 'E': ++eCnt; break; case 'i': case 'I': ++iCnt; break; case 'o': case 'O': ++oCnt; break; case 'u': case 'U': ++uCnt; break; case ' ': ++spaceCnt; break; case '\t': ++tabCnt; break; case '\n': ++newlineCnt; break; case 'f': { char ch2; cin >> noskipws >> ch2; switch (ch2) { case 'f': ++ffCnt; break; case 'l': ++flCnt; break; case 'i': { ++fiCnt; ++iCnt; } break; case 'a': case 'A': ++aCnt; break; case 'e': case 'E': ++eCnt; break; case 'I': ++iCnt; break; case 'o': case 'O': ++oCnt; break; case 'u': case 'U': ++uCnt; break; case ' ': ++spaceCnt; break; case '\t': ++tabCnt; break; case '\n': ++newlineCnt; break; default: break; } } default: break; } } cout << "Number of vowel a: \t" << aCnt << '\n' << "Number of vowel e: \t" << eCnt << '\n' << "Number of vowel i: \t" << iCnt << '\n' << "Number of vowel o: \t" << oCnt << '\n' << "Number of vowel u: \t" << uCnt << '\n' << "Number of vowel space: \t" << spaceCnt << '\n' << "Number of vowel tab: \t" << tabCnt << '\n' << "Number of vowel newline: \t" << newlineCnt<<'\n' << "Number of vowel ffCnt: \t" << ffCnt << '\n' << "Number of vowel flCnt: \t" << flCnt << '\n' << "Number of vowel fiCnt: \t" << fiCnt << '\n' << endl; return 0; }

测试文本5.12中存入的是测试代码(将要被统计),程序测试结果如图5-4所示:

图5-4

练习5.13:下面显示的每个程序都含有一个常见的编程错误,指出错误在哪里,然后修改它们。

(a) unsigned aCnt=0, eCnt= 0,iouCnt=0;     char ch = next_text();     switch (ch)  {        case 'a' :  aCnt++;        case 'e' :  eCnt++;        default :  iouCnt++;     } (b)unsigned index = some_value();    switch (index)  {       case 1 :           int ix=get_value();           ivec[ ix ] = index;           break;       default :           ix = ivec.size() -1;           ivec [ ix ] = index;       } (c)unsigned evenCnt = 0,oddCnt = 0;    int digit = get_num()%10;    switch (digit)  {      case 1,3,5,7,9:           oddcnt++;           break;      case 2,4,6,8,10 :           evencnt++;           break;     } (d)unsigned ival =512, jval=1024,kval=4096;    unsigned bufsize;    unsigned swt = get_bufCnt();    switch(swt) {       case ival :         buffsize=ival * sizeof(int);         break;       case jval :         buffsize = jval * sizeof(int);         break;       case kval :         bufsize = kval *sizeof(int);         break;     }

答:

(a) 如果程序执行aCnt++,必然会继续执行eCnt++和iouCnt++;

修正:

(a) unsigned aCnt=0, eCnt= 0,iouCnt=0;     char ch = next_text();     switch (ch)  {        case 'a' :  aCnt++; break;        case 'e' :  eCnt++; break;        default :  iouCnt++; break;     }

(b) int ix=get_value();//error这是一个隐式初始化变量,后面的ix = ivec.size() -1//未定义的标识符ix;

修正:

(b)unsigned index = some_value(); int ix=get_value();    switch (index)  {       case 1 :           ivec[ ix ] = index;           break;       default :           ix = ivec.size() -1;           ivec [ ix ] = index;       }

(c) case标签后只能存放一个值:

修正:

(c)unsigned evenCnt = 0,oddCnt = 0;    int digit = get_num()%10;    switch (digit)  {      case 1: case 3: case 5: case 7: case 9:           oddcnt++;           break;      case 2: case 4: case 6: case 8: case 10:           evencnt++;           break;     }

(d) case标签后应该是整形常量表达式,而case后面的值不是常量:

修改:

(d)const unsigned ival =512, jval=1024,kval=4096;    unsigned bufsize;    unsigned swt = get_bufCnt();    switch(swt) {       case ival :         buffsize=ival * sizeof(int);         break;       case jval :         buffsize = jval * sizeof(int);         break;       case kval :         bufsize = kval *sizeof(int);         break;     }

练习6.6:说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时用到这三种形式。

答: 形参是一个函数的组成部分,可以为空,它和局部变量一样只有在函数体(或自己所在的块)存在期间存在,函数体(或自己所在的块)结束而消亡,每一次重新调用都需要进行初始化。而局部静态变量在第一次初始化之后,此后它的生命周期直至程序运行结束,并且在这期间它的值修改后,会一直保持修改后的值(直到再次被修改)。

程序代码如下:

#include<iostream> using namespace std; int fact(int val) { int val2 = 0;//局部变量 ++val2; cout << "int val2 =" << val2 << endl; static size_t ctr = 0; //局部静态变量 ++ctr; cout << "static size_t ctr=" << ctr << endl; ++val; cout << "形参Val=" << val << endl; return 0; } int main() { int n; // cout << "请输入一个整数:"; while (cin >> n) { fact(n); cout<< "请输入一个整数:"; } return 0; }

程序测试结果如图6-1所示:

图6-1

练习6.7:编写一个程序,当它第一次被调用返回0,以后每次被调用返回值加1。

答:程序代码如下:

#include<iostream> using namespace std; int fact(void) { static size_t ctr = 0; //局部静态变量 return ctr++; } int main() { while (true) { cout << " static size_t ctr =" << fact() << endl; cout << "是否继续调用函数fact()?[y/n] : "; char ch; cin >> ch; if (!cin || ch == 'n' || ch == 'N') break; } return 0; }

程序测试结果如图6-2所示:

图6-2

6.11:编写并验证你自己的reset函数,使其作用于引用类型的参数。

答:程序代码如下:

#include<iostream> using namespace std; int reset(int &n) { n = 0; return 0; } int main() { int n = 9; reset(n); cout << n; return 0; }

练习6.12:改写6.2.1节中6.10的程序,使用引用而非指针交换两个整数的值。你觉得哪种方法更易于使用?为什么?

答: 我觉得使用引用更方便,因为这样可以少写几个字符,和值传递相似,只不过形参换成了引用。程序代码如下:

#include<iostream> using namespace std; void exchange(int& p, int& q) { int tmp; tmp = p; p = q; q = tmp; } int main() { int a = 8, b = 10; cout << "a=" << a << ", b=" << b << endl; exchange(a, b); cout << "a=" << a << ", b=" << b << endl; return 0; }

程序测试如图6-1所示:

图6-1

练习6.13:假设T是某种类型的名字,说明一下两个函数声明的区别:一个是void f( T ), 另一个是 void f( &T )。

答:void f( T )里面的T是形参,是实参的一个拷贝,它的改变不会影响实参。而void f( &T )里面的T是引用,是实参的别名,T的值改变等价于实参的值改变。

练习6.14:举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。

答:交换两个整数的值的函数里需要使用引用,而计算一个整数的阶乘的函数里面的整数建议是一个形参,因为整数在函数中会递减,我们不能影响实参的值。

练习6.15:说明find_char函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occues是引用类型而c不是?如果令s是普通引用会发生什么事情?如果occurs是常量引用会发生什么情况?

答:为什么s是常量引用:因为s是一个字符串,所以我们尽量不做拷贝而做引用,又因为它的值不能够被改变,所以最终使用常量引用。

为什么occurs是普通引用:因为我们需要计算某个字母出现的次数,occurs代表次数,它需要被改变,并且在函数外会被使用,所以使用普通引用。

为什么s和occues是引用类型而c不是:因为c是一个字符且使用一次之后不再使用它,当然它也可以使用引用。

如果令s是普通引用会发生什么事情:这样的话在函数find_char中改变s是允许的,但这违背了我们的意愿。

如果occurs是常量引用会发生什么情况:这会使得程序不能执行,因为常量是不能够被修改的。

原程序代码如下:

#include<iostream> #include<string> using namespace std; string::size_type find_char(const string& s, char c, string::size_type& occurs) { auto ret = s.size(); //第一次出现的为止(如果为空的话) occurs = 0; //设置表示出现次数的形参的值 for (decltype(ret) i = 0; i != s.size(); ++i) { if (s[i] == c) { if (ret == s.size()) ret = i; //记录c第一次出现额位置 ++occurs; } } return ret; } int main() { string s = "hello world"; decltype(s.size()) Cnt; auto firstOut = find_char(s, 'l', Cnt); cout << "字母'l'在" << s << "中出现了" << Cnt << "次!" << endl; cout << "字母'l'在" << s << "中第一次出现的位置是:" << firstOut << endl; return 0; }

练习6.30:编译第200页的str_subrange函数,看看你的编译器是如何处理函数中的错误的。

答:

程序代码如下:

#include<iostream> #include<string> #include<vector> using namespace std; bool str_subrange(const string& str1, const string& str2) { //大小相同:此时用普通的相等性判断结果作为返回值 if (str1.size() == str2.size()) return str1 == str2; //正确:==运算符返回bool值 //得到较短string对象的大小 auto size = (str1.size() < str2.size()) ? str1.size() : str2.size(); //检查两个string 对象的对应字符是否相等,以较短的字符串长度为限 for (decltype(size) i = 0; i != size; ++i) if (str1[i] != str2[i]) return ;//错误:没有返回值,编译器将报告这一错误 } int main(int argc, char argv) { return 0; }

修复编译报告的错误( //错误:没有返回值,编译器将报告这一错误 )之后,程序能允许,同时会警告:warning C4715: “str_subrange”: 不是所有的控件路径都返回值。

练习6.31:什么情况下返回的引用无效?什么情况下返回常量的引用无效?

答: 函数返回的引用必须满足:引用的对象是一个在调用函数之前就存在的对象;如果引用的对象只是函数中的一个局部对象,那么函数返回这个引用是无效的。同样,返回的常量的引用有效的条件是:引用的对象是一个在调用函数之前就存在的对象。如果引用的对象只是函数中的一个局部对象,那么函数返回这个引用是无效的。

练习6.32:下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。

int &get(int *array, int index) {return array[index]; } int main() { int ia[10]; for( int i = 0; i != 10; ++i) get(ia, i) = i; }

答:合法,该函数get()返回ia[i]并且在main函数中给返回的引用赋值。

测试结果如图6-1所示:

图6-1

练习6.33:编写一个递归函数,输出vector对象的内容。

答:程序代码如下:

#include<iostream> #include<vector> using namespace std; void printstr(vector<string> str,vector<string>::size_type i) { if (i < str.size()) { cout << str[i] << endl; printstr(str, ++i); } } int main() { vector<string> str = {"hello","world","I","am","fine"}; printstr(str, 0); return 0; }

程序测试结果如图6-2所示:

图6-2

练习6.34:如果factorial函数的停止条件如下所示,将发生什么情况?

if(val!=0)

答:如果factorial函数的停止条件改为if(val!=0),那么当输入一个负数时,程序进入死循环。

#include<iostream> #include<vector> using namespace std; //计算val的阶乘 int factorial(int val) { //if(val!=1) if (val >1) return factorial(val - 1) * val; return 1; } int main() { int n = 9; cout << n << "的阶乘=" << factorial(n) << endl; return 0; }

练习6.35:在调用factorial函数时,为什么我们传入的值是val-1而非val--?

答:

图6-3

调用factorial函数执行过程如图6-3所示,递归函数需要从图中的最下面计算上去,如果将传入的值修改为val -- ,假设实参val=5,那么程序将会一直执行factorial(val - -),因为val=5>1,而val--永远先使用val=5,相当于程序不断的执行factorial(5)。

我们还可以假设把传入的值改为--val,那么输出facotrial(5)的结果不再是正确的120,而是24,因为--val先执行val=val-1=4,当程序从递归中返回到最顶层时,此时要相乘val=4而不是5,相当于factorial(4)*4。

练习7.47:说明接收一个string参数的Sales_data构造函数是否应该是explicit的,并解释这样做的优缺点。

答:接收一个string参数的Sales_data构造函数应该是explicit的,因为这样可以避免隐式类型转换带来的意想不到的错误,我们希望程序具有较友好可读性,尽量不要增加理解代码的压力。

练习7.48:假定Sales_data的构造函数不是explicit的,则下述定义将执行什么样的操作?如果Sales_data的构造函数是explicit的,又会发生什么呢?

string numm_isbn("9-999-99999-9"); Sales_data item1(null_isbn); Sales_data item2("9-999-99999-9");

答:第一行定义一个string对象,第二行、第三行都是调用了带一个参数的默认构造函数,定义了两个Sales_data对象,item1和item2。即使没有explicit修饰构造函数,上面也没有发生隐式转换。

练习7.49:对于combine函数的三种不同声明,当我们调用i.combine(s)时分别发生什么情况?其中i是一个Sales_data,而s是一个string对象。

(a) Sales_data &combine(Sales_data); (b) Sales_data &combine(Sales_data&); (c) Sales_data &combine(const Sales_data&) const;

答:(a) 创建一个临时Sales_data对象,然后作为combine函数的形参被调用调用。

(b) 无法通过编译,因为combine函数的参数是一个非常量引用,而s是一个string对象,编译器用s自动创建一个Sales_data临时对象,但是这个新生成的临时对象无法传递给combine所需的非常量引用。如果我们把函数声明修改为Sales_data &combine(const Sales_data&);就可以。

(c) 无法通过编译,因为我们把combine声明成了常量成员函数,所以该函数无法修改数据成员的值。

练习7.50:确定在你的Person类中是否有一些构造函数应该是explicit的。

答:

public: //构造函数 Person() = default; Person(const string& name, const string& add) :name(name), address(add) {} explicit Person(istream& is);

练习7.51:vector将其单参数的构造函数定义成explicit的,而string则不是,你觉得原因何在?

答:string接收的单参数是const char*类型,如果我们得到了一个非常量指针(字符数组),则把它看作string对象是自然而然的过程,编译器自动把参数类型转换成类类型也非常符合逻辑,因此我们无须指定为explicit的。

与string相反,vector接收的单参数类型是int类型,这个参数的原意是指定vector的容量。如果我们在本来需要vector的地方提供一个int值并且希望这个int值自动转换成vector,则这个过程显得比骄傲牵强,因此把vector的单参数构造函数定义成explicit的更加合理。

练习14.52:在下面的加法表达式中分别选用了哪个operator+?列出候选函数、可行函数及每个可行函数的实参执行的类型转换:

struct LongDouble { //用于演示的成员operator+;在通常情况下+是一个非成员 LongDouble operator+(const SmallInt&); LongDouble(double = 0.0); operator double(); operator float(); }; LongDouble operator+(LongDouble& ,double); SmallInt si; LongDouble ld; ld=si+ld; ld=ld+si;

答:

1)ld=si+ld;

由于LongDouble不能转换为SmallInt,因此SmallInt的成员operator+和friend operator+都不可行。

由于SmallInt不能转换成LongDouble,LongDouble的成员operator+和非成员operator+也不可行。

由于SmallInt可以转换为int,LongDouble可以转换为float和double,所以内置的operator+(int,float)和operator+(int,double)都可行,会产生二义性。

2)ld=ld+si;

由于SmallInt不能转换成Double,LongDouble不能转换为SmallInt,SmallInt的成员operator+和两者的非成员operator+也不可行。

由于SmallInt可以转换为int,LongDouble可以转换为float和double,所以内置的operator+(int,float)和operator+(int,double)都可行,但它们都需要类型转换。

精确匹配: LongDouble operator+(const SmallInt&);

练习14.53:假设我们已经定义了如第522页所示的SmallInt,判断下面的加法表达式是否合法。如果合法,使用哪个加法运算符?如果不合法,应该怎样修改代码才能使其合法化?

SmallInt s1; double d=s1+3.14;

答:不合法,产生二义性。

为了保留3.14的小数部分,我改成:

double d=static_cast<double>(s1)+3.14;

练习15.34:针对图15.3构建的表达式:

(a)列举出在处理表达式的过程中执行的所有构造函数。 (b)列举出cout<<q所调用的rep。 (c)列举出q.eval()所调用的eval。

答:(a)

(b)

(c)

练习15.35:实现Query类和Query_base类,其中需要定义rep而无需定义eval。

答:

//Query.h: #pragma once #include<string> #include"my_QueryResult.h" #include"my_TextQuery.h" #include"Query_base.h" class Query { friend ostream& operator<<(ostream& os, const Query& query); //这些运算符需要访问接受shared_ptr的构造函数,而该函数是私有的 friend Query operator~(const Query&); friend Query operator|(const Query&, const Query&); friend Query operator&(const Query&, const Query&); public: Query(const std::string&); //构建一个新的WordQuery //接口函数:调用对应的Query_base操作 QueryResult eval(const TextQuery& t)const { return q->eval(t); } string rep() const { return q->rep(); } private: Query(shared_ptr<Query_base> query) :q(query) {} shared_ptr<Query_base> q; }; ostream& operator<<(ostream& os, const Query& query) { //Query::rep通过它的Query_base指针对rep()进行虚调用 return os << query.rep(); } inline Query::Query(const string& s) :q(new WordQuery(s)) {}
//Query_base.h: #pragma once #include<string> #include"my_QueryResult.h" #include"my_TextQuery.h" //这是一个抽象基类,具体的查询类型从中派生,所有成员都是private的 class Query_base { friend class Query; protected: using line_no = TextQuery::line_no; //用于eval函数 virtual ~Query_base() = default; private: //eval返回与当前Query匹配的QueryResult virtual QueryResult eval(const TextQuery&) const = 0; //rep是表示查询的一个string virtual string rep() const = 0; };

练习15.36:在构造函数和rep成员中添加打印语句,运行你的代码以检验你对本节第一个练习中(a)、(b)两个小题的回答是否正确。

答:

//练习12.2:编写你自己的StrBlob,包含const版本的front和back。 #pragma once #include<iostream> #include<string> #include<vector> #include<memory> #include<algorithm> using namespace std; class StrBlob { public: typedef vector<string>::size_type size_type; StrBlob(); explicit StrBlob(initializer_list<string> il); size_type size() const { return data->size(); } auto use_count()const { return data.use_count(); } bool empty() const { return data->empty(); } //添加和删除元素 void push_back(const string& s) { data->push_back(s); } void pop_back(); //元素访问 const string& front() const; const string& back() const; const string& iter(size_type n) const;//访问vector中第n个元素 private: shared_ptr<vector<string>> data; //如果data[i]不合法,抛出一个异常 void check(size_type i, const string& msg) const; }; //定义构造函数 StrBlob::StrBlob() :data(make_shared<vector<string>>()) {} StrBlob::StrBlob(initializer_list<string> il) :data(make_shared<vector<string>>(il)) {} //删除元素 void StrBlob::pop_back() { //如果vector为空,check抛出异常 check(0, "pop_back on empty StrBlob"); data->pop_back(); } //访问元素 const string& StrBlob::front() const { check(0, "front on empty StrBlob"); return data->front(); } const string& StrBlob::back() const { check(0, "back on empty StrBlob"); return data->back(); } const string& StrBlob::iter(size_type n)const { check(n, "n out_of_range []"); return (*data)[n]; } //如果data[i]不合法,抛出一个异常 void StrBlob::check(size_type i, const string& msg) const { if (i >= data->size()) throw out_of_range(msg); }
//my_TextQuery.h: #pragma once #include<iostream> #include<fstream> #include<string> #include<sstream> #include<map> #include<set> #include<vector> #include"my_QueryResult.h" using namespace std; class QueryResult; class TextQuery { public: using line_no = vector<string>::size_type;// 行号 public: TextQuery(ifstream& in); QueryResult query(const string&) const; string& trans(string& s) const; //去掉单词中的部分标点符号 private: StrBlob file;//存储文件内容(逐行存储) map<string, shared_ptr<set<line_no>>> wm;//存储<单词->行号> }; ////////////////////////////////TextQuery///////////////////////////////////////// string& TextQuery::trans(string& s)const { for (size_t i = 0; i < s.size(); ++i) { if (s[i] >= '!' && s[i] <= '/') s[i] = ' '; if (s[i] >= ':' && s[i] <= '@') s[i] = ' '; if (s[i] >= '[' && s[i] <= '`') s[i] = ' '; if (s[i] >= '{' && s[i] <= '~') s[i] = ' '; } return s; } TextQuery::TextQuery(ifstream& in) { //file.reset(new vector<string>); string line_word; while (getline(in, line_word)) { file.push_back(line_word);//逐行将文字存入vector istringstream line_word2(trans(line_word)); string word; while (line_word2 >> word) { auto& line = wm[word];//向map中插入元素,返回mapped_type的引用 if (!line)//如果line为空 line.reset(new set<line_no>);//更新line的指向 line->insert(file.size() - 1);//插入行号(默认从0开始) } } } QueryResult TextQuery::query(const string& word) const { auto find_word = wm.find(word);//返回指向查找到的元素的迭代器 if (find_word == wm.end()) { //如果没有找到,返回的行号是一个空shared_ptr static shared_ptr<set<line_no>> nodata(new set<line_no>); return QueryResult(word, nodata, file); } else return QueryResult(word, find_word->second, file); }
//my_QueryResult.h: #pragma once #include<iostream> #include<fstream> #include<string> #include<sstream> #include<map> #include<set> #include<vector> #include"my_StrBlob.h" class QueryResult { friend ostream& print(ostream&, const QueryResult&); public: using line_no = vector<string>::size_type; using line_iter = set<line_no>::iterator; QueryResult(string s, shared_ptr<set<line_no>> l, StrBlob f) :sought(s), lines(l), file(f) {} line_iter begin() const { return lines->cbegin(); } line_iter end() const { return lines->cend(); } shared_ptr<StrBlob> get_file() const { return make_shared<StrBlob>(file); } private: string sought;//查询单词 shared_ptr<set<line_no>> lines;//出现的行号 StrBlob file;//文件内容 }; /////////////////////////////////////////////////////////////////////// ostream& print(ostream& os, const QueryResult& qr) { os << "单词\"" << qr.sought << "\"在文件中出现了 " << qr.lines->size() << " 次" << endl; for (const auto i : *qr.lines) { os << "( line " << i + 1 << " ) " << qr.file.iter(i) << endl; } return os; }
//Query.h: #pragma once #include<string> #include"my_QueryResult.h" #include"my_TextQuery.h"
//Query_base.h: #pragma once #include<string> #include"Query.h" //这是一个抽象基类,具体的查询类型从中派生,所有成员都是private的 class Query_base { friend class Query; protected: using line_no = TextQuery::line_no; //用于eval函数 virtual ~Query_base() = default; private: //eval返回与当前Query匹配的QueryResult //virtual QueryResult eval(const TextQuery&) const = 0; //rep是表示查询的一个string virtual string rep() const = 0; }; class Query { friend ostream& operator<<(ostream& os, const Query& query); //这些运算符需要访问接受shared_ptr的构造函数,而该函数是私有的 friend Query operator~(const Query&); friend Query operator|(const Query&, const Query&); friend Query operator&(const Query&, const Query&); public: Query(const std::string&); //构建一个新的WordQuery //接口函数:调用对应的Query_base操作 //QueryResult eval(const TextQuery& t)const //{ // return q->eval(t); //} string rep() const { cout << "Query" << endl; return q->rep(); } private: Query(shared_ptr<Query_base> query) :q(query) { cout << "Query::Query(shared_ptr<Query_base> query)" << endl; } shared_ptr<Query_base> q; }; ostream& operator<<(ostream& os, const Query& query) { //Query::rep通过它的Query_base指针对rep()进行虚调用 return os << query.rep(); } class WordQuery :public Query_base { friend class Query; //Query使用WordQuery构造函数 private: WordQuery(const string& s) :query_word(s) { cout << "WordQuery::WordQuery(const string& s)" << endl; } //具体的类:WordQuery将定义所有继承而来的纯虚函数 //QueryResult eval(const TextQuery& t)const //{ // return t.query(query_word); //} string rep()const override { cout << "WordQuery" << endl; return query_word; } string query_word; //要查找的单词 }; inline Query::Query(const string& s) :q(new WordQuery(s)) {} class NotQuery :public Query_base { friend Query operator~(const Query&); private: NotQuery(const Query& q) :query(q) { cout << "NotQuery::NotQuery(const Query& q)" << endl; } //具体的类:NotQuery将定义所有继承而来的纯虚函数 string rep()const { cout << "NotQuery" << endl; return "~(" + query.rep() + ")"; } QueryResult eval(const TextQuery&)const; Query query; }; inline Query operator~(const Query& operand) { return shared_ptr<Query_base>(new NotQuery(operand)); } class BinaryQuery :public Query_base { protected: BinaryQuery(const Query& l, const Query& r, string s) :lhs(l), rhs(r), opSym(s) { cout << "BinaryQuery::BinaryQuery(const Query& l, const Query& r, string s)" << endl; } string rep() const { cout << "BinaryQuery" << endl; return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; } Query lhs, rhs; //左侧和右侧运算对象 string opSym; //运算符的名字 }; class AndQuery :public BinaryQuery { friend Query operator&(const Query&, const Query&); private: AndQuery(const Query& left, const Query& right) :BinaryQuery(left, right, "&") { cout << "AndQuery::AndQuery(const Query& left, const Query& right)" << endl; } //具体的类:AndQuery继承了rep并且定义了其它纯虚函数 //QueryResult eval(const TextQuery&)const; }; inline Query operator&(const Query& lhs, const Query& rhs) { return shared_ptr<Query_base>(new AndQuery(lhs, rhs)); } class OrQuery :public BinaryQuery { friend Query operator|(const Query&, const Query&); private: OrQuery(const Query& left, const Query& right) :BinaryQuery(left, right, "|") { cout << "OrQuery::OrQuery(const Query& left, const Query& right)" << endl; } //QueryResult eval(const TextQuery&)const; }; inline Query operator|(const Query& lhs, const Query& rhs) { return shared_ptr<Query_base>(new OrQuery(lhs, rhs)); }
//测试主函数: #include"Query_base.h" int main(int argc, char argv) { if (argc < 2) { cerr << "文件名错误" << endl; return 0; } ifstream in(argv[1]); if (!in) { cerr << "文件打开失败" << endl; return 0; } TextQuery qt(in); Query q = Query("fiery")&Query("bird") | Query("Wind"); cout << endl; cout << q; return 0; }

测试结果:

练习15.37:如果在派生类中含有shared_ptr<Query_base>类型的成员而非Query类型的成员,则你的类需要做出怎样的改变?

答:书中的实现方式是用Query类封装了Query_base指针,管理实际查询处理用到不同Query类型对象。

如果不使用Query类,则涉及使用Query类型的地方,都要改成Query_base指针。如创建单个词查询时,就必须创建WordQuery类而不是Query对象。几个重载的布尔运算符也不能在针对Query对象,而需针对Query_base指针,从而复杂的请求无法写成目前的简单形式,而需逐运算完成,将结果赋予Query_base指针,然后再进行下一步运算。资源管理方面也需要重写设计。

因此,当前的设计仍是**方式。

练习15.38:下面的声明合法吗?如果不合法,请解释原因;如果合法,请之处该声明的含义。

答:第一条声明不合法,因为BinaryQuery中的eval是虚函数。

第二条声明不合法,不能将Query转换为AndQuery。

第三天声明不合法,不能将Query转换为OrQuery。

小讯
上一篇 2024-12-24 11:01
下一篇 2024-12-29 16:22

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/7279.html