# Chapter 3 Strings, Vectors, and Arrays

# Namespace using Declarations


# using 声明

  • 使用某个命名空间:例如 using std::cin 表示使用命名空间 std 中的名字 cin
  • 头文件中不应该包含 using 声明。这样使用了该头文件的源码也会使用这个声明,会带来风险。

# Exercise 3.1

使用恰当的 using 声明重做 1.4.1 节和 2.6.2 节的练习。

解:

1.4.1

#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main()
{
	int sum = 0;
	for (int val = 1; val <= 10; ++val) sum += val;
	cout << "Sum of 1 to 10 inclusive is " << sum << endl;
	
	return 0;
}

2.6.2 类似

# Library string Type


# string

  • 标准库类型 string 表示可变长的字符序列。
  • #include <string> ,然后 using std::string;
  • string 对象:注意,不同于字符串字面值。

# 定义和初始化 string 对象

初始化 string 对象的方式:

方式解释
string s1默认初始化, s1 是个空字符串
string s2(s1)s2s1 的副本
string s2 = s1等价于 s2(s1)s2s1 的副本
string s3("value")s3 是字面值 “value” 的副本,除了字面值最后的那个空字符外
string s3 = "value"等价于 s3("value")s3 是字面值 "value" 的副本
string s4(n, 'c')s4 初始化为由连续 n 个字符 c 组成的串
  • 拷贝初始化(copy initialization):使用等号 = 将一个已有的对象拷贝到正在创建的对象。
  • 直接初始化(direct initialization):通过括号给对象赋值。

# string 对象上的操作

string 的操作:

操作解释
os << ss 写到输出流 os 当中,返回 os
is >> sis 中读取字符串赋给 s ,字符串以空白分割,返回 is
getline(is, s)is 中读取一行赋给 s ,返回 is
s.empty()s 为空返回 true ,否则返回 false
s.size()返回 s 中字符的个数
s[n]返回 s 中第 n 个字符的引用,位置 n 从 0 计起
s1+s2返回 s1s2 连接后的结果
s1=s2s2 的副本代替 s1 中原来的字符
s1==s2如果 s1s2 中所含的字符完全一样,则它们相等; string 对象的相等性判断对字母的大小写敏感
s1!=s2同上
< , <= , > , >=利用字符在字典中的顺序进行比较,且对字母的大小写敏感(对第一个不相同的位置进行比较)
  • string io:
    • 执行读操作 >> :忽略掉开头的空白(包括空格、换行符和制表符),直到遇到下一处空白为止。
    • getline :读取一整行,包括空白符
  • s.size() 返回的时 string::size_type 类型,记住是一个无符号类型的值,不要和 int 混用
  • s1+s2 使用时,保证至少一侧是 string 类型。 string s1 = "hello" + "world" // 错误,两侧均为字符串字面值
  • 字符串字面值和 string 是不同的类型。

# 处理 string 对象中的字符

  • ctype.h vs. cctype:C++ 修改了 c 的标准库,名称为去掉 .h ,前面加 c

    如 c++ 版本为 cctype ,c 版本为 ctype.h

    • 尽量使用 c++ 版本的头文件,即 cctype

cctype 头文件中定义了一组标准函数:

函数解释
isalnum(c)c 是字母或数字时为真
isalpha(c)c 是字母时为真
iscntrl(c)c 是控制字符时为真
isdigit(c)c 是数字时为真
isgraph(c)c 不是空格但可以打印时为真
islower(c)c 是小写字母时为真
isprint(c)c 是可打印字符时为真
ispunct(c)c 是标点符号时为真
isspace(c)c 是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符)
isupper(c)c 是大写字母时为真
isxdigit(c)c 是十六进制数字时为真
tolower(c)c 是大写字母,输出对应的小写字母;否则原样输出 c
toupper(c)c 是小写字母,输出对应的大写字母;否则原样输出 c
  • 遍历字符串:使用范围 for(range for)语句: for (auto c: str) ,或者 for (auto &c: str) 使用引用直接改变字符串中的字符。 (C++11)
  • str[x] ,[] 输入参数为 string::size_type 类型,给出 int 整型也会自动转化为该类型。

# Exercise 3.2

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

解:

一次读入一行:

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::getline;
int main()
{
	string s;
	while (getline(cin,s))
	{
		cout << s << endl;
	}
	return 0;
}

一次读入一个词

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
using std::getline;
int main()
{
	string s;
	while (cin >> s)
	{
		cout << s << endl;
	}
	return 0;
}

# Exercise 3.3

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

解:

  • 类似 is >> s 的读取:string 对象会忽略开头的空白并从第一个真正的字符开始,直到遇见下一空白为止。
  • 类似 getline(is, s) 的读取:string 对象会从输入流中读取字符,直到遇见换行符为止。

# Exercise 3.4

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

解:

比较大的

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string str1, str2;
	while (cin >> str1 >> str2)
	{
		if (str1 == str2)
			cout << "The two strings are equal." << endl;
		else
			cout << "The larger string is " << ((str1 > str2) ? str1 : str2);
	}
	return 0;
}

长度大的

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string str1, str2;
	while (cin >> str1 >> str2)
	{
		if (str1.size() == str2.size())
			cout << "The two strings have the same length." << endl;
		else
			cout << "The longer string is " << ((str1.size() > str2.size()) ? str1 : str2) << endl;
	}
	return 0;
}

# Exercise 3.5

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

解:

未隔开的:

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string result, s;
	while (cin >> s)
	{
		result += s;
	}
	cout << result << endl;
	return 0;
}

隔开的:

#include <iostream>
#include <string>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string result, s;
	while (cin >> s)
	{
		result += s + " ";
	}
	cout << result << endl;
	return 0;
}

# Exercise 3.6

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

解:

#include <iostream>
#include <string>
#include <cctype>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string s = "this is a string";
	for (auto &x : s)
	{
		x = 'X';
	}
	cout << s << endl;
	return 0;
}

# Exercise 3.7

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

解:

如果设置为 char,那么原来的字符串不会发生改变。

# Exercise 3.8

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

解:

#include <iostream>
#include <string>
#include <cctype>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string s = "this is a string";
	decltype(s.size()) i = 0;
	while (i != s.size())
	{
		s[i] = 'X';
		++i;
	}
	cout << s << endl;
	for (i = 0; i != s.size(); ++i)
	{
		s[i] = 'Y';
	}
	cout << s << endl;
	return 0;
}

范围 for 语句更好,不直接操作索引,更简洁。

# Exercise 3.9

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

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

解:

不合法。使用下标访问空字符串是非法的行为。

# Exercise 3.10

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

解:

#include <iostream>
#include <string>
#include <cctype>
using std::string;
using std::cin;
using std::cout;
using std::endl;
int main()
{
	string s = "this, is. a :string!";
	string result;
	for (auto x : s)
	{
		if (!ispunct(x))
		{
			result += x;
		}
	}
	
	cout << result << endl;
	return 0;
}

# Exercise 3.11

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

const string s = "Keep out!";
for(auto &c : s){ /* ... */ }

解:

要根据 for 循环中的代码来看是否合法,c 是 string 对象中字符的引用,s 是常量。因此如果 for 循环中的代码重新给 c 赋值就会非法,如果不改变 c 的值,那么合法。

# Library vector Type C++ Primer, Fifth Edition


# vector

  • vector 是一个容器,也是一个类模板;
  • #include <vector> 然后 using std::vector;
  • 容器:包含其他对象。
  • 类模板:本身不是类,但可以实例化 instantiation 出一个类。 vector 是一个模板, vector<int> 是一个类型。
  • 通过将类型放在类模板名称后面的尖括号中来指定类型,如 vector<int> ivec

# 定义和初始化 vector 对象

初始化 vector 对象的方法

方法解释
vector<T> v1v1 是一个空 vector ,它潜在的元素是 T 类型的,执行默认初始化
vector<T> v2(v1)v2 中包含有 v1 所有元素的副本
vector<T> v2 = v1等价于 v2(v1)v2 中包含 v1 所有元素的副本
vector<T> v3(n, val)v3 包含了 n 个重复的元素,每个元素的值都是 val
vector<T> v4(n)v4 包含了 n 个重复地执行了值初始化的对象
vector<T> v5{a, b, c...}v5 包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v5={a, b, c...}等价于 v5{a, b, c...}
  • 列表初始化: vector<string> v{"a", "an", "the"}; (C++11)

# 向 vector 对象中添加元素

  • v.push_back(e) 在尾部增加元素。

# 其他 vector 操作

vector 支持的操作:

操作解释
v.emtpy()如果 v 不含有任何元素,返回真;否则返回假
v.size()返回 v 中元素的个数
v.push_back(t)v 的尾端添加一个值为 t 的元素
v[n]返回 v 中第 n 个位置上元素的引用
v1 = v2v2 中的元素拷贝替换 v1 中的元素
v1 = {a,b,c...}用列表中元素的拷贝替换 v1 中的元素
v1 == v2v1v2 相等当且仅当它们的元素数量相同且对应位置的元素值都相同
v1 != v2同上
< , <= , > , >=以字典顺序进行比较
  • 范围 for 语句内不应该改变其遍历序列的大小。
  • vector 对象(以及 string 对象)的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素。

# Exercise 3.12

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

vector<vector<int>> ivec;         // 在 C++11 当中合法
vector<string> svec = ivec;       // 不合法,类型不一样
vector<string> svec(10, "null");  // 合法

# Exercise 3.13

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

vector<int> v1;         // size:0,  no values.
vector<int> v2(10);     // size:10, value:0
vector<int> v3(10, 42); // size:10, value:42
vector<int> v4{ 10 };     // size:1,  value:10
vector<int> v5{ 10, 42 }; // size:2,  value:10, 42
vector<string> v6{ 10 };  // size:10, value:""
vector<string> v7{ 10, "hi" };  // size:10, value:"hi"

# Exercise 3.14

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

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
int main()
{
	vector<int> v;
	int i;
	while (cin >> i)
	{
		v.push_back(i);
	}
	return 0;
}

# Exercise 3.15

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

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main()
{
	vector<string> v;
	string i;
	while (cin >> i)
	{
		v.push_back(i);
	}
	return 0;
}

# Exercise 3.16

编写一段程序,把练习 3.13 中 vector 对象的容量和具体内容输出出来

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main()
{
	vector<int> v1;         // size:0,  no values.
	vector<int> v2(10);     // size:10, value:0
	vector<int> v3(10, 42); // size:10, value:42
	vector<int> v4{ 10 };     // size:1,  value:10
	vector<int> v5{ 10, 42 }; // size:2,  value:10, 42
	vector<string> v6{ 10 };  // size:10, value:""
	vector<string> v7{ 10, "hi" };  // size:10, value:"hi"
	cout << "v1 size :" << v1.size() << endl;
	cout << "v2 size :" << v2.size() << endl;
	cout << "v3 size :" << v3.size() << endl;
	cout << "v4 size :" << v4.size() << endl;
	cout << "v5 size :" << v5.size() << endl;
	cout << "v6 size :" << v6.size() << endl;
	cout << "v7 size :" << v7.size() << endl;
	cout << "v1 content: ";
	for (auto i : v1)
	{
		cout << i << " , ";
	}
	cout << endl;
	cout << "v2 content: ";
	for (auto i : v2)
	{
		cout << i << " , ";
	}
	cout << endl;
	cout << "v3 content: ";
	for (auto i : v3)
	{
		cout << i << " , ";
	}
	cout << endl;
	cout << "v4 content: ";
	for (auto i : v4)
	{
		cout << i << " , ";
	}
	cout << endl;
	cout << "v5 content: ";
	for (auto i : v5)
	{
		cout << i << " , ";
	}
	cout << endl;
	cout << "v6 content: ";
	for (auto i : v6)
	{
		cout << i << " , ";
	}
	cout << endl;
	cout << "v7 content: ";
	for (auto i : v7)
	{
		cout << i << " , ";
	}
	cout << endl;
	return 0;
}

# Exercise 3.17

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

解:

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

# Exercise 3.18

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

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

解:

不合法。应改为:

ivec.push_back(42);

# Exercise 3.19

如果想定义一个含有 10 个元素的 vector 对象,所有元素的值都是 42,请例举三种不同的实现方法,哪种方式更好呢?

如下三种:

vector<int> ivec1(10, 42);
vector<int> ivec2{ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 };
vector<int> ivec3;
for (int i = 0; i < 10; ++i)
	ivec3.push_back(42);

第一种方式最好。

# Exercise 3.20

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

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main()
{
	vector<int> ivec;
	int i;
	while (cin >> i)
	{
		ivec.push_back(i);
	}
	for (int i = 0; i < ivec.size() - 1; ++i)
	{
		cout << ivec[i] + ivec[i + 1] << endl;
	}
	
	//---------------------------------
	cout << "---------------------------------" << endl;
	
	int m = 0;
	int n = ivec.size() - 1;
	while (m < n)
	{
		cout << ivec[m] + ivec[n] << endl;
		++m;
		--n;
	}
	return 0;
}

# Introducing Iterators


# 迭代器 iterator

  • 所有标准库容器都可以使用迭代器。
  • 类似于指针类型,迭代器也提供了对对象的间接访问。

# 使用迭代器

  • vector<int>::iterator iter
  • auto b = v.begin(); 返回指向第一个元素的迭代器。
  • auto e = v.end(); 返回指向最后一个元素的下一个(哨兵,尾后,one past the end)的迭代器(off the end)。
  • 如果容器为空, begin()end() 返回的是同一个迭代器,都是尾后迭代器。
  • 使用解引用符 * 访问迭代器指向的元素。
  • 养成使用迭代器和 != 的习惯(泛型编程)。
  • 容器:可以包含其他对象;但所有的对象必须类型相同。
  • 迭代器(iterator):每种标准容器都有自己的迭代器。 C++ 倾向于用迭代器而不是下标遍历元素。
  • const_iterator:只能读取容器内元素不能改变。
  • 箭头运算符: 解引用 + 成员访问, it->mem 等价于 (*it).mem
  • 谨记:但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素

标准容器迭代器的运算符:

运算符解释
*iter返回迭代器 iter 所指向的元素的引用
iter->mem等价于 (*iter).mem
++iteriter 指示容器中的下一个元素
--iteriter 指示容器中的上一个元素
iter1 == iter2判断两个迭代器是否相等

# 迭代器运算

vectorstring 迭代器支持的运算:

运算符解释
iter + n迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。
iter - n迭代器减去一个证书仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。
iter1 += n迭代器加法的复合赋值语句,将 iter1 加 n 的结果赋给 iter1
iter1 -= n迭代器减法的复合赋值语句,将 iter2 减 n 的加过赋给 iter1
iter1 - iter2两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。
>>=<<=迭代器的关系运算符,如果某迭代器
  • difference_type:保证足够大以存储任何两个迭代器对象间的距离,可正可负。

# Exercise 3.21

请使用迭代器重做 3.3.3 节的第一个练习。

解:

#include <vector>
#include <iterator>
#include <string>
#include <iostream>
using std::vector;
using std::string;
using std::cout;
using std::endl;
void check_and_print(const vector<int>& vec)
{
	cout << "size: " << vec.size() << "  content: [";
	for (auto it = vec.begin(); it != vec.end(); ++it)
		cout << *it << (it != vec.end() - 1 ? "," : "");
	cout << "]\n" << endl;
}
void check_and_print(const vector<string>& vec)
{
	cout << "size: " << vec.size() << "  content: [";
	for (auto it = vec.begin(); it != vec.end(); ++it)
		cout << *it << (it != vec.end() - 1 ? "," : "");
	cout << "]\n" << endl;
}
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" };
	check_and_print(v1);
	check_and_print(v2);
	check_and_print(v3);
	check_and_print(v4);
	check_and_print(v5);
	check_and_print(v6);
	check_and_print(v7);
	return 0;
}

# Exercise 3.22

修改之前那个输出 text 第一段的程序,首先把 text 的第一段全部改成大写形式,然后输出它。

解: 略

# Exercise 3.23

编写一段程序,创建一个含有 10 个整数的 vector 对象,然后使用迭代器将所有元素的值都变成原来的两倍。输出 vector 对象的内容,检验程序是否正确。

解:

#include <iostream>
#include <vector>
using namespace std;
int main()
{
	vector<int> v(10, 1);
    for (auto it=v.begin(); it!=v.end(); it++){
        *it *= 2;
    }
    for (auto one : v){
        cout << one <<endl;
    }
	return 0;
}

# Exercise 3.24

请使用迭代器重做 3.3.3 节的最后一个练习。

解:

#include <iostream>
#include <string>
#include <cctype>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
int main()
{
	vector<int> ivec;
	int i;
	while (cin >> i)
	{
		ivec.push_back(i);
	}
	for (auto it = ivec.begin(); it != ivec.end() - 1; ++it)
	{
		cout << *it + *(it + 1) << endl;
	}
	//---------------------------------
	cout << "---------------------------------" << endl;
	auto it1 = ivec.begin();
	auto it2 = ivec.end() - 1;
	while (it1 < it2)
	{
		cout << *it1 + *it2 << endl;
		++it1;
		--it2;
	}
	return 0;
}

# Exercise 3.25

3.3.3 节划分分数段的程序是使用下标运算符实现的,请利用迭代器改写该程序实现完全相同的功能。

解:

#include <vector>
#include <iostream>
using std::vector; using std::cout; using std::cin; using std::endl;
int main()
{
	vector<unsigned> scores(11, 0);
	unsigned grade;
	while (cin >> grade)
	{
		if (grade <= 100)
			++*(scores.begin() + grade / 10);
	}
	for (auto s : scores)
		cout << s << " ";
	cout << endl;
	return 0;
}

# Exercise 3.26

在 100 页的二分搜索程序中,为什么用的是 mid = beg + (end - beg) / 2 , 而非 mid = (beg + end) / 2 ; ?

解:

因为两个迭代器相互之间支持的运算只有 - ,而没有 +
但是迭代器和迭代器差值(整数值)之间支持 +

# Arrays


# 数组

  • 相当于 vector 的低级版,长度固定

# 定义和初始化内置数组

  • 初始化: char input_buffer[buffer_size]; ,长度必须是 const 表达式,或者不写,让编译器自己推断。
  • 数组不允许直接赋值给另一个数组。

# 访问数组元素

  • 数组下标的类型: size_t
  • 字符数组的特殊性:结尾处有一个空字符,如 char a[] = "hello";
  • 用数组初始化 vectorint a[] = {1,2,3,4,5}; vector<int> v(begin(a), end(a));

# 数组和指针

  • 使用数组时,编译器一般会把它转换成指针。
  • 标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。
  • 指针访问数组:在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。

# C 风格字符串

  • 从 C 继承来的字符串。
  • 用空字符结束( \0 )。
  • 对大多数应用来说,使用标准库 string 比使用 C 风格字符串更安全、更高效。
  • 获取 string 中的 cstringconst char *str = s.c_str();

C 标准库 String 函数,定义在 <cstring> 中:

函数介绍
strlen(p)返回 p 的长度,空字符不计算在内
strcmp(p1, p2)比较 p1p2 的相等性。如果 p1==p2 ,返回 0;如果 p1>p2 ,返回一个正值;如果 p1<p2 ,返回一个负值。
strcat(p1, p2)p2 附加到 p1 之后,返回 p1
strcpy(p1, p2)p2 拷贝给 p1 ,返回 p1

尽量使用 vector 和迭代器,少用数组


# Exercise 3.26

在 100 页的二分搜索程序中,为什么用的是 mid = beg + (end - beg) / 2 , 而非 mid = (beg + end) / 2 ; ?

解:

因为两个迭代器相互之间支持的运算只有 - ,而没有 +
但是迭代器和迭代器差值(整数值)之间支持 +

# Exercise 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) 非法。维度必须是一个常量表达式。
  • (b) 合法。
  • (c) 非法。txt_size () 的值必须要到运行时才能得到。
  • (d) 非法。数组的大小应该是 12。

# Exercise 3.28

下列数组中元素的值是什么?

string sa[10];
int ia[10];
int main() {
	string sa2[10];
	int ia2[10];
}

解:

数组的元素会被默认初始化。
sa 的元素值全部为空字符串, ia 的元素值全部为 0。
sa2 的元素值全部为空字符串, ia2 的元素值全部未定义。

# Exercise 3.29

相比于 vector 来说,数组有哪些缺点,请例举一些。

解:

  • 数组的大小是确定的。
  • 不能随意增加元素。
  • 不允许拷贝和赋值。

# Exercise 3.30

指出下面代码中的索引错误。

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

解:

ix 增长到 10 的时候, ia[ix] 的下标越界。

# Exercise 3.31

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

#include <iostream>
using std::cout; using std::endl;
int main()
{
    int arr[10];
    for (auto i = 0; i < 10; ++i) arr[i] = i;
    for (auto i : arr) cout << i << " ";
    cout << endl;
    return 0;
}

# Exercise 3.32

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

#include <iostream>
#include <vector>
using std::cout; using std::endl; using std::vector;
int main()
{
    // array
    int arr[10];
    for (int i = 0; i < 10; ++i) arr[i] = i;
    int arr2[10];
    for (int i = 0; i < 10; ++i) arr2[i] = arr[i];
    // vector
    vector<int> v(10);
    for (int i = 0; i != 10; ++i) v[i] = arr[i];
    vector<int> v2(v);
    for (auto i : v2) cout << i << " ";
    cout << endl;
    return 0;
}

# Exercise 3.33

对于 104 页的程序来说,如果不初始化 scores 将会发生什么?

解:

数组中所有元素的值将会未定义。

# Exercise 3.34

假定 p1p2 都指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的?

p1 += p2 - p1;

解:

p1 移动到 p2 的位置。任何情况下都合法。

# Exercise 3.35

编写一段程序,利用指针将数组中的元素置为 0。

解:

#include <iostream>
using std::cout; using std::endl;
int main()
{
    const int size = 10;
    int arr[size];
    for (auto ptr = arr; ptr != arr + size; ++ptr) *ptr = 0;
    for (auto i : arr) cout << i << " ";
    cout << endl;
    return 0;
}

# Exercise 3.36

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

解:

#include <iostream>
#include <vector>
#include <iterator>
using std::begin; using std::end; using std::cout; using std::endl; using std::vector;
// pb point to begin of the array, pe point to end of the array.
bool compare(int* const pb1, int* const pe1, int* const pb2, int* const pe2)
{
    if ((pe1 - pb1) != (pe2 - pb2)) // have different size.
        return false;
    else
    {
        for (int* i = pb1, *j = pb2; (i != pe1) && (j != pe2); ++i, ++j)
            if (*i != *j) return false;
    }
    return true;
}
int main()
{
    int arr1[3] = { 0, 1, 2 };
    int arr2[3] = { 0, 2, 4 };
    if (compare(begin(arr1), end(arr1), begin(arr2), end(arr2)))
        cout << "The two arrays are equal." << endl;
    else
        cout << "The two arrays are not equal." << endl;
    cout << "==========" << endl;
    vector<int> vec1 = { 0, 1, 2 };
    vector<int> vec2 = { 0, 1, 2 };
    if (vec1 == vec2)
        cout << "The two vectors are equal." << endl;
    else
        cout << "The two vectors are not equal." << endl;
    return 0;
}

# Exercise 3.37

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

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

解:

会将 ca 字符数组中的元素打印出来。但是因为没有空字符的存在,程序不会退出循环。

# Exercise 3.38

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

解:

将两个指针相减可以表示两个指针 (在同一数组中) 相距的距离,将指针加上一个整数也可以表示移动这个指针到某一位置。但是两个指针相加并没有逻辑上的意义,因此两个指针不能相加。

# Exercise 3.39

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

解:

#include <iostream>
#include <string>
#include <cstring>
using std::cout; using std::endl; using std::string;
int main()
{
    // use string.
    string s1("Mooophy"), s2("Pezy");
    if (s1 == s2)
        cout << "same string." << endl;
    else if (s1 > s2)
        cout << "Mooophy > Pezy" << endl;
    else
        cout << "Mooophy < Pezy" << endl;
    cout << "=========" << endl;
    // use C-Style character strings.
    const char* cs1 = "Wangyue";
    const char* cs2 = "Pezy";
    auto result = strcmp(cs1, cs2);
    if (result == 0)
        cout << "same string." << endl;
    else if (result < 0)
        cout << "Wangyue < Pezy" << endl;
    else
        cout << "Wangyue > Pezy" << endl;
    return 0;
}

# Exercise 3.40

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

解:

#include <iostream>
#include <cstring>
const char cstr1[]="Hello";
const char cstr2[]="world!";
int main()
{
    constexpr size_t new_size = strlen(cstr1) + strlen(" ") + strlen(cstr2) +1;
    char cstr3[new_size];
    
    strcpy(cstr3, cstr1);
    strcat(cstr3, " ");
    strcat(cstr3, cstr2);
    
    std::cout << cstr3 << std::endl;
}

# Exercise 3.41

编写一段程序,用整型数组初始化一个 vector 对象。

#include <iostream>
#include <vector>
using std::vector; using std::cout; using std::endl; using std::begin; using std::end;
int main()
{
    int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    vector<int> v(begin(arr), end(arr));
    for (auto i : v) cout << i << " ";
    cout << endl;
    return 0;
}

# Exercise 3.42

编写一段程序,将含有整数元素的 vector 对象拷贝给一个整型数组。

解:

#include <iostream>
#include <vector>
using std::vector; using std::cout; using std::endl; using std::begin; using std::end;
int main()
{
    vector<int> v{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int arr[10];
    for (int i = 0; i != v.size(); ++i) arr[i] = v[i];
    for (auto i : arr) cout << i << " ";
    cout << endl;
    return 0;
}

# Multidimensional Arrays Chapter Summary


# 多维数组

  • 多维数组的初始化int ia[3][4] = {{0,1,2,3}, ...}
  • 使用范围 for 语句时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。

# 指针 vs 引用

  • 引用总是指向某个对象,定义引用时没有初始化是错的。
  • 给引用赋值,修改的是该引用所关联的对象的值,而不是让引用和另一个对象相关联。

# 指向指针的指针

  • 定义: int **ppi = &pi;
  • 解引用: **ppi

# 动态数组

  • 使用 newdelete 表达和 c 中 mallocfree 类似的功能,即在堆(自由存储区)中分配存储空间。
  • 定义: int *pia = new int[10]; 10 可以被一个变量替代。
  • 释放: delete [] pia; ,注意不要忘记 []

# Exercise 3.43

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

解:

#include <iostream>
using std::cout; using std::endl;
int main()
{
    int arr[3][4] = 
    { 
        { 0, 1, 2, 3 },
        { 4, 5, 6, 7 },
        { 8, 9, 10, 11 }
    };
    // range for
    for (const int(&row)[4] : arr)
        for (int col : row) cout << col << " ";
    cout << endl;
    // for loop
    for (size_t i = 0; i != 3; ++i)
        for (size_t j = 0; j != 4; ++j) cout << arr[i][j] << " ";
    cout << endl;
    // using pointers.
    for (int(*row)[4] = arr; row != arr + 3; ++row)
        for (int *col = *row; col != *row + 4; ++col) cout << *col << " ";
    cout << endl;
    return 0;
}

# Exercise 3.44

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

解:

#include <iostream>
using std::cout; using std::endl;
int main()
{
    int ia[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    // a range for to manage the iteration
    // use type alias
    using int_array = int[4];
    for (int_array& p : ia)
        for (int q : p)
            cout << q << " ";
    cout << endl;
    // ordinary for loop using subscripts
    for (size_t i = 0; i != 3; ++i)
        for (size_t j = 0; j != 4; ++j)
            cout << ia[i][j] << " ";
    cout << endl;
    // using pointers.
    // use type alias
    for (int_array* p = ia; p != ia + 3; ++p)
        for (int *q = *p; q != *p + 4; ++q)
            cout << *q << " ";
    cout << endl;
    return 0;
}

# Exercise 3.45

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

解:

#include <iostream>
using std::cout; using std::endl;
int main()
{
    int ia[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
    // a range for to manage the iteration
    for (auto& p : ia)
        for (int q : p)
            cout << q << " ";
    cout << endl;
    // ordinary for loop using subscripts
    for (size_t i = 0; i != 3; ++i)
        for (size_t 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;
    return 0;
}

# Chapter Summary

🍓:)