C++ Primer 第三章 学习笔记及习题答案

7次阅读

共计 14444 个字符,预计需要花费 37 分钟才能阅读完成。

知识点
string、vector、数组初始化:

string 初始化:
string s1; // 默认初始化,s1 为一个空串
string s2(s1); string s2 = s1; //s2 是 s1 的副本
string s3(“value”); //s3 是字面值 ”value” 的副本(除字符串字面值最后那个空字符)
string s4(n,’c’); // 把 s4 初始化为由连续 n 个字符 c 组成的字符串

tips:使用等号 (‘=’) 初始化一个变量,执行的是拷贝初始化;如果不使用等号,直接初始化,使用的是直接初始化。

vector 初始化:
vector<T> v1; //v1 是一个空 vector,类型为 T,执行默认初始化
vector<T> v2(v1); vector<T> v2 = v1; //v2 中包含有 v1 所有元素的副本
vcetor<T> v3(n, val); //v3 中包含 n 个重复的元素,每个元素值均为 val
vector<T> v4(n); //v4 包含 n 个重复执行值初始化的对象
vector<T> v5{a,b,c,..}; vector v5{a,b,c}; //v5 包含初始值个数的元素,每个元素赋予相应值
tips:
· 列表初始化的形式可以确保不会发生类型转换
· 学会区分花括号 {} 和圆括号 () 初始的不同;前后顺序:编译器会在确认无法执行列表初始化后,尝试用默认值初始化 vector 对象。

数组初始化:
T arr[d]; // 数组声明方式(类型为 T 名字为 arr 含有 d 个元素),默认初始化,T 不可用 auto 推断
// 显示初始化方式
int a1[] {0,1,2}; // 编译器根据初始值数量推测维度,等价于 int a1[3] {0,1,2};
init a2[5]{0,1,2}; // 用提供的初始值初始化靠前元素,剩下元素被默认初始化

// 字符数组
char a1[] {‘c’,’+’,’+’,’\0′}; <==> 等价 char a2[] = “c++”;
// 利用字符串字面值进行初始化时,’\0’ 保留(与 string 初始化相反)

// 数组不允许拷贝和赋值
// int a1[] {0,1,2};
// int a2[] = a1; // 错误
// a2 = a1; 错误

string、vector 操作:

string 操作
含义
vector 操作
含义

os << s
将 s 写到输出流 os 当中
vi.push_back(1)
在 vector 对象尾端加入元素 1

is >> s
从 is 中读取字符串赋给 s,遇到空白符停止读入

getline(is,s)
从 is 中读取一行赋给 s

s.empty()
s 为空返回 true,否则返回 false
vi.empty()
如果 v 中不含有任何元素,返回 true,否则返回 false

s.size()
返回 s 中字符的个数
vi.size()
返回 v 中元素个数

s[n]
返回第 n 个字符的引用,从 0 开始记数
vi[n]
返回 vi 中第 n 个元素的引用

s1 + s2
返回 s1 和 s2 连接后的结果

<, <=, >, >=
利用字符在字典中的顺序进行比较,大小写敏感
<, <=, >, >=
利用字典顺序比较

while(cin>> s1)
读取未知数量的 string 对象

tips:

string:

getline 遇到换行符就结束读取操作并返回结果,触发 getline 函数的换行符会被丢弃,得到的 string 对象并不包含换行符
s.size()的返回类型为 string::size_type(无符号类型的值),混用有符号和无符号数可能产生错误,因此如果一个表达式中 size()和 int 不要混用

当字面值和 string 对象相加时,必须保证每个加法运算符 (‘+’) 两侧的运算对象至少有一个是 string;

vector:
vector 和 string 对象的下标运算符可用于访问已存在的元素,不能用于添加元素,vector 用 push_back()添加元素

string/vector/ 数组访问元素的方式:

三种访问元素的方式:
范围 for:
for(declaration: expression)
statement
// 如果想要改变 string 对象中字符的值,必须把循环变量定义成引用类型

下标访问:注意下标不要越界,也不可使用下标访问空 string
迭代器访问

string 和 vector 以上三种方式均可访问元素;数组可以利用范围 for 语句和下标访问,不可用迭代器访问

迭代器(string 和 vector 对象均可用)

迭代器有有效和无效之分,有效迭代器指向某个元素或者指向容器中尾元素的下一位置,其他所有情况均为无效。
迭代器有 begin 和 end 成员:begin 负责返回指向第一个元素(或第一个字符)的迭代器,end 成员负责返回指向容器(或 string 对象)尾元素的下一位置,end 成员返回的迭代器常被称为尾后迭代器(off-the-end iterator)。

标准迭代器支持的运算:

*iter:返回迭代器 iter 所指元素的引用
iter->mem:解引用 iter 并获取该元素的名为 mem 的成员,等价于(*iter.mem)
++iter:令 iter 指示容器中的下一个元素
–iter:令 iter 指示容器中的上一个元素
iter1 == iter2/iter1 != iter2:判断两个迭代器是否相等,如果两个迭代器指向的是同一个元素或者它们是同一个容器的尾后迭代器,则相等,否则不等

vector 和 string 迭代器支持的运算(vector 和 string 的迭代器在标准迭代器上进行扩充):

iter + n; // 迭代器向前移动 n 个位置
iter – n; // 迭代器向后移动 n 个位置
iter1 += n; // 将 iter1 加 n 的结果赋给 iter1
iter1 -= n; // 将 iter1 减 n 的结果赋给 iter1
iter1 – iter2; // 两个迭代器相减的结果是它们的距离,返回类型为 difference_type(带符号类型)

>, >=, <, <=; // 如果迭代器指向的容器位置在另一个迭代器位置之前,则说前者小于后者

迭代器类型:iterator 和 const_iterator(要在前面加上具体域,如 string::iterator/vector<int>::const_iterator)。iterator 的对象可读可写,const_iterator 的对象只可读但不能修改它所指的元素值。为了得到 const_iterator 类型的返回值,c++11 引入了 cbegin 和 cend。
箭头运算符(->):it->mem 等价(*it).mem
凡是使用了迭代器的循环体,不要向迭代器所属的容器添加元素
养成循环判断中迭代器和!= 一起使用的习惯

数组

数组内的元素本身没有名字,通过其所在位置访问,数组大小确定不变,不能随意像数组中增加元素 s

指针和数组:

在大部分情况下,使用数组类型的对象其实是使用一个指向该数组首元素的指针

对数组的元素使用取地址符就可以得到该元素的指针
string nums[] {“one”, “two”, “three”};
string *p = &nums[0];
string *p = nums; // 等价于指向该数组首元素的指针

vector 和 string 迭代器支持的运算,数组的指针都支持

为了指针的使用更简单安全,可以将 C ++11 中的 begin 和 end 函数作为数组的参数:
int ia[]{0, 1, 2, 3, 4, 5, 6};
int *beg = begin(ia);
int *last = end(ia);

解引用和指针:
int ia[]{0, 1, 2, 3, 4, 5, 6, 7, 8};
*(ia + 4); //*(ia+4)等价于 ia[4]
*ia + 4; // 等价于 ia[0]+4

tips:

数组下标类型为 size_t(无符号类型)

使用 auto 推断数组时得到指向数组的指针 T*;使用 decltype 推断数组时得到同类型的数组:
int arr[] {1, 2, 3};
auto(arr) arr1; //arr1 类型为 int*
decltype(arr) arr2 = {4, 5, 6}; //arr2 类型为含有 3 个整数的数组

两个指针相减为 ptrdiff_t(带符号类型)

string 和 vector 的下表必须是无符号类型,但数组的下标是内置的,不是无符号类型,可以处理负值

C 风格字符串

在 C 中,把字符串放在字符数组中并以空字符结束(‘0’)

C 风格字符串函数
含义

strlen(p)
返回 p 的长度,空字符不计在内

strcmp(p1, p2)
比较 p1 和 p2 的相等性。如果 p1 == p2,返回 0;如果 p1 > p2,返回一个正值;如果 p1 < p2,返回一个负值

strcat(p1, p2)
将 p2 附加到 p1 之后,返回 1

strcpy(p1,p2)
将 p2 拷贝给 p1,返回 p1

可以使用数组初始化 vector,只需指明要拷贝区域的首元素地址和尾后地址即可;但不可以使用 vector 或数组初始化数组。

多维数组

多维数组即数组的数组。
使用范围 for 语句处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型,这样可以避免数组被自动转成指针。(auto arr[10]{1,2,3}; -> auto 推断类型为 int*)

练习题
3.1 节练习

练习 3.1:使用恰当的 using 申明重做 1.4.1 节(第 11 页)和 2.6.2 节(第 67 页)练习。
加入 using namespace std;

3.2 节练习
3.2.2 节练习

练习 3.2:编写一段程序从标准输入中一次读入一整行,然后修改该程序使其一次读入一个词。
读入一整行:
//getline 接受两个参数:输入流和 string 对象,每次读入一整行
string s;
while (getline(cin,s)) {
cout << s << endl;
}
一次读入一个词:
// 使用 cin 读取遇到空格停止
string s1;
while (cin>>s1) {
cout << s1 << endl;
}

练习 3.3:请说明 string 类的输入运算符和 getline 函数分别是如何处理空白字符的。
在执行读取操作时 string 类的输入运算符会自动忽略开头的空白后开始读取,并在遇到下一个空白字符时停止,
getline 函数从给定的输入流中读取字符直到遇到换行符 \n 为止(换行符也读取进来),然后把所读的内容存入到 string 对象中去(存的过程不包含换行符)。因此,string 对象遇到换行符停止,且得到的 string 对象不包含换行符,在换行符出现前的空白字符均正常读取。

练习 3.4:编写一段程序读入两个字符串,比较其是否相等 并输出结果。如果不相等,输出较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较大的那个字符串。
判断字符串大小:
string s, s1;
cin >> s >> s1;
if (s1 >= s)
cout << s1;
else
cout << s;

判断字符串等长:
string s, s1;
cin >> s >> s1;
if (s1.size() == s.size())
cout << “the length of two strings is equal.”;
else if (s1.size() >= s.size())
cout << s1;
else
cout << s;

练习 3.5:编写一段程序从标准输入中读入多个字符串并将他们连接在一起,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分隔开来。
连接成大字符串:
string s, s1;
while (cin >> s) {
s1 += s;
}
cout << s1;

用空格分割:
string s, s1;
while (cin >> s) {
s1 += s + ‘ ‘;
}
cout << s1;

3.2.3 节练习

练习 3.6:编写一段程序,使用范围 for 语句将字符串内所有的字符用 X 代替。
string s, s1;
while (cin >> s) {
for (auto &c : s) // 传引用!保证可以修改字符串中的值,而不是拷贝出来赋值
c = ‘X’;
}
cout << s << endl;

练习 3.7:就上一题完成的程序而言,如果将循环控制变量的类型设为 char 将发生什么?先估计一下结果,然后实际编程进行验证。
没有变化,字符串的每一位中存放的为字符(char)类型

练习 3.8:分别用 while 循环和传统的 for 循环重写第一题的程序,你觉得哪种形式更好呢?为什么?
while 循环:
string s, s1;
cin >> s;
int len = s.size();
while (((–len) >= 0)) {
s[len] = ‘X’;
}
cout << s << endl;

for 循环:
string s, s1;
cin >> s;
int len = s.size();
for (int i = 0; i != len; ++i)
s[i] = ‘X’;
cout << s << endl;

for 循环可读性好些

练习 3.9:下面的程序有何作用?它合法吗?如果不合法,为什么?
string s;
cout << s[0] <<endl;

不合法,s 为空字符串,使用下标访问空 string 会引发不可预知的错误。

练习 3.10:编写一段程序,读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩下的部分。
string s, s1;
cin >> s;
for (auto c : s) {
if (!(ispunct(c)))
s1 += c;
}
cout << s1 <<endl;

练习 3.11:下面的范围 for 语句合法吗?如果合法,c 的类型是什么?
const string s = “Keep out!”;
for (auto c : s) {/* … */}

合法,c 的类型为 char,因为 auto 会忽略顶层 const。

3.3 节练习
3.3.1 节练习

练习 3.12:下列 vector 对象的定义有不正确的吗?如果有,请指出来。对于正确的,描述其执行结果,对于不正确的,说明其错误的原因。
(a) vector<vector<int>> ivec;
(b) vector<string> svec = ivec;
(c) vector<string> svec(10, “null”);

(a) 正确,创建一个 vector,该 vector 的元素是 int 型的 vector 对象
(b) 错误,ivec 和 svec 对象类型不同,svec 的元素是 string 对象,ivec 的元素是 int 型的 vector 对象
(c) 正确,创建一个 vector,svec 的元素是 string 对象,并对其初始化,svec 含有 10 个元素,每个的值都是 ”null”

练习 3.13:下列的 vector 对象各包含多少个元素?这些元素的值分别是多少?
(a) vector<int> v1;
(b) vector<int> v2(10);
(c) vector<int> v3(10,42);
(d) vector<int> v4{10};
(e) vector<int> v5{10,42};
(f) vector<string> v6{10};
(g) vector<string> v7{10,”hi”};

(a) v1 是一个空 vector,不含任何元素;
(b) v2 是一个含有 10 个元素的 vector,每个元素的值被默认初始化为 0
(c) v3 是一个含有 10 个元素的 vector,每个元素的值为 42
(d) v4 是一个含有 1 个元素的 vector,该元素值为 10
(e) v5 是一个含有 2 个元素的 vector,元素值分别为 10,42
(f) v6 是一个含有 1 个元素的 vector,v6 的元素是 string 对象,值为 null
(g) v7 是一个含有 10 个元素的 vector,v6 的元素是 string 对象,每个元素的值为 ”hi”

tips:v7 的花括号中提供的元素值不能作为元素的初始值,在确认无法执行列表初始化后,编译器会尝试用默认值初始化 vector 对象。
(g) vector<string> v7{10,”hi”}; -> 无法列表初始化,等价于 <=> vector<string> v7(10,”hi”);

3.3.2 节练习

练习 3.14:编写一段程序,用 cin 读入一组整数并把它们存入一个 vector 对象。
int n;
vector<int> ivec;
while (cin >> n) {
ivec.push_back(n);
}

练习 3.15:改写上题的程序,不过这次读入的是字符串。
string s;
vector<string> svec;
while (cin >> s) {
svec.push_back(s);
}

3.3.3 节练习

练习 3.16:编写一段程序,把练习 3.13 中 vector 对象的容量和具体内容输出出来。检验你之前的回答是否正确,如果不对,回过头重新学习 3.3.1 节(第 87 页)知道弄明白错在何处为止。
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” << endl;
for (auto c : v1)
cout << c << endl;

cout << “v2” << endl;
for (auto c : v2)
cout << c << endl;

cout << “v3” << endl;
for (auto c : v3)
cout << c << endl;

cout << “v4” << endl;
for (auto c : v4)
cout << c << endl;

cout << “v5” << endl;
for (auto c : v5)
cout << c << endl;

cout << “v6” << endl;
for (auto c : v6)
cout << c << endl;

cout << “v7″ << endl;
for (auto c : v7)
cout << c << endl;

检验后输出结果正确,注意 v6 中 string 对象默认初始化为 null

练习 3.17:从 cin 读入一组词并把它们存入一个 vector 对象,然后设法把所有词都改写成大写形式。输出改变后的结果,每个词占一行。
string s;
vector<string> svec;
while (cin >> s) {
for (auto &c : s) {// 注意传引用
c = toupper(c); // 将改为大写的 char 赋值给原 char
}
svec.push_back(s);
}
for (int i = 0; i != svec.size(); ++i)
cout << svec[i] << endl;

练习 3.18:下面的程序合法吗?如果不合法,你准备如何修改?
vector<int> ivec;
ivec[0] = 42;

不合法,因为 ivec 为空 vector,vector 的下标只能用于访问已存在的元素,且不能使用下标形式添加元素,只能使用 push_back 添加

改为:
vector<int> ivec;
ivec.push_back(42);

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

vector<int> ivec{42,42,42,42,42,42,42,42,42,42};

vector<int> ivec;
for(int i = 0; i < 10; ++i)
ivec.push_back(42);

使用圆括号形式初始化固定大小且元素值相同的 vector 最方便。

练习 3.20:读入一组整数并把它们存入一个 vector 对象,将每对相邻整数的和输出出来。改写你的程序,这次要求先输出第 1 个和最后 1 个元素的和,接着输出第 2 个和倒数第 2 个元素的和,以此类推。
// 输出相邻整数
int n;
vector<int> ivec;
while (cin >> n) {
ivec.push_back(n);
}
for (int i = 1; i != ivec.size(); ++i)
cout << ivec[i – 1] + ivec[i] << ” “;

// 输出首尾元素和
int n;
vector<int> ivec;
while (cin >> n) {
ivec.push_back(n);
}
int len = ivec.size();
for (int i = 0; i != (len / 2) + 1; ++i)
cout << ivec[i] + ivec[len – i – 1] << ” “;// 下标千万不要越界

3.4 节练习
3.4.1 节练习

练习 3.21:请使用迭代器重做 3.3.3 节(第 94 页)的第一个练习。
// 使用!= 进行 for 循环的判断,在标准库提供的所有容器均有效,
// 养成使用迭代器和!= 的习惯,就不用太在意用的到底是哪种容器类型
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” << endl;
for (auto it = v1.begin(); it != v1.end(); ++it)
cout << *it << endl;

cout << “v2” << endl;
for (auto it = v2.begin(); it != v2.end(); ++it)
cout << *it << endl;

cout << “v3” << endl;
for (auto it = v3.begin(); it != v3.end(); ++it)
cout << *it << endl;

cout << “v4” << endl;
for (auto it = v4.begin(); it != v4.end(); ++it)
cout << *it << endl;

cout << “v5” << endl;
for (auto it = v5.begin(); it != v5.end(); ++it)
cout << *it << endl;

cout << “v6” << endl;
for (auto it = v6.begin(); it != v6.end(); ++it)
cout << *it << endl;

cout << “v7″ << endl;
for (auto it = v7.begin(); it != v7.end(); ++it)
cout << *it << endl;

练习 3.22:修改之前那个输出 text 第一段的程序,首先把 text 的第一段全都改成大写形式,然后再输出它。
vector<string> text;
string str;
while (cin >> str) {
text.push_back(str);
}
for (auto it = text.begin(); it != text.end() && !it->empty(); ++it) {
for (auto &c : *it)
c = toupper(c);
cout << *it << endl;
}

练习 3.23:编写一段程序,创建一个含有 10 个整数的 vector 对象,然后使用迭代器将所有元素的值都变成原来的两倍。输出 vector 对象的内容,检验程序是否正确。
vector<int> ivec;
int n;
while(cin>>n)
ivec.push_back(n);
for (auto it = ivec.begin(); it != ivec.end(); ++it) {
*it *= 2;
cout << *it << ” “;
}

3.4.2 节练习

练习 3.24:请使用迭代器重做 3.3.3 节(第 94 页)的最后一个练习。
// 输出相邻整数和
vector<int> ivec;
int n;
while(cin>>n)
ivec.push_back(n);
for (auto it = ivec.begin() + 1; it != ivec.end(); ++it) {
cout << *it + *(it-1) << ” “;
}

// 输出首尾相加和
// 学会尾部迭代器的使用
vector<int> ivec;
int n;
while(cin>>n)
ivec.push_back(n);
auto beg = ivec.begin();
auto end = ivec.end() – 1;
for (; beg <= end; ++beg, –end) {
cout << *beg + *end << ” “;
}

练习 3.25:3.3.3 节(第 93 页)划分分数段的程序是使用下标运算符实现的,请利用迭代器改写该程序并实现完全相同的功能。
vector<int> ivec(11);
int n;
while (cin >> n) {
n /= 10;
++ivec[n];
}
for (auto it = ivec.begin(); it != ivec.end(); ++it)
cout << *it << ” “;

练习 3.26:在 100 页的二分搜索程序中,为什么用的是 mid = beg + (end – beg) / 2,而非 mid = (beg + end) / 2?
迭代器不支持两个迭代器相加的运算,但支持迭代器加上一个整数的运算
(end – beg) / 2 两个迭代器相减返回 difference_type,是带符号整型数,可以与迭代器相加。

3.5.1 节练习

练习 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) 合法
(c) 若 text_size()为常量表达式(constexpr)时,在编译时确定具体指并替换,则正确;否则错误
(d) 非法,溢出,字符数组利用字符串字面值进行初始化时 ’\0’ 会包含

练习 3.28:下列数组中的元素值是什么?
string sa[10];
int ia[10];
int main(){
string sa2[10];
int ia2[10];
}
sa 和 ia 位于定义于任何函数体之外,sa 为空字符串;ia 数组内每个值均初始化为 0。
sa2 和 ia2 在函数体内部,srting 类规定没有指定初值则声称一个空串;ia2 不被初始化,其中的值未定义。

综上,sa 和 sa2 初始化为空串;ia 数组内每个值均为 0,ia2 中值未定义。

练习 3.29:相比 vector,数组有哪些缺点,请列举一些。
数组没有 vector 灵活,数组的大小确定不变,不能随意向数组内增加元素。

3.5.2 节练习

练习 3.30:指出下面代码中的索引错误。
constexpr size_t array_size = 10;
int ia[array_size];
for (size_t ix = 1; ix <= array_size; ++ix)
ia[ix] = ix;

int ia[10]中存放 ia[0]~is[9]的值,for 循环中访问了 ia[10],导致下标越界。

练习 3.31:编写一段程序,定义一个含有 10 个 int 的数组,令每个元素的值就是其下标值。
int a[10];
for(int i = 0; i < 10; ++i)
a[i] = i;

练习 3.32:将上一题刚刚创建的数组拷贝给另外一个数组。利用 vector 重写程序,实现类似的功能。
// 数组不允许直接拷贝
int b[10];
for(int i = 0; i < 10; ++i)
b[i] = a[i];

vector<int> vi;
for (int i = 0; i < 10; ++i)
vi.push_back(a[i])

练习 3.33:对于 104 页的程序来说,如果不初始化 scores 将发生什么。
不初始化 scores 的话,其内部的值是未定义的,无法实现利用下标统计人数段的功能。

3.5.3 节练习

练习 3.34:假定 p1 和 p2 指向同一个数组中的元素,则下面程序的功能是什么?
p1 += p2 – p1;

两个指针相减的结果是它们之间的距离,p2 – p1 为类型为 ptrdiff_t,值为 0 的数。p1 加上这个数指针不变。
上面程序的功能是 p1 经过运算后指向元素不变

练习 3.35:编写一段程序,利用指针将数组中的元素置为 0。
int a[10];
for (int i = 0; i < 10; ++i)
a[i] = i;
int *beg = &a[0];
int *end = &a[10];
for (auto it = beg; it != end; ++it) {
*it = 0;
cout << *it << ” “;
}

练习 3.36:编写一段程序,比较两个数组是否相等。再编写一段程序,比较两个 vector 对象是否相等。
// 判断数组是否相等
int arr1[]{ 0,1,2,3,4};
int arr2[]{ 0,1,2,3,6};
auto *beg1 = begin(arr1);
auto *last1 = end(arr1);
auto *beg2 = begin(arr2);
auto *last2 = end(arr2);
auto n1 = *last1 – *beg1;
auto n2 = *last2 – *beg2;
decltype(n1) acount = 0;
if (n1 == n2) {
for (; beg1 != last1, beg2 != last2; ++beg1, ++beg2); {
if (beg1 != beg2) {
cout << “The two array have same size, but they are not equal.” << endl;
}
if (beg1 == beg2) {
++acount;
}
}
if (acount == n1)
cout << “The two array are equal.” << endl;
}
else
cout << “The two array are not equal.” << endl;

// 判断 vector 是否相等
vector<int> v1{0,1,2,3,4};
vector<int> v2{0,1,2,4,3};
int vcount = 0;
if (v1.size() == v2.size()) {
for (auto i = 0; i != v1.size(); ++i) {
if (v1[i] != v2[i]) {
cout << “The two vector have same size, but they are not equal.” << endl; break;
}
else
++vcount;
}
if (vcount == v1.size())
cout << “The two vector are equal.” << endl;
}
else
cout << “The two vector are not equal.” << endl;

3.5.4 节练习

练习 3.37:下面的程序是何定义,程序的输出结果是什么?
const char ca[] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’};
const char *cp = ca;
while(*cp){
cout << *cp <<endl;
++cp;
}

程序输出会报错,ca 为一个 c 风格的字符串,必须以 ’\0’ 结束,
正确写法为 const char ca[] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
这样会依次输出字符串每一位(除 \0 结束符),即输出 h e l l o

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

练习 3.39:编写一段程序,比较两个 string 对象。再编写一段程序,比较两个 C 风格字符串的内容。
// 比较两个 string 对象
string s1 = “abcde”;
string s2 = “abded”;
if (s1 > s2)
cout << “s1 is bigger than s2.” << endl;
else if (s1 < s2)
cout << “s1 is smaller than s2.” << endl;
else
cout << “s1 and s2 are equal.” << endl;

// 比较两个 C 风格字符串
const char ca1[] = “abcde”;
const char ca2[] = “abced”;
if (strcmp(ca1, ca2) > 0)
cout << “ca1 is bigger than ca2.” << endl;
else if (strcmp(ca1, ca2) < 0)
cout << “ca1 is smaller than ca2.” << endl;
else
cout << “ca1 and ca2 are equal.” << endl;

练习 3.40:编写一段程序,定义两个字符数组并用字符串字面值初始化它们;接着再定义一个字符数组存放前两个数组连接后的结果。使用 strcpy 和 strcat 把两个数组的内容拷贝到第三个数组。
// 注意要给出被操作数组的具体大小
char ca1[100] = “abcde”;
const char ca2[] = “feagr”;
char ca3[100] = “”;
strcat_s(ca1, ca2);
strcpy_s(ca3, ca1);
cout << ca3 << endl;

3.5.5 节练习

练习 3.41:编写一段程序,用整型数组初始化一个 vector 对象。
int arr[]{1, 2, 3, 4, 5};
vector<int> vi(begin(arr), end(arr));

练习 3.42:编写一段程序,将含有整数元素的 vector 对象拷贝给一个整型数组。
// 使用 begin 和 end 函数得到数组首指针和尾后指针
vector<int> vi{1,2,3,4,5};
int arr[5];
int *beg = begin(arr);
int *last = end(arr);
for (auto i : vi) {
*beg = i;
++beg;
}

3.6 节练习

练习 3.43:编写 3 个不同版本的程序,令其均能输出 ia 的元素。版本 1 使用范围 for 语句管理迭代过程;版本 2 和版本 3 都使用普通的 for 语句,其中版本 2 要求用下标运算符,版本 3 要求用指针。此外,在所有 3 个版本的程序中都要直接写出数据类型,而不能使用类型别名、auto 关键字和 decltype 关键字。
int ia[3][4]{{0,1,2,3},
{4,5,6,7},
{8,9,10,11} };
//version 1
for (int (&row)[4] : ia) {
for (int col : row) {
cout << col << ” “;
}
cout << endl;
}

//version 2
for (int i = 0; i != 3; ++i) {
for (int j = 0; j != 4; ++j) {
cout << ia[i][j] << ” “;
}
cout << endl;
}

//version 3
for (int (*p)[4] = ia; p != ia + 3; ++p) {
for (int *q = *p; q != *p + 4; ++q) {
cout << *q << ” “;
}
cout << endl;
}

练习 3.44:改写上一个练习中的程序,使用类型别名来代替循环控制变量的类型。
int ia[3][4]{{0,1,2,3},
{4,5,6,7},
{8,9,10,11} };
//version 1
using elem1_1 = int[4];
using elem1_2 = int;
for (elem1_1 &row : ia) {
for (elem1_2 col : row) {
cout << col << ” “;
}
cout << endl;
}

//version 2
using elem2 = int;
for (elem2 i = 0; i != 3; ++i) {
for (elem2 j = 0; j != 4; ++j) {
cout << ia[i][j] << ” “;
}
cout << endl;
}

//version 3
using elem3_1 = int(*)[4];
using elem3_2 = int*;
for (elem3_1 p = ia; p != ia + 3; ++p) {
for (elem3_2 q = *p; q != *p + 4; ++q) {
cout << *q << ” “;
}
cout << endl;
}

练习 3.45:再一次改写程序,这一次使用 auto 关键字。
int ia[3][4]{{0,1,2,3},
{4,5,6,7},
{8,9,10,11} };
//version 1
for (auto &row : ia) {
for (auto col : row) {
cout << col << ” “;
}
cout << endl;
}

//version 2
for (auto i = 0; i != 3; ++i) {
for (auto j = 0; j != 4; ++j) {
cout << ia[i][j] << ” “;
}
cout << endl;
}

//version 3
for (auto p = ia; p != ia + 3; ++p) {
for (auto q = *p; q != *p + 4; ++q) {
cout << *q << ” “;
}
cout << endl;
}

正文完
 0