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 <iostream>
#include <vector>
#include <string>

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 <iostream>
#include <vector>
#include <string>

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 <iostream>
#include <vector>
#include <string>
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 <iostream>
#include <vector>
#include <string>
void reset(int &i)
{
    i = 0;
}

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

練習題6.11

編寫驗證你自己的reset函數,使其作用於引用類型的參數。

代碼:

#include <iostream>
#include <vector>
#include <string>
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 <iostream>
#include <vector>
#include <string>
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 <iostream>
#include <vector>
#include <string>
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<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'); 合法
© calc (66); 合法,但會有警告
(d) sum (vec.begin(), vec.end(), 3.8); 合法,最後一個參數是int類型,傳double會截斷

練習題6.20

引用形參什麼時候應該是常量引用?如果形參應該是常量引用,我們將其設爲了普通引用,會發生什麼情況?

當函數不會改變參數值的時候,應該將形參設爲常量引用。若其該爲常量引用,而我們將其設爲普通引用,當函數內部改變其值,將不會報錯。

練習題6.21

編寫一個函數,令其接受兩個參數,一個是int型,另一個是int指針。函數比較int型值和指針所指值,返回較大的那個。在該函數中,指針的類型應該是什麼?

代碼:

#include <iostream>
#include <vector>
#include <string>
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 <iostream>
#include <vector>
#include <string>
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 <iostream>
#include <vector>
#include <string>
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 <iostream>
#include <vector>
#include <string>
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 <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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 <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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 <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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 <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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 <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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 <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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 <iostream>
#include <vector>
#include <string>
#include <initializer_list>
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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章