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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM