C++ Primer(第五版)|练习题答案与解析(第六章:函数)

C++ Primer(第五版)|练习题答案与解析(第六章:函数)

本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++ Primer
C++ Primer

练习题6.1

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

形参指的是定义在函数参数列表中的局部变量。被调用者初始化。
实参指的是调用函数时给参数赋的初始值,实参的类型必须与形参类型匹配。P183

练习题6.2

指出下列是否有错误?如何修改错误?

(a)

int f()
{
    string s;// 定义函数f的返回值是int,但实际却返回的string类型,错误。应改为:string f()
    // ...
    return s;
}

(b)f2 (int i) {/* ... */} 未定义函数的返回值。改为void f2(int i)
(c)int calc (int v1, int v1) { /* ... */ } 形参命名冲突。
(d)double square(double x) return x * x; 函数体应该用{}而不是()

练习题6.3

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

代码:

#include 
#include 
#include 

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

}
int main(void)
{
        int val = 0;
        cin >> val;

        int result = fact (val);
        cout << "val's factorial is " << result << endl;
}

测试:

5
val's factorial is 120

练习题6.5

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

代码:

#include 
#include 
#include 

using namespace std;
int absolute(int val)
{
        return (val > 0) ? val : (-val);
}
int main(void)
{
        int val;

        while (cin >> val)
        {
                cout << "the absolute of val is " << absolute(val) << endl;
        }
}

测试:

-10
the absolute of val is 10

练习题6.6

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

“形参,指的是函数声明时的函数列表中定义的局部变量,在整个函数中起作用。
局部变量,指的是定义在块中的变量,只在块中起作用。
局部静态变量:在函数体内定义的静态变量,但是当函数结束时不会被销毁,直到整个程序结束之后才会销毁。”P184-185
函数如6.7所示。

练习题6.7

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

如P185举例所示。

练习题6.8

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

int fact(int val);
int absolute(int val);

练习题6.10

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

代码:

#include 
#include 
#include 
void swap(int* lhs, int* rhs)
{
    int tmp;
    tmp = *lhs;
    *lhs = *rhs;
    *rhs = tmp;
}

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

    return 0;
}

测试:

Please Enter:
10 5
5 10
Please Enter:
8 2
2 8

练习题6.11

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

代码:

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

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

练习题6.11

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

代码:

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

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

练习题6.12

使用引用改写6.10中的交换程序,哪种方法更易于使用呢?

代码:

#include 
#include 
#include 
void swap(int& lhs, int& rhs)
{
    int temp = lhs;
    lhs = rhs;
    rhs = temp;
}

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

    return 0;
}

练习题6.13

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

前者以传值方式传入参数,不能修改实参。后者以传址的方式传入参数,可以修改实参。

练习题6.14

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

swap函数中,参数就应该使用引用。find_char函数张参数c就不应该使用引用。原因在各题目中有阐述。

练习题6.15

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

“s是常量的原因 – find_char函数不应该在函数内部修改s的值,只读不写,因此该参数应该使用常量引用。
occurs是普通引用,因为occurs需要在函数中重写,因此不能是常量引用。
c不能是引用类型的原因,用户可能这样使用find_char(s, ‘a’, occurs);此时给参数c传递的是一个字符常量,如果定义c为引用就会报错,因为不能用字面值初始化一个非常量引用。”
“如果s是普通引用,则s有可能会在函数中被修改,导致原值发生变化。
如果occurs定义为常量引用,则occurs则不能在函数中被复制,不能统计字符出现的次数。”

练习题6.16

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

函数:bool is_empty (string& s) { return s.empty(); }
因为函数不会改变s的值,因此传递的参数应为cons string& s,否则实参为字符常量或const字符串都无法正常使用该函数,应改为:bool is_empty (const string& s) { return s.empty(); }

练习题6.17

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

代码:

#include 
#include 
#include 
using namespace std;
bool any_capital(string const& str)
{
    for (auto ch : str)
        if (isupper(ch)) return true;
    return false;
}

void to_lowercase(string& str)
{
    for (auto& ch : str) ch = tolower(ch);
}

int main()
{
    string hello("Hello World!");
    cout << any_capital(hello) << endl;

    to_lowercase(hello);
    cout << hello << endl;

    return 0;
}

测试:

1
hello world!

判断是否有大写字母应该使用const 引用类型,因为不会改变该参数的值。而转换为大写字母不能使用const类型。

练习题6.18

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

(a) 名为compare的函数,返回bool,两个参数都是matrix类的引用:bool compare (const matrix& a, const matrix& b);
(b) 名为change_val的函数,返回vector的迭代器,有两个参数:一个是int,另一个是vector的迭代器:vector::iterator change_val (int val, vector::iterator iter);

练习题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); 不合法,calc函数只有一个参数。
(b) count ("abcda", 'a'); 合法
© calc (66); 合法,但会有警告
(d) sum (vec.begin(), vec.end(), 3.8); 合法,最后一个参数是int类型,传double会截断

练习题6.20

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

当函数不会改变参数值的时候,应该将形参设为常量引用。若其该为常量引用,而我们将其设为普通引用,当函数内部改变其值,将不会报错。

练习题6.21

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

代码:

#include 
#include 
#include 
using namespace std;
int larger_one(const int i, const int *const p)
{
    return (i > *p) ? i : *p;
}

int main()
{
    int i = 6;
    cout << larger_one(7, &i);

    return 0;
}

练习题6.22

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

代码:

#include 
#include 
#include 
using namespace std;
void swap(int*& lft, int*& rht)
{
    auto tmp = lft;
    lft = rht;
    rht = tmp;
}

int main()
{
    int i = 10, j = 20;
    auto lft = &i;
    auto rht = &j;
    swap(lft, rht);
    std::cout << *lft << " " << *rht << std::endl;

    return 0;
}

练习题6.23

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

代码:

#include 
#include 
#include 
using namespace std;
void print(const int *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++ << endl;
}

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

void print(int (&arr)[2])
{
    for (auto i : arr)
        cout << i << 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(j);

    return 0;
}

输出:

pezy
0
1
0
0
1
0
1

练习题6.24

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

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

数组做参数时,会退化为指针,因此函数中的const int a[10]等同于const int *a,并且长度是不确定的,传a[3]或a[255]是没有区别的。因此如果我们要确定传递一个长度为10的数组,应该这样定义:void print (const int (*a)[10]);

练习题6.25

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

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

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

练习题6.26

同上

练习题6.27

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

代码:

#include 
#include 
#include 
#include 
using namespace std;
int sum(initializer_list<int> const& il)
{
    int sum = 0;
    for (auto i : il) sum += i;
    return sum;
}
int main(void)
{
    auto il = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    cout << sum(il) << endl;

    return 0;
}

练习题6.28

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

该函数中的elem应该是const string& 类型。

练习题6.29

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

当循环控制变量是基本类型,可以不声明为引用,否则还hi应该声明成引用,因为initializer_list对象可能是各种类型,有可能是自定义类型或者string类型。此时使用引用可以避免拷贝。

练习题6.30

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

返回值的类型必须与函数类型相同(void对应无返回值)。

Non-void function 'str_subrange' should return a value. // error #1
Control may reach end of non-void function. // error #2

练习题6.31

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

返回局部引用时无效,返回局部定义的常量引用无效。要确保返回的引用有效,就要确定引用所返回的是在函数之前已经存在的某个对象。P202

练习题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;
}

该函数合法,其功能是从0递增,初始化一个数组。本题中是将一个长度未10的数组初始化未0–9。

练习题6.33

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

代码:

#include 
#include 
#include 
#include 
using namespace std;
void print(vector<int>::const_iterator first, vector<int>::const_iterator last)
{
    if (first != last)
    {
        cout << *first << " ";
        print(++first, last);
    }
}

int main()
{
    vector<int> vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    print(vec.cbegin(), vec.cend());
    return 0;
}

1.iterator,const_iterator作用:遍历容器内的元素,并访问这些元素的值。iterator可以改元素值,但const_iterator不可改。跟C的指针有点像。
2.const_iterator 对象可以用于const vector 或非 const vector,它自身的值可以改(可以指向其他元素),但不能改写其指向的元素值。
3.cbegin()和cend()是C++11新增的,它们返回一个const的迭代器,不能用于修改元素。
参考:begin( )和cbegin( )异同

练习题6.34

如果factorial函数的停止条件如下所示,将发生什么情况?
if (val != 0)

那该函数的递归将永远不会停止,因为一直满足递归条件。

练习题6.35

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

val – 返回的是val的值,相当于又把val当作参数传递,递归将永远不会停止,并且第一次递归不断重复执行。

练习题6.36

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

由于数组不能被拷贝,所以函数不可以返回数组,但是我们可以返回函数的指针。利用的是类型别名的方法。
写作:string (&func(string (&str)[10]))[10]

练习题6.37

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

1.类型别名。using arrStr = string[10];arrStr& func( arrStr& arr_str );
2.尾置返回类型。auto func (string (&str)[10] ) -> string(&)[10];
3.decltype 关键字。string str[10]; decltype(str) &func ( decltype(str)& );

练习题6.38

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

代码:

#include 
#include 
#include 
#include 
using namespace std;
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
decltype(odd) &arrPtr (int i)
{
    return (i % 2) ? odd : even;
}
int main ()
{
    int (*p)[5]= &arrPtr(4);

    for (auto i : *p) {
        // 注意使用数组指针输出数组的方法
         cout << i << " ";
    }
    cout << endl;
    return 0;
}

练习题6.39

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

(a)

int calc (int, int);
int calc (const int, const int);   

属于顶层const形参,第二行是重复声明,与第一行含义相同。但是c++中允许函数重复声明。因此这两行代码合法,编译器不会报错。
(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); 错误,如果要为参数加默认值,第一个参数加了,后面的都要加。可以把需要加默认值的参数放在最后。

练习题6.41

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

char *init (int ht, int wd = 80, char bckgrnd = ' ');

(a)init (); 非法,第一个参数无默认值,应该初始化赋值。
(b) init (24, 10); 合法
© init (14, '*'); 合法,但与初衷不符,初衷应是让ht = 14, bakgrnd = ‘*’。但实际是ht = 14, wd = ‘*’

练习题6.42

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

代码:

#include 
#include 
#include 
#include 
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 << "singual: " << make_plural(1, "success", "es") << " "
         << make_plural(1, "failure") << endl;
    cout << "plural : " << make_plural(2, "success", "es") << " "
         << make_plural(2, "failure") << endl;

    return 0;
}

测试:

singual: success failure
plural : successes failures

练习题6.43

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

(a) inline bool eq (const BigInt&, const BigInt& ) { ... } 内联函数一般放在头文件中
(b) void putValues (int *arr, int size); 普通函数的声明,一般也放在头文件中

练习题6.44

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

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

练习题6.45

回顾前面练习中所写的函数,它们应该是内联函数吗?如果是,请改写为内联函数。如果不是,请说明原因。

练习题中的函数短小的,应该被定义成内联函数。改写为内联函数只需要在函数声明前加inline关键字就可以。

练习题6.46

回顾前面练习中所写的函数,它们应该是内联函数吗?如果是,请改写为内联函数。如果不是,请说明原因。

不能,因为isShorter函数中传入的参数不是字面值类型,str1.size() < str2.size()返回的也不是字面值类型。

练习题6.47

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

代码:

#include 
#include 
#include 
#include 
using namespace std;
#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;

    return 0;
}

测试:

1 2 3 4 5 6 7 8 9

注释掉://#define NDEBUG
测试:

vector size: 9
vector size: 8
vector size: 7
vector size: 6
vector size: 5
vector size: 4
vector size: 3
vector size: 2
vector size: 1
vector size: 0
1 2 3 4 5 6 7 8 9

练习题6.48

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

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

不合理,函数的意义是让用户进行输入,直到输入的字符串是sought是停止。因此assert (cin)一直为真,这条语句也就没有意义。可以改为:assert ( s == sought)

练习题6.49

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

重载函数集合中的函数称为候选函数,候选函数具备两个特征:(1)与被调用的函数同名;(2)其声明在调用点可见。
从候选函数中选出能被这组实参调用的函数成为可行函数,可行函数也有两个特征:(1)其形参数量与本次调用提供的实参数量相等;(2)每个实参的类型与对应的形参类型相同,或是能转换成形参的类型。

练习题6.50

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

(a) f (2.56, 42) 非法,因为实参类型是double, int,没有可匹配的函数。如果不是重载函数,只有一个声明f(double, double),则该语句合法。只有在重载时时非法的,要严格执行参数类型匹配。
(b)f (42) 调用 f (int)
© f (42, 0) 调用 f (int, int)
(d) f (2.56, 3.14) 调用 f (double, double = 3.14)

练习题6.51

编写函数f的4个版本,令其各输出一条可以区分的消息。验证上一个练习中的答案。

代码:

#include 
#include 
#include 
#include 
using namespace std;
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;
}
测试:

```cpp
f(int)
f(int, int)
f(double, double)

练习题6.52

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

(a) manip ('a', 'z'); 类型提升
(b) manip (55.4, dobj); 算术类型转换

练习题6.53

说明下面每组声明中的第二条语句会产生什么影响,并指出哪些不合法?

(a)

int calc (int &, int &);
    int calc (const int&, const int&); 

合法,会根据传入的实参是否时const类型决定使用哪个函数。
(b)

 int calc (char*, char*);
    int calc (const char*, const char*);

合法,会根据传入的实参是否时const类型决定使用哪个函数。
©

int calc (char*, char*);
     int calc (char* const, char* const); 

非法,与第一行含义相同,属于重复定义。

练习题6.54

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

练习题6.55

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

练习题6.55

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

代码:

#include 
#include 
#include 
#include 
using namespace std;
//
// @brief 练习题 6.54
// @note  定义函数类型fp
//
inline int f(const int, const int);
typedef decltype(f) fp;//fp只是一个函数类型,而不是一个函数指针

//
// @brief 练习题 6.55
// @note  将指向这些函数的指针存储在向量中
//
inline int NumAdd(const int n1, const int n2)  { return n1 + n2; }
inline int NumSub(const int n1, const int n2)  { return n1 - n2; }
inline int NumMul(const int n1, const int n2)  { return n1 * n2; }
inline int NumDiv(const int n1, const int n2)  { return n1 / n2; }

vector<fp*> v{ NumAdd, NumSub, NumMul, NumDiv };

int main()
{
    //
    // @brief 练习题 6.56
    // @note  调用向量中的每个元素并打印它们的结果。
    //
    for (auto it = v.cbegin(); it != v.cend(); ++it)
        cout << (*it)(2, 2) << std::endl;

    return 0;
}

测试:

4
0
4
1

你可能感兴趣的:(C++,Primer,自学)