习题4.1
假设get_size是一个没有参数并返回int值的函数,下列哪些定义是非法的?为什么?
unsigned buf_size = 1024
(a) int ia[buf_size];
(b) int ia[get_size()];
(c) int ia[4*7-14];
(d) char st[11] = "fundamental" ;
【解答】
(a)非法,buf_size是一个变量,不能用于定义数组的维数(维长度)。
(b)非法,get_size()是函数调用,不是常量表达式,不能用于定义数组的维数(维长度)。
(d)非法,存放字符串"fundamental"的数组必须有12个元素,st只有11个元素。
习题4.2
下列数组的值是什么?
string sa[10];
int ia[10];
int main(){
string sa2[10];
int ia2[10];
}
【解答】
sa和sa2为元素类型为string的数组,自动调用string类的默认构造函数将各元素初始化为空字符串;ia为在函数体外定义的内置数组,各元素初始化为0;ia2为在函数体内定义的内置数组,各元素未初始化,其值不确定。
习题4.3
下列哪些定义是错误的?
(a) int ia[7] = {0, 1, 1, 2, 3, 5, 8};
(b) vector<int> ivec = {0, 1, 1, 2, 3, 5, 8};
(c) int ia2[] = ia;
(d) int ia3[] = ivec;
【解答】
(b)错误。vector对象不能用这种方式进行初始化。
(c)错误。不能用一个数组来初始化另一个数组。
(d)错误。不能用vector对象来初始化数组。
习题4.4
如何初始化数组的一部分或全部元素?
【解答】
定义数组时可使用初始化列表(用花括号括住的一组以逗号分隔的元素初值)来初始化数组的部分或全部元素。如果是初始化全部元素,可以省略定义数组时方括号中给出的数组维数值。如果指定了数组维数,则初始化列表提供的元素个数不能超过维数值。如果数组维数大于列出的元素初值个数,则只初始化前面的数组元素,剩下的其他元素,若是内置类型则初始化为0,若是类类型则调用该类的默认构造函数进行初始化。字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。
习题4.5
列出使用数组而不是vector的缺点。
【解答】
与vector类型相比,数组具有如下缺点:数组的长度是固定的,而且数组不提供获取其容量大小的size操作,也不提供自动添加元素的push_back操作。因此,程序员无法在程序运行时知道一个给定数组的长度,而且如果需要更改数组的长度,程序员只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组的存储空间中去。与使用vector类型的程序相比,使用内置数组的程序更容易出错且难以调试。
习题4.6
下面的程序段企图将下标值赋给数组的每个元素,其中在下标操作上有一些错误,请指出这些错误。
const size_t array_size = 10 ;
int ia[array_size];
for (size_t ix = 1; ix <= array_size; ++ix)
ia[ix] = ix ;
【解答】
该程序段的错误是:数组下标使用越界。
根据数组ia的定义,该数组的下标值应该是0~9(即array_size-1),而不是从1到array_size,因此其中的for语句出错,可更正如下:
for (size_t ix = 0; ix < array_size; ++ix)
ia[ix] = ix ;
习题4.7
编写必要的代码将一个数组赋给另一个数组,然后把这段代码改用vector实现。考虑如何将一个vector赋给另一个vector。
【解答】
将一个数组赋给另一个数组,就是将一个数组的元素逐个赋值给另一数组的对应元素,可用如下代码实现:
int main()
{
const size_t array_size = 10;
int ia1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int ia2[array_size];
for (size_t ix = 0; ix != array_size; ++ix)
ia2[ix] = ia1[ix];
return 0;
}
将一个vector赋给另一个vector,也是将一个vector的元素逐个赋值给另一vector的对应元素,可用如下代码实现:
//将一个vector赋值给另一vector
//使用迭代器访问vector中的元素
#include <vector>
using namespace std;
int main()
{
vector<int> ivec1(10, 20);//每个元素初始化为20
vector<int> ivec2;
for (vector<int>::iterator iter = ivec1.begin();
iter != ivec1.end(); ++iter)
ivec2.push_back(*iter);
return 0;
}
习题4.8
编写程序判断两个数组是否相等,然后编写一段类似的程序比较两个vector。
【解答】
判断两个数组是否相等,可用如下程序:
//判断两个数组是否相等
#include <iostream>
using namespace std;
int main()
{
const int arr_size = 6;
int ia1[arr_size], ia2[arr_size];
size_t ix;
//读入两个数组的元素值
cout << "Enter " << arr_size
<< " numbers for array1:" << endl;
for (ix = 0; ix != arr_size; ++ix)
cin >> ia1[ix];
cout << "Enter " << arr_size
<< " numbers for array2:" << endl;
for (ix = 0; ix != arr_size; ++ix)
cin >> ia2[ix];
//判断两个数组是否相等
for (ix = 0; ix != arr_size; ++ix)
if (ia1[ix] != ia2[ix]) {
cout << "Array1 is not equal to array2." << endl;
return 0;
}
cout << "Array1 is equal to array2." << endl;
return 0;
}
判断两个vector是否相等,可用如下程序:
//判断两个vector是否相等
//使用迭代器访问vector中的元素
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec1, ivec2;
int ival;
//读入两个vector的元素值
cout << "Enter numbers for vector1(-1 to end):" << endl;
cin >> ival;
while (ival != -1) {
ivec1.push_back(ival);
cin >> ival;
}
cout << "Enter numbers for vector2(-1 to end):" << endl;
cin >> ival;
while (ival != -1) {
ivec2.push_back(ival);
cin >> ival;
}
//判断两个vector是否相等
if (ivec1.size() != ivec2.size()) //长度不等的vector不相等
cout << "Vector1 is not equal to vector2." << endl;
else if (ivec1.size() == 0) //长度都为0的vector相等
cout << "Vector1 is equal to vector2." << endl;
else {//两个vector长度相等且不为0
vector<int>::iterator iter1, iter2;
iter1 = ivec1.begin();
iter2 = ivec2.begin();
while (*iter1 == *iter2 && iter1 != ivec1.end()
&& iter2 != ivec2.end()) {
++iter1;
++iter2;
}
if (iter1 == ivec1.end())//所有元素都相等
cout << "Vector1 is equal to vector2." << endl;
else
cout << "Vector1 is not equal to vector2." << endl;
}
return 0;
}
习题4.9
编写程序定义一个有10个int型元素的数组,并以元素在数组中的位置作为各元素的初值。
【解答】
//定义一个有10个int型元素的数组,
//并以元素在数组中的位置(1~10)作为各元素的初值
int main()
{
const int array_size = 10;
int ia[array_size];
for (size_t ix = 0; ix != array_size; ++ix)
ia[ix] = ix+1;
return 0;
}
习题4.10
下面提供了两种指针声明的形式,解释宁愿使用第一种形式的原因:
int *ip; // good practice
int* ip; // legal but misleading
【解答】
第一种形式强调了ip是一个指针,这种形式在阅读时不易引起误解,尤其是当一个语句中同时定义了多个变量时。
习题4.11
解释下列声明语句,并指出哪些是非法的,为什么?
(a) int* ip;
(b) string s, *sp = 0;
(c) int i; double* dp = &i;
(d) int* ip, ip2;
(e) const int i = 0, *p = i;
(f) string *p = NULL;
【解答】
(a)合法。定义了一个指向int型对象的指针ip。
(b)合法。定义了string对象s和指向string型对象的指针sp,sp初始化为0值。
(c)非法。dp为指向double型对象的指针,不能用int型对象i的地址进行初始化。
(d)合法。定义了int对象ip2和指向int型对象的指针ip。
(e)合法。定义了const int型对象i和指向const int型对象的指针p,i初始化为0,p初始化为0。
(f)合法。定义了指向string型对象的指针p,并将其初始化为0值。
习题4.12
已知一指针p,你可以确定该指针是否指向一个有效的对象吗?如果可以,如何确定?如果不可以,请说明原因。
【解答】
无法确定某指针是否指向一个有效对象。因为,在C++语言中,无法检测指针是否未被初始化,也无法区分一个地址是有效地址,还是由指针所分配的存储空间中存放的不确定值的二进制位形成的地址。
习题4.13
下列代码中,为什么第一个指针的初始化是合法的,而第二个则不合法?
int i = 42;
void *p = &i;
long *lp = &i;
【解答】
具有void*类型的指针可以保存任意类型对象的地址,因此p的初始化是合法的;而指向long型对象的指针不能用int型对象的地址来初始化,因此lp的初始化不合法。
习题4.14
编写代码修改指针的值;然后再编写代码修改指针所指对象的值。
【解答】
下列代码修改指针的值:
int *ip;
int ival1, ival2;
ip = &ival1;
ip = &ival2;
下列代码修改指针所指对象的值:
int ival = 0;
int *ip = &ival;
*ip = 8;
习题4.15
解释指针和引用的主要区别。
【解答】
使用引用(reference)和指针(pointer)都可间接访问另一个值,但它们之间存在两个重要区别:(1)引用总是指向某个确定对象(事实上,引用就是该对象的别名),定义引用时没有进行初始化会出现编译错误;(2) 赋值行为上存在差异:给引用赋值修改的是该引用所关联的对象的值,而不是使该引用与另一个对象关联。引用一经初始化,就始终指向同一个特定对象。给指针赋值修改的是指针对象本身,也就是使该指针指向另一对象,指针在不同时刻可指向不同的对象(只要保证类型匹配)。
习题4.16
下列程序段实现什么功能?
int i = 42, j = 1024;
int *p1 = &i, *p2 = &j;
*p2 = *p1 * * p2;
*p1 *= *p1;
【解答】
该程序段使得i被赋值为42的平方,j被赋值为42与1024的乘积。
习题4.17
已知p1和p2指向同一个数组中的元素,下面语句实现什么功能?
p1 += p2 ? p1;
当p1和p2具有什么值时这个语句是非法的?
【解答】
此语句使得p1也指向p2原来所指向的元素。原则上说,只要p1和p2的类型相同,则该语句始终是合法的。只有当p1和p2不是同类型指针时,该语句才不合法(不能进行-操作)。
但是,如果p1和p2不是指向同一个数组中的元素,则这个语句的执行结果可能是错误的。因为-操作的结果类型ptrdiff_t只能保证足以存放同一数组中两个指针之间的差距。如果p1和p2不是指向同一个数组中的元素,则-操作的结果有可能超出ptrdiff_t类型的表示范围而产生溢出,从而该语句的执行结果不能保证p1指向p2原来所指向的元素(甚至不能保证p1为有效指针)。
习题4.18
编写程序,使用指针把一个int型数组的所有元素设置为0。
【解答】
// 使用指针把一个int型数组的所有元素设置为0
int main()
{
const size_t arr_size = 8;
int int_arr[arr_size] = { 0, 1, 2, 3, 4, 5, 6, 7 };
// pbegin指向第一个元素,pend指向最后一个元素的下一内存位置
for (int *pbegin = int_arr, *pend = int_arr + arr_size;
pbegin != pend; ++pbegin)
*pbegin = 0; // 当前元素置0
return 0;
}
习题4.19
解释下列5个定义的含义,指出其中哪些定义是非法的:
(a) int i;
(b) const int ic;
(c) const int *pic;
(d) int *const cpi;
(e) const int *const cpic;
【解答】
(a) 合法:定义了int型对象i。
(b) 非法:定义const对象时必须进行初始化,但ic没有初始化。
(c) 合法:定义了指向int型const对象的指针pic。
(d) 非法:因为cpi被定义为指向int型对象的const指针,但该指针没有初始化。
(e) 非法:因为cpic被定义为指向int型const对象的const指针,但该指针没有初始化。
习题4.20
下列哪些初始化是合法的?为什么?
(a) int i = -1;
(b) const int ic = i ;
(c) const int *pic = ⁣
(d) int *const cpi = ⁣
(e) const int *const cpic = ⁣
【解答】
(a) 合法:定义了一个int型对象i,并用int型字面值-1对其进行初始化。
(b) 合法:定义了一个int型const对象ic,并用int型对象对其进行初始化。
(c) 合法:定义了一个指向int型const对象的指针pic,并用ic的地址对其进行初始化。
(d) 不合法:cpi是一个指向int型对象的const指针,不能用const int型对象ic的地址对其进行初始化。
(e) 合法:定义了一个指向int型const对象的const指针cpic,并用ic的地址对其进行初始化。
习题4.21
根据上述定义,下列哪些赋值运算是合法的?为什么?
(a) i = ic; (b) pic = ⁣
(c) cpi = pic; (d) pic = cpic;
(e) cpic = ⁣ (f) ic = *cpic;
【解答】
(a)、(b)、(d)合法。
(c)、(e)、(f)均不合法,因为cpi、cpic和ic都是const变量(常量),常量不能被赋值。
习题4.22
解释下列两个while循环的差别:
const char *cp = "hello";
int cnt;
while (cp) { ++cnt; ++cp; }
while (*cp) { ++cnt; ++cp; }
【解答】
两个while循环的差别为:前者的循环结束条件是cp为0值(即指针cp为0值);后者的循环结束条件是cp所指向的字符为0值(即cp所指向的字符为字符串结束符null(即’\0’))。因此后者能正确地计算出字符串"hello"中有效字符的数目(放在cnt中),而前者的执行是不确定的。
注意,题目中的代码还有一个小问题,即cnt没有初始化为0值。
习题4.23
下列程序实现什么功能?
const char ca[] = {’h’, ’e’, ’l’, ’l’, ’o’};
const char *cp = ca ;
while (*cp) {
cout << *cp << endl;
++cp;
}
【解答】
该程序段从数组ca的起始地址(即字符’h’的存储地址)开始,输出一段内存中存放的字符,每行输出一个字符,直至存放0值(null)的字节为止。(注意,输出的内容一般来说要多于5个字符,因为字符数组ca中没有null结束符。)
习题4.24
解释strcpy和strncpy的差别在哪里,各自的优缺点是什么?
【解答】
strcpy和strncpy的差别在于:前者复制整个指定的字符串,后者只复制指定字符串中指定数目的字符。
strcpy比较简单,而使用strncpy可以适当地控制复制字符的数目,因此比strcpy更为安全。
习题4.25
编写程序比较两个string类型的字符串,然后编写另一个程序比较两个C风格字符串的值。
【解答】
比较两个string类型的字符串的程序如下:
//比较两个string 类型的字符串
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1, str2;
//输入两个字符串
cout << "Enter two strings:" << endl;
cin >> str1 >> str2;
//比较两个字符串
if (str1 > str2)
cout << "\"" << str1 << "\"" << " is bigger than "
<< "\"" << str2 << "\"" << endl;
else if (str1 < str2)
cout << "\"" << str2 << "\"" << " is bigger than "
<< "\"" << str1 << "\"" << endl;
else
cout << "They are equal" << endl;
return 0;
}
比较两个C风格字符串的程序如下:
//比较两个C风格字符串的值
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
//char *str1 = "string1", *str2 = "string2";
const int str_size = 80;
char *str1, *str2;
//为两个字符串分配内存
str1 = new char[str_size];
str2 = new char[str_size];
if (str1 == NULL || str2 == NULL) {
cout << "No enough memory!" << endl;
return -1;
}
//输入两个字符串
cout << "Enter two strings:" << endl;
cin >> str1 >> str2;
//比较两个字符串
int result;
result = strcmp(str1, str2);
if (result > 0)
cout << "\"" << str1 << "\"" << " is bigger than "
<< "\"" << str2 << "\"" << endl;
else if (result < 0)
cout << "\"" << str2 << "\"" << " is bigger than "
<< "\"" << str1 << "\"" << endl;
else
cout << "They are equal" << endl;
//释放字符串所占用的内存
delete [] str1 ;
delete [] str2 ;
return 0;
}
注意,此程序中使用了内存的动态分配与释放(见4.3.1节)。如果不用内存的动态分配与释放,可将主函数中第2、3两行代码、有关内存分配与释放的代码以及输入字符串的代码注释掉,再将主函数中第一行代码
//char *str1 = "string1", *str2 = "string2";
前的双斜线去掉即可。
习题4.26
编写程序从标准输入设备读入一个string类型的字符串。考虑如何编程实现从标准输入设备读入一个C风格字符串。
【解答】
从标准输入设备读入一个string类型字符串的程序段:
string str;
cin >> str;
从标准输入设备读入一个C风格字符串可如下实现:
const int str_size = 80;
char str[str_size];
cin >> str;
习题4.27
假设有下面的new表达式,请问如何释放pa?
int *pa = new int[10];
【解答】
用语句delete [] pa;释放pa所指向的数组空间。
习题4.28
编写程序由从标准输入设备读入的元素数据建立一个int型vector对象,然后动态创建一个与该vector对象大小一致的数组,把vector对象的所有元素复制给新数组。
【解答】
// 从标准输入设备读入的元素数据建立一个int型vector对象,
// 然后动态创建一个与该vector对象大小一致的数组,
// 把vector对象的所有元素复制给新数组
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
int ival;
//读入元素数据并建立vector
cout << "Enter numbers:(Ctrl+Z to end)" << endl;
while (cin >> ival)
ivec.push_back(ival);
//动态创建数组
int *pia = new int[ivec.size()];
//复制元素
int *tp = pia;
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter, ++tp)
*tp = *iter;
//释放动态数组的内存
delete [] pia;
return 0;
}
习题4.29
对本节第5条框中的两段程序:
(a) 解释这两段程序实现的功能。
(b) 平均来说,使用string类型的程序执行速度要比用C风格字符串的快很多,在我们用了5年的PC机上其平均执行速度分别是:
user 0.47 # string class
user 2.55 # C-style character string
你预计的也一样吗?请说明原因。
【解答】
(a) 这两段程序的功能是:执行一个循环次数为1000000的循环,在该循环的循环体中:创建一个新字符串,将一个已存在的字符串复制给新字符串,然后比较两个字符串,最后释放新字符串。
(b) 使用C风格字符串的程序需要自己管理内存的分配和释放,而使用string类型的程序由系统自动进行内存的分配和释放,因此比使用C风格字符串的程序要简短,执行速度也要快一些。
习题4.30
编写程序连接两个C风格字符串字面值,把结果存储在一个C风格字符串中。然后再编写程序连接两个string类型字符串,这两个string类型字符串与前面的C风格字符串字面值具有相同的内容。
【解答】
连接两个C风格字符串字面值的程序如下:
// 连接两个C风格字符串字面值,
// 把结果存储在一个C风格字符串中
#include <cstring>
int main()
{
const char *cp1 = "Mary and Linda ";
const char *cp2 = "are firends.";
size_t len = strlen(cp1) + strlen(cp2);
char *result_str = new char[len+1];
strcpy(result_str, cp1);
strcat(result_str, cp2);
delete [] result_str;
return 0;
}
相应的连接两个string类型字符串的程序如下:
// 连接两个string类型字符串
#include <string>
using namespace std;
int main()
{
const string str1("Mary and Linda ");
const string str2("are firends.");
string result_str;
result_str = str1;
result_str += str2;
return 0;
}
习题4.31
编写程序从标准输入设备读入字符串,并把该串存放在字符数组中。描述你的程序如何处理可变长的输入。提供比你分配的数组长度长的字符串数据测试你的程序。
【解答】
// 从标准输入设备读入字符串,并把该串存放在字符数组中
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int main()
{
string in_str;// 用于读入字符串的string对象
const size_t str_size = 10;
char result_str[str_size+1];
// 读入字符串
cout << "Enter a string(<=" << str_size
<< " characters):" << endl;
cin >> in_str;
// 计算需复制的字符的数目
size_t len = strlen(in_str.c_str());
if (len > str_size) {
len = str_size;
cout << "String is longer than " << str_size
<< " characters and is stored only "
<< str_size << " characters!" << endl;
}
// 复制len个字符至字符数组result_str
strncpy(result_str, in_str.c_str(), len);
// 在末尾加上一个空字符(null字符)
result_str[len+1] = ’\0’;
return 0;
}
为了接受可变长的输入,程序中用一个string对象存放读入的字符串,然后使用strncpy函数将该对象的适当内容复制到字符数组中。因为字符数组的长度是固定的,因此首先计算字符串的长度。若该长度小于或等于字符数组可容纳字符串的长度,则复制整个字符串至字符数组,否则,根据数组的长度,复制字符串中前面部分的字符,以防止溢出。
注意,上述给出的是满足题目要求的一个解答,事实上,如果希望接受可变长的输入并完整地存放到字符数组中,可以采用动态创建数组来实现。
习题4.32
编写程序用int型数组初始化vector对象。
【解答】
// 用int型数组初始化vector对象
#include <iostream>
#include <vector>
using namespace std;
int main()
{
const size_t arr_size = 8;
int int_arr[arr_size];
// 输入数组元素
cout << "Enter " << arr_size << " numbers:" << endl;
for (size_t ix = 0; ix != arr_size; ++ix)
cin >> int_arr[ix];
// 用int型数组初始化vector对象
vector<int> ivec(int_arr, int_arr + arr_size);
return 0;
}
习题4.33
编写程序把int型vector复制给int型数组。
【解答】
// 把int型vector复制给int型数组
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
int ival;
// 输入vector元素
cout << "Enter numbers: (Ctrl+Z to end)" << endl;
while (cin >> ival)
ivec.push_back(ival);
// 创建数组
int *parr = new int[ivec.size()];
// 复制元素
size_t ix = 0;
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter, ++ix)
parr[ix] = *iter;
// 释放数组
delete [] parr;
return 0;
}
习题4.34
编写程序读入一组string类型的数据,并将它们存储在vector中。接着,把该vector对象复制给一个字符指针数组。为vector中的每个元素创建一个新的字符数组,并把该vector 元素的数据复制到相应的字符数组中,最后把指向该数组的指针插入字符指针数组。
【解答】
//4-34.cpp
//读入一组string类型的数据,并将它们存储在vector中。
//接着,把该vector对象复制给一个字符指针数组。
//为vector中的每个元素创建一个新的字符数组,
//并把该vector元素的数据复制到相应的字符数组中,
//最后把指向该数组的指针插入字符指针数组
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> svec;
string str;
// 输入vector元素
cout << "Enter strings:(Ctrl+Z to end)" << endl;
while (cin >> str)
svec.push_back(str);
// 创建字符指针数组
char **parr = new char*[svec.size()];
// 处理vector元素
size_t ix = 0;
for (vector<string>::iterator iter = svec.begin();
iter != svec.end(); ++iter, ++ix) {
// 创建字符数组
char *p = new char[(*iter).size()+1];
// 复制vector元素的数据到字符数组
strcpy(p, (*iter).c_str());
// 将指向该字符数组的指针插入到字符指针数组
parr[ix] = p;
}
// 释放各个字符数组
for (ix =0; ix != svec.size(); ++ix)
delete [] parr[ix];
// 释放字符指针数组
delete [] parr;
return 0;
}
习题4.35
输出习题4.34中建立的vector对象和数组的内容。输出数组后,记得释放字符数组。
【解答】
//4-35.cpp
//读入一组string类型的数据,并将它们存储在vector中。
//接着,把该vector对象复制给一个字符指针数组:
//为vector中的每个元素创建一个新的字符数组,
//并把该vector元素的数据复制到相应的字符数组中,
//然后把指向该数组的指针插入字符指针数组。
//输出建立的vector对象和数组的内容
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<string> svec;
string str;
// 输入vector元素
cout << "Enter strings:(Ctrl+Z to end)" << endl;
while (cin >> str)
svec.push_back(str);
// 创建字符指针数组
char **parr = new char*[svec.size()];
// 处理vector元素
size_t ix = 0;
for (vector<string>::iterator iter = svec.begin();
iter != svec.end(); ++iter, ++ix) {
// 创建字符数组
char *p = new char[(*iter).size()+1];
// 复制vector元素的数据到字符数组
strcpy(p, (*iter).c_str());
// 将指向该字符数组的指针插入到字符指针数组
parr[ix] = p;
}
// 输出vector对象的内容
cout << "Content of vector:" << endl;
for (vector<string>::iterator iter2 = svec.begin();
iter2 != svec.end(); ++iter2)
cout << *iter2 << endl;
// 输出字符数组的内容
cout << "Content of character arrays:" << endl;
for (ix =0; ix != svec.size(); ++ix)
cout << parr[ix] << endl;
// 释放各个字符数组
for (ix =0; ix != svec.size(); ++ix)
delete [] parr[ix];
// 释放字符指针数组
delete [] parr;
return 0;
}
习题4.36
重写程序输出ia数组的内容,要求在外层循环中不能使用typedef定义的类型。
【解答】
//4-36.cpp
//重写程序输出ia数组的内容
//在外层循环中不使用typedef定义的类型
#include <iostream>
using namespace std;
int main()
{
int ia[3][4] = { // 3个元素,每个元素是一个有4个int元素的数组
{0, 1, 2, 3} , // 0行的初始化列表
{4, 5, 6, 7} , // 1行的初始化列表
{8, 9, 10, 11} // 2行的初始化列表
};
int (*p)[4];
for (p = ia; p != ia + 3; ++p)
for (int *q = *p; q != *p + 4; ++q)
cout << *q << endl;
return 0;
}
习题5.1
在下列表达式中,加入适当的圆括号以标明其计算顺序。编译该表达式并输出其值,从而检查你的回答是否正确。
12 / 3 * 4 + 5 * 15 + 24 % 4 / 2
【解答】
加入如下所示的圆括号以标明该表达式的计算顺序:
(((12 / 3) * 4) + (5 * 15)) + ((24 % 4) / 2)
习题5.2
计算下列表达式的值,并指出哪些结果值依赖于机器?
-30 * 3 + 21 / 5
-30 + 3 * 21 / 5
30 / 3 * 21 % 5
-30 / 3 * 21 % 4
【解答】
各表达式的值分别为-86、-18、0、-2。其中,最后一个表达式的结果值依赖于机器,因为该表达式中除操作只有一个操作数为负数。
习题5.3
编写一个表达式判断一个int型数值是偶数还是奇数。
【解答】
如下表达式可以判断一个int型数值(假设为ival)是偶数还是奇数:
ival % 2 == 0
若ival是偶数,则该表达式的值为真(true),否则为假(false)。
习题5.4
定义术语“溢出”的含义,并给出导致溢出的三个表达式。
【解答】
溢出:表达式的求值结果超出了其类型的表示范围。
如下表达式会导致溢出(假设int类型为16位):
1000 * 1000
32766 + 5
3276 * 20
在这些表达式中,各操作数均为int类型,因此这些表达式的类型也是int,但它们的计算结果均超出了16位int型的表示范围(-32768~32767),导致溢出。
习题5.5
解释逻辑与操作符、逻辑或操作符以及相等操作符的操作数在什么时候计算。
【解答】
逻辑与、逻辑或操作符采用称为“短路求值”(short-circuit evaluation)的求值策略,即先计算左操作数,再计算右操作数,且只有当仅靠左操作数的值无法确定该逻辑运算的结果时,才会计算右操作数。
相等操作符的左右操作数均需进行计算。
习题5.6
解释下列while循环条件的行为:
char *cp = "Hello World" ;
while ( cp && *cp )
【解答】
该while循环的条件为:当指针cp为非空指针并且cp所指向的字符不为空字符null(’\0’)时执行循环体。即该循环可以对字符串"Hello World"中的字符进行逐个处理。
习题5.7
编写while循环条件从标准输入设备读入整型(int)数据,当读入值为42时循环结束。
【解答】
int val;
cin >> val;
while (val != 42)
或者,while循环条件也可以写成
while (cin >> ival && ival != 42)
习题5.8
编写表达式判断4个值a、b、c和d是否满足a大于b、b大于c而且c大于d的条件。
【解答】
表达式如下:
a > b && b > c && c > d
习题5.9
假设有下面两个定义:
unsigned long ul1 =3, ul2 = 7;
下列表达式的结果是什么?
(a) ul1 & ul2 (b) ul1 && ul2
(c) ul1 | ul2 (d) ul1 || ul2
【解答】
各表达式的结果分别为3、true、7、true。
习题5.10
重写bitset表达式:使用下标操作符对测验结果进行置位(置1)和复位(置0)。
【解答】
bitset<30> bitset_quiz1;
bitset_quiz1[27] = 1;
bitset_quiz1[27] = 0;
习题5.11
请问每次赋值操作完成后,i和d的值分别是多少?
int i; double d;
d = i = 3.5;
i = d = 3.5;
【解答】
赋值语句d=i=3.5;完成后,i和d的值均为3。因为赋值操作具有右结合性,所以首先将3.5赋给i(此时发生隐式类型转换,将double型字面值3.5转换为int型值3,赋给i),然后将表达式i=3.5的值(即赋值后i所具有的值3)赋给d。
赋值语句i=d=3.5;完成后,d的值为3.5,i的值为3。因为先将字面值3.5赋给d,然后将表达式d=3.5的值(即赋值后d所具有的值3.5)赋给i(这时也同样发生隐式类型转换)。
习题5.12
解释每个if条件判断产生什么结果?
if ( 42 = i ) // ...
if ( i = 42 ) // ...
【解答】
前者发生语法错误,因为其条件表达式42=i是一个赋值表达式,赋值操作符的左操作数必须为一个左值,而字面值42不能作为左值使用。
后者代码合法,但其条件表达式i=42是一个永真式(即其逻辑值在任何情况下都为true),因为该赋值表达式的值为赋值操作完成后的i值(42),而42为非零值,解释为逻辑值true。
习题5.13
下列赋值操作是不合法的,为什么?怎样改正?
double dval; int ival; int *pi;
dval = ival = pi = 0;
【解答】
该赋值语句不合法,因为该语句首先将0值赋给pi,然后将pi的值赋给ival,再将ival的值赋给dval。pi、ival和dval的类型各不相同,因此要完成赋值必须进行隐式类型转换,但系统无法将int型指针pi的值隐式转换为ival所需的int型值。
可改正如下:
double dval; int ival; int *pi;
dval = ival = 0;
pi = 0;
习题5.14
虽然下列表达式都是合法的,但并不是程序员期望的操作,为什么?怎样修改这些表达式以使其能反映程序员的意图?
(a) if ( ptr = retrieve_pointer() != 0 )
(b) if ( ival = 1024 )
(c) ival += ival + 1;
【解答】
对于表达式(a),程序员的意图应该是将retrieve_pointer()的值赋给ptr,然后判断ptr的值是否为0,但因为操作符“=”的优先级比“!=”低,所以该表达式实际上是将retrieve_pointer()是否为0的判断结果true或false赋给ptr,因此不是程序员期望的操作。
对于表达式(b),程序员的意图应该是判断ival的值是否与1024相等,但误用了赋值操作符。
对于表达式(c),程序员的意图应该是使ival的值增加1,但误用了操作符“+=”。
各表达式可修改如下:
(a) if ( (ptr = retrieve_pointer()) != 0 )
(b) if ( ival == 1024 )
(c) ival += 1; 或 ival++; 或 ++ival;
习题5.15
解释前自增操作和后自增操作的差别。
【解答】
前自增操作和后自增操作都使其操作数加1,二者的差别在于:前自增操作将修改后操作数的值作为表达式的结果值;而后自增操作将操作数原来的、未修改的值作为表达式的结果值。
习题5.16
你认为为什么C++不叫作++C?
【解答】
C++之名是Rick Mascitti在1983年夏天定名的(参见The C++ Programming Language(Special Edition) 1.4节),C说明它本质上是从C语言演化而来的,“++”是C语言的自增操作符。C++语言是C语言的超集,是在C语言基础上进行的扩展(引入了new、delete等C语言中没有的操作符,增加了对面向对象程序设计的直接支持,等等),是先有C语言,再进行++。根据自增操作符前、后置形式的差别(参见习题5.15的解答),C++表示对C语言进行扩展之后,还可以使用C语言的内容;而写成++C则表示无法再使用C的原始值了,也就是说C++不能向下兼容C了,这与实际情况不符。
习题5.17
如果输出vector内容的while循环使用前自增操作符,那会怎么样?
【解答】
将导致错误的结果:ivec的第一个元素没有输出,并企图对一个多余的元素进行解引用。
习题5.18
编写程序定义一个vector对象,其每个元素都是指向string类型的指针,读取该vector对象,输出每个string的内容及其相应的长度。
【解答】
//定义一个vector对象,其每个元素都是指向string类型的指针,
//读取该vector对象,输出每个string的内容及其相应的长度
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
vector<string*> spvec;
//读取vector对象
string str;
cout << "Enter some strings(Ctrl+Z to end)" << endl;
while (cin >> str) {
string *pstr = new string; //指向string对象的指针
*pstr = str;
spvec.push_back(pstr);
}
//输出每个string的内容及其相应的长度
vector<string*>::iterator iter = spvec.begin();
while (iter != spvec.end()) {
cout << **iter << (**iter).size() << endl;
iter++;
}
//释放各个动态分配的string对象
iter = spvec.begin();
while (iter != spvec.end()) {
delete *iter;
iter++;
}
return 0;
}
习题5.19
假设iter为vector<string>::iterator类型的变量,指出下面哪些表达式是合法的,并解释这些合法表达式的行为。
(a) *iter++; (b) (*iter)++;
(c) *iter.empty(); (d) iter->empty();
(e) ++*iter; (f) iter++->empty();
【解答】
(a)、(d)、(f)合法。
这些表达式的执行结果如下:
(a)返回iter所指向的string对象,并使iter加1。
(d)调用iter所指向的string对象的成员函数empty。
(f)调用iter所指向的string对象的成员函数empty,并使iter加1。
习题5.20
编写程序提示用户输入两个数,然后报告哪个数比较小。
【解答】
可编写程序如下:
//提示用户输入两个数,然后报告哪个数比较小
#include <iostream>
using namespace std;
int main()
{
int val1, val2;
//提示用户输入两个数并接受输入
cout << "Enter two integers:" << endl;
cin >> val1 >> val2;
//报告哪个数比较小
cout << "The smaller one is"
<< (val1 < val2 ? val1 : val2) << endl;
return 0;
}
习题5.21
编写程序处理vector<int>对象的元素:将每个奇数值元素用该值的两倍替换。
【解答】
//处理vector<int>对象的元素:
//将每个奇数值元素用该值的两倍替换
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec(20,1);//ivec包含20个值为1的元素
//将每个奇数值元素用该值的两倍替换
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter)
*iter = (*iter % 2 == 0 ? *iter : *iter * 2);
return 0;
}
习题5.22
编写程序输出每种内置类型的长度。
【解答】
//输出每种内置类型的长度
#include <iostream>
using namespace std;
int main()
{
cout << "type\t\t\t" << "size" << endl
<< "bool\t\t\t" << sizeof(bool) << endl
<< "char\t\t\t" << sizeof(char) << endl
<< "signed char\t\t" << sizeof(signed char) << endl
<< "unsigned char\t\t" << sizeof(unsigned char) << endl
<< "wchar_t\t\t\t" << sizeof(wchar_t) << endl
<< "short\t\t\t" << sizeof(short) << endl
<< "signed short\t\t" << sizeof(signed short) << endl
<< "unsigned short\t\t" << sizeof(unsigned short) << endl
<< "int\t\t\t" << sizeof(int) << endl
<< "signed int\t\t" << sizeof(signed int) << endl
<< "unsigend int\t\t" << sizeof(unsigned int) << endl
<< "long\t\t\t" << sizeof(long) << endl
<< "sigend long\t\t" << sizeof(signed long) << endl
<< "unsigned long\t\t" << sizeof(unsigned long) << endl
<< "float\t\t\t" << sizeof(float) << endl
<< "double\t\t\t" << sizeof(double) << endl
<< "long double\t\t" << sizeof(long double) << endl;
return 0;
}
习题5.23
预测下列程序的输出,并解释你的理由。然后运行该程序,输出的结果和你预测的一样吗?如果不一样,为什么?
int x[10]; int *p = x;
cout << sizeof(x)/sizeof(*x) << endl;
cout << sizeof(p)/sizeof(*p) << endl;
【解答】
在表达式sizeof(x)中,x是数组名,该表达式的结果为数组x所占据的存储空间的字节数,为10个int型元素所占据的字节数。
表达式sizeof(*x)的结果是指针常量x所指向的对象(数组中第一个int型元素)所占据的存储空间的字节数。
表达式sizeof(p)的结果是指针变量p所占据的存储空间的字节数。
表达式sizeof(*p)的结果是指针变量p所指向的对象(一个int型数据)所占据的存储空间的字节数。
各种数据类型在不同的系统中所占据的字节数不一定相同,因此在不同的系统中运行上述程序段得到的结果不一定相同。在Microsoft Visual C++ .NET 2003系统中,一个int型数据占据4个字节,一个指针型数据也占据4个字节,因此运行上述程序得到的输出结果为:
10
1
习题5.24
本节的程序与5.5节在vector对象中添加元素的程序类似。两段程序都使用递减的计数器生成元素的值。本程序中,我们使用了前自减操作,而5.5节的程序则使用了后自减操作。解释为什么一段程序中使用前自减操作而在另一段程序中使用后自减操作。
【解答】
5.5节的程序中必须使用后自减操作。如果使用前自减操作,则是用减1后的cnt值创建ivec的新元素,添加到ivec中的元素将不是10~1,而是9~0。
本节的程序中使用后自减操作或前自减操作均可,因为对cnt的自减操作和对cnt值的使用不是出现在同一表达式中,cnt自减操作的前置或后置形式不影响对cnt值的使用。
习题5.25
根据表5-4的内容,在下列表达式中添加圆括号说明其操作数分组的顺序(即计算顺序):
(a) ! ptr == ptr->next
(b) ch = buf[ bp++ ] != ’\n’
【解答】
添加圆括号说明其计算顺序如下:
(a) ((! ptr) == (ptr->next))
(b) (ch = ((buf[ (bp++) ]) != ’\n’))
习题5.26
习题5.25中的表达式的计算次序与你的意图不同,给它们加上圆括号使其以你所希望的操作次序求解。
【解答】
添加圆括号获得与上题不同的操作次序如下:
(a) ! (ptr == ptr->next)
(b) (ch = buf[ bp++ ]) != ’\n’
习题5.27
由于操作符优先级的问题,下列表达式编译失败。请参照表5-4解释原因,应该如何改正?
string s = "word";
// add an ’s’ to the end, if the word doesn’t already end in ’s’
string pl = s + s[s.size() - 1] == ’s’ ? "" : "s" ;
【解答】
由表5-4可知,在语句string pl = s + s[s.size() - 1] == ’s’ ? "" : "s" ;中,赋值、加法、条件操作符三者的操作次序为:先执行“+”操作,再用表达式s + s[s.size() - 1]的结果参与条件操作,最后将条件操作的结果赋给pl。但表达式s + s[s.size() - 1]的结果是一个string对象,不能与字符’s’进行相等比较,所以编译失败。
改正为:string pl = s + (s[s.size() - 1] == ’s’ ? "" : "s") ;。
习题5.28
除了逻辑与和逻辑或外,C++没有明确定义二元操作符的求解次序,编译器可自由地提供最佳的实现方式。只能在“实现效率”和程序语言使用中“潜在的缺陷”之间寻求平衡。你认为这可以接受吗?说出你的理由。
【解答】
这可以接受。
因为,操作数的求解次序通常对结果没什么影响。只有当二元操作符的两个操作数涉及同一对象,并改变该对象的值时,操作数的求解次序才会影响计算结果;后一种情况只会在部分(甚至是少数)程序中出现。在实际使用中,这种“潜在的缺陷”可以通过程序员的努力得到弥补,但“实现效率”的提高却能使所有使用该编译器的程序受益,因此利大于弊。
习题5.29
假设ptr指向类类型对象,该类拥有一个名为ival的int型数据成员,vec是保存int型元素的vector对象,而ival、jval和kval都是int型变量。请解释下列表达式的行为,并指出哪些(如果有的话)可能是不正确的,为什么?如何改正?
(a) ptr->ival != 0 (b) ival != jval < kval
(c) ptr != 0 && *ptr++ (d) ival++ && ival
(e) vec[ival++] <= vec[ival]
【解答】
表达式的行为如下:
(a) 判断ptr所指向的对象的ival成员是否不等于0。
(b) 判断ival是否不等于“jval是否小于kval”的判断结果,即判断ival是否不等于true(1)或false(0)。
(c) 判断ptr是否不等于0。如果ptr不等于0,则求解&&操作的右操作数,即,ptr加1,且判断ptr原来所指向的对象是否为0。
(d) 判断ival及ival+1是否为true(非0值)(注意,如果ival为false,则无需继续判断ival+1)。
(e) 判断vec[ival]是否小于或等于vec[ival+1]。
其中,(d)和(e)可能不正确,因为二元操作符的两个操作数涉及同一对象,并改变该对象的值。
可改正如下:
(d) ival && ival + 1
(e) vec[ival] <= vec[ival + 1]
习题5.30
下列语句哪些(如果有的话)是非法的或错误的?
(a) vector<string> svec(10);
(b) vector<string> *pvec1 = new vector<string>(10);
(c) vector<string> **pvec2 = new vector<string>[10];
(d) vector<string> *pv1 = &svec;
(e) vector<string> *pv2 = pvec1;
(f) delete svec;
(g) delete pvec1;
(h) delete [] pvec2;
(i) delete pv1;
(j) delete pv2;
【解答】
错误的有(c)和(f)。
(c)的错误在于:pvec2是指向元素类型为string的vector对象的指针的指针(即pvec2的类型为vector<string> **),而new操作返回的是一个指向元素类型为string的vector对象的指针,不能用于初始化pvec2。
(f)的错误在于:svec是一个vector对象,不是指针,不能对它进行delete操作。
习题5.31
根据5.12.2节的变量定义,解释在计算下列表达式的过程中发生了什么类型转换?
(a) if (fval)
(b) dval = fval + ival;
(c) dval + ival + cval;
记住,你可能需要考虑操作符的结合性,以便在表达式含有多个操作符的情况下确定答案。
【解答】
(a) 将fval的值从float类型转换为bool类型。
(b) 将ival的值从int类型转换为float类型,再将fval + ival的结果值转换为double类型,赋给dval。
(c) 将ival的值从int类型转换为double类型,cval的值首先提升为int类型,然后从int型转换为double型,与dval + ival的结果值相加。
习题5.32
给定下列定义:
char cval; int ival; unsigned int ui;
float fval; double dval;
指出可能发生的(如果有的话)隐式类型转换:
(a) cval = ’a’ + 3; (b) fval = ui ? ival * 1.0;
(c) dval = ui * fval; (d) cval = ival + fval + dval;
【解答】
(a) ’a’首先提升为int类型,再将’a’ + 3的结果值转换为char型,赋给cval。
(b) ival转换为double型与1.0相乘,ui转换为double型再减去ival * 1.0的结果值,减操作的结果转换为float型,赋给fval。
(c) ui转换为float型与fval相乘,结果转换为double型,赋给dval。
(d) ival转换为float型与fval相加,结果转换为double型,再与dval相加,结果转换为char型,赋给cval。
习题5.33
给定下列定义:
int ival; double dval;
const string *ps; char *pc; void *pv;
用命名的强制类型转换符号重写下列语句:
(a) pv = (void*)ps; (b) ival = int(*pc);
(c) pv = &dval; (d) pc = (char*) pv;
【解答】
(a) pv = static_cast<void*> (const_cast<string*> (ps));
(b) ival = static_cast<int> (*pc);
(c) pv = static_cast<void*> (&dval);
(d) pc = static_cast<char*> (pv);