学习《c++_primer》之第三章
3.1 节练习
练习 3.1
不赘叙了
3.2 节练习
练习 3.2
第一种方式是使用 getline 函数一次读入一整行,行的结束标识是回车符。如果一开始输入的就是回车符,则 getline 直接结束本次读取,所得的结果是一个空字符串。
第二种方式是使用 cin 一次读入一个单词,遇到空白停止。
练习 3.3
标准库 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 << "请输入字符串: good good study day day up! " << endl;
cin >> word;
cout << "系统读取的有效字符串是:" << endl;
cout << word << endl;
return 0;
}
// 清空输入缓冲区
cin.ignore(numeric_limits<streamsize>::max(), '\n');
if (ch == '2')
{
cout << "请输入字符串: good good study day day up! " << endl;
getline(cin, line);
cout << "系统读取的有效字符串是:" << endl;
cout << line << endl;
return 0;
}
cout << "您的输入有误!";
return -1;
}
输出如下:
补充:
为什么要加cin.ignore(numeric_limits<streamsize>::max(), '\n');
这条语句?
原因分析
在选择读取方式的输入步骤中,cin >> ch; 会读取用户输入的一个字符,但它只读取一个字符的内容,用户在输入字符后可能还按了“回车键” (Enter),这会留在输入缓冲区里。如果不清空缓冲区,后续调用 getline(cin, line); 时,会直接读取缓冲区中残留的“回车”,导致 line 获得一个空字符串,无法读取到用户实际输入的内容。
如果不加这条语句的话,就会出现如下情况:
可以看看这篇博客 写什么代码来清空缓冲区
练习 3.4
比较字符串大小:
比较字符串长度:
练习 3.5
连接多个字符串:
连接多个字符串并以空格分隔:
练习 3.6
有两点值得注意:一是利用 auto 关键字推断字符串中每一个元素的类型;二是 c 必须定义为引用类型,否则无法修改字符串内容。
练习 3.7
就本题而言,将循环控制变量的类型设为 char 不会对程序运行结果造成影响,因为我们使用 auto 自动推断字符串 s 的元素类型,结果同样是char。
练习 3.8
用while循环:
对其中的s[i] != '\0'
有疑问的话,可以看下这篇博客string类型末尾有空字符吗
用传统 for 循环:
在本例中,我们希望处理字符串中的每一个字符,且无须在意字符的处理顺序,因此与传统的 while 循环和 for 循环相比,使用范围 for 循环更简洁直观。
练习 3.9
该程序的原意是输出字符串 s 的首字符,但程序是错误的。因为初始状态下没有给 s 赋任何初值,所以字符串 s 的内容为空,当然也就不存在首字符,下标 0 是非法的。
但是在某些编译器环境中,上述语句并不会引发编译错误
练习 3.10
解题思路一,利用范围 for 语句遍历字符串,逐个输出非标点字符:
解题思路二,利用普通 for 循环遍历字符串,通过下标执行随机访问,把非标点字符拼接成一个新串后输出:
练习 3.11
该程序段从语法上来说是合法的,s 是一个常量字符串,则 c 的推断类型是常量引用,即 c 所绑定的对象值不能改变。
为了证明这一点,可以做如下尝试:
编译不通过,报错
3.3 节练习
练习 3.12
(a)是正确的,定义了一个名为 ivec 的 vector 对象,其中的每个元素都是vector<int>
对象。
(b)是错误的,svec 的元素类型是 string,而 ivec 的元素类型是 int,因此不能使用 ivec 初始化 svec。
(c)是正确的,定义了一个名为 svec 的 vector 对象,其中含有 10 个元素,每个元素都是字符串 null。
练习 3.13
(a)的元素数量为 0。
(b)的元素数量为 10,每一个元素都被初始化为 0。
(c)的元素数量为 10,每一个元素都被初始化为 42。
(d)的元素数量为 1,元素的值为 10。
(e)的元素数量为 2,两个元素的值分别是 10 和 42。
(f)的元素数量为 10,每一个元素都被初始化为空串。
(g)的元素数量为 10,每一个元素都被初始化为"hi"。
附上书上一段原话:
练习 3.14
对于 vector 对象来说,直接初始化的方式适用于三种情况:一是初始值已知且数量较少;二是初始值是另一个 vector 对象的副本;三是所有元素的初始值都一样。然而一般情况下,上述条件很难满足,这时就需要利用 push_back 函数向vector 对象中逐个添加元素。
练习 3.15
插一句题外话
如果一开始输入good good
的话,输出会是这样的:
为什么?
和cin >> s
有关,该语句只读取了第一个good(为什么会这样,上面的练习 3.3有解释),然后第二个good还留在缓冲区中,最后被cin >> ch
读取了,自然执行了那个if语句。
练习 3.16
#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.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;
54 C++ Primer 习题集(第 5 版)
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;
}
return 0;
}
输出如下:
练习 3.17
#include <iostream>
#include <vector>
#include <string>
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 循环语句遍历 vString 中的每个元素
{
for (auto& c : mem)
c = toupper(c); // 改写为大写字母形式
cout << mem << endl;
}
return 0;
}
输出如下:
练习 3.18
该程序是非法的,因为 ivec 目前没有任何元素,因此 ivec[0]
的形式是错误的,程序试图访问的元素根本不存在。要想向 vector 对象中添加新元素,需要使用 push_back 函数。
下面是修改后的程序代码:
vector<int> ivec;
ivec.push_back(42);
练习 3.19
解决思路一:先定义一个空 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(10, 42);
解决思路四:先指定元素个数,再利用范围 for 循环依次为元素赋值。
vector<int> vInt(10);
for(auto &i : vInt)
i = 42;
显然,思路三采用的初始化方式形式上最简洁直观,当 vector 对象的元素数量较多且取值重复时是最好的选择;而思路一在开始的时候不限定元素的个数,比较灵活。
练习 3.20
求相邻元素和:
#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];
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 + 1) % 5 == 0)
cout << endl;
}
// 如果元素数是奇数,单独处理最后一个元素
if (vInt.size() % 2 != 0)
cout << vInt[vInt.size() / 2];
return 0;
}
输出如下:
3.4 节练习
练习 3.21
#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;
}
return 0;
}
练习 3.22
#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; // 输出当前字符串
}
return 0;
}
输出如下:
插一个题外话
用输入文件流写的
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;
int main()
{
ifstream infile("input_22.txt");
if (!infile)
{
cerr << "无法打开文件 input.txt" << endl;
return 1;
}
vector<string> text;
string s;
// 利用 getline 读取一句话,直接回车产生一个空串,表示段落结束
while (getline(infile, 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; // 输出当前字符串
}
return 0;
}
输出如下:
其中input_22.txt
文件内容如下:
练习 3.23
#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++) // 循环 10 次
{
// 每次循环生成一个 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;
return 0;
}
输出如下:
插一句题外话
srand((unsigned)time(NULL));
是什么?
在 C/C++ 中,srand((unsigned)time(NULL));
这行代码用于为随机数生成器设置种子,从而确保每次程序运行时生成不同的随机数序列。具体解释如下:
-
time(NULL)
:调用 time 函数并传入参数 NULL,获取当前的系统时间(以秒为单位的 Unix 时间戳)。该函数会返回一个 time_t 类型的值,表示自1970年1月1日以来经过的秒数。 -
(unsigned)time(NULL)
:将 time(NULL) 返回的 time_t 类型值转换为 unsigned 类型,以确保该值适合作为 srand 的参数。 -
srand()
:srand 函数用于设置随机数生成器的种子值。在 C/C++ 中,rand() 函数用于生成随机数,而 srand() 用来初始化其种子,以控制 rand() 生成的随机数序列。传入不同的种子会导致生成不同的随机数序列。如果种子不变,那么每次运行程序时生成的随机数序列也会相同。
所以,srand((unsigned)time(NULL));
这行代码的作用是根据当前时间设置随机数种子,使得每次运行程序时生成的随机数序列不同。
rand()
函数的头文件是cstdlib
。
练习 3.24
求相邻元素和:
#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.begin() == vInt.end())
{
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);
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();
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);
return 0;
}
输出如下:
练习 3.25
#include <iostream>
#include <vector>
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 << "各分数段的人数分布是(成绩从低到高):" << endl;
// 利用迭代器遍历 vUS 的元素并逐个输出
for (it = vUS.begin(); it != vUS.end(); it++)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
输出如下:
练习 3.26
C++并没有定义两个迭代器的加法运算,实际上直接把两个迭代器加起来是没有意义的。
与之相反,C++定义了迭代器的减法运算,两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动多少个元素后可以得到左侧的迭代器,参与运算的两个迭代器必须指向同一个容器中的元素或尾后元素。
另外,C++还定义了迭代器与整数的加减法运算,用以控制迭代器在容器中左右移动。
在本题中,因为迭代器的加法不存在,所以 mid = (beg + end) /2;
不合法。
mid = beg+ (end - beg) / 2;
的含义是,先计算 end-beg 的值得到容器中的元素个数,然后控制迭代器从开始处向右移动二分之一容器的长度,从而定位到容器正中间的元素。
3.5 节练习
练习 3.27
(a)是非法的,buf_size 是一个普通的无符号数,不是常量,不能作为数组的维度。
(b)是合法的,4*7-14=14 是一个常量表达式。
(c)是非法的,txt_size()是一个普通的函数调用,没有被定义为 constexpr,不能作为数组的维度。
(d)是非法的,当使用字符串初始化字符数组时,默认在尾部添加一个空字符 '\0',算上这个符号该字符串共有 12 个字符,但是字符数组 st 的维度只有 11,无法容纳题目中的字符串。
需要指出的是,在某些编译器环境中,上面的个别语句被判定为合法,这是所
谓的编译器扩展。不过一般来说,建议读者避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上失效。
补充:
数组是一种复合类型,其声明形如 a[d],a 是数组的名字,d 是数组的维度(容量)。对数组维度的要求有两个,一是维度表示数组中元素的个数,因此必须大于 0;二是维度属于数组类型的一部分,因此在编译时应该是已知的,必须是一个常量表达式。
练习 3.28
与练习 2.10 类似,对于 string 类型的数组来说,因为 string 类本身接受无参数的初始化方式,所以不论数组定义在函数内还是函数外都被默认初始化为空串。
对于内置类型 int 来说,数组 ia 定义在所有函数体之外,根据 C++的规定,ia 的所有元素默认初始化为 0;而数组 ia2 定义在 main 函数的内部,将不被初始化,如果程序试图拷贝或输出未初始化的变量,将遇到未定义的奇异值。
下面的程序可以验证上述分析:
#include <iostream>
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 << " ";
return 0;
}
输出如下:
补充:
练习 3.29
数组与 vector 的相似之处是都能存放类型相同的对象,且这些对象本身没有名字,需要通过其所在位置访问。
数组与 vector 的最大不同是,数组的大小固定不变,不能随意向数组中增加额外的元素,虽然在某些情境下运行时性能较好,但是与 vector 相比损失了灵活性。
具体来说,数组的维度在定义时已经确定,如果我们想更改数组的长度,只能
创建一个更大的新数组,然后把原数组的所有元素复制到新数组中去。我们也无法像 vector 那样使用 size 函数直接获取数组的维度。如果是字符数组,可以调用strlen 函数得到字符串的长度;如果是其他数组,只能使用
sizeof(array)/sizeof(array[0])
的方式计算数组的维度。
插一句题外话
调用strlen 函数得到字符串的长度时是不包括字符串字面值结尾处的空字符
'\0'
的。
练习 3.30
本题的原意是创建一个包含 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
练习 3.32
实现数组拷贝:
#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;
return 0;
}
实现 vector 拷贝:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
const int sz = 10; // 常量 sz 作为 vector 的容量
vector<int> vInt, vInt2;
// 通过 for 循环为 vector 对象的元素赋值
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;
return 0;
}
练习 3.33
该程序对 scores 执行了列表初始化,为所有元素赋初值为 0,这样在后续统计时将会从 0 开始计算各个分数段的人数,是正确的做法。
如果不初始化 scores,则该数组会含有未定义的数值,这是因为 scores 是定义在函数内部的整型数组,不会执行默认初始化。
练习 3.34
如果 p1 和 p2 指向同一个数组中的元素,则该条语句令 p1 指向 p2 原来所指向的元素。从语法上来说,即使 p1 和 p2 指向的元素不属于同一个数组,但只要 p1 和 p2 的类型相同,该语句也是合法的。如果 p1 和 p2 的类型不同,则编译时报错。
练习 3.35
#include <iostream>
using namespace std;
int main()
{
const int sz = 10;
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;
return 0;
}
输出如下:
练习 3.36
因为长度不等的数组一定不相等,并且数组的维度一开始就要确定,所以为了简化起见,程序中设定两个待比较的数组维度一致,仅比较对应的元素是否相等。该例类似于一个彩票游戏,先由程序随机选出 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 循环为数组元素赋值
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;
return -1;
}
p++; // p 向后移动一位
q++; // q 向后移动一位
}
cout << "恭喜您全都猜对了!" << endl;
return 0;
}
输出如下:
对比两个 vector 对象是否相等:
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <vector>
using namespace std;
int main()
{
const int sz = 5; // 常量 sz 作为 vector 的容量
int i;
vector<int> a, b;
srand((unsigned)time(NULL)); // 生成随机数种子
// 通过 for 循环为数组元素赋值
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;
return -1;
}
it1++; // p 向后移动一位
it2++; // q 向后移动一位
}
cout << "恭喜您全都猜对了!" << endl;
return 0;
}
3.6 节练习
练习 3.37
程序第一行声明了一个包含 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 个,分别是指针本身的值(value)、指针所指的对象(content)以及指针本身在内存中的存储位置(address)。
它们的含义分别是:指针本身的值是一个内存地址值,表示指针所指对象在内存中的存储地址;指针所指的对象可以通过解引用指针访问;因为指针也是一个对象,所以指针也存储在内存的某个位置,它有自己的地址,这也是为什么有“指针的指针”的原因。
通过上述分析我们知道,指针的值是它所指对象的内存地址,如果我们把两个指针加在一起,就是试图把内存中两个对象的存储地址加在一起,这显然是没有任何意义的。与之相反,指针的减法是有意义的。如果两个指针指向同一个数组中的不同元素,则它们相减的结果表征了它们所指的元素在数组中的距离。
练习 3.39
比较两个 string 对象:
#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;
return 0;
}
输出如下:
比较两个 C 风格字符串:
#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;
}
return 0;
}
输出如下:
练习 3.40
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char str1[] = "good good study ";
char str2[] = "day day up!";
// 利用 strlen 函数计算两个字符串的长度,并求得结果字符串的长度
char result[strlen(str1) + strlen(str2) - 1];
strcpy(result, str1); // 把第一个字符串拷贝到结果字符串中
strcat(result, str2); // 把第二个字符串拼接到结果字符串中
cout << "第一个字符串是:" << str1 << endl;
cout << "第二个字符串是:" << str2 << endl;
cout << "拼接后的字符串是:" << result << endl;
return 0;
}
输出如下:
注意:
如果将上面的代码放在Visual Studio中运行的话,编译不会通过,放在Dev-C++中运行的话,编译会通过。
在Visual Studio可以这样写:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char str1[] = "good good study ";
char str2[] = "day day up!";
// 利用 strlen 函数计算两个字符串的长度,并求得结果字符串的长度
char result[80];
strcpy_s(result, str1); // 把第一个字符串拷贝到结果字符串中
strcat_s(result, str2); // 把第二个字符串拼接到结果字符串中
cout << "第一个字符串是:" << str1 << endl;
cout << "第二个字符串是:" << str2 << endl;
cout << "拼接后的字符串是:" << result << endl;
return 0;
}
输出如下:
练习 3.41
C++不允许用一个数组初始化另一个数组,也不允许使用 vector 对象直接初始化数组,但是允许使用数组来初始化 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;
return 0;
}
输出如下:
练习 3.42
C++允许使用数组直接初始化 vector 对象,但是不允许使用 vector 对象初始化数组。如果想用 vector 对象初始化数组,则必须把 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[sz];
cout << "数组的内容是:" << endl;
// 利用范围 for 循环遍历数组的每个元素
for (auto& val : a)
{
val = *it;
cout << val << " ";
it++;
}
cout << endl;
return 0;
}
输出如下:
3.7 节练习
练习 3.43
#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 (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 *p= *p; q != *p + 4; q++)
cout << *q << " ";
cout << endl;
}
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 (int_array& row : 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;
}
return 0;
}
输出如下:
练习 3.45
#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 (auto& row : ia)
{
for (auto 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;
}
cout << "利用普通 for 语句和指针输出多维数组的内容:" << endl;
for (auto p = ia; p != ia + 3; p++)
{
for (auto q = *p; q != *p + 4; q++)
cout << *q << " ";
cout << endl;
}
return 0;
}
输出如下: