《C++ Primer》第3章 3.5节习题答案

第3章 字符串、向量和数组

3.5节 数组

练习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[d],a是数组的名字,d是数组的维度(容量)。对数组维度的要求有两个,一是维度表示数组中元素的个数,因此必须大于0;二是维度属于数组类型的一部分,因此在编译时应该是已知的,必须是一个常量表达式。

【解答】

(a)是非法的,buf_size是一个普通的无符号数,不是常量,不能作为数组的维度。

(b)是合法的,4*7-14=14是一个常量表达式。

(c)是非法的,txt_size()是一个普通的函数调用,没有被定义为constexpr,不能作为数组的维度。

(d)是非法的,当使用字符串初始化字符数组时,默认在尾部添加一个空字符'\0',算上这个符号该字符串共有12个字符,但是字符数组st的维度只有11,无法容纳题目中的字符串。

需要指出的是,在某些编译器环境中,上面的个别语句被判定为合法,这是所谓的编译器扩展。不过一般来说,建议读者避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上失效。

练习3.28:下列数组中元素的值是什么?

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

【出题思路】

本题旨在考查数组默认初始化的几种不同情况,如全局变量和局部变量的区别、内置类型和复合类型的区别。

【解答】

与练习2.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];
    cout << " array output:" << endl;
    for(auto c: sa)
        cout << "*  " << c << "  ";
    cout << endl;

    for(auto c: sa2)
        cout << "&  " << c << "  ";
    cout << endl;

    for(auto c: ia)
        cout << "#  " << c << "  ";
    cout << endl;

    for(auto c: ia2)
        cout << "@  " << c << "  ";
    cout << endl;

    return 0;
}

运行结果:

 

练习3.29:相比于vector来说,数组有哪些缺点,请列举一些。

【出题思路】

数组与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 <= arraay_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 < arraay_size; ++ix)
	ia[ix] = ix;

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

【出题思路】

通过循环为数组的元素赋值,通过范围for循环输出数组的全部元素。

【解答】

满足题意的程序如下所示:

#include <iostream>

using namespace std;

int main()
{
    const int sz = 10;
    int vInt[sz];
    //通过for循环为数组元素赋值
    for(int i = 0; i < 10; ++i)
    {
        vInt[i] = i;
    }
    //通过范转for循环输出数组的全部元素
    for(auto a: vInt)
    {
        cout << a << "   ";
    }

    cout << endl;

    return 0;
}

运行结果:

 

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

【出题思路】

如果想把数组的内容拷贝给另一个数组,不能直接对数组使用赋值运算符,而应该逐一拷贝数组的元素。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;

    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:对于104页的程序来说,如果不初始化scores将发生什么?

【出题思路】

本题旨在考查内置类型数组的初始化。

【解答】

该程序对scores执行了列表初始化,为所有元素赋初值为0,这样在后续统计时将会从0开始计算各个分数段的人数,是正确的做法。

如果不初始化scores,则该数组会含有未定义的数值,这是因为scores是定义在函数内部的整型数组,不会执行默认初始化。

练习3.34:假定p1和p2指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的?

p1 += p2 - p1;

【出题思路】

指针的算术运算与vector类似,也可以执行递增、递减、比较、与整数相加、两个指针相减等操作。

【解答】

如果p1和p2指向同一个数组中的元素,则该条语句令p1指向p2原来所指向的元素。从语法上来说,即使p1和p2指向的元素不属于同一个数组,但只要p1和p2的类型相同,该语句也是合法的。如果p1和p2的类型不同,则编译时报错。

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

【出题思路】

C++11新标准为数组引入了名为begin和end的两个函数,这两个函数与容器中的同名成员功能类似,利用begin和end可以方便地定位到数组的边界。令指针在数组的元素间移动,解引用指针即可得到当前所指的元素值。

【解答】

满足题意的程序如下所示:

#include <iostream>

using namespace std;

int main()
{
    const int sz = 10;  //常量sz作为数组的维度
    int a[sz];
    //通过for循环为数组元素赋值
    for(int 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:编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector对象是否相等。

【出题思路】

无论对比两个数组是否相等还是两个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循环为数组元素赋值
    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对象是否相等的程序如下所示,其中使用迭代器遍历vector对象的元素。

#include <iostream>
#include <ctime>
#include <cstdlib>
#include <vector>

using namespace std;

int main()
{
    const int sz = 5;  //常量sz作为数组的维度
    vector<int> a, b;
    int i;
    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();
    auto it2 = b.cbegin();
    while(it1 != a.cend() && it2 != b.cend())
    {
        if(it1 != it2)
        {
            cout << "您猜测错误,两个数组不相等" << endl;
            return -1;
        }
        ++it1;   //it1向后移动一位
        ++it2;   //it2向后移动一位
    }

    cout << "恭喜您全都猜对了!" << endl;

    return 0;
}

运行结果:

 

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

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

【出题思路】

考查C风格字符串和字符数组的关系,尤其是串尾是否含有空字符的问题。C风格字符串与标准库string对象既有联系又有区别。

【解答】

程序第一行声明了一个包含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:在本节中我们提到,将两个指针相加不但是非法的,而且也没什么意义。请问为什么两个指针相加没什么意义?

【出题思路】

与标准库vector类似,C++也为指针定义了一系列算术运算,包括递增、递减、指针求差、指针与整数求和等,但是并没有定义两个指针的求和运算。要想理解这一规定,必须首先明白指针的含义。

【解答】

指针也是一个对象,与指针相关的属性有3个,分别是指针本身的值(value)、指针所指的对象(content)以及指针本身在内存中的存储位置(address)。

它们的含义分别是:指针本身的值是一个内存地址值,表示指针所指对象在内存中的存储地址;指针所指的对象可以通过解引用指针访问;因为指针也是一个对象,所以指针也存储在内存的某个位置,它有自己的地址,这也是为什么有“指针的指针”的原因。

通过上述分析我们知道,指针的值是它所指对象的内存地址,如果我们把两个指针加在一起,就是试图把内存中两个对象的存储地址加在一起,这显然是没有任何意义的。与之相反,指针的减法是有意义的。如果两个指针指向同一个数组中的不同元素,则它们相减的结果表征了它们所指的元素在数组中的距离。

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

【出题思路】

由于标准库string类定义了关系运算符,所以比较两个string对象可以直接使用<、>、==等;比较两个C风格字符串则必须使用cstring头文件中定义的strcmp函数。

【解答】

比较两个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风格字符串的程序如下所示,其中的分支部分选用了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);
    cout << "result================" << result << endl;
    if(result > 0)
        cout << "第一个字符串大于第二个字符串" << endl;
    else if(result < 0)
        cout << "第一个字符串小于第二个字符串" << endl;
    else if(0 == result)
        cout << "两个字符串相等" << endl;
    else
        cout << "未定义的结果" << endl;

    return 0;
}

运行结果:

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

【出题思路】

C风格字符串的操作函数定义在cstring头文件中。其中,strcpy函数负责把字符串的内容拷贝给另一个字符串,strcat函数则负责把字符串的内容拼接到另一个字符串之后。此外,strlen函数用于计算字符串的长度。

需要注意的是,利用字符串字面值常量初始化C风格字符串时,默认在数组最后添加一个空字符,因此,strlen的计算结果比字面值显示的字符数量多1。为了细致起见,计算两个字符串拼接后的长字符串长度时,应该在两个字符串各自长度求和后减去1,即减去1个多余空字符所占的额外空间。

【解答】

满足题意的程序如下所示:

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    char str1[] = "Welcome to ";
    char str2[] = "C++ family!";
    //利用strlen函数计算两个字符串的长度,并求得结果字符串的长度
    char result[23];
    //char result[strlen(str1) + strlen(str2) - 1];//表达式的计算结果不是常量
    strcpy(result, str1);  //把第一个字符串拷贝到结果字符串中
    strcat(result, str2);  //把第二个字符串拼接到结果字符串中

    cout << "第一个字符串是: " << str1 << endl;
    cout << "第二个字符串是: " << str2 << endl;
    cout << "拼接后的字符串是: " << result << endl;

    return 0;
}

 运行结果:

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

【出题思路】

C++不允许用一个数组初始化另一个数组,也不允许使用vector对象直接初始化数组,但是允许使用数组来初始化vector对象。要实现这一目的,只需要指明要拷贝区域的首元素地址和尾后地址。

【解答】

满足题意的程序如下所示。使用随机数初始化数组,然后利用begin和end获得数组的范围。在用数组初始化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:编写一段程序,将含有整数元素的vector对象拷贝给一个整型数组。

【出题思路】

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;
}

运行结果:

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值