zoukankan      html  css  js  c++  java
  • c++ primer 第五版第六章

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

    形参指的是定义在函数参数列表中的局部变量。被调用者初始化。
    实参指的是调用函数时给参数赋的初始值。

    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函数,上机检查是否正确。

    int my_fact (int val)
    {
            int result = 1;
            if (val == 1 || val == 0) {
                    return 1;
            } else {
                    return val * my_fact(val-1);
            }
    
    }
    
    void test603()
    {
            int val = 0;
            cin >> val;
    
            int result = my_fact (val);
            cout << "val's factorial is " << result << endl;
    }
    

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

    int my_fact (int val)
    {
            int result = 1;
            if (val == 1 || val == 0) {
                    return 1;
            } else {
                    return val * my_fact(val-1);
            }
    
    }
    
    void test604()
    {
            int val = 0;
    
            cout << "Please input a number to calculate the factorial : ";
            cin >> val;
    
            int result = my_fact (val);
            cout << "val's factorial is " << result << endl;
    }
    
    

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

    int absolute(int val)
    {
            return (val > 0) ? val : (-val);
    }
    void test605()
    {``
            int val;
    
            while (cin >> val)
            {
                    cout << "the absolute of val is " << absolute(val) << endl;
            }
    
    }
    

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

    形参,指的是函数声明时的函数列表中定义的局部变量,在整个函数中起作用。
    局部变量,指的是定义在块中的变量,只在块中起作用。
    局部静态变量:在函数体内定义的静态变量,但是当函数结束时不会被销毁,直到整个程序结束之后才会销毁。
    举例如下题,形参时int val,局部变量时result,局部静态变量时static int cnt。

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

    int my_fact (int val)
    {
            static int cnt = 0;
            int result = 1;
            cout << "The function is call " << ++ cnt << "times.";
            if (val == 1 || val == 0) {
                    return 1;
            } else {
                    return val * my_fact(val-1);
            }
    }
    

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

    int my_fact(int val);
    int absolute(int val);
    

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

    // fact.cpp 中的内容如下:
    #include <iostream>
    #include "Chapter06.h"
    
    using namespace std;
    
    int my_fact (int val)
    {
             int result = 1;
    
             if (val == 1 || val == 0) {
                     return 1;
             } else {
                     return val * my_fact(val-1);
             }
    }
    // factMain.cpp中的内容如下:
    #include <iostream>
    #include "Chapter06.h"
    
    using namespace std;
    
    int main()
    {
            int val = 0;
            cin >> val;
    
            int result = my_fact (val);
            cout << "val's factorial is " << result << endl;
    }
    // 我使用的g++编译器,编译指令如下:
     g++ -c factMain.cpp        // 生成factMain.o
     g++ -c fact.cpp                // 生成fact.o
    g++ factMain.o fact.o -o main        // 生成main.exe
    // 如果fact文件内容改变,只需要重编fact.cpp生成新的fact.o,再链接就可以,不需要再重编factMain.cpp。
    

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

    void swap_pointer (int *a, int *b)
    {
            int tmp;
    
            tmp = *a;
            *a = *b;
            *b = tmp;
    }
    
    void test610()
    {
            int x = 5, y = 10;
    
            swap_pointer (&x, &y);
    
            cout << "x = " << x << ", y = " << y << endl;
    }
    

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

    void my_reset(int &val)
    {
            val = 0;
    }
    
    void test611()
    {
            int a = 20;
            my_reset (a);
    
            cout << "a = " << a << endl;
    }
    

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

    void swap_ref(int &a, int& b)
    {
            int tmp;
            tmp = a;
            a = b;
            b = tmp;
    }
    
    void test612()
    {
            int a = 4, b = 5;
            swap_ref (a, b);
    
            cout << "a = " << a << ", b = " << b << endl;
    }
    

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

    题目写错了吧,感觉应该是void f (T&)才对。
    void f(T),在传参时,会把T类型的变量整个复制一份。
    void f(T&),在传参时,只会传递类型位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对象全部改成小写。在这两个函数中你所使用的形参类型相同吗?为什么?

    bool is_have_upper (const string& s)
    {
        for (int i = 0; i < s.size(); i++) {
            if (isupper(s[i])) {
                return true;
            }
        }
        return false;
    }
    
    void to_upper (string& s)
    {
        for (int i = 0; i < s.size(); i++) {
            s[i] = toupper(s[i]);
        }
    }
    
    void test617 ()
    {
        string s = "Hello World!";
    
        if (is_have_upper (s)) {
            cout << "s has upper letter." << endl;
        }
    
        to_upper(s);
        cout << s << endl;
    }
    

    // 判断是否有大写字母应该使用const 引用类型,因为不会改变该参数的值。而转换为大写字母不能使用const类型。
    6.18 为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。
    (a) 名为compare的函数,返回bool,两个参数都是matrix类的引用。

    bool compare (const matrix& a, const matrix& b);
    

    (b) 名为change_val的函数,返回vector的迭代器,有两个参数:一个是int,另一个是vector的迭代器。

    vector<int>::iterator change_val (int val, vector<int>::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');        // 合法
    (c) calc (66);                        // 合法,但会有警告
    (d) sum (vec.begin(), vec.end(), 3.8);        // 合法,最后一个参数是int类型,传double会截断
    

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

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

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

    int compare(int x, const int *y)
    {
        return (x > *y) ? x : *y;
    }
    
    void test621()
    {
        int i = 5, j = 9;
    
        int bigger_num = compare(i, &j);
        cout << bigger_num << endl;
    }
    

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

    void swap_pointer(int* &x, int* &y)
    {
        int *temp = x;
        x = y;
        y = temp;
    }
    
    void test622()
    {
        int i = 5, j = 9;
        int *p = &i, *q = &j;
    
        cout << "p = " << p << ", q = " << q << endl;
        swap_pointer(p, q);
        cout << "*p = " << *p << ", *q = " << *q << endl;
        cout << "p = " << p << ", q = " << q << endl;
    }
    

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

    void print (const int *p)
    {
       if (p != nullptr) {
            cout << *p << 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 a[], size_t size)
    {
        for (size_t i = 0; i < size; i++) {
            cout << a[i] << " ";
        }
        cout << endl;
    }
    // i = 0, 如果对指针进行判空,i将不会输出。因为0与空指针对编译器来说一样。
    

    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对象并输出。

    int main(int argc, char **argv)
    {
        if (argc != 3) {
            cout << "Usage: should have two arguments." << endl;
        }
    
        string str;
    
        for (int i = 1; i < argc; i++) {
            str += string(argv[i]) + " ";
        }
        cout << str << endl;
        return 0;
    }
    

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

    与上题相同。

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

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

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

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

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

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

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

    运行该函数,现在使用的MinGW编译器报错误1,不报错误2。调用该函数发现,如果一个string对象是另一个的子集,则函数的返回值打印出来是5,是未定义的bool值。结果如下图,打印结果是5,判断是真是假都成立。

    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;
    }
    // 该函数合法,其功能是从0递增,初始化一个数组。本题中是将一个长度未10的数组初始化未0--9。
    

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

    void print_vector(vector<int>::const_iterator beg, vector<int>::const_iterator end)
    {
        if (beg != end) {
            cout << *beg << " ";
            print_vector(++ beg, end);
        }
    }
    
    void test633 ()
    {
        vector<int> ivec = {0, 1, 2, 3, 4, 5, 6, 7, 8};
    
        print_vector(ivec.cbegin(), ivec.cend());
    }
    

    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关键字,你觉得哪种形式最好?为什么?

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

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

    decltype(odd) &arrPtr (int i)
    {
        return (i % 2) ? odd : even;
    }
    void test638 ()
    {
        int (*p)[4] = arrPtr(3);
    
        for (auto i : *p) {                                // 注意使用数组指针输出数组的方法
             cout << i << " ";
        }
        cout << endl;
    }
    
    

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

    -- 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);    // 合法
    (c) init (14, '*');        // 合法,但与初衷不符,初衷应是让ht = 14, bakgrnd = *。但实际是ht = 14, wd = ‘*’。
    

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

    string make_plural (size_t ctr, const string &word, const string &ending = "s")
    {
        return (ctr > 1) ? word + ending : word;
    }
    
    void test642()
    {
        string str1 = "apple";
        string str2 = "banana";
    
        cout << make_plural(2, str1) << endl;
        cout << make_plural(1, str2) << endl;
    }
    

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

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

    6.44 将isShorter函数改写成内联函数。

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

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

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

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

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

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

    void print_vec(vector<int>& ivec)
    {
    #ifndef NDEBUG
        cout << "vector's size is " << ivec.size() << endl;
    #endif // NDEBUG
    
        if (!ivec.empty()) {
            auto tmp = ivec.back();
            ivec.pop_back();
            print_vec(ivec);
            cout << tmp << " ";
        }
        cout << endl;
    }
    
    void test647 ()
    {
       vector<int> ivec = {0, 1, 2, 3, 4, 5, 6, 7, 8};
       print_vec(ivec);
    }
    

    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)
    (c) f (42, 0)                // 调用 f (int, int)
    (d) f (2.56, 3.14)        // 调用 f (double, double = 3.14)
    

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

    error: call of overloaded 'f(double, int)' is ambiguous|

    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类型决定使用哪个函数。
    (c)  int calc (char*, char*);
         int calc (char* const, char* const);        // 非法,与第一行含义相同,属于重复定义。
    

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

    using func1 = int (int, int);
    vector<func1*> fvec;
    
    int func (int a, int b);
    typedef decltype(func) * func2;
    vector<func2> fvec;
    
    using func3 = int (*) (int, int);
    vector<func3> fvec;
    

    6.55 编写4个函数,分别对两个int值执行加、减、乘、除运算;在上一题创建的vector对象中保存指向这些函数的指针。
    6.56 调用上述vector对象中的每个元素并输出其结果。

    int add (int a, int b) {    return a+b; }
    int sub (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; }
    void test655()
    {
        typedef int(*p) (int, int);
        vector<p> vec{add, sub,multiply, divide};
        for (auto f : vec) {
            cout << f(2, 4) << endl;
        }
    }
    
  • 相关阅读:
    WPF编程系列
    使用ListBox控件来实现直方图控件(一)
    在WebBrowser控件中获取鼠标在网页上点击的位置
    使用WPF Resource以及Transform等技术实现鼠标控制图片缩放和移动的效果
    浮点数类型在计算机里面的表示方法
    点击asp:button按钮后,不刷新当前页面
    .NET ComponentArt 使用背景
    关于Microsoft ASP.NET 2.0 AJAX Extensions UpdatePanel 中使用 javascript 产生错误的问题
    基础平台数据导入(游标的使用)
    STUFF 函数的使用
  • 原文地址:https://www.cnblogs.com/songshuguiyu/p/9214419.html
Copyright © 2011-2022 走看看