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

目 录CONTENT

文章目录

第 6 章

6.1

【出题思路】

函数名、返回值、参数三者组成了函数,其中参数分为实参和形参。

【解答】

形参出现在函数定义的地方,形参列表可以包含 0 个、1 个或多个形参,多个形参之间以逗号分隔。形参规定了一个函数所接受数据的类型和数量。

实参出现在函数调用的地方,实参的数量与形参一样多。实参的主要作用是初始化形参,并且这种初始化过程是一一对应的,即第一个实参初始化第一个形参、第二个实参初始化第二个形参,以此类推。实参的类型必须与对应的形参类型匹配。

6.2

【出题思路】

本题旨在考查函数的一般语法规则。首先,函数的三要素(函数名、返回值、参数)必不可少;其次,函数的主体内容必须放在一对花括号内;最后,函数体内返回的结果必须与函数的返回值类型相同。

【解答】

(a)是错误的,因为函数体返回的结果类型是 string,而函数的返回值类型是 int,二者不一致且不能自动转换。修改后的程序是:

string f() {
    string s;
    // ...
    return s;
}

(b)是错误的,因为函数缺少返回值类型。如果该函数确实不需要返回任何值,则程序应该修改为:

void f2(int i) { /* ... */ }

(c)是错误的,同一个函数如果含有多个形参,则这些形参的名字不能重复;另外,函数体左侧的花括号丢失了。修改后的程序应该是:

int calc(int v1, int v2) { /* ... */ }

(d)是错误的,因为函数体必须放在一对花括号内。修改后的程序是:

double square(double x) { return x * x; }

6.3

【出题思路】

通过练习编写自己的阶乘函数,熟悉函数的语法规则和定义函数的方法。

【解答】

程序如下所示:

int fact(int val) {
    if (val < 0)
        return -1;
    int ret = 1;        // 局部变量,用于保存计算结果
    while (val > 1)
        ret *= val--;   // 把 ret 和 val 的乘积赋给 ret,然后将 val 减 1
    return ret;         // 返回结果
}

6.4

【出题思路】

理解函数的调用过程,尤其是注意区别形参和实参。

【解答】

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

#include <iostream>

using namespace std;

int fact(int val) {
    if (val < 0)
        return -1;
    int ret = 1;        // 局部变量,用于保存计算结果
    while (val > 1)
        ret *= val--;   // 把 ret 和 val 的乘积赋给 ret,然后将 val 减 1
    return ret;         // 返回结果
}

int main() {
    int num;
    cout << "请输入一个整数:";
    cin >> num;
    cout << num << " 的阶乘是:" << fact(num) << endl;
    return 0;
}
// 运行结果
请输入一个整数:5
5 的阶乘是:120

Process finished with exit code 0

6.5

【出题思路】

练习定义函数并在 main 中调用该函数。

【解答】

根据参数类型的不同,我们可以分别求整数的绝对值和浮点数的绝对值。因为本题主要考查的知识点是编写和使用函数,因此参数的类型不是关键。从通用性的角度出发,我们不妨设定参数类型是双精度浮点数 double。满足题意的程序如下所示:

#include <iostream>
#include <cmath>

using namespace std;

// 第一个函数通过 if-else 语句计算绝对值
double myABS(double val) {
    if (val < 0)
        return -val;
    else
        return val;
}

// 第二个函数调用 cmath 头文件的 abs 函数计算绝对值
double myABS2(double val) {
    return abs(val);
}

int main() {
    double num;
    cout << "请输入一个数:";
    cin >> num;
    cout << num << " 的绝对值是:" << myABS(num) << endl;
    cout << num << " 的绝对值是:" << myABS2(num) << endl;

    return 0;
}
// 运行结果
请输入一个数:-1
-1 的绝对值是:1
-1 的绝对值是:1

Process finished with exit code 0

在这个程序中,我们定义了两个函数 myABS 和 myABS2,它们使用两种不同的方法求绝对值。第一个函数使用 if-else 分支语句判断实参是正数还是负数,从而计算实参的绝对值。第二个函数直接调用 cmath 头文件的 abs 函数实现同样的功能。

6.6

【出题思路】

理解局部变量的含义,理解自动对象的创建和销毁机制,弄清楚我们为什么需要局部静态变量,应该如何使用局部静态变量。

【解答】

形参和定义在函数体内部的变量统称为局部变量,它们对函数而言是局部的,仅在函数的作用域内可见。函数体内的局部变量又分为普通局部变量和静态局部变量,对于形参和普通局部变量来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为自动对象。这几个概念的区别是:

  • 形参是一种自动对象,函数开始时为形参申请内存空间,我们用调用函数时提供的实参初始化形参对应的自动对象。
  • 普通变量对应的自动对象也容易理解,我们在定义该变量的语句处创建自动对象,如果定义语句提供了初始值,则用该值初始化;否则,执行默认初始化。当该变量所在的块结束后,变量失效。
  • 局部静态变量比较特殊,它的生命周期贯穿函数调用及之后的时间。局部静态变量对应的对象称为局部静态对象,它的生命周期从定义语句处开始,直到程序结束才终止。

下面的程序同时使用了形参、普通局部变量和静态局部变量:

#include <iostream>
using namespace std;

// 该函数同时使用了形参、普通局部变量和静态局部变量
double myADD(double val1, double val2) {    // val1 和 val2 是形参
    double result = val1 + val2;    // result 是普通局部变量
    static unsigned iCnt = 0;       // iCnt 是静态局部变量
    ++iCnt;
    cout << "该函数已经累计执行了 " << iCnt << " 次" << endl;
    return result;
}

int main() {
    double num1, num2;
    cout << "请输入两个数:";
    while (cin >> num1 >> num2) {
        cout << num1 << " 与 " << num2 << " 的求和结果是:"
             << myADD(num1, num2) << endl;
        cout << "请输入两个数:";
    }
    return 0;
}
// 运行结果
请输入两个数:4 3
4 与 3 的求和结果是:该函数已经累计执行了 1 次
7
请输入两个数:5 7
5 与 7 的求和结果是:该函数已经累计执行了 2 次
12
请输入两个数:45 1
45 与 1 的求和结果是:该函数已经累计执行了 3 次
46
请输入两个数:

6.7

【出题思路】

本题旨在考查局部静态变量的使用方法。

【解答】

我们定义一个非常简单的函数,该函数除了统计执行次数外什么也不做。该函数的定义和调用形式如下所示:

#include <iostream>
using namespace std;

// 该函数仅用于统计执行的次数
int myCnt() {               // 完成该函数的任务不需要任何参数
    static int iCnt = -1;   // iCnt 是局部静态变量
    ++iCnt;
    return iCnt;
}

int main() {
    cout << "请输入任意字符后按回车键继续" << endl;
    char ch;
    while (cin >> ch) {
        cout << "函数 myCnt() 的执行次数是:" << myCnt() << endl;
    }
    return 0;
}
// 运行结果
请输入任意字符后按回车键继续
a
函数 myCnt() 的执行次数是:0
b
函数 myCnt() 的执行次数是:1
c
函数 myCnt() 的执行次数是:2
def
函数 myCnt() 的执行次数是:3
函数 myCnt() 的执行次数是:4
函数 myCnt() 的执行次数是:5
sd
函数 myCnt() 的执行次数是:6
函数 myCnt() 的执行次数是:7

6.8

【出题思路】

函数声明与函数定义的形式非常类似,唯一的区别是:

  • 函数声明无须函数体,用一个分号代替即可;
  • 形参列表只需列出参数类型,形参名可加可不加。

函数应该在头文件(.h)中声明而在源文件(.cppccc)中定义。

【解答】

根据题目要求,Chapter6.h 头文件的内容如下:

#ifndef CHAPTER6_H_INCLUDED
#define CHAPTER6_H_INCLUDED

int fact(int );
double myABS(double );
double myABS2(double );

#endif  // CHAPTER6_H_INCLUDED

6.9

【出题思路】

理解分离式编译的机制。

【解答】

fact.cc 文件的内容是:

#include "Chapter6.h"

int fact(int val) {
    if (val < 0)
        return -1;
    int ret = 1;        // 局部变量,用于保存计算结果
    while (val > 1)
        ret *= val--;   // 把 ret 和 val 的乘积赋给 ret,然后将 val 减 1
    return ret;         // 返回结果
}

factMain.cc 的内容是:

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

int main() {
    int num;
    std::cout << "请输入一个整数:";
    std::cin >> num;
    std::cout << num << " 的阶乘是:" << fact(num) << std::endl;
    return 0;
}

Chapter6.h 的内容是:

#ifndef CHAPTER_6_H
#define CHAPTER_6_H

int fact(int);

#endif  // CHAPTER_6_H

6.10

【出题思路】

使用指针作为参数时,在函数内部交换指针的值只改变局部变量,不会影响实参原本的值,无法满足题目要求。我们应该在函数内部通过解引用操作改变指针所指的内容。

【解答】

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

#include <iostream>
using namespace std;

// 在函数体内部通过解引用操作改变指针所指的内容
void mySwap(int *p, int *q) {
    int tmp = *p;
    *p = *q;
    *q = tmp;

    cout << "指针 p 的地址为:" << p << endl;
    cout << "指针 q 的地址为:" << q << endl;
}

int main() {
    int a = 5, b = 10;
    int *r = &a, *s = &b;
    cout << "交换前:a = " << a << ",b = " << b << endl;
    cout << "指针 r 的地址为:" << r << endl;
    cout << "指针 s 的地址为:" << s << endl;
    mySwap(r, s);
    cout << "交换后:a = " << a << ",b = " << b << endl;

    return 0;
}
// 运行结果
交换前:a = 5,b = 10
指针 r 的地址为:0x7ffee9145878
指针 s 的地址为:0x7ffee9145874
指针 p 的地址为:0x7ffee9145878
指针 q 的地址为:0x7ffee9145874
交换后:a = 10,b = 5

Process finished with exit code 0

请特别注意,下面这个程序无法满足题目要求:

#include <iostream>
using namespace std;

// 在函数体内部交换了两个形参指针本身的值(地址),未能影响实参
void mySwap(int *p, int *q) {
    int *tmp = p;
    p = q;
    q = tmp;

    cout << "指针 p 的地址为:" << p << endl;
    cout << "指针 q 的地址为:" << q << endl;
}

int main() {
    int a = 5, b = 10;
    int *r = &a, *s = &b;
    cout << "交换前:a = " << a << ",b = " << b << endl;
    cout << "指针 r 的地址为:" << r << endl;
    cout << "指针 s 的地址为:" << s << endl;
    mySwap(r, s);
    cout << "交换后:a = " << a << ",b = " << b << endl;

    return 0;
}
// 运行结果
交换前:a = 5,b = 10
指针 r 的地址为:0x7ffeeafde878
指针 s 的地址为:0x7ffeeafde874
指针 p 的地址为:0x7ffeeafde874
指针 q 的地址为:0x7ffeeafde878
交换后:a = 5,b = 10

Process finished with exit code 0

6.11

#include <iostream>
using namespace std;

void reset(int &i) {
    i = 0;
}

int main() {
    int num = 10;
    cout << "重置前:num = " << num << endl;
    reset(num);
    cout << "重置后:num = " << num << endl;
    return 0;
}
// 运行结果
重置前:num = 10
重置后:num = 0

Process finished with exit code 0

6.12

【出题思路】

使用引用可以直接操作引用的对象,从而实现对象内容的交换。

【解答】

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

#include <iostream>
using namespace std;

void mySwap(int &i, int &j) {
    int tmp = i;
    i = j;
    j = tmp;
}

int main() {
    int a = 5, b = 10;
    cout << "交换前:a = " << a << ",b = " << b << endl;
    mySwap(a, b);
    cout << "交换后:a = " << a << ",b = " << b << endl;

    return 0;
}
// 运行结果
交换前:a = 5,b = 10
交换后:a = 10,b = 5

Process finished with exit code 0

与使用指针相比,使用引用交换变量的内容从形式上看更简单一些,并且无须额外声明指针变量,也避免了拷贝指针的值。

6.13

【出题思路】

区别传值参数与传引用参数。

【解答】

void f(T) 的形参采用的是传值方式,也就是说,实参的值被拷贝给形参,形参和实参是两个相互独立的变量,在函数 f 内部对形参所做的任何改动都不会影响实参的值。

void f(&T) 的形参采用的是传引用方式,此时形参是对应的实参的别名,形参绑定到初始化它的对象。如果我们改变了形参的值,也就是改变了对应实参的值。

下面这个程序可以说明两个函数声明的区别:

#include <iostream>
using namespace std;

void a(int);        // 传值参数
void b(int &);      // 传引用参数

int main() {
    int s = 0, t = 10;
    a(s);
    cout << s << endl;
    b(t);
    cout << t << endl;
    return 0;
}

void a(int i) {
    ++i;
    cout << i << endl;
}

void b(int &j) {
    ++j;
    cout << j << endl;
}
// 运行结果
1
0
11
11

Process finished with exit code 0

6.14

【出题思路】

与值传递相比,引用传递的优势主要体现在三方面:

  1. 可以直接操作引用形参所引用的对象;
  2. 使用引用形参可以避免拷贝大的类类型对象或容器类型对象;
  3. 使用引用形参可以帮助我们从函数中返回多个值。

【解答】

基于对引用传递优势的分析,我们可以举出几个适合使用引用类型形参的例子:

  1. 当函数的目的是交换两个参数的内容时应该使用引用类型的形参;
  2. 当参数是 string 对象时,为了避免拷贝很长的字符串,应该使用引用类型。

在其他情况下可以使用值传递的方式,而无须使用引用传递,例如求整数的绝对值或者阶乘的程序。

6.15

【出题思路】

理解并对比普通变量、引用类型和常量引用作为函数参数的区别。

【解答】

find_char 函数的三个参数的类型设定与该函数的处理逻辑密切相关,原因分别如下:

  • 对于待查找的字符串 s 来说,为了避免拷贝长字符串,使用引用类型;同时我们只执行查找操作,无须改变字符串的内容,所以将其声明为常量引用。
  • 对于待查找的字符 c 来说,它的类型是 char,只占 1 字节,拷贝的代价很低,而且我们无须操作实参在内存中实际存储的内容,只把它的值拷贝给形参即可,所以不需要使用引用类型。
  • 对于字符出现的次数 occurs 来说,因为需要把函数内对实参值的更改反映在函数外部,所以必须将其定义成引用类型;但是不能把它定义成常量引用,否则就不能改变所引的内容了。

6.16

【出题思路】

本题旨在考查普通引用和常量引用作为参数类型的区别。一般情况下,与普通引用相比,我们更应该选择使用常量引用作为参数类型。

通过本节的阅读学习,发现对 const 关键字的书写位置,以及 top-level / low-level const 的概念不是很清晰。

记住const的位置与区别

顶层const与底层const。C++的对象中的对象究竟是指?

【解答】

本题的程序把参数类型设为非常量引用,这样做有几个缺陷:一是容易给使用者一种误导,即程序允许修改变量 s 的内容;二是限制了该函数所能接受的参数类型,我们无法把 const 对象、字面值常量或者需要进行类型转换的对象传递给普通的引用形参。

根据上述分析,该函数应该修改为:

bool is_empty(const string &s) { return s.empty(); }

6.17

【出题思路】

当函数对参数所做的操作不同时,应该选择适当的参数类型。如果需要修改参数的内容,则将其设置为普通引用类型;否则,如果不需要对参数内容做任何更改,最好设为常量引用类型。

【解答】

第一个函数的任务是判断 string 对象中是否含有大写字母,无须修改参数的内容,因此将其设为常量引用类型。第二个函数需要修改参数的内容,所以应该将其设定为非常量引用类型。满足题意的程序如下所示:

#include <iostream>
#include <string>
using namespace std;

bool HasUpper(const string &str) {      // 判断字符串是否含有大写字母
    for (auto c : str)
        if (isupper(c))
            return true;
        return false;
}

void ChangeToLower (string &str) {      // 把字符串中的所有大写字母转成小写
    for (auto &c : str)
        c = tolower(c);
}

int main() {
    cout << "请输入一个字符串:" << endl;
    string str;
    cin >> str;
    if (HasUpper(str)) {
        ChangeToLower(str);
        cout << "转换后的字符串是:" << str << endl;
    } else
        cout << "该字符串不含大写字母,无须转换" << endl;

    return 0;
}
// 运行结果
请输入一个字符串:
sdfsDFSFfSDdfs
转换后的字符串是:sdfsdfsffsddfs

Process finished with exit code 0

6.18

【出题思路】

根据题意确定函数名、函数返回值以及函数的参数列表。

【解答】

(a)的函数声明是:

bool compare(const matrix &, const matrix &);

(b)的函数声明是:

vector<int>::iterator change_val(int, vector<int>::iterator);

6.19

【出题思路】

调用函数时,实参类型必须与形参类型匹配。

【解答】

(a)是非法的,函数的声明只包含一个参数,而函数的调用提供了两个参数,因此无法编译通过。

(b)是合法的,字面值常量可以作为常量引用形参的值,字符 a 作为 char 类型形参的值也是可以的。

(c)是合法的,66 虽然是 int 类型,但是在调用时自动转换为 double 类型。

(d)是合法的,vec.begin()vec.end() 的类型都是形参所需的 vector<int>::iterator ,第三个实参 3.8 可以自动转换为形参所需的 int 类型。

6.20

【出题思路】

本题考查常量引用形参和非常量引用形参的原理及适用范围。

【解答】

当函数对参数所做的操作不同时,应该选择适当的参数类型。如果需要修改参数的内容,则将其设置为普通引用类型;否则,如果不需要对参数内容做任何更改,最好设为常量引用类型。

就像前面几个练习题展示的那样,如果把一个本来应该是常量引用的形参设成了普通引用类型,有可能遇到几个问题:一是容易给使用者一种误导,即程序允许修改实参的内容;二是限制了该函数所能接受的实参类型,无法把 const 对象、字面值常量或者需要进行类型转换的对象传递给普通的引用形参。

6.21

【出题思路】

第一个参数的实际值毫无疑问是 int 类型,第二个参数是 int 指针,实际上有可能表示的是一个 int 数组,该指针指向了数组的首元素。

【解答】

通过分析题意我们知道,函数实际上比较的是第一个实参的值和第二个实参所指数组首元素的值。因为两个参数的内容都不会被修改,所以指针的类型应该是 const int * 。满足题意的程序如下所示:

#include <iostream>
#include <string>
#include <ctime>
#include <cstdlib>
using namespace std;

int myCompare(const int val, const int *p) {
    return (val > *p) ? val : *p;
}

int main() {
    srand((unsigned) time(NULL));
    int a[10];
    for (auto &i : a)
        i = rand() % 100;
    cout << "请输入一个数:";
    int j;
    cin >> j;
    cout << "您输入的数与数组首元素中较大的是:" << myCompare(j, a) << endl;
    cout << "数组的全部元素是:" << endl;
    for (auto i : a)
        cout << i << " ";
    cout << endl;

    return 0;
}
// 运行结果
请输入一个数:99
您输入的数与数组首元素中较大的是:99
数组的全部元素是:
95 0 3 45 73 36 8 57 95 1 

Process finished with exit code 0

6.22

【出题思路】

对于题目的要求有两种理解,一种是交换指针本身的值,即指针所指的内存地址;另一种是交换指针所指的内容。

【解答】

为了全面考虑,我们在程序中实现了三个不同版本的函数。

第一个函数以值传递的方式使用指针,所有改变都局限于函数内部,当函数执行完毕后即不会改变指针本身的值,也不会改变指针所指的内容。

第二个函数同样以值传递的方式使用指针,但是函数内部通过解引用的方式直接访问内存并修改了指针所指的内容。

第三个函数的参数形式是 int &* ,其含义是(从右向左理解),该参数是一个引用,引用的对象是内存中的一个 int 指针,使用这种方式可以把指针当成对象,交换指针本身的值。需要注意的是,最后一个函数既然交换了指针,当然解引用该指针所得的结果也会相应发生改变。

#include <iostream>
using namespace std;

// 该函数既不交换指针,也不交换指针所指的内容
void SwapPointer1(int *p, int *q) {
    int *temp = p;
    p = q;
    q = temp;
    cout << "p 的值是:" << p << ",q 的值是:" << q << endl;
}

// 该函数交换指针所指的内容
void SwapPointer2(int *p, int *q) {
    int temp = *p;
    *p = *q;
    *q = temp;
    cout << "p 的值是:" << p << ",q 的值是:" << q << endl;
}

// 该函数交换指针本身的值,即交换指针所指的内存地址
void SwapPointer3(int *&p, int *&q) {
    int *temp = p;
    p = q;
    q = temp;
    cout << "p 的值是:" << p << ",q 的值是:" << q << endl;
}

int main() {
    int a = 5, b = 10;
    int *r = &a, *s = &b;
    cout << "交换前:" << endl;
    cout << "r 的值是:" << r << ",s 的值是:" << s << endl;
    cout << "r 所指的值是:" << *r << ",s 所指的值是:" << *s << endl;
    SwapPointer1(r, s);
    cout << "交换后:" << endl;
    cout << "r 的值是:" << r << ",s 的值是:" << s << endl;
    cout << "r 所指的值是:" << *r << ",s 所指的值是:" << *s << endl;
    cout << endl;

    a = 5, b = 10;
    r = &a, s = &b;
    cout << "交换前:" << endl;
    cout << "r 的值是:" << r << ",s 的值是:" << s << endl;
    cout << "r 所指的值是:" << *r << ",s 所指的值是:" << *s << endl;
    SwapPointer2(r, s);
    cout << "交换后:" << endl;
    cout << "r 的值是:" << r << ",s 的值是:" << s << endl;
    cout << "r 所指的值是:" << *r << ",s 所指的值是:" << *s << endl;
    cout << endl;

    a = 5, b = 10;
    r = &a, s = &b;
    cout << "交换前:" << endl;
    cout << "r 的值是:" << r << ",s 的值是:" << s << endl;
    cout << "r 所指的值是:" << *r << ",s 所指的值是:" << *s << endl;
    SwapPointer3(r, s);
    cout << "交换后:" << endl;
    cout << "r 的值是:" << r << ",s 的值是:" << s << endl;
    cout << "r 所指的值是:" << *r << ",s 所指的值是:" << *s << endl;

    return 0;
}
// 运行结果
交换前:
r 的值是:0x7ffee03b4778,s 的值是:0x7ffee03b4774
r 所指的值是:5,s 所指的值是:10
p 的值是:0x7ffee03b4774,q 的值是:0x7ffee03b4778
交换后:
r 的值是:0x7ffee03b4778,s 的值是:0x7ffee03b4774
r 所指的值是:5,s 所指的值是:10

交换前:
r 的值是:0x7ffee03b4778,s 的值是:0x7ffee03b4774
r 所指的值是:5,s 所指的值是:10
p 的值是:0x7ffee03b4778,q 的值是:0x7ffee03b4774
交换后:
r 的值是:0x7ffee03b4778,s 的值是:0x7ffee03b4774
r 所指的值是:10,s 所指的值是:5

交换前:
r 的值是:0x7ffee03b4778,s 的值是:0x7ffee03b4774
r 所指的值是:5,s 所指的值是:10
p 的值是:0x7ffee03b4774,q 的值是:0x7ffee03b4778
交换后:
r 的值是:0x7ffee03b4774,s 的值是:0x7ffee03b4778
r 所指的值是:10,s 所指的值是:5

Process finished with exit code 0

此题和题目 6.11 结合起来理解。

6.23

【出题思路】

根据参数的不同,为 print 函数设计几个版本。版本的区别主要体现在对指针参数的管理方式不同。

【解答】

实现了三个版本的 print 函数,第一个版本不控制指针的边界,第二个版本由调用者指定数组的维度,第三个版本使用 C++11 新规定的 begin 和 end 函数限定数组边界。满足题意的程序如下所示:

#include <iostream>
using namespace std;

// 参数是常量整型指针
void print1(const int *p) {
    cout << *p << endl;
}

// 参数有两个,分别是常量整型指针和数组的容量
void print2(const int *p, const int sz) {
    int i = 0;
    while (i != sz) {
        cout << *p++ << endl;
        ++i;
    }
}

// 参数有两个,分别是数组的首尾边界
void print3(const int *b, const int *e) {
    for (auto q = b; q != e; ++q)
        cout << *q << endl;
}

int main() {
    int i = 0, j[2] = {0, 1};
    print1(&i);
    cout << "------- a ---------" << endl;
    print1(j);
    cout << "------- b ---------" << endl;
    print2(&i, 1);
    cout << "------- c ---------" << endl;
    // 计算得到数组 j 的容量
    print2(j, sizeof(j) / sizeof(*j));
    cout << "------- d ---------" << endl;
    auto b = begin(j);
    auto e = end(j);
    print3(b, e);

    return 0;
}
// 运行结果
0
------- a ---------
0
------- b ---------
0
------- c ---------
0
1
------- d ---------
0
1

Process finished with exit code 0

6.24

【出题思路】

当我们想把数组作为函数的形参时,有三种可供选择的方式:一是声明为指针,二是声明为不限维度的数组,三是声明为维度确定的数组。实际上,因为数组传入函数时实参自动转换成指向数组首元素的指针,所以这三种方式是等价的。

【解答】

有之前的分析可知,print 函数的参数实际上等同于一个常量整型指针 const int * ,形参 ia 的维度 10 只是我们期望的数组维度,实际上不一定。即使实参数组的真实维度不是 10,也可以正常调用 print 函数。

上述 print 函数的定义存在一个潜在的风险,即虽然我们期望传入的数组维度是 10,但实际上任意维度的数组都可以传入。如果传入的数组维度较大,print 函数输出数组的前 10 个元素,不至于引发错误;相反,如果传入的数组维度不足 10,则 print 函数将强行输出一些未定义的值。

void print(const int ia[], const int sz) {
  for (size_t i = 0; i != sz; ++i)
    cout << ia[i] << endl;
}

6.25

【出题思路】

传递给 main 函数的参数有两个,第一个参数 argc 指明数组中字符串的数量,第二个参数 argv 是存有字符串的数组。

【解答】

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

#include <iostream>
using namespace std;

int main(int argc, char *argv[]) {
	string str;
	for (int i = 0; i != argc; ++i) {
		str += argv[i];
	}
	cout << str << endl;
	return 0;
}
// 将编译、链接生成的可执行程序 ex6_25 在命令行执行
// ex6_25 位于路径 Desktop 下
➜  ~ cd Desktop
➜  Desktop ex6_25 3 4 5
ex6_25 3 4 5
➜  Desktop ex6_25 welcome to China !
ex6_25 welcome to China !

6.26

【出题思路】

格式化输出 argv 所包含的每一个字符串。

【解答】

满足题意的程序如下所示(Main.cpp):

#include <iostream>
using namespace std;

int main(int argc, char **argv) {
    for (int i = 0; i != argc; ++i) {
        cout << "argc[" << i << "]: " << argv[i] << endl;
    }

    return 0;
}
// 将编译、链接生成的可执行程序 Main 在命令行执行
// Main 位于路径 Desktop 下
➜  Desktop Main -d -o ofile data0
argc[0]: Main
argc[1]: -d
argc[2]: -o
argc[3]: ofile
argc[4]: data0
➜  Desktop

另外也可以在编译链接运行前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 -d -o ofile data0 。同样可得到结果:

argc[0]: /Users/macOS/CLionProjects/test/cmake-build-debug/test
argc[1]: -d
argc[2]: -o
argc[3]: ofile
argc[4]: data0

Process finished with exit code 0

注:因为我用的 C++ 环境是 CLion,所以我是按照这样配置的。用微软 VS 环境的可参考此贴

6.27

【出题思路】

掌握 initializer_list 对象的声明和初始化方法,利用 initializer_list 对象设计形参可变的函数。

【解答】

满足题意的程序如下所示,注意 iCount 的参数是 initializer_list 对象,在调用该函数时,我们使用了列表初始化的方式生成实参。

#include <iostream>
using namespace std;

int iCount(initializer_list<int> il) {
    int count = 0;
    // 遍历 il 的每一个元素
    for (auto val : il)
        count += val;
    return count;
}

int main(int argc, char **argv) {
    // 使用列表初始化的方式构建 initializer_list<int> 对象
    // 然后把它作为实参传递给函数 iCount
    cout << "1, 6, 9 的和是:" << iCount({1, 6, 9}) << endl;
    cout << "4, 5, 9, 18 的和是:" << iCount({4, 5, 9, 18}) << endl;
    cout << "10, 10, 10, 10, 10, 10, 10, 10, 10, 10 的和是:"
         << iCount({10, 10, 10, 10, 10, 10, 10, 10, 10, 10}) << endl;

    return 0;
}
// 运行结果
1, 6, 9 的和是:16
4, 5, 9, 18 的和是:36
10, 10, 10, 10, 10, 10, 10, 10, 10, 10 的和是:100

Process finished with exit code 0

6.28

【出题思路】

理解 initializer_list 的含义和用法。

【解答】

initializer_list<string> 的所有元素类型都是 string,因此 const auto &elem : il 推断得到的 elem 的类型是 const string & 。使用引用是为了避免拷贝长字符串,把它定义为常量的原因是我们只需读取字符串的内容,不需要修改它(另外, initializer_list 列表中的元素原本属性就是 const,想修改也无法修改)。

6.29

【出题思路】

考虑引用类型与普通类型的区别。

【解答】

引用类型的优势主要是可以直接操作引用的对象以及避免拷贝较为复杂的类型对象和容器对象。因为 initializer_list 对象的元素永远是常量值,所以我们不可能通过设定引用类型来更改循环控制变量的内容。只有当 initializer_list 对象的元素类型是类类型或容器类型(比如 string)时,才有必要把范围 for 循环的循环控制变量设为引用类型。

6.30

【出题思路】

函数对于返回结果的要求是:每一条 return 语句的结果类型必须与函数的返回值类型相同,并且在函数执行逻辑中每一个可能的结束点都应该有一条 return 语句。

【解答】

该函数在我的编译环境中无法编译通过,编译器发现了一个严重错误,即 for 循环中的 return 语句是非法的。函数的返回值类型是布尔值,而该条 return 语句没有返回任何值。

事实上程序还存在另一个严重错误,按照程序的逻辑,for 循环有可能不会中途退出而是一直执行完毕,此时显然缺少一条 return 语句处理这种情况。遗憾的是,编译器无法发现这一错误。

#include <iostream>
using namespace std;

// 因为含有不正确的返回值,所以这段代码无法通过编译
bool str_subrange(const string &str1, const string str2) {
    // 大小相同:此时用普通的相等性来判断结果作为返回值
    if (str1.size() == str2.size())
        return str1 == str2;        // 正确:== 运算符返回布尔值
    // 得到较短 string 对象大小
    auto size = (str1.size() < str2.size())
                ? str1.size() : str2.size();
    // 检查两个 string 对象的对应字符是否相等,以较短的字符长度为限
    for (decltype(size) i = 0; i != size; ++i) {
        if (str1[i] != str2[i])
            return;                 // 错误 #1:没有返回值,编译器将报告这一错误
    }
    // 错误 #2:控制流可能尚未返回任何值就结束了函数的执行
    // 编译器可能检查不出这一错误
}

int main(int argc, char **argv) {


    return 0;
}
// 运行结果
[ 50%] Building CXX object CMakeFiles/test.dir/main.cpp.o
/Users/macOS/CLionProjects/test/main.cpp:15:13: error: non-void function 'str_subrange' should return a value [-Wreturn-type]
            return;                 // 错误 #1:没有返回值,编译器将报告这一错误
            ^
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

6.31

【出题思路】

函数返回其结果的过程与它接受参数的过程类似。如果返回的是值,则创建一个未命名的临时对象,并把要返回的值拷贝给这个临时对象;如果返回的是引用,则该引用是它所引对象的别名,不会真正拷贝对象。

【解答】

如果引用所引的是函数开始之前就已经存在的对象,则返回该引用是有效的;如果引用所引的是函数的局部变量,则随着函数结束局部变量也失效了,此时返回的引用无效。

当不希望返回的对象被修改时,返回对常量的引用。

6.32

【出题思路】

考查当函数和返回值是复合类型时,如何向函数传入数据及如何接受返回结果。读者尤其需要理解函数是怎样返回引用类型的。

【解答】

该函数是合法的。get 函数接受一个整型指针,该指针实际指向一个整型数组的首元素,另外还接受一个整数表示数组中某个元素的索引值。它的返回值类型是整型引用,引用的对象是 array 数组的某个元素。当 get 函数执行完毕后,调用者得到实参数组 array 中索引为 index 的元素的引用。

在 main 函数中,首先创建一个包含 10 个整数的数组,名字是 ia。请注意,由于 ia 定义在 main 函数的内部,所以 ia 不会执行默认初始化操作,如果此时我们直接输出 ia 每个元素的值,则这些值都是未定义的。接下来进入循环,每次循环使用 get 函数得到数组 ia 中第 i 个元素的引用,为该引用赋值 i,也就是说,为第 i 个元素赋值 i 。循环结束时,ia 的元素依次被赋值为 0~9。

编程测试如下所示:

#include <iostream>
using namespace std;

int &get(int *arry, int index) {
    return arry[index];
}

int main(int argc, char **argv) {
    int ia[10];
    for (int a : ia)
        cout << a << " ";
    cout << endl;
    for (int i = 0; i != 10; ++i)
        get(ia, i) = i;
    for (int a : ia)
        cout << a << " ";
    cout << endl;

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

Process finished with exit code 0

6.33

【出题思路】

函数的递归分为直接递归和间接递归。编写递归函数的关键是确定递归规律和递归终止条件。

【解答】

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

#include <iostream>
#include <vector>
using namespace std;

// 递归函数输出 vector<int> 的内容
void print(vector<int> vInt, unsigned index) {
    unsigned sz = vInt.size();
    if (!vInt.empty() && index < sz) {
        cout << vInt[index] << endl;
        print(vInt, index + 1);
    }
}

int main() {
    vector<int> v = {1, 3, 5, 7, 9, 11, 13, 15};
    print(v, 0);
    return 0;
}
// 运行结果
1
3
5
7
9
11
13
15

Process finished with exit code 0

6.34

【出题思路】

理解递归函数的执行逻辑。

【解答】

因为原文中递归函数的参数类型是 int,所以理论上用户传入 factorial 函数的参数可以是负数。按照原程序的逻辑,参数为负数时函数的返回值是 1.

如果修改递归函数的停止条件,则当参数的值为负时,会依次递归下去,执行连续乘法操作直至溢出。因此,不能把 if 语句的条件改成上述形式。

6.35

【出题思路】

回顾后置递减运算符参与表达式运算时的求值规律。

【解答】

如果把 val - 1 改成 val-- ,则出现一种我们不期望看到的情况,即变量的递减操作与读取变量值的操作共存于同一条表达式中,这时有可能产生未定义的值。

6.36

【出题思路】

因为数组不能被拷贝,所以函数不能直接返回数组,但是可以返回数组的指针或引用。

【解答】

要想使函数返回数组的引用并且该数组包含 10 个 string 对象,可以按照如下所示的形式声明函数:

string (&func())[10];

上述声明的含义是:func() 表示调用 func 函数无须任何实参,(&func()) 表示函数的返回结果是一个引用,(&func())[10] 表示引用的对象是一个维度为 10 的数组,string (&func())[10] 表示数组的元素是 string 对象。

6.37

【出题思路】

直接编写返回数组引用的函数比较繁琐且不易理解,使用类型别名、尾置返回类型和 decltype 关键字都可以简化这一过程。

【解答】

使用类型别名:

typedef string arr[10];
arr &func();

使用尾置返回类型:

auto func() -> string (&)[10];

使用 decltype 关键字:

string str[10];
decltype(str) &func();

6.38

【出题思路】

数组也是一个对象,所以可以定义数组的引用。要想为数组的引用赋值,只需要把数组名赋给该引用即可。

【解答】

满足题意的 arrPtr 函数是:

int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
// 返回一个引用,该引用所引的对象是一个含有 5 个整数的数组。
decltype(odd) &arrPtr(int i) {
  return (i % 2) ? odd : even;		// 返回数组的引用。
}

6.39

【出题思路】

本题旨在考查重载函数的含义以及如何判断两个函数是否是重载关系。

【解答】

(a)的第二个声明是非法的。它的意图是声明另外一个函数,该函数只接受整型常量作为实参,但是因为顶层 const 不影响传入函数的对象,所以一个拥有顶层 const 的形参无法与另一个没有顶层 const 的形参区分开来。

(b)的第二个声明是非法的。它的意图是通过函数的返回值区分两个同名的函数,但是这不可行,因为 C++ 规定重载函数必须在形参数量或形参类型上有所区别。如果两个同名函数的形参数量和类型都一样,那么即使返回类型不同也不行。

(c)的两个函数是重载关系,它们的形参类型有区别。

6.40

【出题思路】

如果函数的某个形参在多次调用中都被赋予了同一个值,则我们可以把这个反复出现的值定义为默认实参。C++ 对于默认实参在参数列表中可能出现的位置有明确规定。

【解答】

在上面的两个声明中,(a)是正确的而(b)是错误的。它们都用到了默认实参,但是 C++ 规定一旦某个形参被赋予了默认实参,则它后面的所有形参都必须有默认实参。

这一规定是为了防范可能出现的二义性,显然(b)违反了这一规定。

6.41

【出题思路】

要想使用默认实参,只需要在调用函数时省略该实参就可以了。实参按照其位置解析,默认实参负责填补函数调用缺少的尾部实参。

【解答】

(a)是非法的,该函数有两个默认实参,但是总计有三个形参,其中第一个形参并未设定默认实参,所以要想调用该函数,至少需要提供一个实参。

(b)是合法的,本次调用提供了两个实参,第一个实参对应第一个形参 ht,第二个实参对应第二个形参 wd,其中 wd 的默认实参没有用到,第三个形参 bckgrnd 使用它的默认实参。

(c)在语法上是合法的,但是与程序的原意不符。从语法上来说,第一个实参对应第一个形参 ht,第二个实参的类型虽然是 char,但是它可以自动转换为第二个形参 wd 所需的 int 类型,所以编译时可以通过,但这显然违背了程序的原意,正常情况下,字符 * 应该被用来构成背景。

6.42

【出题思路】

对于英语单词来说,大多数名词的复数是在单词末尾加 s 得到的,也有一部分名词在单数转变为复数时需要在末尾加 es 。我们可以把 s 作为默认实参,大多数情况下不必考虑这个参数,只有在遇到末尾是 es 的单词时才专门处理。

【解答】

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

#include <iostream>
#include <string>
using namespace std;

//最后一个形参赋予了默认实参
string make_plural(size_t ctr, const string &word, const string &ending = "s") {
    return (ctr > 1) ? word + ending : word;
}

int main() {
    cout << "success 的单数形式是:" << make_plural(1, "success", "es") << endl;
    cout << "success 的复数形式是:" << make_plural(2, "success", "es") << endl;
    // 一般情况下调用该函数只需要两个实参
    cout << "failure 的单数形式是:" << make_plural(1, "failure") << endl;
    cout << "failure 的复数形式是:" << make_plural(2, "failure") << endl;

    return 0;
}
// 运行结果
success 的单数形式是:success
success 的复数形式是:successes
failure 的单数形式是:failure
failure 的复数形式是:failures

Process finished with exit code 0

6.43

【出题思路】

函数的声明应该放在头文件中,同时内联函数的定义也应该放在头文件中。

【解答】

(a)应该放在头文件中。因为内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码,所以仅有函数的原型不够。并且,与一般的函数不同,内联函数有可能在程序中定义不止一次,此时必须保证在所有源文件中定义完全相同,把内联函数的定义放在头文件中可以确保这一点。

(b)是函数声明,应该放在头文件中。

6.44

【出题思路】

只需要在普通函数的前面加上关键字 inline,就可以将该函数设置为内联了。内联函数在编译时展开,从而消除了调用函数时产生的开销。

【解答】

改写后的内联函数是:

inline bool isShorter(const string &s1, const string &s2) {
    return s1.size() < s2.size();
}

6.45

【出题思路】

决定一个函数是否应该是内联函数有很多评判的依据。一般来说,内联机制适用于规模较小、流程直接、频繁调用的函数。一旦函数被定义成内联的,则在编译阶段就展开该函数,以消除运行时产生的额外开销。如果函数的规模很大(比如上百行)不利于展开或者函数只被调用了一两次,那么这样的函数就没必要也不应该是内联的。

【解答】

在本章前面实现的函数中,大多规模较小且流程直接,适合于设置为内联函数;如果以后遇到一些代码行数较多的函数,就不适合了。举两个例子:

练习 6.11 中的 reset 函数改写后的形式是:

inline void reset(int &i) {
    i = 0;
}

练习 6.21 中的 myCompare 函数改写后的形式是:

inline int myCompare(const int val, const int *p) {
    return (val > *p) ? val : *p;
}

6.46

【出题思路】

constexpr 函数是指能用于常量表达式的函数,constexpr 函数的返回类型和所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条 return 语句。

【解答】

显然 isShorter 函数不符合 constexpr 函数的要求,它虽然只有一条 return 语句,但是返回的结果调用了标准库 string 类的 size() 函数和 < 比较符,无法构成常量表达式,因此不能改写成 constexpr 函数。

6.47

【出题思路】

本题旨在考查如何在程序中打开和关闭调试器。

我们可以使用一个 #define 语句定义的 NDEBUG,从而关闭调试状态。同时,很多编译器都提供了一个命令行选项使我们可以定义预处理变量:

$ CC -D NDEBUG main.C # use /D with the Microsoft compiler

这条命令的作用等价于在 main.C 文件的一开始写 #define NDEBUG

【解答】

Error: non-aggregate type ‘vector’ cannot be initialized with an initializer list

关闭调试状态的程序如下所示:

#include <iostream>
#include <vector>
#define NDEBUG
using namespace std;

// 递归函数输出 vector<int> 的内容
void print(vector<int> vInt, unsigned index) {
    unsigned sz = vInt.size();
    // 设置在此处输出调试信息
    #ifndef NDEBUG
    cout << "vector 对象的大小是:" << sz << endl;
    #endif // NDEBUG
    if (!vInt.empty() && index < sz) {
        cout << vInt[index] << endl;
        print(vInt,index + 1);
    }
}

int main() {
    vector<int> v = {1, 3, 5, 7, 9, 11, 13, 15};
    print(v, 0);
    return 0;
}
// 运行结果
1
3
5
7
9
11
13
15

Process finished with exit code 0

打开调试状态的程序如下所示:

#include <iostream>
#include <vector>

using namespace std;

// 递归函数输出 vector<int> 的内容
void print(vector<int> vInt, unsigned index) {
    unsigned sz = vInt.size();
    // 设置在此处输出调试信息
    #ifndef NDEBUG
    cout << "vector 对象的大小是:" << sz << endl;
    #endif // NDEBUG
    if (!vInt.empty() && index < sz) {
        cout << vInt[index] << endl;
        print(vInt,index + 1);
    }
}

int main() {
    vector<int> v = {1, 3, 5, 7, 9, 11, 13, 15};
    print(v, 0);
    return 0;
}
// 运行结果
vector 对象的大小是:8
1
vector 对象的大小是:8
3
vector 对象的大小是:8
5
vector 对象的大小是:8
7
vector 对象的大小是:8
9
vector 对象的大小是:8
11
vector 对象的大小是:8
13
vector 对象的大小是:8
15
vector 对象的大小是:8

Process finished with exit code 0

打开调试器时,每次递归调用 print 函数都会输出“vector 对象的大小是:8”;关闭调试器时,程序只输出 vector 对象的内容,不再输出其大小。

6.48

【出题思路】

assert 是一种预处理宏,当 assert 的条件为真时什么也不做,当它的条件为假时输出信息并终止程序。

【解答】

该程序对 assert 的使用有不合理之处。在调试器打开的情况下,当用户输入字符串 s 并且 s 的内容与 sought 不相等时,执行循环体,否则继续执行 assert(cin); 语句。换句话说,程序执行到 assert 的原因可能有两个,一是用户终止了输入,二是用户输入的内容正好与 sought 的内容一样。如果用户尝试终止输入(事实上用户总有停止输入结束程序的时候),则 assert 的条件为假,输出错误信息,这与程序的原意是不相符的。

当调试器关闭时,assert 什么也不做。

6.49

【出题思路】

本题考查候选函数和可行函数的含义,理解函数调用和匹配的过程,尤其是当程序中存在一组重载函数时。

【解答】

当程序中存在多个同名的重载函数时,编译器需要判断调用的是其中哪个函数,这时就有了候选函数和可行函数两个概念。

函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数。候选函数具备两个典型特征:一是与被调用的函数同名;二是其声明在调用点可见。

函数匹配的第二步是考查本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数称为可行函数。可行函数也有两个特征:一是其形参数量与本次调用提供的实参数量相等;二是每个实参的类型与对应的形参类型相同或者能转换成形参的类型。

6.50

【出题思路】

该题需要明确两点:什么是可行函数?满足什么条件的函数是最佳匹配?

【解答】

可行函数是指形参数量与本次调用提供的实参数量相等且每个实参的类型都与对应的形参类型相同或者能够转换成形参类型的函数。

最佳匹配是指该函数每个实参的匹配都不劣于其他可行函数需要的匹配且至少有一个实参的匹配优于其他可行函数提供的匹配。

根据上述分析,我们可以推断出:

f(2.56, 42) 的可行函数是 void f(int, int) 和 void f(double, double = 3.14)。但是最佳匹配不存在,因为这两个可行函数各有所长。对于这次调用来说,如果只考虑第一个实参 2.56,我们发现,void f(double, double = 3.14) 能够精确匹配,但是要匹配第二个参数,int 类型的实参 42 必须转换成 double 类型。如果考虑第二个实参 42,我们发现,void f(int, int) 能够精确匹配,但是要想调用 void f(int, int) 就必须把第一个 double 类型的实参 2.56 转换成 int 类型。最终的结果是这两个可行函数各自在一个实参上实现了更好的匹配,但是把它们比较起来无从判断孰优孰劣,因此编译器将因为这个调用具有二义性而拒绝其请求。

f(42) 的可行函数是 void f(int) 和 void f(double, double = 3.14),其中最佳匹配是 void f(int),因为参数无须做任何类型转换。

f(42, 0) 的可行函数是 void f(int, int) 和 void f(double, double = 3.14),其中最佳匹配是 void f(int, int),因为参数无须做任何类型转换。

f(2.56, 3.14) 的可行函数是 void f(int, int) 和 void f(double, double = 3.14),其中最佳匹配是 void f(double, double = 3.14),因为参数无须做任何类型转换。

6.51

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

#include <iostream>
void f();
void f(int);
void f(int, int);
void f(double, double);
using namespace std;

int main() {
    // f(2.56, 42);
    f(42);
    f(42, 0);
    f(2.56, 3.14);

    return 0;
}

void f() {
    cout << "f()" << endl;
}

void f(int i) {
    cout << "f(int)" << endl;
}

void f(int i, int j) {
    cout << "f(int, int)" << endl;
}

void f(double d1, double d2 = 3.14) {
    cout << "f(double, double)" << endl;
}
// 运行结果
f(int)
f(int, int)
f(double, double)

Process finished with exit code 0

6.52

【出题思路】

为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,读者需要掌握精确匹配、const 匹配、类型提升匹配、算术类型转换和类类型转换的使用。

【解答】

(a)发生的参数类型转换是类型提升,字符型实参自动提升成整型。

(b)发生的参数类型转换是算术类型转换,双精度浮点型自动转换成整型。

6.53

【出题思路】

考查顶层 const 和底层 const 对函数重载的影响。

【解答】

(a)是合法的,两个函数的区别是它们的引用类型的形参是否引用了常量,属于底层 const,可以把两个函数区分开来。

(b)是合法的,两个函数的区别是它们的指针类型的形参是否指向了常量,属于底层 const,可以把两个函数区分开来。

(c)是非法的,两个函数的区别是它们的指针类型的形参本身是否是常量,属于顶层 const,根据本节介绍的匹配规则可知,向实参添加顶层 const 或者从实参中删除顶层 const 属于精确匹配,无法区分两个函数。

6.54

【出题思路】

考查函数指针的声明和使用。

【解答】

满足题意的函数声明如下所示:

int func(int, int);

满足题意的 vector 对象如下所示:

vector<decltype(func) *> vF;

6.55

【出题思路】

考查函数指针的声明和使用。

【解答】

满足题意的函数和 vector 对象如下所示:

#include <iostream>
#include <vector>
using namespace std;

int func1(int a, int b) {
    return a + b;
}

int func2(int a, int b) {
    return a - b;
}

int func3(int a, int b) {
    return a * b;
}

int func4(int a, int b) {
    return a / b;
}

int main() {
    decltype(func1) *p1 = func1, *p2 = func2, *p3 = func3, *p4 = func4;
    vector<decltype(func1) *> vF = {p1, p2, p3, p4};
    return 0;
}

6.56

【出题思路】

构建一个新的函数,以指向 4 种运算的函数的指针为参数。

【解答】

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

#include <iostream>
#include <vector>
using namespace std;

int func1(int a, int b) {
    return a + b;
}

int func2(int a, int b) {
    return a - b;
}

int func3(int a, int b) {
    return a * b;
}

int func4(int a, int b) {
    return a / b;
}

void compute(int a, int b, int (*p)(int, int)) {
    cout << p(a, b) << endl;
}

int main() {
    int i = 5, j = 10;
    decltype(func1) *p1 = func1, *p2 = func2, *p3 = func3, *p4 = func4;
    vector<decltype(func1) *> vF = {p1, p2, p3, p4};
    for (auto p : vF) {
        // 遍历 vector 中的每个元素,依次调用四则运算函数
        compute(i, j, p);
    }
    return 0;
}
// 运行结果
15
-5
50
0

Process finished with exit code 0
32

评论区