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