《C++ Primer》第6章 6.2节习题答案

《C++ Primer》第6章 函数

6.2节 参数传递

练习6.10:编写一个函数,使用指针形参交换两个整数的值。在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。
【出题思路】
使用指针作为参数时,在函数内部交换指针的值只改变局部变量,不会影响实参原本的值,无法满足题目要求。我们应该在函数内部通过解引用操作改变指针所指的内容。
【解答】
满足题意的程序如下所示:

#include <iostream>

using namespace std;

//在函数体内部通过解引用操作改变指针所指的内容
void mySWAP(int *p, int *q)
{
    int temp = *p;   //temp是一个整数
    *p = *q;
    *q = temp;
}

int main()
{
    int a = 5, b = 10;
    int *r = &a, *s = &b;
    cout << "交换前:a = " << a << ", b = " << b << endl;
    mySWAP(r, s);
    cout << "交换后:a = " << a << ", b = " << b << endl;
    return 0;
}

运行结果:

练习6.11:编写并验证你自己的reset函数,使其作用于引用类型的参数。
【出题思路】
参考原书中的示例编写reset函数,它的参数是引用类型,从而使得我们可以直接操作引用所引的对象,再把该函数置于main函数体中检验效果。
【解答】
满足题意的程序如下所示:

#include <iostream>

using namespace std;

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

int main()
{
    int a = 10;
    cout << "重置前:a = " << a << endl;
    reset(a);
    cout << "重置后:a = " << a << endl;
    return 0;
}

运行结果:

练习6.12:改写6.2.1节中练习6.10(第188页)的程序,使用引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?
【出题思路】
使用引用可以直接操作所引用的对象,从而实现对象内容的交换。
【解答】
满足题意的程序如下所示:

#include <iostream>

using namespace std;

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

int main()
{
    int a = 55, b = 99;
    cout << "交换前: a = " << a << ", b = " << b << endl;
    mySWAP(a, b);
    cout << "交换后: a = " << a << ", b = " << b << endl;
    return 0;
}

运行结果:

 练习6.13:假设T是某种类型的名字,说明以下两个函数声明的区别:一个是voidf(T),另一个是void f(T&)。
【出题思路】
区别传值参数与传引用参数。
【解答】
voidf(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;
}

运行结果:

 练习6.14:举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。
【出题思路】
与值传递相比,引用传递的优势主要体现在三个方面:一是可以直接操作引用形参所引的对象;二是使用引用形参可以避免拷贝大的类类型对象或容器类型对象;三是使用引用形参可以帮助我们从函数中返回多个值。
【解答】
基于对引用传递优势的分析,我们可以举出几个适合使用引用类型形参的例子:第一,当函数的目的是交换两个参数的内容时应该使用引用类型的形参;第二,当参数是string对象时,为了避免拷贝很长的字符串,应该使用引用类型。在其他情况下可以使用值传递的方式,而无须使用引用传递,例如求整数的绝对值或者阶乘的程序。

练习6.15:说明find_char函数中的三个形参为什么是现在的类型,特别说明为什么s是常量引用而occurs是普通引用?为什么s和occurs是引用类型而c不是?如果令s是普通引用会发生什么情况?如果令occurs是常量引用会发生什么情况?
【出题思路】
理解并对比普通变量、引用类型和常量引用作为函数参数的区别。
【解答】
find_char函数的三个参数的类型设定与该函数的处理逻辑密切相关,原因分别如下:
●对于待查找的字符串s来说,为了避免拷贝长字符串,使用引用类型;同时我们只执行查找操作,无须改变字符串的内容,所以将其声明为常量引用。
●对于待查找的字符c来说,它的类型是char,只占1字节,拷贝的代价很低,而且我们无须操作实参在内存中实际存储的内容,只把它的值拷贝给形参即可,所以不需要使用引用类型。
●对于字符出现的次数occurs来说,因为需要把函数内对实参值的更改反映在函数外部,所以必须将其定义成引用类型;但是不能把它定义成常量引用,否则就不能改变所引的内容了。

练习6.16:下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法改善。
bool is_empty(string& s) { return s.empty(); }
【出题思路】
本题旨在考查普通引用和常量引用作为参数类型的区别。一般情况下,与普通引用相比,我们更应该选择使用常量引用作为参数类型。
【解答】
本题的程序把参数类型设为非常量引用,这样做有几个缺陷:一是容易给使用者一种误导,即程序允许修改变量s的内容;二是限制了该函数所能接受的实参类型,我们无法把const对象、字面值常量或者需要进行类型转换的对象传递给普通的引用形参。根据上述分析,该函数应该修改为:
bool is_empty(const string& s) { return s.empty(); }

练习6.17:编写一个函数,判断string对象中是否含有大写字母。编写另一个函数,把string对象全都改成小写形式。在这两个函数中你使用的形参类型相同吗?为什么?
【出题思路】
当函数对参数所做的操作不同时,应该选择适当的参数类型。如果需要修改参数的内容,则将其设置为普通引用类型;否则,如果不需要对参数内容做任何更改,最好设为常量引用类型。
【解答】
第一个函数的任务是判断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;
}

运行结果:

练习6.18:为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。
(a)名为compare的函数,返回布尔值,两个参数都是matrix类的引用。
(b)名为change_val的函数,返回vector<int>的迭代器,有两个参数:一个是int,另一个是vector<int>的迭代器。
【出题思路】
根据题意确定函数名、函数返回值以及函数的参数列表。
【解答】
(a)的函数声明是:
bool compare(const matrix&, const matrix&)
(b)的函数声明是:
vector<int>::iterator change_val(int, vector<int>::iterator)

练习6.19:假定有如下声明,判断哪个调用合法、哪个调用不合法。对于不合法的函数调用,说明原因。
double calc(double);
int count(const string &, char);
int sum(vector<int>::iterator, vector<int>::iterator, int);
(a)calc(23.4, 55.1);        (b)count("abcda", 'a');
(c)calc(66);                (d)sum(vec.begin(), vec.end(), 3.8);
【出题思路】
调用函数时,实参类型必须与形参类型匹配。
【解答】
(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的值和指针所指的值,返回较大的那个。在该函数中指针的类型应该是什么?
【出题思路】
第一个参数的实际值毫无疑问是int类型,第二个参数是int指针,实际上有可能表示的是一个int数组,该指针指向了数组的首元素。
【解答】
通过分析题意我们知道,函数实际上比较的是第一个实参的值和第二个实参所指数组首元素的值。因为两个参数的内容都不会被修改,所以指针的类型应该是constint*。满足题意的程序如下所示:

#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 << "请输入一个数:" << endl;
    int j;
    cin >> j;
    cout << "您输入的数与数组首元素中较大的是:" << myCompare(j,a) << endl;
    cout << "数组的全部元素是:" << endl;
    for(auto i: a)
        cout << i << "  ";
    cout << endl;
    return 0;
}

 运行结果:

 练习6.22:编写一个函数,令其交换两个int指针。
【出题思路】
对于题目的要求有两种理解,一种是交换指针本身的值,即指针所指的内存地址;另一种是交换指针所指的内容。
【解答】
为了全面性考虑,我们在程序中实现了三个不同版本的函数。
第一个函数以值传递的方式使用指针,所有改变都局限于函数内部,当函数执行完毕后既不会改变指针本身的值,也不会改变指针所指的内容。
第二个函数同样以值传递的方式使用指针,但是在函数内部通过解引用的方式直接访问内存并修改了指针所指的内容。
第三个函数的参数形式是int *&,其含义是,该参数是一个引用,引用的对象是内存中的一个int指针,使用这种方式可以把指针当成对象,交换指针本身的值。需要注意的是,最后一个函数既然交换了指针,当然解引用该指针所得的结果也会相应发生改变。

#include <iostream>

using namespace std;

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

//该函数交换指针所指的内容
void SwapPointer2(int *p, int *q)
{
    int temp = *p;
    *p = *q;
    *q = temp;
}

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

int main()
{
    int a = 5, b = 10;
    int *p = &a, *q = &b;
    cout << "交换前:" << endl;
    cout << "p所指的值是:" << p << ", q的值是:" << q << endl;
    cout << "p所指的值是:" << *p << ",q所指的值是:" << *q << endl;
    SwapPointer1(p, q);
    cout << "交换后:" << endl;
    cout << "p所指的值是:" << p << ", q的值是:" << q << endl;
    cout << "p所指的值是:" << *p << ",q所指的值是:" << *q << endl;
    
    a = 5, b = 10;
    p = &a, q = &b;
    cout << "交换前:" << endl;
    cout << "p所指的值是:" << p << ", q的值是:" << q << endl;
    cout << "p所指的值是:" << *p << ",q所指的值是:" << *q << endl;
    SwapPointer2(p, q);
    cout << "交换后:" << endl;
    cout << "p所指的值是:" << p << ", q的值是:" << q << endl;
    cout << "p所指的值是:" << *p << ",q所指的值是:" << *q << endl;
    
    a = 5, b = 10;
    p = &a, q = &b;
    cout << "交换前:" << endl;
    cout << "p所指的值是:" << p << ", q的值是:" << q << endl;
    cout << "p所指的值是:" << *p << ",q所指的值是:" << *q << endl;
    SwapPointer3(p, q);
    cout << "交换后:" << endl;
    cout << "p所指的值是:" << p << ", q的值是:" << q << endl;
    cout << "p所指的值是:" << *p << ",q所指的值是:" << *q << endl;
    
    
    return 0;
}

运行结果:

练习6.23:参考本节介绍的几个print函数,根据理解编写你自己的版本。依次调用每个函数使其输入下面定义的i和j:
int i = 0, j[2] = {0,1};
【出题思路】
根据参数的不同,为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 = 20, j[2] = {10, 11};
    print1(&i);
    print1(j);
    print2(&i, 1);
    
    //计算得到数组j的容量
    print2(j, sizeof(j) / sizeof(*j));
    auto b = begin(j);
    auto e = end(j);
    print3(b, e);
    return 0;
}

 

练习6.24:描述下面这个函数的行为。如果代码中存在问题,请指出并改正。
void print(const int ia[10])
{
    for(size_t i = 0; i != 10; ++i)
        cout << ia[i] << endl;
}
【出题思路】
当我们想把数组作为函数的形参时,有三种可供选择的方式:一是声明为指针,二是声明为不限维度的数组,三是声明为维度确定的数组。实际上,因为数组传入函数时实参自动转换成指向数组首元素的指针,所以这三种方式是等价的。
【解答】
由之前的分析可知,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函数,令其接受两个实参。把实参的内容连接成一个string对象并输出出来。
【出题思路】
传递给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;
}

运行结果:

 练习6.26:编写一个程序,使其接受本节所示的选项;输出传递给main函数的实参的内容。
【出题思路】
格式化输出argv所包含的每一个字符串。
【解答】
满足题意的程序如下所示:

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


练习6.27:编写一个函数,它的参数是initializer_list<int>类型的对象,函数的功能是计算列表中所有元素的和。
【出题思路】
掌握initializer_list对象的声明和初始化方法,利用initializer_ list对象设计形参可变的函数。
【解答】
满足题意的程序如下所示,注意iCount的参数是initializer_list对象,在调用该函数时,我们使用了列表初始化的方式生成实参。

#include <iostream>

using namespace std;

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

int main()
{
    //使用列表初始化的方式构建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的和是:" << iCount({10, 10, 10, 10, 10, 10, 10}) << endl;
    
    return 0;
}

运行结果:

 练习6.28:在error_msg函数的第二个版本中包含ErrCode类型的参数,其中循环内的elem是什么类型?
【出题思路】
理解initializer_list的含义和用法。
【解答】
initializer_list<string>的所有元素类型都是string,因此const auto &elem : il推断得到的elem的类型是const string&。使用引用是为了避免拷贝长字符串,把它定义为常量的原因是我们只需读取字符串的内容,不需要修改它。

练习6.29:在范围for循环中使用initializer_list对象时,应该将循环控制变量声明成引用类型吗?为什么?
【出题思路】
考虑引用类型与普通类型的区别。
【解答】
引用类型的优势主要是可以直接操作所引用的对象以及避免拷贝较为复杂的类类型对象和容器对象。因为initializer_list对象的元素永远是常量值,所以我们不可能通过设定引用类型来更改循环控制变量的内容。只有当initializer_list对象的元素类型是类类型或容器类型(比如string)时,才有必要把范围for循环的循环控制变量设为引用类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值