专栏C++学习笔记
《C++ Primer》学习笔记/习题答案 总目录
——————————————————————————————————————————————————————
第三章 字符串、向量和数组
练习3.1
使用恰当的 using 声明重做 1.4.1 节 和 2.6.2 节 的练习。
解:
使用 using 声明重做 1.4.1 节中 1.9 的程序如下所示:
#include <iostream>
// 使用过using声明使得cout和endl在程序中可见
using namespace std;
int main()
{
int sum = 0, val = 50;
while (val <= 100){
sum += val;
val += 1;
}
cout << "Sum of 50 to 100 inclusive is "
<< sum << endl;
system("pause");
return 0;
}
使用 using 声明重做 1.4.1 节中 1.10 的程序如下所示:
#include <iostream>
// 使用过using声明使得cout和endl在程序中可见
using namespace std;
int main()
{
int val = 10;
while (val >= 0){
cout << val << " ";
val--;
}
cout << endl;
system("pause");
return 0;
}
使用 using 声明重做 1.4.1 节中 1.11 的程序如下所示:
#include <iostream>
// 使用过using声明使得cout和endl在程序中可见
using namespace std;
int main()
{
int start = 0, end = 0;
cout << "Please input two num: ";
cin >> start >> end;
if (start <= end) {
while (start <= end){
cout << start << " ";
++start;
}
cout << std::endl;
}
else{
cout << "start should be smaller than end !!!";
}
system("pause");
return 0;
}
使用 using 声明重做 2.6.2 节中的程序如下所示:
using namespace std;
或者
using std::cin;
using std::cout;
using std::endl;
using std::istream;
using std::ostream;
using std::string;
练习3.2
编写一段程序从标准输入中一次读入一行,然后修改该程序使其一次读入一个词。
解:
第一种方式是使用 getline
函数一次读入一整行,结束标志是结束符 Ctrl + z
。
#include <iostream>
#include <string>
using namespace std;
int main() // 使用getline一次读入一整行
{
string s;
// 循环读取,每次读入一整行,直至文件结束或遇到异常输入
cout << "请输入字符串,可以包含空格:" << endl;
while (getline(cin,s))
cout << s << endl;
system("pause");
return 0;
}
第二种方式是使用 cin
一次读入一个单词,遇到空白挺固执。
#include <iostream>
#include <string>
using namespace std;
int main() // 使用cin一次读入一个词
{
string s;
// 循环读取,每次读入一整行,直至文件结束或遇到异常输入
cout << "请输入单词,不可以包含空格:" << endl;
while (cin >> s)
cout << s << endl; // 为了便于观察,输出每个单词后换行
system("pause");
return 0;
}
练习3.3
请说明string类的输入运算符和getline函数分别是如何处理空白字符的。
解:
标准库string的输入运算符自动忽略字符串开头的空白(包括空格符、换行符、制表符等),从第一个真正的字符开始读取,知道遇到下一处空白位置。
如果希望在最终的字符串中保存输入时的空白符,应该使用getline函数代替原来的>>运算符,getline从给定个的输入流中读取数据,知道遇到换行符为止,此时换行符也被读取进来,但是并不存储在最后的字符串中。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string word, line;
cout << "请选择读取字符串的方式:1表示逐词读取,2表示整行读取" << endl;
char ch;
cin >> ch;
if (ch == '1')
{
cout << "请输入字符串: welcome to C++ family! " << endl;
cin >> word;
cout << "系统读取的有效字符串是:" << endl;
cout << word << endl;
system("pause");
return 0;
}
cin.clear();
cin.sync();
if (ch == '2')
{
cout << "请输入字符串: welcome to C++ family! " << endl;
getline(cin, line);
cout << "系统读取的有效字符串是:" << endl;
cout << line << endl;
system("pause");
return 0;
}
cout << "输入有误!";
system("pause");
return -1;
}
如果用户输入1,则输出是 welcome
,此时字符串开头的空格和第一个单词之后的所有内容都被忽略掉了。
如果用户输入2,则输出是 welcome to C++ family!
,字符串开头、中间、结尾的空格都保留了下来。
练习3.4
编写一段程序读取两个字符串,比较其是否相等并输出结果。如果不相等,输出比较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较大的那个字符串。
解:
比较字符串大小:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1, s2;
cout << "请输入两个字符串:" << endl;
cin >> s1 >> s2;
if (s1 == s2)
cout << "两个字符串相等" << endl;
else if(s1 > s2)
cout << s1 << "大于" << s2 << endl;
else
cout << s2 << "大于" << s1 << endl;
system("pause");
return 0;
}
比较字符串长度:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1, s2;
cout << "请输入两个字符串:" << endl;
cin >> s1 >> s2;
auto len1 = s1.size();
auto len2 = s2.size();
if (len1 == len2)
cout << s1 << "和" << s2 << "的长度都是" << len1 << endl;
else if(len1 > len2)
cout << s1 << "比" << s2 << "的长度多" << len1 - len2 << endl;
else
cout << s1 << "比" << s2 << "的长度短" << len2 - len1 << endl;
system("pause");
return 0;
}
练习3.5
编写一段程序从标准输入中读入多个字符串并将他们连接起来,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分割开来。
解:
连接多个字符串的程序如下所示:
#include <iostream>
#include <string>
using namespace std;
int main()
{
char cont = 'y';
string s, result;
cout << "请输入第一个字符串:" << endl;
while(cin >> s)
{
result += s;
cout << "是否继续(y or n)?" << endl;
cin >> cout;
if(cont == 'y' || cont == 'Y')
cout << "请输入下一个字符串:" << endl;
else
break;
}
cout << "拼接后的字符串是:" << result << endl;
system("pause");
return 0;
}
连接多个字符串并以空格分隔的程序如下所示:
#include <iostream>
#include <string>
using namespace std;
int main()
{
char cont = 'y';
string s, result;
cout << "请输入第一个字符串:" << endl;
while (cin >> s)
{
if (!result.size()) // 第一个拼接的字符串之前不加空格
result += s;
else // 之后拼接的每个字符串之前加一个空格
result = result + " " + s;
cout << "是否继续(y or n)?" << endl;
cin >> cont;
if (cont == 'y' || cont == 'Y')
cout << "请输入下一个字符串:" << endl;
else
break;
}
cout << "拼接后的字符串是:" << result << endl;
system("pause");
return 0;
}
练习3.6
编写一段程序,使用范围for语句将字符串内所有字符用X代替。
解:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cout << "请输入一个字符串,可以包含空格:" << endl;
getline(cin, s); // 读取整行,遇回车符结束
for (auto &c : s) // 依次处理字符串中的每一个字符
{
c = 'X';
}
cout << s << endl;
system("pause");
return 0;
}
练习3.7
就上一题完成的程序而言,如果将循环控制的变量设置为char将发生什么?先估计一下结果,然后实际编程进行验证。
解:
修改后的程序如下所示:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cout << "请输入一个字符串,可以包含空格:" << endl;
getline(cin, s); // 读取整行,遇回车符结束
for (char &c : s) // 依次处理字符串中的每一个字符
{
c = 'X';
}
cout << s << endl;
system("pause");
return 0;
}
就本题而言,将循环控制变量的类型设为 char
不会对程序运行结果造成影响,因为使用 auto
自动推断字符串 s
的元素类型,结果同样是 char
。
练习3.8
分别用while循环和传统for循环重写联系3.6的程序,你觉得哪种形式更好呢?为什么?
解:
使用while循环实现的程序如下所示:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cout << "请输入一个字符串,可以包含空格:" << endl;
getline(cin, s);
int i = 0;
while (s[i] != '\0')
{
s[i] = 'X';
++i;
}
cout << s << endl;
system("pause");
return 0;
}
使用传统for循环实现的程序如下所示:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cout << "请输入一个字符串,可以包含空格:" << endl;
getline(cin, s);
for (unsigned int i = 0; i < s.size(); i++)
{
s[i] = 'X';
}
cout << s << endl;
system("pause");
return 0;
}
在本例中,我们希望处理字符串中的每一个字符,且无需在意字符的处理顺序,因此与传统的while循环和for循环相比,使用范围for循环更简洁直观。
练习3.9
下面的程序有何作用?它合法吗?如果不合法?为什么?
string s;
cout << s[0] << endl;
解:
该程序的原意是输出字符串s的首字符,但程序是错误的。因为初始状态下没有给赋任何初值,所以字符串s的内容为空,当然也就不存在首字符,下标0是非法的。但是在某些编译器环境中,上述语句并不会引发编译错误。
练习3.10
编写一段程序,读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩余的部分。
解:
解题思路一,利用范围for语句变量字符串,逐个输出非标点字符:
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main()
{
string s;
cout << "请输入一个字符串,最好含有某些标点符号:" << endl;
getline(cin, s);
for (auto c : s)
{
if (!ispunct(c))
cout << c;
}
cout << endl;
system("pause");
return 0;
}
解题思路二,利用普通for循环遍历字符串,通过下标执行随机访问,把非标点字符拼接成一个新串输出:
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main()
{
string s, result;
cout << "请输入一个字符串,最好含有某些标点符号:" << endl;
getline(cin, s);
for (decltype(s.size()) i = 0; i < s.size(); i++)
{
if (!ispunct(s[i]))
result += s[i];
}
cout << result << endl;
system("pause");
return 0;
}
练习3.11
下面的范围for语句合法吗?如果合法,c的类型是什么?
const string s = "Keep out!";
for(auto &c : s){ /* ... */ }
解:
该程序段从语法上来说是合法的,s是一个常量字符串,则c的推断类型是常量引用,即c所绑定的对象值不能改变。
为了证明这一点,举一个下面的例子:
#include <iostream>
#include <string>
using namespace std;
int main()
{
const string s = "Keep out!";
for (auto &c : s)
{
c = 'X'; // 其他对c的操作
}
system("pause");
return 0;
}
该段程序不能正确编译,因为c是绑定到常量的引用,其值不能改变。
错误信息是:
练习3.12
下列vector对象的定义有不正确的吗?如果有,请指出来。对于正确的,描述其执行结果;对于不正确的,说明其错误的原因。
vector<vector<int>> ivec;
vector<string> svec = ivec;
vector<string> svec(10, "null");
(a) 是正确的,定义了一个名为 ivec
的 vector
对象,其中的每个元素都是 vector<int>
对象。
(b) 是错误的,svec
的元素类型是 string
,而 ivec
的元素类型是 int
,因此不能使用 ivec
初始化 svec
。
© 是正确的,定义了一个名为 svec
的 vector
对象,其中含有10个元素,每个元素都是字符串 null
。
练习3.13
下列的vector对象各包含多少个元素?这些元素的值分别是多少?
vector<int> v1;
vector<int> v2(10);
vector<int> v3(10, 42);
vector<int> v4{10};
vector<int> v5{10, 42};
vector<string> v6{10};
vector<string> v7{10, "hi"};
(a)的元素数量为0。
(b)的元素数量为10,每一个元素都被初始化为0。
©的元素数量为10,每一个元素都被初始化为42。
(d)的元素数量为1,元素的值为10。
(e)的元素数量为2,两个元素的值分别为10和42。
(f)的元素数量为10,每一个元素都被初始化为空串。
(g)的元素数量为10,每一个元素都被初始化为 “hi”。
练习3.14
编写一段程序,用cin读入一组整数并把它们存入一个vector对象。
解:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vInt; // 元素类型为int的vector对象
int i; // 记录用户的输入值
char cont = 'y'; // 与用户交互,决定是否继续输入
while (cin >> i)
{
vInt.push_back(i); // 向vector对象中添加元素
cout << "要继续吗(y or n)?" << endl;
cin >> cont;
if(cont != 'y' && cont != 'Y')
break;
}
for(auto mem : vInt) // 使用范围for循环语句遍历vInt中的每个元素
cout << mem << " ";
cout << endl;
system("pause");
return 0;
}
练习3.15
改写上题程序,不过这次读入的是字符串。
解:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> vString; // 元素类型为string的vector对象
string s; // 记录用户的输入值
char cont = 'y'; // 与用户交互,决定是否继续输入
while (cin >> s)
{
vString.push_back(s); // 向vector对象中添加元素
cout << "要继续吗(y or n)?" << endl;
cin >> cont;
if(cont != 'y' && cont != 'Y')
break;
}
for(auto mem : vString) // 使用范围for循环语句遍历vString中的每个元素
cout << mem << " ";
cout << endl;
system("pause");
return 0;
}
练习3.16
编写一段程序,把练习3.13中vector对象的容量和具体内容输出出来。
解:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
vector<int> v1;
vector<int> v2(10);
vector<int> v3(10, 42);
vector<int> v4{ 10 };
vector<int> v5{ 10, 42 };
vector<string> v6{ 10 };
vector<string> v7{ 10, "hi" };
cout << "v1的元素个数是: " << v1.size() << endl;
if (v1.size() > 0) // 当vector含有元素时逐个输出
{
cout << "v1的元素分别是:" << endl;
for(auto e : v1) // 使用范围for语句遍历每一个元素
cout << e << " ";
cout << endl;
}
cout << "v2的元素个数是: " << v2.size() << endl;
if (v2.size() > 0) // 当vector含有元素时逐个输出
{
cout << "v2的元素分别是:" << endl;
for(auto e : v2) // 使用范围for语句遍历每一个元素
cout << e << " ";
cout << endl;
}
cout << "v3的元素个数是: " << v3.size() << endl;
if (v3.size() > 0) // 当vector含有元素时逐个输出
{
cout << "v3的元素分别是:" << endl;
for(auto e : v3) // 使用范围for语句遍历每一个元素
cout << e << " ";
cout << endl;
}
cout << "v4的元素个数是: " << v4.size() << endl;
if (v4.size() > 0) // 当vector含有元素时逐个输出
{
cout << "v4的元素分别是:" << endl;
for(auto e : v4) // 使用范围for语句遍历每一个元素
cout << e << " ";
cout << endl;
}
cout << "v5的元素个数是: " << v5.size() << endl;
if (v5.size() > 0) // 当vector含有元素时逐个输出
{
cout << "v5的元素分别是:" << endl;
for(auto e : v5) // 使用范围for语句遍历每一个元素
cout << e << " ";
cout << endl;
}
cout << "v6的元素个数是: " << v6.size() << endl;
if (v6.size() > 0) // 当vector含有元素时逐个输出
{
cout << "v6的元素分别是:" << endl;
for(auto e : v6) // 使用范围for语句遍历每一个元素
cout << e << " ";
cout << endl;
}
cout << "v7的元素个数是: " << v7.size() << endl;
if (v7.size() > 0) // 当vector含有元素时逐个输出
{
cout << "v7的元素分别是:" << endl;
for(auto e : v7) // 使用范围for语句遍历每一个元素
cout << e << " ";
cout << endl;
}
system("pause");
return 0;
}
练习3.17
从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改为大写形式。输出改变后的结果,每个词占一行。
解:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
vector<string> vString; // 元素类型为string的vector对象
string s;
char cont = 'y'; // 与用户交互,决定是否继续输入
cout << "请输入第一个词:" << endl;
while (cin >> s)
{
vString.push_back(s); // 向vector对象中添加元素
cout << "要继续嘛(y or n)?" << endl;
cin >> cont;
if (cont != 'y' && cont != 'Y')
break;
cout << "请输入下一个词:" << endl;
}
cout << "请输入下一个词:" << endl;
for (auto &mem : vString)
{
for (auto &c : mem)
c = toupper(c);
cout << mem << endl;
}
system("pause");
return 0;
}
练习3.18
下面的程序合法吗?如果不合法,你准备如何修改?
vector<int> ivec;
ivec[0] = 42;
解:
该程序是非法的,因为 ivec
目前没有任何元素,因此 ivec[0]
的形式是错误的,程序试图访问的元素根本不存在。想要向 vector
对象中添加新元素,需要使用 push_back
函数。
修改后的代码如下:
vector<int> ivec;
ivec.push_back(42);
练习3.19
如果想定义一个含有10个元素的vector对象,所有元素的值都是42,请例举三种不同的实现方法,哪种方式更好呢?
解:
# 解决思路一:先定义一个空vector对象,然后添加元素。
vector<int> vInt;
for (int i = 0; i < 10; ++i)
vInt.push_back(42);
# 解决思路二:列表初始化,罗列出全部10个元素的值。
vector<int> vInt = {42, 42, 42, 42, 42, 42, 42, 42, 42, 42};
# 解决思路三:用括号给出所有元素的值,效果类似于解决思路二。
vector<int> vInt{42, 42, 42, 42, 42, 42, 42, 42, 42, 42};
# 解决思路四:定义的时候使用参数指定元素个数及重复的值。
vector<int> vInt(10, 42);
# 解决思路五:现制定元素个数,再利用范围for循环依次为元素赋值。
vector<int> vInt(10);
for(auto &i : vInt)
i = 42;
显然,思路四 采用的初始化形式上最简洁直观,当vector对象的元素数量较多且取值重复时是最好的选择;而 思路一 在开始的时候不限定元素的个数,比较灵活。
练习3.20
读入一组整数并把他们存入一个vector对象,将每对相邻整数的和输出出来。改写你的程序,这次要求先输出第一个和最后一个元素的和,接着输出第二个和倒数第二个元素的和,以此类推。
解:
求相邻元素和的程序如下所示:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vInt;
int iVal;
cout << "请输入一组数字:" << endl;
while (cin >> iVal)
vInt.push_back(iVal);
if(vInt.size() == 0)
{
cout << "没有任何元素" << endl;
return -1;
}
cout << "相邻两项的和依次是:" << endl;
// 利用decltype推断i的类型
for(decltype(vInt.size()) i = 0; i < vInt.size() - 1; i += 2)
{
// 求相邻两项的和
cout << vInt[i] + vInt[i+1] << " ";
// 每行输出5个数字
if((i+2) % 10 == 0)
cout << endl;
}
// 如果元素数是奇数,单独处理最后一个元素
if (vInt.size() % 2 != 0)
cout << vInt[vInt.size() - 1];
system("pause");
return 0;
}
求首尾元素和的程序如下所示:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vInt;
int iVal;
cout << "请输入一组数字:" << endl;
while (cin >> iVal)
vInt.push_back(iVal);
if(vInt.size() == 0)
{
cout << "没有任何元素" << endl;
return -1;
}
cout << "首尾两项的和依次是:" << endl;
// 利用decltype推断i的类型
for(decltype(vInt.size()) i = 0; i < vInt.size() / 2 ; i++)
{
// 求相邻两项的和
cout << vInt[i] + vInt[vInt.size() - i - 1] << " ";
// 每行输出5个数字
if((i+2) % 10 == 0)
cout << endl;
}
// 如果元素数是奇数,单独处理最后一个元素
if (vInt.size() % 2 != 0)
cout << vInt[vInt.size() - 1];
system("pause");
return 0;
}
练习3.21
请使用迭代器重做3.3.3节的第一个练习。
解:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<int> v1;
vector<int> v2(10);
vector<int> v3(10, 42);
vector<int> v4{10};
vector<int> v5{10, 42};
vector<string> v6{10};
vector<string> v7{10, "hi"};
cout << "v1的元素分别是:" << v1.size() << endl;
if (v1.cbegin() != v1.cend()) // 当vector含有元素时逐个输出
{
cout << "v1的元素分别是:" << endl;
// 使用范围for语句遍历每一个元素
for (auto it = v1.cbegin(); it != v1.cend(); it++)
cout << *it << " ";
cout << endl;
}
cout << "v2的元素分别是:" << v2.size() << endl;
if (v2.cbegin() != v2.cend()) // 当vector含有元素时逐个输出
{
cout << "v2的元素分别是:" << endl;
// 使用范围for语句遍历每一个元素
for (auto it = v2.cbegin(); it != v2.cend(); it++)
cout << *it << " ";
cout << endl;
}
cout << "v3的元素分别是:" << v3.size() << endl;
if (v3.cbegin() != v3.cend()) // 当vector含有元素时逐个输出
{
cout << "v3的元素分别是:" << endl;
// 使用范围for语句遍历每一个元素
for (auto it = v3.cbegin(); it != v3.cend(); it++)
cout << *it << " ";
cout << endl;
}
cout << "v4的元素分别是:" << v4.size() << endl;
if (v4.cbegin() != v4.cend()) // 当vector含有元素时逐个输出
{
cout << "v4的元素分别是:" << endl;
// 使用范围for语句遍历每一个元素
for (auto it = v4.cbegin(); it != v4.cend(); it++)
cout << *it << " ";
cout << endl;
}
cout << "v5的元素分别是:" << v5.size() << endl;
if (v5.cbegin() != v5.cend()) // 当vector含有元素时逐个输出
{
cout << "v5的元素分别是:" << endl;
// 使用范围for语句遍历每一个元素
for (auto it = v5.cbegin(); it != v5.cend(); it++)
cout << *it << " ";
cout << endl;
}
cout << "v6的元素分别是:" << v6.size() << endl;
if (v6.cbegin() != v6.cend()) // 当vector含有元素时逐个输出
{
cout << "v6的元素分别是:" << endl;
// 使用范围for语句遍历每一个元素
for (auto it = v6.cbegin(); it != v6.cend(); it++)
cout << *it << " ";
cout << endl;
}
cout << "v7的元素分别是:" << v7.size() << endl;
if (v7.cbegin() != v7.cend()) // 当vector含有元素时逐个输出
{
cout << "v7的元素分别是:" << endl;
// 使用范围for语句遍历每一个元素
for (auto it = v7.cbegin(); it != v7.cend(); it++)
cout << *it << " ";
cout << endl;
}
system("pause");
return 0;
}
练习3.22
修改之前那个输出text第一段的程序,首先把text的第一段全部改成大写形式,然后输出它。
解:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> text;
string s;
// 利用getline读取一句话,直接回车产生一个空串,表示段落结束
while(getline(cin, s))
text.push_back(s); // 逐个添加到text中
// 利用迭代器遍历全部字符串,遇到空串停止循环
for(auto it = text.begin(); it != text.end() && !it -> empty(); it++)
{
// 利用迭代器遍历当前字符串
for(auto it2 = it -> begin(); it2 != it -> end(); it2++)
*it2 = toupper(*it2); // 利用toupper改写成大写形式
cout << *it << endl; // 输出当前字符串
}
system("pause");
return 0;
}
练习3.23
编写一段程序,创建一个含有10个整数的vector对象,然后使用迭代器将所有元素的值都变成原来的两倍。输出vector对象的内容,检验程序是否正确。
解:
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
using namespace std;
int main()
{
vector<int> vInt;
srand((unsigned)time(NULL)); // 生成随机数种子
for(int i = 0; i < 10; i++)
{
// 每次循环生成一个1000以内的随机数并添加到vInt中
vInt.push_back(rand() % 1000);
}
cout << "随机生成的10个数字是:" << endl;
//利用常量迭代器读取原始数据
for(auto it = vInt.cbegin(); it != vInt.cend(); it++)
{
cout << *it << " "; // 输出当前数字
}
cout << endl;
cout << "翻倍后的10个数字是:" << endl;
// 利用非常量迭代器修改vInt内容并输出
for(auto it = vInt.begin(); it != vInt.end(); it++)
{
*it *= 2;
cout << *it << " "; // 输出当前数字
}
cout << endl;
system("pause");
return 0;
}
练习3.24
请使用迭代器重做3.3.3节的最后一个练习。
解:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vInt;
int iVal;
cout << "请输入一组数字:" << endl;
while (cin >> iVal)
vInt.push_back(iVal);
if(vInt.cbegin() == vInt.cend())
{
cout << "没有任何元素" << endl;
return -1;
}
cout << "相邻两项的和依次是:" << endl;
// 利用auto推断it的类型
for(auto it = vInt.cbegin(); it != vInt.cend() - 1; it++)
{
// 求相邻两项的和
cout << (*it + *(++it)) << " ";
// 每行输出5个数字
if((it - vInt.cbegin() + 1) % 10 == 0)
cout << endl;
}
// 如果元素数是奇数,单独处理最后一个元素
if (vInt.size() % 2 != 0)
cout << *(vInt.cend() - 1);
system("pause");
return 0;
}
求首尾元素和的程序如下所示:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vInt;
int iVal;
cout << "请输入一组数字:" << endl;
while (cin >> iVal)
vInt.push_back(iVal);
if(vInt.cbegin() == vInt.cend())
{
cout << "没有任何元素" << endl;
return -1;
}
cout << "首尾两项的和依次是:" << endl;
auto beg = vInt.begin();
auto end = vInt.end();
// 利用auto推断it的类型
for(auto it = beg; it != beg + (end - beg) / 2; it++)
{
// 求首尾两项的和
cout << (*it + *(beg + (end - it) - 1)) << " ";
// 每行输出5个数字
if((it - beg + 1) % 5 == 0)
cout << endl;
}
// 如果元素数是奇数,单独处理最后一个元素
if (vInt.size() % 2 != 0)
cout << *(beg + (end - beg) / 2);
system("pause");
return 0;
}
练习3.25
3.3.3节划分分数段的程序是使用下标运算符实现的,请利用迭代器改写该程序实现完全相同的功能。
解:
#include <vector>
#include <iostream>
using namespace std;
int main()
{
// 该vector对象记录各分数段的人数,初始值均为0
vector<unsigned> vUS(11);
auto it = vUS.begin();
int iVal;
cout << "请输入一组成绩(0~100):" << endl;
while (cin >> iVal)
if (iVal <= 101) // 成绩应在合理范围之内
++*(it + iVal / 10); // 利用迭代器定位到对应的元素,加1
cout << "总计输入了" << vUS.size() << "个成绩" << endl;
cout << "各分数段的人数分布式(成绩从高到低):" << endl;
// 利用迭代器遍历vUS元素并逐个输出
for(it = vUS.begin(); it != vUS.end(); it++)
{
cout << *it << " ";
}
cout << endl;
system("pause");
return 0;
}
练习3.26
在100页的二分搜索程序中,为什么用的是 mid = beg + (end - beg) / 2
, 而非 mid = (beg + end) / 2 ;
?
解:
C++并没有定义两个迭代器的加法运算,实际上直接把两个迭代器加起来是没有意义的。与之相反的,C++定义了迭代器的减法运算,两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动多少个元素后可以得到左侧的迭代器,参与运算的两个迭代器必须指向同一容器中的元素或尾后元素。另外,C++还定义了迭代器与整数的加减法运算,用以控制迭代器在容器中左右移动。
在本题中,因为迭代器的加法不存在,所以 mid = (beg + end) / 2;
不合法。mid = beg + (end - beg) / 2;
的含义是,先计算 end - beg
的值得到容器中的元素个数,然后控制迭代器从开始出向右移动二分之一容器的长度,从而定位到容器正中间的元素。
练习3.27
假设txt_size
是一个无参函数,它的返回值是int
。请回答下列哪个定义是非法的,为什么?
unsigned buf_size = 1024;
(a) int ia[buf_size];
(b) int ia[4 * 7 - 14];
(c) int ia[txt_size()];
(d) char st[11] = "fundamental";
解:
(a) 是非法的,buf_size
是一个普通的无符号数,不是常量,不能作为数组的维度。
(b) 是合法的,4*7-14=14
是一个常量表达式。
© 是非法的,text_size()
是一个普通的函数调用,没有被定义为 constexpr
,不能作为数组的维度。
(d) 是非法的,当使用字符串初始化字符数组时,默认在尾部添加一个空字符 ‘\0’,算上这个符号该字符串共有12个字符,但是字符数组 st
的维度只有11,无法容纳题目中的字符串。
练习3.28
下列数组中元素的值是什么?
string sa[10];
int ia[10];
int main() {
string sa2[10];
int ia2[10];
}
解:
对于 string
类型的数组来说,因为 string
类本身接受无参数的初始化方式,所以不论数组定义在函数内还是函数外都被默认初始化为空串。
对于内置类型 int
来说,数组 ia
定义在所有函数体之外,根据C++的规定,ia
的所有元素默认初始化为0;而数组 ia2
定义在 main
函数的内部,将不被初始化,如果程序试图拷贝或输出未初始化的变量,将遇到未定义的奇异值。
验证程序如下:
#include <iostream>
#include <string>
using namespace std;
// 定义在全局作用域中的数组
string sa[10];
int ia[10];
int main()
{
// 定义在局部作用域中的数组
string sa2[10];
int ia2[10];
for(auto c : sa)
cout << c << " ";
cout << endl;
for(auto c : ia)
cout << c << " ";
cout << endl;
for(auto c : sa2)
cout << c << " ";
cout << endl;
for(auto c : ia2)
cout << c << " ";
system("pause");
return 0;
}
练习3.29
相比于vector 来说,数组有哪些缺点,请例举一些。
解:
数组与 vector
的相似之处是都能存放类型相同的对象,且这些对象本身没有名字,需要通过其所在位置访问。
数组与 vector
的最大不同是,数组的大小固定不变,不能随意向数组中增加额外的元素,虽然在某些情境下运行时性能较好,但是与 vector
相比损失了灵活性。
具体来说,数组的维度在定义时已经确定,如果我们想更改数组的长度,只能创建一个更大的新数组,然后原数组的所有元素复制到新数组中去。我们也无法像 vector
那样使用 size
函数直接获取数组的维度。如果是字符数组,可以调用 strlen
函数得到字符串的长度;如果是其他数组,只能使用 sizeof(array).sizeof(array[0])
的方式计算数组的维度。
练习3.30
指出下面代码中的索引错误。
constexpr size_t array_size = 10;
int ia[array_size];
for (size_t ix = 1; ix <= array_size; ++ix)
ia[ix] = ix;
解:
本题的愿意是创建一个包含10个整数的数组,并把数组的每个元素初始化为元素的下标值。
上面的程序在 for
循环终止条件处有错,数组的下标应该大于等于0而小于数组的大小,在本题中下标的范围应该是0~9。
因此程序应该修改为:
constexpr size_t array_size = 10;
int ia[array_size];
for (size_t ix = 0; ix <= array_size; ++ix)
ia[ix] = ix;
练习3.31
编写一段程序,定义一个含有10个int的数组,令每个元素的值就是其下标值。
#include <iostream>
using namespace std;
int main()
{
const int sz = 10; // 常量sz作为数组的维度
int a[sz];
// 通过for循环为数组元素赋值
for (int i = 0; i < sz; ++i)
a[i] = i;
// 通过范围for循环输出数组的全部元素
for (auto val : a)
cout << val << " ";
cout << endl;
system("pause");
return 0;
}
练习3.32
将上一题刚刚创建的数组拷贝给另一数组。利用vector重写程序,实现类似的功能。
解:
实现数组拷贝的程序如下所示:
#include <iostream>
using namespace std;
int main()
{
const int sz = 10; // 常量sz作为数组的维度
int a[sz], b[sz];
// 通过for循环为数组元素赋值
for(int i = 0; i < sz; i++)
a[i] = i;
for(int j = 0; j < sz; j++)
b[j] = a[j];
// 通过范围for循环输出数组的全部元素
for (auto val : b)
cout << val << " ";
cout << endl;
system("pause");
return 0;
}
使用vector拷贝的程序如下所示:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
const int sz = 10; // 常量sz作为vector的维度
vector<int> vInt, vInt2;
// 通过for循环为数组元素赋值
for(int i = 0; i < sz; i++)
vInt.push_back(i);
for(int j = 0; j < sz; j++)
vInt2.push_back(vInt[j]);
// 通过范围for循环输出vector的全部元素
for (auto val : vInt2)
cout << val << " ";
cout << endl;
system("pause");
return 0;
}
练习3.33
对于104页的程序来说,如果不初始化scores将会发生什么?
解:
该程序对 scores
执行了列表初始化,为所有元素赋初值为0,这样在后续统计时将会从0开始计算各个分数段的人数,是正确的做法。
如果不初始化 scores
,则该数组会含有未定义的数值,这是因为 scores
是定义在函数内部的整形数组,不会执行默认初始化。
练习3.34
假定p1
和 p2
都指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的?
p1 += p2 - p1;
解:
如果 p1
和 p2
指向同一数组中的元素,则该条语句令 p1
指向 p2
原来所指向的元素。
从语法上来说,即使 p1
和 p2
指向的元素不属于同一个数组,但是只要 p1
和 p2
的类型相同,该语句也是合法的。
如果 p1
和 p2
的类型不同,则编译时报错。
练习3.35
编写一段程序,利用指针将数组中的元素置为0。
解:
#include <iostream>
using namespace std;
int main()
{
const int sz = 10; // 常量sz作为数组的维度
int a[sz], i = 0;
// 通过for循环为数组元素赋值
for (i = 0; i < 10; i++)
a[i] = i;
cout << "初始状态下数组的内容是:" << endl;
for (auto val : a)
cout << val << " ";
cout << endl;
int *p = begin(a); // 令p指向数组首元素
while (p != end(a))
{
*p = 0; // 修改p所指元素的值
p++; // p向后移动一位
}
cout << "修改后的数组内容是:" << endl;
// 通过范围for循环输出数组的全部元素
for (auto val : a)
cout << val << " ";
cout << endl;
system("pause");
return 0;
}
练习3.36
编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector对象是否相等。
解:
对比两个数组是否相等的程序如下所示,因为长度不等的数组一定不相等,并且数组的维度一开始就要确定,所以为了简化起见没程序中设定两个待比较的数组维度一致,仅比较对应的元素是否相等。
该例类似于一个彩票游戏,先由程序随机选出5个0~9的数字,此过程类似于摇奖;再由用户手动输入5个猜测的数字,类似于购买彩票;分别把两组数字存入数组 a
和 b
,然后逐一比对两个数组的元素;一旦有数字不一致,则告知用户猜测错误,只有当两个数组的所有元素都相等时,判定数组相等,即用户猜测正确。
#include <iostream>
#include <ctime>
#include <cstdlib>
using namespace std;
int main()
{
const int sz = 5; // 常量sz作为数组的维度
int a[sz], b[sz], i;
srand((unsigned)time(NULL)); // 生成随机数种子
for (i = 0; i < sz; i++)
// 每次循环生成一个10以内的随机数并添加到a中
a[i] = rand() % 10;
cout << "系统数据已经生成,请输入猜测的5个数字(0~9),可以重复:" << endl;
int uVal;
// 通过for循环为数组元素赋值
for (i = 0; i < sz; i++)
if (cin >> uVal)
b[i] = uVal;
cout << "系统生成的数据是:" << endl;
for (auto val : a)
cout << val << " ";
cout << endl;
cout << "猜测的数据是" << endl;
for (auto val : b)
cout << val << " ";
cout << endl;
int *p = begin(a), *q = begin(b); // 令p和q分别指向数组a和b的首元素
while (p != end(a) && q != end(b))
{
if (*p != *q)
{
cout << "猜测错误,两个数据不相等" << endl;
system("pause");
return -1;
}
p++; // p向后移动一位
q++; // q向后移动一位
}
cout << "恭喜,全部都猜对了" << endl;
system("pause");
return 0;
}
对比两个 vector
对象是否相等的程序如下所示,其中使用迭代器遍历 vector
对象的元素。
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <vector>
using namespace std;
int main()
{
const int sz = 5; // 常量sz作为数组的维度
int i;
vector<int> a, b;
srand((unsigned)time(NULL)); // 生成随机数种子
for (i = 0; i < sz; i++)
// 每次循环生成一个10以内的随机数并添加到a中
a.push_back(rand() % 10);
cout << "系统数据已经生成,请输入猜测的5个数字(0~9),可以重复:" << endl;
int uVal;
// 通过for循环为数组元素赋值
for (i = 0; i < sz; i++)
if (cin >> uVal)
b.push_back(uVal);
cout << "系统生成的数据是:" << endl;
for (auto val : a)
cout << val << " ";
cout << endl;
cout << "猜测的数据是" << endl;
for (auto val : b)
cout << val << " ";
cout << endl;
// 令it1,it2分别指向vector对象a和b的首元素
auto it1 = a.cbegin(), it2 = b.cbegin();
while (it1 != a.cend() && it2 != b.cend())
{
if (*it1 != *it2)
{
cout << "猜测错误,两个vector不相等" << endl;
system("pause");
return -1;
}
it1++; // it1向后移动一位
it2++; // it2向后移动一位
}
cout << "恭喜,全部都猜对了" << endl;
system("pause");
return 0;
}
练习3.37
下面的程序是何含义,程序的输出结果是什么?
const char ca[] = { 'h', 'e', 'l', 'l', 'o' };
const char *cp = ca;
while (*cp) {
cout << *cp << endl;
++cp;
}
解:
程序第一行声明了一个包含5个字符的字符数组,因为无须修改数组的内容,所以将其定义为常量。第二行定义了一个指向字符常量的指针,该指针可以指向不通过的字符常量,但是不允许通过该指针修改所指常量的值。
while
循环的条件时 *cp
,只要指针 cp
所指的字符不是空字符 ‘\0’,循环就重复执行,循环的任务有两项:首先输出指针当前所指的字符,然后将指针向后移动一位。
该程序的愿意是输出 ca
中存储的5个字符,每个字符占一行,但实际的执行效果无法符合预期。因为以列表初始化方式复制的c风格字符串与以字符串字面值赋值的有所区别,后者会在字符串最后额外增加一个空字符以示字符串的结束,而前者不会这样做。
因此在该程序中,ca
的5个字符全都输出后,并没有遇到预期的空字符,也就是说,while
循环的条件仍将满足,无法跳出。程序继续在内存中 ca
的存储位置之后挨个寻找空字符,直到找到为止。在这个过程中,额外经历的内容也将被输出出来,从而产生错误。
想要实现程序的原意,应该修改为:
const char ca[] = { 'h', 'e', 'l', 'l', 'o', '\0' };
const char *cp = ca;
while (*cp) {
cout << *cp << endl;
++cp;
}
或者
const char ca[] = "hello";
const char *cp = ca;
while (*cp) {
cout << *cp << endl;
++cp;
}
练习3.38
在本节中我们提到,将两个指针相加不但是非法的,而且也没有什么意义。请问为什么两个指针相加没有意义?
解:
指针也是一个对象,与指针相关的属性有3个,分别是指针本身的值、指针所指的对象以及指针本身在内存中的存储位置。它们的含义分别是:
指针本身的值是一个个内存地址值,表示指针所指对象在内存中的存储地址;指针所指的对象可以通过解引用指针访问;因为指针也是一个对象,所以指针也存储在内存的某个位置,它有自己的地址,这也是为什么有“指针的指针”的原因。
通过上述分析可知,指针的值是它所指对象的内存地址,如果把两个指针加在一起,就是试图把内存中两个对象的存储地址加在一起,这显然是没有任何意义的。与之相反,指针的减法是有意义的。如果两个指针指向同一个数组中的不同元素,则它们相减的结果表征了它们所指的元素在数组中的距离。
练习3.39
编写一段程序,比较两个 string
对象。再编写一段程序,比较两个C风格字符串的内容。
解:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1, str2;
cout << "请输入两个字符串:" << endl;
cin >> str1 >> str2;
if (str1 > str2)
cout << "第一个字符串大于第二个字符串" << endl;
else if (str1 < str2)
cout << "第一个字符串小于第二个字符串" << endl;
else
cout << "两个字符串相等" << endl;
system("pause");
return 0;
}
比较两个c风格字符串的程序如下所示,其中的分支部分选用了 switch-case
语句,其效果与上一个程序中的 if-else
语句非常类似。
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char str1[80], str2[80];
cout << "请输入两个字符串:" << endl;
cin >> str1 >> str2;
// 利用cstring头文件中定义的strcmp函数比较大小
auto result = strcmp(str1, str2);
switch (result)
{
case 1:
cout << "第一个字符串大于第二个字符串" << endl;
break;
case -1:
cout << "第一个字符串小于第二个字符串" << endl;
break;
case 0:
cout << "两个字符串相等" << endl;
break;
default:
cout << "未定义的结果" << endl;
break;
}
system("pause");
return 0;
}
练习3.40
编写一段程序,定义两个字符数组并用字符串字面值初始化它们;接着再定义一个字符数组存放前面两个数组连接后的结果。使用strcpy
和strcat
把前两个数组的内容拷贝到第三个数组当中。
解:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char str1[] = "welcome to";
char str2[] = "C++ family";
// 利用strlen函数计算两个字符串的长度
// 定义一个内存变量result,并申请长度为求得结果字符串的长度
char *result = new char[strlen(str1) + strlen(str2) - 1];
strcpy(result, str1); // 把第一个字符串拷贝到结果字符串中
strcat(result, str2); // 把第二个字符串拼接到结果字符串中
cout << "第一个字符串是:" << str1 << endl;
cout << "第二个字符串是:" << str2 << endl;
cout << "拼接后的字符串是:" << result << endl;
system("pause");
return 0;
}
练习3.41
编写一段程序,用整型数组初始化一个vector对象。
解:
满足题意的程序如下:
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
using namespace std;
int main()
{
const int sz = 10; // 常量sz作为数组的维度
int a[sz];
srand((unsigned) time (NULL)); // 生成随机数种子
cout << "数组的内容是" << endl;
// 利用范围for循环遍历数组的每个元素
for(auto &val : a)
{
val = rand() % 100; // 生成一个100以内的随机数
cout << val << " ";
}
cout << endl;
// 利用begin和end初始化vector对象
vector<int> vInt(begin(a), end(a));
cout << "vector的内容是:" << endl;
// 利用范围for循环遍历vector的每个元素
for(auto val : vInt)
{
cout << val << " ";
}
cout << endl;
system("pause");
return 0;
}
使用随机数组初始化数组,然后利用 begin
和 end
获得数组的范围。在用数组初始化 vector
对象时,只需要提供数组的元素区域。
练习3.42
编写一段程序,将含有整数元素的 vector
对象拷贝给一个整型数组。
解:
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
using namespace std;
int main()
{
const int sz = 10; // 常量sz作为vector对象的容量
vector<int> vInt;
srand((unsigned)time(NULL)); // 生成随机数种子
cout << "vector对象的内容是" << endl;
// 利用范围for循环遍历vector对象的每个元素
for (int i = 0; i != sz; i++)
{
vInt.push_back(rand() % 100); // 生成一个100以内的随机数
cout << vInt[i] << " ";
}
cout << endl;
auto it = vInt.cbegin();
int *a = new int[vInt.size()];
cout << "数组的内容是:" << endl;
// 利用范围for循环遍历数组的每个元素
for (int i = 0; i < vInt.size(); i++)
{
a[i] = *it;
cout << a[i] << " ";
it++;
}
cout << endl;
system("pause");
return 0;
}
练习3.43
编写3个不同版本的程序,令其均能输出ia
的元素。版本1使用范围for
语句管理迭代过程;版本2和版本3都使用普通for
语句,其中版本2要求使用下标运算符,版本3要求使用指针。此外,在所有3个版本的程序中都要直接写出数据类型,而不能使用类型别名、auto
关键字和decltype
关键字。
解:
#include <iostream>
using namespace std;
int main()
{
int ia[3][4] =
{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}
};
cout << "利用范围for语句输出多为数组的内容:" << endl;
for (const int(&row)[4] : ia)
{
for (int col : row)
cout << col << " ";
cout << endl;
}
cout << "利用普通for语句和下标运算符输出多维数组的内容:" << endl;
for (int i = 0; i != 3; i++)
{
for (int j = 0; j != 4; j++)
cout << ia[i][j] << " ";
cout << endl;
}
cout << "利用普通for语句和指针输出多维数组的内容:" << endl;
for (int(*p)[4] = ia; p != ia + 3; p++)
{
for (int *q = *p; q != *p + 4; q++)
cout << *q << " ";
cout << endl;
}
system("pause");
return 0;
}
练习3.44
改写上一个练习中的程序,使用类型别名来代替循环控制变量的类型。
解:
#include <iostream>
using namespace std;
using int_array = int[4];
int main()
{
int ia[3][4] =
{
{ 0, 1, 2, 3 },
{ 4, 5, 6, 7 },
{ 8, 9, 10, 11 }
};
cout << "利用范围for语句输出多为数组的内容:" << endl;
for (const int(&row)[4] : ia)
{
for (int col : row)
cout << col << " ";
cout << endl;
}
cout << "利用普通for语句和下标运算符输出多维数组的内容:" << endl;
for (int i = 0; i != 3; ++i)
{
for (int j = 0; j != 4; j++)
cout << ia[i][j] << " ";
cout << endl;
}
cout << "利用普通for语句和指针输出多维数组的内容:" << endl;
for (int_array *p = ia; p != ia + 3; p++)
{
for (int *q = *p; q != *p + 4; q++)
cout << *q << " ";
cout << endl;
}
system("pause");
return 0;
}
练习3.45
再一次改写程序,这次使用 auto
关键字。
解:
#include <iostream>
using namespace std;
int main()
{
int ia[3][4] =
{
{ 0, 1, 2, 3 },
{ 4, 5, 6, 7 },
{ 8, 9, 10, 11 }
};
cout << "利用范围for语句输出多为数组的内容:" << endl;
for (const int(&row)[4] : ia)
{
for (int col : row)
cout << col << " ";
cout << endl;
}
cout << "利用普通for语句和下标运算符输出多为数组的内容:" << endl;
for (auto i = 0; i != 3; i++)
for (auto j = 0; j != 4; j++)
cout << ia[i][j] << " ";
cout << endl;
// using pointers.
for (auto p = ia; p != ia + 3; p++)
for (int *q = *p; q != *p + 4; q++)
cout << *q << " ";
cout << endl;
system("pause");
return 0;
}
转载:https://blog.csdn.net/TeFuirnever/article/details/101976043