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