侧边栏壁纸
  • 累计撰写 47 篇文章
  • 累计创建 0 个标签
  • 累计收到 39 条评论

目 录CONTENT

文章目录

第 3 章

3.1

1.9

#include <iostream>

using std::cout;
using std::endl;
// using std::cout, std::endl;  // c++17 增加的特性

int main() {
    int sum = 0, val = 50;
    while (val <= 100) {
        sum += val;
        ++val;
    }
    cout << "Sum of 50 to 100 inclusive is "
              << sum << endl;

    return 0;
}
// 运行结果
Sum of 50 to 100 inclusive is 3825

Process finished with exit code 0

1.10

#include <iostream>

using std::cout;
using std::endl;

int main() {
    int val = 10;
    while (val >= 0) {
        cout << val << " ";
        --val;
    }
    cout << endl;

    return 0;
}
// 运行结果
10 9 8 7 6 5 4 3 2 1 0 

Process finished with exit code 0

1.11

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

int main() {
    int val1, val2, temp;
    cout << "Please input two integers";
    cin >> val1 >> val2;
    if (val1 > val2) {
        temp = val1;
        val1 = val2;
        val2 = temp;
    }
    ++val1;
    while (val1 < val2) {
        cout << val1 << " ";
        ++val1;
    }
    cout << endl;

    return 0;
}
// 运行结果
Please input two integers23 45
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 

Process finished with exit code 0

3.1

Sales_data.h

#ifndef SALESDATA_H
// we're here only if SALESDATA_H has not yet been defined
#define SALESDATA_H

// Definition of Sales_data class and related functions goes here
#include <iostream>
#include <string>

// 头文件不应包含 using 声明
// 因为头文件可能包含在多个文件中,从而引发命名冲突
// using namespace std;

class Sales_data {
    // 友元函数
    friend std::istream &operator>>(std::istream &, Sales_data &);

    // 友元函数
    friend std::ostream &operator<<(std::ostream &, const Sales_data &);

    // 友元函数
    friend bool operator<(const Sales_data &, const Sales_data &);

    // 友元函数
    friend bool operator==(const Sales_data &, const Sales_data &);

public:     // 构造函数的 3 种形式
    Sales_data() = default;

    Sales_data(const std::string &book) : bookNo(book) {}

    Sales_data(std::istream &is) { is >> *this; }

    Sales_data &operator+=(const Sales_data &);

    std::string isbn() const { return bookNo; }

private:
    std::string bookNo;         // 书籍编号,隐式初始化为空串
    unsigned units_sold = 0;    // 销售量,显式初始化为 0
    double sellingprice = 0.0;  // 原始价格,显式初始化为 0.0
    double saleprice = 0.0;     // 实售价格,显式初始化为 0.0
    double discount = 0.0;      // 折扣,显式初始化为 0.0
};

inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {
    return lhs.isbn() == rhs.isbn();
}

Sales_data operator+(const Sales_data &, const Sales_data &);

inline bool operator==(const Sales_data &lhs, const Sales_data &rhs) {
    return lhs.units_sold == rhs.units_sold &&
           lhs.sellingprice == rhs.sellingprice &&
           lhs.saleprice == rhs.saleprice &&
           lhs.isbn() == rhs.isbn();
}

inline bool operator!=(const Sales_data &lhs, const Sales_data &rhs) {
    return !(lhs == rhs);   // 基于运算符 == 给出 != 的定义
}

Sales_data &Sales_data::operator+=(const Sales_data &rhs) {
    units_sold += rhs.units_sold;
    saleprice = (rhs.saleprice * rhs.units_sold + saleprice * units_sold)
                / (rhs.units_sold + units_sold);
    if (sellingprice != 0)
        discount = saleprice / sellingprice;
    return *this;
}

Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
    Sales_data ret(lhs);    // 把 lhs 的内容拷贝到临时变量 ret 中,这种做法便于运算
    ret += rhs;             // 把 rhs 的内容加入其中
    return ret;             // 返回 ret
}

std::istream &operator>>(std::istream &in, Sales_data &s) {
    in >> s.bookNo >> s.units_sold >> s.sellingprice >> s.saleprice;
    if (in && s.sellingprice != 0)
        s.discount = s.saleprice / s.sellingprice;
    else
        s = Sales_data();   // 输入错误,重置输入的数据
    return in;
}

std::ostream &operator<<(std::ostream &out, const Sales_data &s) {
    out << s.isbn() << " " << s.units_sold << " "
        << s.sellingprice << " " << s.saleprice << " " << s.discount;
    return out;
}

#endif

main.cpp

#include <iostream>
#include "Sales_data.h"

using std::cout;
using std::endl;
using std::cin;

int main() {
    Sales_data book;
    cout << "请输入销售记录:" << endl;
    // 输入格式:ISBN 售出本数 原始价格 实际售价
    while (cin >> book) {
        cout << "ISBN、售出本数、原始价格、实售价格、折扣为:" << book << endl;
    }

    return 0;
}
// 运行结果
请输入销售记录:
0-201-78345-X 3 20.00 19.00
ISBN、售出本数、原始价格、实售价格、折扣为:0-201-78345-X 3 20 19 0.95
0-202-78345-X 4 30.00 29.00
ISBN、售出本数、原始价格、实售价格、折扣为:0-202-78345-X 4 30 29 0.966667

3.2

// 一次读入一整行
#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;

int main() {
    string line;
    while (getline(cin, line))
        cout << line << endl;

    return 0;
}
// 一次读入一个词,遇到空白停止
#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;

int main() {
    string word;
    while (cin >> word)
        cout << word << endl;

    return 0;
}

3.3

标准库 string 的输入运算符自动忽略字符串开头的空白(包括空格符、换行符、制表符等),从第一个真正的字符开始读起,直到遇见下一处空白为止。

如果希望在最终的字符串中保留输入时的空白符,应该使用 getline 函数代替原来的 >> 运算符,getline 从给定的输入流中读取数据,直到遇到换行符为止,此时换行符也被读取进来,但是并不存储在最后的字符串中。

一个典型的示例如下所示:

#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;

int main() {
    string word, line;
    cout << "请选择读取字符串的方式:1 表示逐词读取, 2 表示整行读取" << endl;
    char ch;
    cin >> ch;
    if (ch == '1') {
        cout << "请输入字符串:    Welcome to C++ family!  " << endl;
        cin >> word;
        cout << "系统读取的有效字符串是:" << endl;
        cout << word << endl;
        return 0;
    }

    // 清空输入缓冲区
    cin.clear();
    cin.ignore();

    if (ch == '2') {
        cout << "请输入字符串:    Welcome to C++ family!  " << endl;
        getline(cin, line);
        cout << "系统读取的有效字符串是:" << endl;
        cout << line << endl;
        return 0;
    }
    cout << "您的输入有误!";

    return -1;
}
// 测试 1,运行结果
请选择读取字符串的方式:1 表示逐词读取, 2 表示整行读取
1
请输入字符串:    Welcome to C++ family!  
	Welcome to C++ family!
系统读取的有效字符串是:
Welcome

Process finished with exit code 0

// 测试 2,运行结果
请选择读取字符串的方式:1 表示逐词读取, 2 表示整行读取
2
请输入字符串:    Welcome to C++ family!  
	Welcome to C++ family!
系统读取的有效字符串是:
	Welcome to C++ family!

Process finished with exit code 0

// 测试 3,运行结果
请选择读取字符串的方式:1 表示逐词读取, 2 表示整行读取
5
您的输入有误!
Process finished with exit code 255

The difference between cin.ignore and cin.sync

3.4

【出题思路】

本题考查的知识点是 string 对象的比较以及 string 对象长度的计算,在 C++11 新标准中,可以使用 auto 推断 string 对象长度的实际类型。

【解答】

比较字符串大小的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s1, s2;
    cout << "请输入两个字符串:" << endl;
    cin >> s1 >> s2;
    if (s1 == s2)
        cout << "两个字符串相等" << endl;
    else if (s1 > s2)
        cout << s1 << " 大于 " << s2 << endl;
    else
        cout << s2 << " 大于 " << s1 << endl;

    return 0;
}
// 运行结果
请输入两个字符串:
abcdefg
abd
abd 大于 abcdefg

Process finished with exit code 0

比较字符串长度的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s1, s2;
    cout << "请输入两个字符串:" << endl;
    cin >> s1 >> s2;
    auto len1 = s1.size();
    auto len2 = s2.size();
    if (len1 == len2)
        cout << s1 << " 和 " << s2 << " 的长度都是 " << len1 << endl;
    else if (len1 > len2)
        cout << s1 << " 比 " << s2 << " 的长度多 " << len1 - len2 << endl;
    else
        cout << s1 << " 比 " << s2 << " 的长度小 " << len2 - len1 << endl;

    return 0;
}
// 测试数据1,运行结果
请输入两个字符串:
abcdefg
evc
abcdefg 比 evc 的长度多 4

Process finished with exit code 0

// 测试数据2,运行结果
请输入两个字符串:
abcd
efgh
abcd 和 efgh 的长度都是 4

Process finished with exit code 0

3.5

连接多个字符串的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

int main() {
    char cont = 'y';
    string s, result;
    cout << "请输入第一个字符串:" << endl;
    while (cin >> s) {
        result += s;
        cout << "是否继续(y or n)?" << endl;
        cin >> cont;
        if (cont == 'y' || cont == 'Y')
            cout << "请输入下一个字符串:" << endl;
        else
            break;
    }
    cout << "拼接后的字符串是:" << result << endl;

    return 0;
}
// 运行结果
请输入第一个字符串:
hello
是否继续(y or n)?
y
请输入下一个字符串:
	world
是否继续(y or n)?
n
拼接后的字符串是:helloworld

Process finished with exit code 0

连接多个字符串并以空格分隔的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

int main() {
    char cont = 'y';
    string s, result;
    cout << "请输入第一个字符串:" << endl;
    while (cin >> s) {
        if (!result.size())     // 第一个拼接的字符串之前不加空格
            result += s;
        else                    // 之后拼接的字符串加空格
            result = result + " " + s;
        cout << "是否继续(y or n)?" << endl;
        cin >> cont;
        if (cont == 'y' || cont == 'Y')
            cout << "请输入下一个字符串:" << endl;
        else
            break;
    }
    cout << "拼接后的字符串是:" << result << endl;

    return 0;
}
// 运行结果
请输入第一个字符串:
hello
是否继续(y or n)?
y
请输入下一个字符串:
world
是否继续(y or n)?
n
拼接后的字符串是:hello world

Process finished with exit code 0

3.6

【出题思路】

范围 for 语句是 C++11 新定义的语法形式,当我们处理字符串中的每个字符时使用范围 for 语句非常方便。

【解答】

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str;
    cout << "请输入一个字符串,可以包含空格:" << endl;
    getline(cin, str);
    for (auto &c : str)
        c = 'X';
    cout << str << endl;

    return 0;
}
// 运行结果
请输入一个字符串,可以包含空格:
I'm study in Peking University!
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Process finished with exit code 0

有两点值得注意:一是利用 auto 关键字推断字符串中每个元素的类型;二是 c 必须定义为引用类型,否则无法修改字符串内容。

3.7

【出题思路】

本题旨在考查 auto 自动推断变量类型与直接指定类型的异同。

【解答】

修改后的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str;
    cout << "请输入一个字符串,可以包含空格:" << endl;
    getline(cin, str);
    for (char &c : str)
        c = 'X';
    cout << str << endl;

    return 0;
}
// 运行结果
请输入一个字符串,可以包含空格:
I'm study in Peking University.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Process finished with exit code 0

就本题而言,将循环控制变量的类型设为 char 不会对程序运行结果造成影响,因为我们使用 auto 自动推断字符串 s 的元素类型,结果同样是 char。

3.8

【出题思路】

本题旨在引导读者对比 C++11 新引入的范围 for 循环与传统的 while 循环和 for 循环之间的异同,如果准备处理范围内的每一个元素,则一般而言使用范围 for 循环更好。

【解答】

使用 while 循环实现的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str;
    cout << "请输入一个字符串,可以包含空格:" << endl;
    getline(cin, str);
    int i = 0;
    while (str[i] != '\0') {
        str[i] = 'X';
        ++i;
    }
    cout << str << endl;

    return 0;
}
// 运行结果
请输入一个字符串,可以包含空格:
I'm study in Peking University.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Process finished with exit code 0

使用传统 for 循环实现的程序如下所示:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str;
    cout << "请输入一个字符串,可以包含空格:" << endl;
    getline(cin, str);
    for (unsigned int i = 0; i < str.size(); i++) {
        str[i] = 'X';
    }
    cout << str << endl;

    return 0;
}
// 运行结果
请输入一个字符串,可以包含空格:
I'm study in Peking University.
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Process finished with exit code 0

在本例中,我们希望处理字符串中的每一个字符,且无须在意字符的处理顺序,因此与传统的 while 循环和 for 循环相比,使用范围 for 循环更简洁直观。

3.9

【出题思路】

本题旨在提醒读者注意,利用下标访问字符串内容时,必须确保下标在合法范围内。

【解答】

该程序的原意是输出字符串 s 的首字符,但程序是错误的。因为初始状态下没有给 s 赋任何初值,所以字符串 s 的内容为空,当然也就不存在首字符,下标 0 是非法的。

但是在某些编译器环境中,上述语句并不会引发编译错误。

3.10

【出题思路】

字符串遍历和修改有几种不同的方法,本题旨在考查读者是否对该项技能熟练掌握并能够灵活运用。

【解答】

解题思路一,利用范围 for 语句遍历字符串,逐个输出非标点符号:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str;
    cout << "请输入一个字符串,最好含有某些标点符号:" << endl;
    getline(cin, str);
    for (auto c : str)
        if (!ispunct(c))
            cout << c;

    return 0;
}
// 运行结果
请输入一个字符串,最好含有某些标点符号:
I'm study in Peking University.
Im study in Peking University
Process finished with exit code 0

解题思路二,利用普通 for 循环遍历字符串,通过下标执行随机访问,把非标点字符拼接成一个新串后输出:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str, result;
    cout << "请输入一个字符串,最好含有某些标点符号:" << endl;
    getline(cin, str);
    for (decltype(str.size()) i = 0; i < str.size(); i++)
        if (!ispunct(str[i]))
            result += str[i];
    cout << result << endl;

    return 0;
}

其实也可以用范围 for 循环

#include <iostream>
#include <string>

using namespace std;

int main() {
    string str, result;
    cout << "请输入一个字符串,最好含有某些标点符号:" << endl;
    getline(cin, str);
    for (auto c : str)
        if (!ispunct(c))
            result += c;
    cout << result << endl;

    return 0;
}
// 运行结果
请输入一个字符串,最好含有某些标点符号:
I'm study in Peking University.
Im study in Peking University

Process finished with exit code 0

3.11

【出题思路】

本题旨在考查范围 for 循环中循环变量的类型与 auto 自动推断的关系。

【解答】

该程序段从语法上来说是合法的,s 是一个常量字符串,则 c 的推断类型是常量的引用,即 c 所绑定的对象值不能改变。为了证明这一点,读者不妨尝试下面的程序:

#include <iostream>
#include <string>

using namespace std;

int main() {
    const string s = "Keep out!";
    for (auto &c : s) {
        c = 'X';
        // 其他对 c 的操作
    }

    return 0;
}

该段程序不能正确编译,因为 c 是绑定到常量的引用,其值不能改变。我用的编译器给出的错误信息是:

====================[ Build | test | Debug ]====================================
/Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake --build /Users/macOS/CLionProjects/test/cmake-build-debug --target test -- -j 2
Scanning dependencies of target test
[ 50%] Building CXX object CMakeFiles/test.dir/main.cpp.o
/Users/macOS/CLionProjects/test/main.cpp:9:11: error: cannot assign to variable 'c' with const-qualified type 'const char &'
        c = 'X';
        ~ ^
/Users/macOS/CLionProjects/test/main.cpp:8:16: note: variable 'c' declared const here
    for (auto &c : s) {
         ~~~~~~^~~
1 error generated.
make[3]: *** [CMakeFiles/test.dir/main.cpp.o] Error 1
make[2]: *** [CMakeFiles/test.dir/all] Error 2
make[1]: *** [CMakeFiles/test.dir/rule] Error 2
make: *** [test] Error 2

3.12

【出题思路】

本题考查的知识点是 vector 对象的定义和初始化。

【解答】

(a)是正确的,定义了一个名为 ivec 的 vector 对象,其中的每个元素都是 vector<int> 对象。

(b)是错误的,svec 的元素类型是 string,而 ivec 的元素类型是 int,因此不能使用 ivec 初始化 svec。

(c)是正确的,定义了一个名为 svec 的 vector 对象,其中含有 10 个元素,每个元素都是字符串 null。

3.13

【出题思路】

初始化 vector 对象的方法有很多,其中一些形式上比较类似,本题旨在考查读者对这些初始化方法的掌握情况。

【解答】

(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 对象中逐个添加元素。

【解答】

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

#include <iostream>
#include <vector>

using namespace std;

int main() {
    vector<int> vInt;       // 元素类型为 int 的 vector 对象
    int i;                  // 记录用户的输入值
    char cont = 'y';        // 与用户交互,决定是否继续输入
    while (cin >> i) {
        vInt.push_back(i);  // 向 vector 对象中添加元素
        cout << "您要继续吗(y or n)?" << endl;
        cin >> cont;
        if (cont != 'y' && cont != 'Y')
            break;
    }
    for (auto mem : vInt)   // 使用范围 for 循环语句遍历 vInt 中的每个元素
        cout << mem << " ";
    cout << endl;

    return 0;
}
// 运行结果
1
您要继续吗(y or n)?
y
2
您要继续吗(y or n)?
y
1
您要继续吗(y or n)?
n
1 2 1 

Process finished with exit code 0

3.15

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main() {
    vector<string> vString;       // 元素类型为 string 的 vector 对象
    string s;                  // 记录用户的输入值
    char cont = 'y';        // 与用户交互,决定是否继续输入
    while (cin >> s) {
        vString.push_back(s);  // 向 vector 对象中添加元素
        cout << "您要继续吗(y or n)?" << endl;
        cin >> cont;
        if (cont != 'y' && cont != 'Y')
            break;
    }
    for (auto mem : vString)   // 使用范围 for 循环语句遍历 vString 中的每个元素
        cout << mem << " ";
    cout << endl;

    return 0;
}
// 运行结果
i
您要继续吗(y or n)?
y
am
您要继续吗(y or n)?
n
i am 

Process finished with exit code 0

3.16

【出题思路】

利用 size 函数得到 vector 对象的容量(元素个数),利用范围 for 循环遍历 vector 对象的每一个元素,然后逐个输出。

【解答】

用于检验的程序如下所示:

#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) {
        cout << "v2 的元素分别是:" << endl;
        for (auto e : v2)
            cout << e << " ";
        cout << endl;
    }

    cout << "v3 的元素个数是:" << v3.size() << endl;
    if (v3.size() > 0) {
        cout << "v3 的元素分别是:" << endl;
        for (auto e : v3)
            cout << e << " ";
        cout << endl;
    }

    cout << "v4 的元素个数是:" << v4.size() << endl;
    if (v4.size() > 0) {
        cout << "v4 的元素分别是:" << endl;
        for (auto e : v4)
            cout << e << " ";
        cout << endl;
    }

    cout << "v5 的元素个数是:" << v5.size() << endl;
    if (v5.size() > 0) {
        cout << "v5 的元素分别是:" << endl;
        for (auto e : v5)
            cout << e << " ";
        cout << endl;
    }

    cout << "v6 的元素个数是:" << v6.size() << endl;
    if (v6.size() > 0) {
        cout << "v6 的元素分别是:" << endl;
        for (auto e : v6)
            cout << e << " ";
        cout << endl;
    }

    cout << "v7 的元素个数是:" << v7.size() << endl;
    if (v7.size() > 0) {
        cout << "v7 的元素分别是:" << endl;
        for (auto e : v7)
            cout << e << " ";
        cout << endl;
    }

    return 0;
}
// 运行结果
v1 的元素个数是:0
v2 的元素个数是:10
v2 的元素分别是:
0 0 0 0 0 0 0 0 0 0 
v3 的元素个数是:10
v3 的元素分别是:
42 42 42 42 42 42 42 42 42 42 
v4 的元素个数是:1
v4 的元素分别是:
10 
v5 的元素个数是:2
v5 的元素分别是:
10 42 
v6 的元素个数是:10
v6 的元素分别是:
          
v7 的元素个数是:10
v7 的元素分别是:
hi hi hi hi hi hi hi hi hi hi 

Process finished with exit code 0

3.17

【出题思路】

本题的知识点包括:向 vector 对象中添加元素、范围 for 循环语句、利用 toupper 函数将词改写为大写形式。

【解答】

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

#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)     // 使用范围 for 循环语句遍历 mem 中的每个字符
            c = toupper(c);     // 改写为大写字母形式
        cout << mem << endl;
    }

    return 0;
}
// 运行结果
请输入第一个词:
apple
您要继续吗(y or n)?
y
请输入下一个词:
pear
您要继续吗(y or n)?
y
请输入下一个词:
orange
您要继续吗(y or n)?
n
转换后的结果是:
APPLE
PEAR
ORANGE

Process finished with exit code 0

3.18

【出题思路】

本题旨在考查利用下标运算符访问 vector 元素的问题,注意:vector 对象的下标运算符只能用于访问已经存在的元素,而不能用于添加元素。

【解答】

该程序是非法的,因为 ivec 目前没有任何元素,因此 ivec[0] 的形式是错误的,程序试图访问的元素根本不存在。要想向 vector 对象中添加新元素,需要使用 push_back 函数。

下面是修改后的程序代码:

vector<int> ivec;
ivec.push_back(42);

3.19

【出题思路】

本题旨在考查定义及初始化 vector 对象的方法,不同的初始化方法有各自适应的场景。

【解答】

解决思路一:先定义一个空 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{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

【出题思路】

本题旨在考查 vector 对象的初始化及利用下标访问 vector 对象的元素的方法。

【解答】

求相邻元素和的程序如下所示:

#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;
}
// 运行结果
请输入一组数字:
1 2 3 4 5 6 7 8 9 10 11 12 13 q
相邻两项的和依次是:
3 7 11 15 19 
23 13
Process finished with exit code 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;
}
// 运行结果
请输入一组数字:
1 2 3 4 5 6 7 8 9 10 11 12 13 q
首尾两项的和依次是:
14 14 14 14 14 
14 7
Process finished with exit code 0

3.21

【出题思路】

迭代器是一种访问容器元素的通用机制,与指针类型类似,迭代器也提供了对对象的间接访问。使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另外一个元素。因为本题只需输出 vector 对象的内容而无须对其进行更改,所以使用的迭代器应该是 cbegin 和 cend,而非 begin 和 end。

【解答】

利用迭代器改写练习 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.cbegin() != v1.cend()) {        // 当 vector 含有元素时逐个输出
        cout << "v1 的元素分别是:" << endl;
        for (auto it = v1.cbegin(); it != v1.cend(); it++)
            cout << *it << " ";
        cout << endl;
    }

    cout << "v2 的元素个数是:" << v2.size() << endl;
    if (v2.cbegin() != v2.cend()) {
        cout << "v2 的元素分别是:" << endl;
        for (auto it = v2.cbegin(); it != v2.cend(); it++)
            cout << *it << " ";
        cout << endl;
    }

    cout << "v3 的元素个数是:" << v3.size() << endl;
    if (v3.cbegin() != v3.cend()) {
        cout << "v3 的元素分别是:" << endl;
        for (auto it = v3.cbegin(); it != v3.cend(); it++)
            cout << *it << " ";
        cout << endl;
    }

    cout << "v4 的元素个数是:" << v4.size() << endl;
    if (v4.cbegin() != v4.cend()) {
        cout << "v4 的元素分别是:" << endl;
        for (auto it = v4.cbegin(); it != v4.cend(); it++)
            cout << *it << " ";
        cout << endl;
    }

    cout << "v5 的元素个数是:" << v5.size() << endl;
    if (v5.cbegin() != v5.cend()) {
        cout << "v5 的元素分别是:" << endl;
        for (auto it = v5.cbegin(); it != v5.cend(); it++)
            cout << *it << " ";
        cout << endl;
    }

    cout << "v6 的元素个数是:" << v6.size() << endl;
    if (v6.cbegin() != v6.cend()) {
        cout << "v6 的元素分别是:" << endl;
        for (auto it = v6.cbegin(); it != v6.cend(); it++)
            cout << *it << " ";
        cout << endl;
    }

    cout << "v7 的元素个数是:" << v7.size() << endl;
    if (v7.cbegin() != v7.cend()) {
        cout << "v7 的元素分别是:" << endl;
        for (auto it = v7.cbegin(); it != v7.cend(); it++)
            cout << *it << " ";
        cout << endl;
    }

    return 0;
}
// 运行结果
v1 的元素个数是:0
v2 的元素个数是:10
v2 的元素分别是:
0 0 0 0 0 0 0 0 0 0 
v3 的元素个数是:10
v3 的元素分别是:
42 42 42 42 42 42 42 42 42 42 
v4 的元素个数是:1
v4 的元素分别是:
10 
v5 的元素个数是:2
v5 的元素分别是:
10 42 
v6 的元素个数是:10
v6 的元素分别是:
          
v7 的元素个数是:10
v7 的元素分别是:
hi hi hi hi hi hi hi hi hi hi 

Process finished with exit code 0

3.22

【出题思路】

与原书的示例程序相比,需要将第一段(vector 对象第一个空串元素之前的所有元素)改写成大写字母的形式再输出。因为需要更改 vector 对象的内容,所以使用的迭代器是 begin 和 end,而非 cbegin 和 cend。

【解答】

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

#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;
}
// 运行结果
it's the first paragraph.

it's the second paragraph.

#
IT'S THE FIRST PARAGRAPH.

Process finished with exit code 0

3.23

【出题思路】

本题与之前题目的区别是由程序自动生成随机数并添加到 vector 对象中,当输出原始数据时,只需要读取而无须更改,所以迭代器选用 cbegin 和 cend;当执行翻倍计算时,需要读写元素内容,所以迭代器选用 begin 和 end。

【解答】

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

#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;
}
// 运行结果
随机生成的 10 个数字是:
903 607 634 894 593 412 878 410 261 712 
翻倍后的 10 个数字是:
1806 1214 1268 1788 1186 824 1756 820 522 1424 

Process finished with exit code 0

3.24

【出题思路】

本题旨在考查 vector 对象的初始化及利用下标访问 vector 对象的元素的方法。

【解答】

求相邻元素和的程序如下所示:

#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 推断 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;
}
// 运行结果
请输入一组数字:
1 2 3 4 5 6 7 8 9 10 11 12 13 q
相邻两项的和依次是:
3 7 11 15 19 
23 13
Process finished with exit code 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();
    // 利用 auto 推断 it 的类型
    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;
}
// 运行结果
请输入一组数字:
1 2 3 4 5 6 7 8 9 10 11 12 13 q
首尾两项的和依次是:
14 14 14 14 14 
14 7
Process finished with exit code 0

求解本题时应该特别注意迭代器 begin 和 end 的含义,其中 begin 指向容器的首元素而 end 指向容器的最后一个元素的下一位置。只有熟悉上述定义才能精确推断迭代器的当前位置在哪里。

3.25

【出题思路】

与指针类似,C++提供了迭代器的算术运算操作,使得迭代器可以在元素间移动;同时,我们也可以通过解引用迭代器来获取它所指示的元素,前提是确保迭代器合法。

【解答】

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

#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 << "您总计输入了 " << vUS.size() << " 个成绩" << endl;
    cout << "各分数段的人数分布是(成绩从低到高):" << endl;
    // 利用迭代器遍历 vUS 的元素并逐个输出
    for (; it != vUS.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;

    return 0;
}
// 运行结果
请输入一组成绩(0 ~ 100):
42 65 95 100 39 67 95 76 88 76 83 92 76 93 q
您总计输入了 11 个成绩
各分数段的人数分布是(成绩从低到高):
0 0 0 1 1 0 2 3 2 4 1 

Process finished with exit code 0

3.26

【出题思路】

本题旨在考查迭代器的运算类型及含义。

【解答】

C++并没有定义两个迭代器的加法运算,实际上直接把两个迭代器加起来是没有意义的。

与之相反,C++定义了迭代器的减法运算,两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动多少个元素后得到左侧的迭代器,参与运算的两个迭代器必须指向同一个容器中的元素或尾后元素。

另外,C++还定义了迭代器与整数的加减法运算,用以控制迭代器在容器中左右移动。

在本题中,因为迭代器的加法不存在,所以 mid = (beg + end) / 2; 不合法。mid = beg + (end - beg) / 2; 的含义是,先计算 end - beg 的值得到容器中的元素个数,然后控制迭代器与整数(end - beg) / 2 )进行运算,从开始处向右移动二分之一容器的长度,从而定位到容器中间的元素。

3.27

【出题思路】

本题考查数组的定义和初始化。数组是一种复合类型,其声明形如 a[d],a 是数组的名字,d 是数组的维度(容量)。对数组维度的要求有两个,一是维度表示数组中元素的个数,因此必须大于 0;二是维度属于数组类型的一部分,因此在编译时应该是已知的,必须是一个常量表达式。

【解答】

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

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

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

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

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

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 << " ";
    cout << endl;

    return 0;
}
// 运行结果

0 0 0 0 0 0 0 0 0 0 
          
0 0 0 0 0 0 0 0 0 0 

Process finished with exit code 0

3.29

【出题思路】

数组与 vector 有一些相似之处,但是也有若干区别。

【解答】

数组与 vector 的相似之处是都能存放类型相同的对象,且这些对象本身没有名字,需要通过其所在位置访问。

数组与 vector 的最大不同是,数组的大小固定不变,不能随意向数组中增加额外的元素,虽然在某些情境下运行时性能较好,但是与 vector 相比损失了灵活性。

具体来说,数组的维度在定义时已经确定,如果我们想更改数组的长度,只能创建一个更大的新数组,然后把原数组的所有元素复制到新数组中去。数组也无法像 vector 那样使用 size 函数直接获取数组的维度。如果是字符数组,可以调用 strlen 函数得到字符串的长度;如果是其他数组,只能使用 sizeof(array) / sizeof(array[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

【出题思路】

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

【解答】

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

#include <iostream>

using namespace std;

int main() {
    const int sz = 10;          // 常量 sz 作为数组的维度
    int a[sz];
    // 通过 for 循环为数组元素赋值
    for (int i = 0; i < sz; i++)
        a[i] = i;
    // 通过范围 for 循环输出数组的全部元素
    for (auto val : a)
        cout << val << " ";
    cout << endl;

    return 0;
}
// 运行结果
0 1 2 3 4 5 6 7 8 9 

Process finished with exit code 0

3.32

【出题思路】

如果想把数组的内容拷贝给另一个数组,不能直接对数组使用赋值运算符,而应该逐一拷贝数组的元素。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;
}
// 运行结果
0 1 2 3 4 5 6 7 8 9 

Process finished with exit code 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;
}
// 运行结果
0 1 2 3 4 5 6 7 8 9 

Process finished with exit code 0

3.33

【出题思路】

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

【解答】

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

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

3.34

【出题思路】

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

【解答】

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

如果 p1 和 p2 的类型不同,则编译时报错。

3.35

【出题思路】

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

【解答】

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

#include <iostream>

using namespace std;

int main() {
    const int sz = 10;          // 常量 sz 作为数组的维度
    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;
}
// 运行结果
初始状态下数组的内容是:
0 1 2 3 4 5 6 7 8 9 
修改后的数组内容是:
0 0 0 0 0 0 0 0 0 0 

Process finished with exit code 0

3.36

【出题思路】

无论对比两个数组是否相等还是两个 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;

    // 令 p 和 q 分别指向数组 a 和 b 的首元素
    int *p = begin(a), *q = begin(b);   
    while (p != end(a) && q != end(b)) {
        if (*p != *q) {
            cout << "您的猜测错误,两个数组不相等" << endl;
            return -1;
        }
        p++;                            // p 向后移动一位
        q++;                            // q 向后移动一位
    }
    cout << "恭喜您全都猜对了!" << endl;

    return 0;
}
// 运行结果
系统数据已经生成,请输入您猜测的 5 个数字(0~9),可以重复:
1 2 3 9 7
系统生成的数据是:
1 8 1 6 2 
您猜测的数据是:
1 2 3 9 7 
您的猜测错误,两个数组不想等

Process finished with exit code 255

对比两个 vector 对象是否相等的程序如下所示,其中使用迭代器遍历 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++;                            // it1 向后移动一位
        it2++;                            // it2 向后移动一位
    }
    cout << "恭喜您全都猜对了!" << endl;

    return 0;
}
// 运行结果
系统数据已经生成,请输入您猜测的 5 个数字(0~9),可以重复:
1 2 3 9 7
系统生成的数据是:
0 6 9 2 8 
您猜测的数据是:
1 2 3 9 7 
您的猜测错误,两个 vector 不相等

Process finished with exit code 255

方法二:

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

    if (a == b)
        cout << "恭喜您全都猜对了!" << endl;
    else
        cout << "您的猜测错误,两个 vector 不相等" << endl;

    return 0;
}
// 运行结果
系统数据已经生成,请输入您猜测的 5 个数字(0~9),可以重复:
1 2 3 9 7
系统生成的数据是:
7 7 5 6 1 
您猜测的数据是:
1 2 3 9 7 
您的猜测错误,两个 vector 不相等

Process finished with exit code 0

3.37

【出题思路】

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

【解答】

程序第一行声明了一个包含 5 个字符的字符数组,因为我们无须修改数组的内容,所以将其定义为常量。第二行定义了一个指向字符常量的指针,该指针可以指向不同的字符常量,但是不允许通过该指针修改所指常量的值。

while 循环的条件是 *cp,只要指针 cp 所指的字符不是空字符 \0,循环就重复执行,循环的任务有两项:首先输出指针当前所指的字符,然后将指针向后移动一位。

该程序的原意是输出 ca 中存储的 5 个字符,每个字符占一行,但实际的执行效果无法符合预期。因为以列表初始化方式赋值的 C 风格字符串与以字符串字面值赋值的有所区别,后者会在字符串最后额外增加一个空字符以示字符串的结束,而前者不会这样做。

因此在该程序中,ca 的 5 个字符全都输出后,并没有遇到预期的空字符,也就是说,while 循环的条件仍将满足,无法跳出。程序继续在内存中 ca 的存储位置之后挨个寻找空字符,直到找到为止。在这个过程中,额外经历的内容也将被输出出来,从而产生错误。

在我的编译环境中,程序的输出结果是:

h
e
l
l
o


>

要想实现程序的原意,应该修改为:

#include <iostream>

using namespace std;

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

    return 0;
}
// 运行结果
h
e
l
l
o

Process finished with exit code 0

或者修改为如下形式也能达到预期效果:

#include <iostream>

using namespace std;

int main() {
    const char ca[] = "hello";
    const char *cp = ca;
    while (*cp) {
        cout << *cp << endl;
        ++cp;
    }

    return 0;
}
// 运行结果
h
e
l
l
o

Process finished with exit code 0

3.38

【出题思路】

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

【解答】

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

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

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

3.39

【出题思路】

由于标准库 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;
}
// 运行结果
请输入两个字符串:
hello
heiio
第一个字符串大于第二个字符串

Process finished with exit code 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);
    if (result > 0)
        cout << "第一个字符串大于第二个字符串" << endl;
    else if (result < 0)
        cout << "第一个字符串小于第二个字符串" << endl;
    else
        cout << "两个字符串相等" << endl;

    return 0;
}
// 运行结果
请输入两个字符串:
hello
heiio
第一个字符串大于第二个字符串

Process finished with exit code 0

我们知道使用数组的时候其实真正用的是指向数组首元素的指针(参见 3.5.3 节,第 105 页)。要想比较两个 C 风格字符串需要调用 strcmp 函数,此时比较的就不再是指针了。如果两个字符串相等,strcmp 返回 0;如果前面的字符串较大,返回正值;如果后面的字符串较大,返回负值。

3.40

【出题思路】

C 风格字符串的操作函数定义在 cstring 头文件中。其中,strcpy 函数负责把字符串的内容拷贝给另一个字符串,strcat 函数则负责把字符串的内容拼接到另一个字符串之后。此外,strlen 函数用于计算字符串的长度,不包括 \0 空字符;size 函数用于计算字符串在内存实际(真实)占用,包括 \0 空字符。

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

【解答】

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

#include <iostream>
#include <cstring>

using namespace std;

int main() {
    char str1[] = "Welcome to ";
    char str2[] = "C++ family!";
    // 利用 strlen 函数计算两个字符串的长度,并求得结果字符串的长度
    char result[size(str1) + size(str2) - 1];
    // char result[strlen(str1) + strlen(str2) + 1];    // 也可

    strcpy(result, str1);       // 把第一个字符串拷贝到结果字符串中
    strcat(result, str2);       // 把第二个字符串拼接到结果字符串中

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

    return 0;
}
// 运行结果
第一个字符串是:Welcome to 
第二个字符串是:C++ family!
拼接后的字符串是:Welcome to C++ family!

Process finished with exit code 0

3.41

【出题思路】

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;
}
// 运行结果
数组的内容是:
93 20 63 93 23 44 51 49 92 37 
vector 的内容是:
93 20 63 93 23 44 51 49 92 37 

Process finished with exit code 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[vInt.size()];
    cout << "数组的内容是:" << endl;
    // 利用范围 for 循环遍历 vector 的每个元素
    for (auto &val : a) {
        val = *it;
        cout << val << " ";
        it++;
    }
    cout << endl;

    return 0;
}
// 运行结果
vector 对象的内容是:
98 57 9 8 46 90 55 36 7 69 
数组的内容是:
98 57 9 8 46 90 55 36 7 69 

Process finished with exit code 0

3.43

【出题思路】

在不使用类型别名、auto 关键字和 decltype 关键字的前提下,必须在 for 循环或范围 for 循环的条件处准确写出循环变量的数据类型,这是本题的关键。

【解答】

满足题意且不使用类型别名、auto 关键字和 decltype 关键字的程序如下所示:

#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 *q = *p; q != *p + 4; q++)
            cout << *q << " ";
        cout << endl;
    }

    return 0;
}
// 运行结果
利用范围 for 语句输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 
利用普通 for 语句和下标运算符输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 
利用普通 for 语句和指针输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 

Process finished with exit code 0

3.44

【出题思路】

利用 C++11 新提供的类型别名声明代替循环控制变量的类型,使得外层循环变量的含义更加直观。

【解答】

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

#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;
}
// 运行结果
利用范围 for 语句输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 
利用普通 for 语句和下标运算符输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 
利用普通 for 语句和指针输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 

Process finished with exit code 0

3.45

【出题思路】

进一步简化编写的程序,利用 C++11 新提供的 auto 关键字自动推断循环控制变量的类型,无须程序员再显示指定,使得程序更加简洁直观,极大降低了编写程序的难度。

【解答】

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

#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;
}
// 运行结果
利用范围 for 语句输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 
利用普通 for 语句和下标运算符输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 
利用普通 for 语句和指针输出多维数组的内容:
0 1 2 3 
4 5 6 7 
8 9 10 11 

Process finished with exit code 0
38

评论区