C ++ Primer(第五版)第六章练习答案

6.1 节练习

练习 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)返回值与函数返回类型不同,将函数返回类型从 int 改为 string。

(b)没有定义函数返回类型。

(c)形参不能同名,函数体没有被花括号包裹。

(d)函数体没有被花括号包裹。

练习 6.3

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

#include<iostream>

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

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

int main()
{
    int i = fact(5);
    cout << i << endl;
    return 0;
}

练习 6.4

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

#include<iostream>

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

void fact()
{
    int val;
    cout << "请输入一个数字: ";
    cin >> val;
    cout << val << " 的阶乘是: ";
    int ret = 1;
    while (val > 1)
    {
        ret *= val--;
    }
    cout << ret << endl;
}

int main()
{
    fact();
    return 0;
}

练习 6.5

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

#include<iostream>

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

int absv(int val)
{
    if (val < 0)
    {
        return -val;
    }
    else
    {
        return val;
    }
}

int main()
{
    int a;
    cout << "请输入一个数字: ";
    cin >> a;
    cout << a << " 的绝对值是: " << absv(a) << endl;
    return 0;
}

6.1.1 节练习

练习 6.6

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

形参是一种在函数内部被初始化和使用的参数,是一种局部变量。

形参和函数体内部定义的变量统称为局部变量。

局部静态变量能够在函数调用结束后仍不被销毁,存在至整个程序结束后才被销毁。

int fact(int val) {    //形参
	static int ret = 1;    //局部静态变量
	int fac = 1;           //局部变量
	while (val > 1)
		fac *= val;
	ret += fac;
	return ret;

练习 6.7

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

size_t count_calls()
{
    static size_t ctr = -1;
    return ++ctr;
}

6.1.2 节练习

练习 6.8

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

#ifndef CHAPTER6_H
#define CHAPTER6_H

int fact(int n);
int absv(int n);

#endif

6.1.3 节练习

练习 6.9

编写你自己的 fact.cc 和 factMain.cc ,这两个文件都应该包含上一小节的练习中编写的 Chapter6.h 头文件。通过这些文件,理解你的编译器是如何支持分离式编译的。

fact.cpp

#include "Chapter6.h"

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

factMain.cc

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

int main()
{
	std::cout << fact(5) << std::endl;

	return 0;
}

6.2.1 节练习

练习 6.10

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

#include<iostream>

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

void swt(int *p,int *q)
{
    int temp = *p;
    *p = *q;
    *q = temp;
}

int main()
{
    int a = 1, b = 2;
    int *p = &a, *q = &b;
    cout << *p << " 与 " << *q << " 交换后为: ";
    swt(p, q);
    cout << *p << " 与 " << *q << endl;
    return 0;
}

6.2.2 节练习

练习 6.11

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

#include<iostream>

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

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

int main()
{
    int a = 2;
    reset(a);
    cout << a << endl;
    return 0;
}

练习 6.12

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

#include<iostream>

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

void swt(int &i,int &j)
{
    int temp = i;
    i = j;
    j = temp;
}

int main()
{
    int a = 1, b = 2;
    cout << a << " 与 " << b << " 交换后为: ";
    swt(a, b);
    cout << a << " 与 " << b << endl;
    return 0;
}

引用更易于使用,引用就是被引用对象的别名,不容易出错,指针本身和指向的对象可能会造成混淆。

练习 6.13

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

void f(T)
是将实参拷贝给形参 T,不能通过改变形参 T 进而改变实参;
void f(&T)
是引用形参 T 绑定到实参上,可以通过改变形参 T 进而改变实参。

练习 6.14

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

将一个字符串的小写字母变成大写应该用引用;
理论上什么情况下都可以是引用类型,如果不能改变实参的参数,就使用常量引用吧。

练习 6.15

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

s:
现在是常量引用。首先因为它不需要改变所以使用常量,如果使用普通引用可能会出现改变实参本身;其次由于 s 是字符串类型,使用引用可以避免拷贝大的类型对象;
occurs:
现在是非常量引用。是用来记录 c 出现的次数,如果使用常量引用则不能改变,无法记录;使用引用类型是因为在函数结束后需要返回记录值。
c:
现在是普通形参。因为它是字符,不用改变,容量小,可以拷贝。

6.2.3 节练习

练习 6.16

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

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

该函数无需改变实参,所有应该设置为 const,这样也可以传入 const 类型的字符串,或字符串字面值。

练习 6.17

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

判断

bool is_upper(const string &s)
{
    for (auto a : s)
    {
        if (isupper(a))
        {
            return true;
        }
    }
    return false;
}

改写

void to_upper(string &s)
{
    for(auto &a:s)
    {
        a = tolower(a);
    }
}

判断函数不需要改变实参本身,所以使用常量引用;而改写需要改变实参本身,所有使用非常量引用。

练习 6.18

为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。
(a)名为 compare 的函数,返回布尔值,两个参数都是 matrix 类的引用。
(b)名为 change_val 的函数,返回 vector 的迭代器,有两个参数:一个是 int,另一个是 vector 的迭代器。

bool compare(const matrix &a, const matrix &b);
// 比较两个 matrix 对象的大小

vector<int>::iterator change_val(int i, vector<int>::iterator a);
// 改变 vector 中的某个值

练习 6.19

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

double calc(double);
int count(const string &, char);
int sum(vector<int>::iterator, vector<int>::iterator, int);
vector<int> vec(10);
(a) calc(23.4, 55.1);
(b) count("abcda",'a');
(c) calc(66);
(d) sum(vec.begin(), vec.end(), 3.8);

(a)不合法。实参形参数量不匹配;
(b)合法;
(c)合法;
(d)合法。

练习 6.20

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

在不改变实参的情况下应该为常量引用;
如果形参是普通引用,则不能把它绑定到常量实参上,而且会让函数认为可以改变实参本身。

6.2.4 节练习

练习 6.21

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

int compare(const int a,const int *p)
{
    return a > *p ? a : *p;
}

练习 6.22

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

void swap_ip(int *&p, int *&q)
{
    int *temp;
    temp = p;
    p = q;
    q = temp;
}

练习 6.23

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

#include<iostream>

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

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

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

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

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

int main()
{
    int i = 0, j[2] = {0, 1};

    print(&i);
    print(begin(j), end(j));
    print(j, end(j) - begin(j));
    print(j);
    return 0;
}

练习 6.24

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

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

ia[10] 实际上是指向数组的首元素的指针,实参可以是任意大小的数组,如果数组大小小于 10,会出现错误。改为:

void print(const int (&arr)[10]);
// 这样实参数组的大小必须是 10

6.2.5 节练习

练习 6.25

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

#include<iostream>
#include<string>

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

int main(int argc,char *argv[])
{
    string s1 = argv[1], s2 = argv[2];
    cout << s1 + s2 << endl;
    return 0;
}

结果

6.25 wo rld ofile data0
world

练习 6.26

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

#include<iostream>
#include<string>

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

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

结果

6.26 -d-o ofile data0
6.26
-d-o
ofile
data0

6.2.6 节练习

练习 6.27

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

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

练习 6.28

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

const string&

练习 6.29

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

如果实参是占大量空间的类型,可以声明成引用类型,如果不大的类型,可以不用,因为 initializer_list 都是常量类型,即使用引用也不能改变实参本身。

6.3.2 节练习

练习 6.30

编译第 200 页的 str_subrange 函数,看看你的编译器是如何处理函数中的错误的。

error: return-statement with no value, in function returning 'bool'

练习 6.31

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

当返回的是局部对象的引用无效,因为局部对象的生命周期只有在函数体内,一旦结束,引用就指向无效的内存区域;
当返回的对象是需要修改的,那么返回常量的引用无效。

练习 6.32

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

int &get(int *array, int index) { return array[index]; }
int main()
{
    int ia[10];
    for (int i = 0; i != 10; ++i)
        get(ia, i) = i;
}

合法。
用数组的下标给其赋值。

练习 6.33

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

#include<iostream>
#include<vector>

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

void print(vector<int>::iterator begin,vector<int>::iterator end)
{
    if (begin!=end)
    {
        cout << *begin << " ";
        print(++begin, end);
    }
    else
    {
        cout << endl;
    }
}

int main()
{
    vector<int> iv = {0, 1, 2, 3, 4, 5};
    print(iv.begin(), iv.end());

    return 0;
}

练习 6.34

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

if (val != 0)

如果实参是正数,最后多乘一个 1;
如果实参是负数,函数会不停的调用自身,直到程序栈空间耗尽为止。

练习 6.35

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

val – 会将 val 返回函数,然后再减 1,则仍是 factorial(val),死循环。

6.3.3 节练习

练习 6.36

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

string (&func(string s))[10];

练习 6.37

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

using arrS = string[10];
arrS &func(string s);

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

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

练习 6.38

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

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

6.4 节练习

练习 6.39

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

(a) int calc(int, int);
	int calc(const int, const int);
(b) int get();
	double get();
(c) int *reset(int *);
	double *reset(double *);

(a)非法;形参的顶层 const 不能与非常量形参区分;

(c)非法;只有返回值类型不同不能区分函数;

(b)合法。reset () 的 double 版本,形参类型不同与 int 版本区分。

6.5.1节练习

练习 6.40

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

(a) int ff(int a, int b = 0, int c = 0);
(b) char *init(int ht = 24, int wd, char bckgrnd);

(b)错误;因为一旦一个形参被赋予了默认值,它后面的形参都必须有默认值,即 wd 和 backgrnd 都应该有默认值。

系列 6.41

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

char *init(int ht, int wd = 80, char backgrnd = ' ')a;
(a) init();
(b) init(24, 10);
(c) init(14, '*');

(a)非法。确实 ht 的实参;

(b)合法;

(c)合法;但是与程序员初衷不符,因为调用函数将 * 转换成 int 类型传递给了 wd,而没有给 backgrnd。

练习 6.42

给 make_plural 函数(参见6.3.2节,第201页)的第二个形参赋予默认实参 ’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()
{
    string w1 = "success", w2 = "failure";
    cout <<w1<<" 的单数形式是: "<< make_plural(1, w1) <<" 复数形式是: "<<make_plural(2,w1,"es")<< endl;
    cout <<w2<<" 的单数形式是: "<< make_plural(1, w2) <<" 复数形式是: "<<make_plural(2,w2)<< endl;
    return 0;
}

6.5.2 节练习

练习 6.43

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

(a) inline bool eq(const BigInt&, const BigInt&) {...}
(b) void putValues(int *arr, int size);

(a)声明和定义都放在头文件中,内联函数在程序中可以多次定义,它的多个定义必须完全一致,所以放在头文件中比较好;

(b)声明放在头文件中,定义放在源文件中。

练习 6.44

将 6.2.2 节(第189页)的 isShorter 函数改写成内联函数。

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

练习 6.45

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

6.38 和 6.42 应该是内联函数;其他规模都较大,假如编译过程中展开太过复杂,不应该改写成内联函数。

练习 6.46

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

不能;constexpr 函数要求形参和返回类型都得是字面值类型。而 s1.size() < s2.size() 不构成一个常量表达式,故也不能返回字面值类型。

6.5.3 节练习

练习 6.47

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

#include<iostream>
#include<vector>
#include<cassert>

// #define NDEBUG

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

void print(vector<int>::iterator begin,vector<int>::iterator end)
{
    #ifndef NDEBUG
        cout << "vector 的大小是:" << end - begin << endl;
    #endif
    if (begin!=end)
    {
        cout << *begin << " ";
        print(++begin, end);
    }
    else
    {
        cout << endl;
    }
}

int main()
{
    vector<int> iv = {0, 1, 2, 3, 4, 5};
    print(iv.begin(), iv.end());

    return 0;
}

练习 6.48

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

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

持续读取 cin 到 s 中,直到输入字符串为空或者等于 sought 为止。

不合理;assert 适于检查“不能发生”的条件,而该句中只要用户有输 cin 便为 true(1),assert 什么也不做。

6.6 节练习

练习 6.49

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

候选函数:一是与被调用的函数同名;二是其声明在调用点可见。

可行函数:一是形参的数量与本次调用提供的实参数量相等;二是每个实参的类型与形参的类型相同,或者能转换成形参的类型。

练习 6.50

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

(a) f(2.56, 42)
(b) f(42)
(c) f(42, 0)
(d) f(2.56, 3.14)

(a)可行函数:

f(int,int);
f(double,double = 3.14);
// 不合法,调用具有二义性

(b)可行函数:

f(int)// 最佳匹配
f(double,double = 3.14)

(c)可行函数:

f(int,int);// 最佳匹配
f(double,double = 3.14);

(d)可行函数:

f(int,int);
f(double,double = 3.14);// 最佳匹配

练习 6.51

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

#include<iostream>

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

void f()
{
    cout << "调用函数 " << __func__ << "()" << endl;
}

void f(int)
{
    cout << "调用函数 " << __func__ << "(int)" << endl;
}

void f(int,int)
{
    cout << "调用函数 " << __func__ << "(int,int)" << endl;
}

void f(double,double = 3.14)
{
    cout << "调用函数 " << __func__ << "(double,double = 3.14)" << endl;
}

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

    return 0;
}

6.6.1 节练习

练习 6.52

已知有如下声明:

void manip(int , int);
double dobj;

请指出下列调用中每个类型转换的等级(参见6.6.1节,第219页)。

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

(a)通过类型提升实现的匹配;

(b)通过算数类型转换实现的匹配。

练习 6.53

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

(a) int calc(int&, int&);
	int calc(const int&, const int&);
(b) int calc(char*, char*);
	int calc(const char*, const char*);
(c) int calc(char*, char*);
	int calc(char* const, char* const);

(a)(b)都对原函数进行重载;

(c)不合法。顶层 const 形参和非顶层 const 形参无法区分。

6.7 节练习

练习 6.54

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

int a(int, int);
vecter<int (*)(int, int)> v;

练习 6.55

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

#include<iostream>
#include<vector>

using std::vector;

int add(int i,int j)
{
    return i + j;
}

int sub(int i,int j)
{
    return i - j;
}

int mult(int i,int j)
{
    return i * j;
}

int divi(int i,int j)
{
    return i / j;
}

int main()
{
    vector<int (*)(int, int)> v = {add, sub, mult, divi};
    return 0;
}

练习 6.56

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

#include<iostream>
#include<vector>

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

int add(int i,int j)
{
    return i + j;
}

int sub(int i,int j)
{
    return i - j;
}

int mult(int i,int j)
{
    return i * j;
}

int divi(int i,int j)
{
    return i / j;
}

int main()
{
    vector<int (*)(int, int)> v = {add, sub, mult, divi};
    for(auto i:v)
    {
        cout << i(10, 2) << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值