C++Primer第五版 第六章习题答案

练习6.1

实参和形参的区别的什么?

      形参:在函数参数列表中声明的局部变量,它们由每个函数调用中提供的参数初始化,作用是说明函数参数的类型。

      实参:函数调用中提供的值,用于初始化函数的参数。

练习6.2

请指出下列函数哪个有错误,为什么?应该如何修改这些错误呢?

(a) int f() {
          string s;
          // ...
          return s;
    }
(b) f2(int i) { /* ... */ }
(c) int calc(int v1, int v1) { /* ... */ }
(d) double square (double x)  return x * x; 

      函数的类型必须与函数的返回类型相匹配

      实参的类型必须与对应的形参相匹配

(a) // 返回类型是string,所以函数类型也应该是string
    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

编写你自己的fact函数,上机检查是否正确。

#include <iostream>

int fact(int val)
{
    if (val == 0 || val == 1) return 1;
    else return val * fact(val-1);
}

int main()
{
    int j = fact(5);
    std::cout << "5! is " << j << std::endl;
    return 0;
}

练习6.4

编写一个与用户交互的函数,要求用户输入一个数字,计算生成该数字的阶乘。在main函数中调用该函数。

#include <iostream>
#include <string>

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

int fact(int val)
{
    int ret = 1;
    while (val > 1) ret *= val--;
    return ret;
}

void factorial_with_interacts()
{
    for (int val = 0; cout << "Enter a number within [0, 13): ", cin >> val;) {
        if (val < 0 || val > 12) continue;                // int最大能表示12的阶乘
        cout << val << "! =" << fact(val) << endl;
    }
}

int main()
{
    factorial_with_interacts();
}

练习6.5

编写一个函数输出其实参的绝对值。

int abs(int My_number)
{
	return My_number >= 0 ? My_number : -My_number;
}

练习6.6

说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时达到这三种形式。

      局部变量:定义在一个块中的变量。

      形参:在函数参数列表中声明的局部变量,函数终值,形参也被销毁。

      局部静态变量:在第一次执行通过对象定义之前初始化局部静态变量(对象)。函数结束时不破坏局部静态;它们在程序终止时被销毁。

size_t count_add(int n)                  // n 是一个形参
{
    static size_t ctr = 0;               // ctr 是一个局部静态变量
    ctr += n;
    return ctr;
}

int main()
{
    for (size_t i = 0; i != 10; ++i)     // i 是一个局部变量
      cout << count_add(i) << endl;

    return 0;
}

练习6.7

编写一个函数,当它第一次被调用时返回0,以后每次被调用返回值加1。

size_t generate()
{
    static size_t ctr = 0;
    return ctr++;
}

练习6.8

编写一个名为Chapter6.h 的头文件,令其包含6.1节练习中的函数声明。

int fact(int val);
int func();
int abs(int My_number);

练习6.10

编写一个函数,使用指针形参交换两个整数的值。在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。

#include <iostream>
#include <string>

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

void swap(int* const lhs, int* const rhs)
{
    auto tmp = *lhs;
    *lhs = *rhs;
    *rhs = tmp;
}

int main()
{
    for (int lht, rht; cout << "Please Enter:\n", cin >> lht >> rht;) {
        swap(&lht, &rht);
        cout << lht << " " << rht << endl;
    }
}

练习6.11

编写并验证你自己的reset函数,使其作用于引用类型的参数。

#include <iostream>

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

int main()
{
    int i = 42;
    reset(i);
    std::cout << i << std::endl;
}

练习6.12

改写6.2.1节练习中的程序,使其引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?

#include <iostream>
#include <string>

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

void swap(int& lhs, int& rhs)
{
    auto tmp = lhs;
    lhs = rhs;
    rhs = tmp;
}

int main()
{
    for (int left, right; cout << "Please Enter:\n", cin >> left >> right;) {
        swap(left, right);
        cout << left << " " << right << endl;
    }
}

练习6.13

假设 T 是某种类型的名字,说明以下两个函数声明的区别:一个是void f(T), 另一个是 void f(&T)。

  • 通过使用引用形参,可以改变实参的值。
  • 拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型根本不支持拷贝操作,所以函数只能通过引用形参访问该类型的对象。
  • 当函数需要多个返回值时,可以使用引用形参返回多个值。一是定义一个新的数据类型,二是传入一个额外的引用实参,隐式的返回。

      void f(T)通过值传递参数。函数对形参做的任何事情都不会影响实参。void f(T&)传递一个引用,将绑定到传递的T对象上,对形参的操作等于对实参操作。

练习6.14

举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。

      应该是引用的例子:

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

      不应该是引用的例子: 

void print(std::vector<int>::iterator begin, std::vector<int>::iterator end)
{
        for (std::vector<int>::iterator iter = begin; iter != end; ++iter)
                std::cout << *iter << std::endl;
}

练习6.15

说明find_char 函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occurs是引用类型而c不是?如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?

      为什么s是常量引用而occurs是普通引用?

因为s不应该被这个函数改变。但是occurs的结果必须由函数计算,并保存字符出现的次数然后隐式的返回。

      为什么s和occurs是引用类型而c不是?

这里使用const引用是可以的,但是直接复制一个char会更节省内存。

      如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?

s能够在函数中被修改,如果令occurs为常量引用,则不能被修改,所以occurs为0,这样得不到我们想要的结果。

练习6.16

下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法改善。

      因为这个函数不会改变参数,所以应该在string&s之前添加“const”,否则这个函数会给函数调用者一个误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大的限制函数所能接受的实参类型,不能把const对象,字面值或者需要类型转换的对象传递给普通的引用形参。

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

练习6.17

编写一个函数,判断string对象中是否含有大写字母。编写另一个函数,把string对象全部改写成小写形式。在这两个函数中你使用的形参类型相同吗?为什么?

      对于第一个参数,使用了“const”,因为不需要对参数做任何更改。对于第二个函数,不能使用“const”,因为参数的内容需要更改。

#include <iostream>
#include <string>

using std::string;

bool hasUppercase(const string& str)
{
    for (auto c : str)
        if (isupper(c)) return true;
    return false;
}

const string& makeLowercase(string& str)
{
    for (auto& c : str)
        if (isupper(c)) c = tolower(c);
    return str;
}

int main()
{
    string str("Hello World!");
    std::cout << std::boolalpha << hasUppercase(str) << std::endl;
    std::cout << makeLowercase(str) << std::endl;
}

练习6.18

为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。

  • (a) 名为 compare 的函数,返回布尔值,两个参数都是 matrix 类的引用。
  • (b) 名为 change_val 的函数,返回vector的迭代器,有两个参数:一个是int,另一个是vector的迭代器。
(a):
bool compare(const matrix &m1, const matrix &m2);

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

练习6.19

假定有如下声明,判断哪个调用合法、哪个调用不合法。对于不合法的函数调用,说明原因。

(a) calc(23.4, 55.1);                   // 不合法, 函数只有一个参数,传入两个不合法
(b) count("abcda", 'a');                // 合法
(c) calc(66);                           // 合法
(d) sum(vec.begin(), vec.end(), 3.8);   // 合法

练习6.20

引用形参什么时候应该是常量引用?如果形参应该是常量引用,而我们将其设为了普通引用,会发生什么情况?

      无需在函数中改变的参数应该设为常量引用,如果我们可以使用const,就使用它。如果我们在一个参数可以作为const的引用时,将其作为普通引用,那么这个引用的值可能会改变。

练习6.21

编写一个函数,令其接受两个参数:一个是int型的数,另一个是int指针。函数比较int的值和指针所指的值,返回较大的那个。在该函数中指针的类型应该是什么?

#include <iostream>

int LargerOne(int i, const int* const ip)
{
    return (i > *ip) ? i : *ip;
}

int main()
{
    int c = 6;
    std::cout << LargerOne(7, &c) << std::endl;
}

练习6.22

编写一个函数,令其交换两个int指针。

#include <iostream>

void swap(const int*& lhs, const int*& rhs)
{
    auto temp = lhs;
    lhs = rhs;
    rhs = temp;
}

int main()
{
    const int i = 42, j = 99;
    auto lhs = &i;
    auto rhs = &j;
    swap(lhs, rhs);
    std::cout << *lhs << " " << *rhs << std::endl;
}

练习6.23

参考本节介绍的几个print函数,根据理解编写你自己的版本。依次调用每个函数使其输入下面定义的i和j:

#include <iostream>

using std::begin;
using std::cout;
using std::end;
using std::endl;

void print(int* const pi)
{
    if (pi) cout << *pi << endl;
}

void print(const char* p)
{
    if (p)
        while (*p) cout << *p++;
    cout << endl;
}

void print(const int* beg, const int* end)
{
    while (beg != end) cout << *beg++ << " ";
    cout << endl;
}

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

void print(const int (&arr)[2])
{
    for (auto i : arr) cout << i << " ";
    cout << endl;
}

int main()
{
    int i = 0, j[2] = {0, 1};
    char ch[5] = "pezy";

    print(ch);
    print(begin(j), end(j));
    print(&i);
    print(j, end(j) - begin(j));
    print(const_cast<const int(&)[2]>(j));
}

练习6.24

描述下面这个函数的行为。如果代码中存在问题,请指出并改正。

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

      当我们传递一个数组给一个函数时,实际上是在传递一个指向数组第一个元素的指针。这题,参数中的const int ia[10]实际上和const int*是一样的,数组的大小是无关的,const int ia[3]或const int ia[255],没有区别。所以如果我们传入的实参数组是int ia[5],那么在该函数中的for循环里访问ia[5]时就会出错,数组越界。如果我们想要传递一个大小为10的数组,我们应该像这样使用引用:

void print10(const int (&ia)[10]) { /*...*/ }

练习6.25&6.26

编写一个main函数,令其接受两个实参。把实参的内容连接成一个string对象并输出出来。

编写一个程序,使其接受本节所示的选项;输出传递给main函数的实参内容。

      使用argv中的实参时,可选参数应该从argv[1]开始,argv[0]保存的是程序的名字。

#include <iostream>
#include <string>

int main(int argc, char** argv)
{
    std::string str;
    for (int i = 1; i != argc; ++i) {
        str += argv[i];
        str += " ";
    }

    std::cout << str << std::endl;
}

练习6.27

编写一个函数,它的参数是initializer_list类型的对象,函数的功能是计算列表中所有元素的和。

      initializer_list对象中的元素永远是常量值,无法改变initializer_list对象中元素的值。

#include <initializer_list>
#include <iostream>

int sum(const std::initializer_list<int>& il)
{
    int sum = 0;
    for (auto i : il) sum += i;
    return sum;
}

int main(void)
{
    std::cout << sum({1, 2, 3, 4, 5}) << std::endl;
}

练习6.28

在error_msg函数的第二个版本中包含ErrCode类型的参数,其中循环内的elem是什么类型?

      for循环中的elem类型是const std::string&。

练习6.29

在范围for循环中使用initializer_list对象时,应该将循环控制变量声明成引用类型吗?为什么?

      取决于initializer_list元素的类型。当类型是内置类型时,引用是不必要的。因为内置类型复制起来很容易(比如int)。否则,使用引用(const)是更好的选择。

练习6.31

什么情况下返回的引用无效?什么情况下返回常量的引用无效?

      返回的引用无效:局部临时变量或者局部对象的引用对于返回都是无效的,因为在函数终止之后,局部变量或者对象的引用不再指向有效的内存区域。若是常量在函数调用之前存在,引用即可用。

练习6.32

下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。

      合法,返回的是数组第1~10个元素。

练习6.33

编写一个递归函数,输出vector对象的内容。

#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;
using Iter = vector<int>::iterator;

void print(Iter beg, Iter end)
{
    if (beg != end) {
        cout << *beg << " ";
        print(++beg, end);
    }else
    {
        cout << endl;
        return;
    }
}

int main()
{
    vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
    print(vec.begin(), vec.end());
}

练习6.34

如果factorial 函数的停止条件如下所示,将发生什么?

if (val != 0)

当递归终止条件变成var != 0时,可能会出现两种情况:

  • 情况1:如果参数为正,递归在0处停止。
  • 情况2:如果参数是负的,递归就不会停止,函数将会不断地调用它自身直到程序栈空间耗尽为止。

练习6.35

在调用factorial函数时,为什么我们传入的值是 val-1 而非 val--

val--会把val减1再返回val的初始值,递归函数会一直使用val作为参数,使程序陷入无限循环。

练习6.36

编写一个函数声明,使其返回数组的引用并且该数组包含10个string对象。不用使用尾置返回类型、decltype或者类型别名。

string (&func(string (&arrStr)[10]))[10]

/************************************************************
逐层理解:
  func(string (&arrStr)[10]),名为func的函数有一个string型的含有10个元素的数组的引用参数
  (&func(string (&arrStr)[10])),表明我们可以对函数返回的结果也是一个引用
  (*func(int i))[10],表明对函数的返回是一个大小是10的数组的引用
  string (&func(string (&arrStr)[10]))[10],表示返回的数组中的元素是string类型
************************************************************/

练习6.37

为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用decltype关键字。你觉得哪种形式最好?为什么?

using ArrT = string[10];
ArrT& func1(ArrT& arr);                      // 使用类型别名

auto func2(ArrT& arr) -> string(&)[10];      // 使用尾置返回类型

string arrS[10];
decltype(arrS)& func3(ArrT& arr);            // 使用decltype关键字

练习6.38

修改arrPtr函数,使其返回数组的引用。

decltype(arrStr)& arrPtr(int i)
{
          return (i % 2) ? odd : even;
}

练习6.39

说明在下面的每组声明中第二条语句是何含义。如果有非法的声明,请指出来。

  • 函数的重载必须有形参数量或者形参类型上的不同
  • 顶层const不影响传入函数的对象
(a) int calc(int, int);
	int calc(const int, const int);          // 合法,重复声明可以,重复定义不行
(b) int get();
	double get();                            // 不合法,仅返回值不同
(c) int *reset(int *);
	double *reset(double *);                 // 合法

练习6.40

下面的哪个声明是错误的?为什么?

在给定的作用域中一个形参只能被赋予一次默认实参,函数的后续声明只能为那些没有默认值的形参添加默认值,而且一旦函数的某个形参被赋予了默认值,他后面所有的参数都必须有默认值。

(a) int ff(int a, int b = 0, int c = 0);                  // 正确
(b) char *init(int ht = 24, int wd, char bckgrnd);        // 错误,缺少参数'wd', 'bckgrnd'的默认参数。

练习6.41

下面的哪个调用是非法的?为什么?哪个调用虽然合法但显然与程序员的初衷不符?为什么?

char *init(int ht, int wd = 80, char bckgrnd = ' ');
(a) init();                              // 非法,函数第一个形参没有默认实参,必须给实参
(b) init(24, 10);                        // 合法
(c) init(14, '*');                       // 合法,但与初衷不符,'*'转换成整型了

练习6.42

给make_plural函数的第二个形参赋予默认实参’s’, 利用新版本的函数输出单词success和failure的单数和复数形式。

#include <iostream>
#include <string>

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

string make_plural(size_t ctr, const string& word, const string& ending = "s")
{
    return (ctr > 1) ? word + ending : word;
}

int main()
{
    cout << "singual: " << make_plural(1, "success", "es") << " "
         << make_plural(1, "failure") << endl;
    cout << "plural : " << make_plural(2, "success", "es") << " "
         << make_plural(2, "failure") << endl;
}

练习6.43

你会把下面的哪个声明和定义放在头文件中?哪个放在源文件中?为什么?

(a) // 放在头文件中,内联函数在程序中可以多次定义,它的多个定义必须完全一致,所以放在头文件中比较好
    inline bool eq(const BigInt&, const BigInt&) {...}
(b) // 放在头文件中,声明放在头文件中
    void putValues(int *arr, int size);

练习6.44

将6.2.2节的isShorter函数改写成内联函数。

      将函数指定为“内联函数(inline)”,将它在每个调用点上“内联的展开”,该说明只是向编译器发出一个请求,编译器可以选择忽略这个请求。内联的机制用于优化规模较小、流程直接、频繁调用的函数,建议不大于75行。

#include <iostream>
#include <string>

using std::string;

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

int main()
{
    std::cout << isShorter("pezy", "mooophy") << std::endl;
}

练习6.45

回顾在前面的练习中你编写的那些函数,它们应该是内联函数吗?如果是,将它们改写成内联函数;如果不是,说明原因。

6.38和6.42应该是内联函数;6.4不应该是,规模不小,调用不频繁。

练习6.46

能把isShorter函数定义成constexpr函数吗?如果能,将它改写成constxpre函数;如果不能,说明原因。

      constexpr函数是指能用于常量表达式的函数:函数的返回值类型和所有形参的类型必须是“字面值类型”:算术、引用、指针。并且函数体内有且只有一条return语句。

      不能,因为std::string::size(); 不是一个constexpr函数,s1.size() == s2.size(); 不是一个常量表达式。

练习6.47

改写6.3.2节练习中使用递归输出vector内容的程序,使其有条件地输出与执行过程有关的信息。例如,每次调用时输出vector对象的大小。分别在打开和关闭调试器的情况下编译并执行这个程序。

  • 预处理宏assert(expr):包含一个表达式,expr为真时,assert什么也不做,为假时输出信息并终止程序。包含在cassert头文件中。通常用于检查不能发生的条件
  • assert依赖于一个NDEBUG的预处理变量的状态,如果定义了NDEBUG,assert什么也不做,默认状态下NDEBUG是未定义的。编译器也可以预先定义该变量。
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;

//#define NDEBUG

void printVec(vector<int>& vec)
{
#ifndef NDEBUG
    cout << "vector size: " << vec.size() << endl;
#endif
    if (!vec.empty()) {
        auto tmp = vec.back();
        vec.pop_back();
        printVec(vec);
        cout << tmp << " ";
    }
}

int main()
{
    vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
    printVec(vec);
    cout << endl;
}

练习6.48

说明下面这个循环的含义,它对assert的使用合理吗?

string s;
while (cin >> s && s != sought) { } //空函数体
assert(cin);

这个循环让用户一直输入一个单词,直到找到sought这个单词为止。

这不是一个好的assert用法。assert宏通常用于检查“不能发生”的条件。但是assert总是在用户直接输入EOF时发生,所以检查是没有意义的。使用assert(!cin || s == sought)更好。

练习6.49

什么是候选函数?什么是可行函数?

      候选函数具备两个特征:一是与被调用的函数同名,二是其声明在调用点可见。
      可行函数是从候选函数中选出的,有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型。

练习6.50

已知有第217页对函数 f 的声明,对于下面的每一个调用列出可行函数。其中哪个函数是最佳匹配?如果调用不合法,是因为没有可匹配的函数还是因为调用具有二义性?

(a) f(2.56, 42)                  // 不合法,出现了二义性
(b) f(42)                        // 合法,最佳匹配void f(int)
(c) f(42, 0)                     // 合法,最佳匹配void f(int, int)
(d) f(2.56, 3.14)                // 合法,最佳匹配void f(double, double = 3.14)

练习6.51

编写函数f的4版本,令其各输出一条可以区分的消息。验证上一个练习的答案,如果你的回答错了,反复研究本节内容直到你弄清自己错在何处。

#include <iostream>
using std::cout; using std::endl;

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

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

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

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

int main()
{
    //f(2.56, 42); // error: 'f' is ambiguous.
    f(42);
    f(42, 0);
    f(2.56, 3.14);
    
    return 0;
}

练习6.52

已知有如下声明:

void manip(int ,int);
double dobj;
请指出下列调用中每个类型转换的等级。

(a) manip('a', 'z');
(b) manip(55.4, dobj);

(a) manip('a', 'z');                      // 通过类型提升实现的匹配
(b) manip(55.4, dobj);                    // 通过算数类型转换

练习6.53

说明下列每组声明中的第二条语句会产生什么影响,并指出哪些不合法(如果有的话)。

(a) int calc(int&, int&); 
	int calc(const int&, const int&);             // 合法,实参可以为const int
(b) int calc(char*, char*);
	int calc(const char*, const char*);           // 合法,实参可以为const char*
(c) int calc(char*, char*);
	int calc(char* const, char* const);           // 合法,顶层const,声明重复(可以重复声明,不可重复定义)

练习6.54

编写函数的声明,令其接受两个int形参并返回类型也是int;然后声明一个vector对象,令其元素是指向该函数的指针。

int func(int a, int b);

using pFunc1 = decltype(func) *;
typedef decltype(func) *pFunc2;
using pFunc3 = int (*)(int a, int b);
using pFunc4 = int(int a, int b);
typedef int(*pFunc5)(int a, int b);
using pFunc6 = decltype(func);

std::vector<pFunc1> vec1;
std::vector<pFunc2> vec2;
std::vector<pFunc3> vec3;
std::vector<pFunc4*> vec4;
std::vector<pFunc5> vec5;
std::vector<pFunc6*> vec6;

练习6.55

编写4个函数,分别对两个int值执行加、减、乘、除运算;在上一题创建的vector对象中保存指向这些函数的指针。

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }

练习6.56

调用上述vector对象中的每个元素并输出结果。

std::vector<decltype(func) *> vec{add, subtract, multiply, divide};
for (auto f : vec)
          std::cout << f(2, 2) << std::endl;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值