天天看點

C++Primer第五版 習題答案 第六章 函數(Functions)

練習6.1

實參和形參的差別的什麼?

形參在函數的定義中聲明;

實參是形參的初始值。

練習6.2

請指出下列函數哪個有錯誤,為什麼?應該如何修改這些錯誤呢?
(a) int f() {
          string s;
          // ...
          return s;
    }
(b) f2(int i) { /* ... */ }
(c) int calc(int v1, int v1) { /* ... */ }
(d) double square (double x)  return x * x; 
           

(a)傳回類型為int,但是實際傳回了字元串:

string f() {  // return should be string, not int
          string s;
          // ...
          return s;
}
           

(b)需要加上函數聲明:

(c)形參的名字不能相同:

(d)需要有大括号(塊):

練習6.3

編寫你自己的fact函數,上機檢查是否正确。
#include <iostream>

int fact(int n)
{
	int ret = 1;
	for(int i = 1;i <= n;++i)
	{
		ret *= i;
	}
	return ret;
}

int main()
{
	std::cout << fact(5) << std::endl;

	return 0;
}
           

練習6.4

編寫一個與使用者互動的函數,要求使用者輸入一個數字,計算生成該數字的階乘。在main函數中調用該函數。
#include <iostream>

int fact(int n)
{
	int ret = 1;
	for(int i = 1;i <= n;++i)
	{
		ret *= i;
	}
	return ret;
}

int main()
{
	int n;
	std::cin >> n;
	std::cout << fact(n) << std::endl;

	return 0;
}
           

練習6.5

編寫一個函數輸出其實參的絕對值。
#include <iostream>

int absolute(int n)
{
	return (n > 0) ? n : -n;
}

int main()
{
	std::cout << absolute(5) << std::endl;

	return 0;
}
           

練習6.6

說明形參、局部變量以及局部靜态變量的差別。編寫一個函數,同時達到這三種形式。

形參是局部變量的一種。

形參和函數體内部定義的變量統稱為局部變量。

局部靜态變量的生命周期貫穿函數調用及之後的時間。

// example
size_t count_add(int n)       // n is a parameter and local variable.
{
    static size_t ctr = 0;    // ctr is a static variable.
    ctr += n;
    return ctr;
}

int main()
{
    for (size_t i = 0; i != 10; ++i)  // i is a local variable.
      cout << count_add(i) << endl;

    return 0;
}
           

練習6.7

編寫一個函數,當它第一次被調用時傳回0,以後每次被調用傳回值加1。
size_t generate()
{
    static size_t ctr = 0;
    return ctr++;
}
           

練習6.8

編寫一個名為Chapter6.h 的頭檔案,令其包含6.1節練習中的函數聲明。
#ifndef CHAPTER6_H
#define CHAPTER6_H

int fact(int n);
int absolute(int n);

#endif
           

練習6.9

編寫你自己的fact.cc 和factMain.cc ,這兩個檔案都應該包含上一小節的練習中編寫的 Chapter6.h 頭檔案。通過這些檔案,了解你的編譯器是如何支援分離式編譯的。

fact.cpp

#include "Chapter6.h"

int fact(int n)
{
	int ret = 1;
	for(int i = 1;i <= n;++i)
	{
		ret *= i;
	}
	return ret;
}

int absolute(int n)
{
	return (n > 0) ? n : -n;
}
           

factMain.cpp

#include <iostream>
#include "Chapter6.h"

int main()
{
	std::cout << fact(5) << std::endl;

	return 0;
}
           
$ g++ -o ex09 factMain.cpp fact.cpp -std=c++11
$ ./ex09 
120
           

練習6.10

編寫一個函數,使用指針形參交換兩個整數的值。在代碼中調用該函數并輸出交換後的結果,以此驗證函數的正确性。
#include <iostream>

void swap_int(int *i,int *j)
{
	int tmp = *i;
	*i = *j;
	*j = tmp;
}

int main()
{
	int i = 1,j = 2;

	std::cout << i << " " << j << std::endl;
	swap_int(&i,&j);
	std::cout << i << " " << j << std::endl;

	return 0;
}
           

練習6.11

編寫并驗證你自己的reset函數,使其作用于引用類型的參數。
#include <iostream>

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

int main(int argc, char const *argv[])
{
	int i = 100;

	std::cout << i << std::endl;
	reset(i);
	std::cout << i << std::endl;

	return 0;
}
           

練習6.12

改寫6.2.1節練習中的程式,使其引用而非指針交換兩個整數的值。你覺得哪種方法更易于使用呢?為什麼?
#include <iostream>

void swap_int(int &i,int &j)
{
	int tmp = i;
	i = j;
	j = tmp;
}

int main()
{
	int i = 1,j = 2;

	std::cout << i << " " << j << std::endl;
	swap_int(i,j);
	std::cout << i << " " << j << std::endl;

	return 0;
}
           

引用更易于使用,不用考慮傳遞的是指針,避免文法錯誤;

使用引用避免拷貝。

練習6.13

假設 T 是某種類型的名字,說明以下兩個函數聲明的差別:一個是void f(T), 另一個是 void f(&T)。

void f(T)

将實參的值拷貝後賦給形參,不能通過改變形參的值來改變實參;

void f(&T)

使用引用将形參綁定到實參上,可以通過改變形參來改變實參。

練習6.14

舉一個形參應該是引用類型的例子,再舉一個形參不能是引用類型的例子。

應該是引用的例子:

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

不能是引用的例子(有關容器的疊代器貌似都不能定義為引用,具體原因不清楚):

void print(std::vector<int>::iterator begin, std::vector<int>::iterator end)
{
        for (std::vector<int>::iterator iter = begin; iter != end; ++iter)
                std::cout << *iter << std::endl;
}
           

練習6.15

說明find_char 函數中的三個形參為什麼是現在的類型,特别說明為什麼s是常量引用而occurs是普通引用?為什麼s和occurs是引用類型而c不是?如果令s是普通引用會發生什麼情況?如果令occurs是常量引用會發生什麼情況?

s不需要改變實參,occurs需要改變實參;s可能會很大,occurs需要改變實參,c沒有上述兩個需求;如果s是普通的引用可能改變實參;occurs是常量引用則不能改變實參,++occurs會報錯

練習6.16

下面的這個函數雖然合法,但是不算特别有用。指出它的局限性并設法改善。

該函數無需改變實參,故将其設定為const比較好,這樣也可以傳入const類型的字元串,或字元串字面值。

練習6.17

編寫一個函數,判斷string對象中是否含有大寫字母。編寫另一個函數,把string對象全部改寫成小寫形式。在這兩個函數中你使用的形參類型相同嗎?為什麼?
#include <iostream>
#include <string>

bool is_upper(const std::string &s)
{
	for(auto c : s)
	{
		if(isupper(c)) return true;
	}
	return false;
}

void to_upper(std::string &s)
{
	for(auto &c : s) c = tolower(c);
}

int main(int argc, char const *argv[])
{
	std::string s("abcdABCD");

	std::cout << (is_upper(s) ? "is upper" : "is not upper") << std::endl;
	std::cout << s << std::endl;
	to_upper(s);
	std::cout << s << std::endl;

	return 0;
}
           

練習6.18

為下面的函數編寫函數聲明,從給定的名字中推測函數具備的功能。
  • (a) 名為 compare 的函數,傳回布爾值,兩個參數都是 matrix 類的引用。
  • (b) 名為 change_val 的函數,傳回vector的疊代器,有兩個參數:一個是int,另一個是vector的疊代器。
(a)bool compare(matrix &m1,matrix &m2);
(b)vector<int>::iterator change_val(int i,vector<int>::iterator);
           

練習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);
(b) count("abcda",'a');
(c) calc(66);
(d) sum(vec.begin(), vec.end(), 3.8);
           

(a)不合法,隻能一個形參;

(b)合法;

(c)合法;

(d)合法。

練習6.20

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

無需改變實參的時候應該用常量引用;可能會改變常亮實參,進而導緻出錯。

練習6.21

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

int compare(int j, int *i)
{
	return j > *i ? j : *i; 
}

int main()
{
	int j = 0, i = 1;
	
	std::cout << compare(j, &i) << std::endl;

	return 0;
}
           

練習6.22

編寫一個函數,令其交換兩個int指針。
#include <iostream>

void swap_intp(int *&i, int *&j)
{
	int *tmp;
	tmp = i;
	i = j;
	j = tmp;
}

int main()
{
	int i = 0, j = 1;
	int *pi = &i, *pj = &j;

	std::cout << pi << " " << pj << std::endl;
	swap_intp(pi, pj);
	std::cout << pi << " " << pj << std::endl;

	return 0;
}
           

練習6.23

參考本節介紹的幾個print函數,根據了解編寫你自己的版本。依次調用每個函數使其輸入下面定義的i和j:
#include <iostream>

using std::cout;
using std::endl;
using std::begin;
using std::end;

void print(const int* pi)
{
	cout << *pi << endl;
}

void print(const int *beg, const int *end)
{
	while(beg != end)
		cout << *beg++ << " ";
	cout << endl;
}

void print(const int ia[], size_t size)
{
	for(size_t i = 0; i != size; ++i)
		cout << ia[i] << " ";
	cout << endl;
}

void print(const int (&arr)[2])
{
	for(auto e : arr)
		cout << e << " ";
	cout << endl;
}

int main()
{
	int i = 0, j[2] = {0, 1};

	print(&i);
	print(begin(j), end(j));
	print(j, end(j) - begin(j));
	print(j);

	return 0;
}
           

練習6.24

描述下面這個函數的行為。如果代碼中存在問題,請指出并改正。
void print(const int ia[10])
{
	for (size_t i = 0; i != 10; ++i)
		cout << ia[i] << endl;
}
           

該函數傳遞的不是數組是const int*,如果實參不是含10個元素的int數組,可能導緻for循環數組越界。修改為:

練習6.25

編寫一個main函數,令其接受兩個實參。把實參的内容連接配接成一個string對象并輸出出來。
#include <iostream>

int main(int argc, char const *argv[])
{
	const std::string s1 = argv[1], s2 = argv[2];

	std::cout << s1 + s2 << std::endl;

	return 0;
}
           
$ ./ex25 hel lo
hello
           

練習6.26

編寫一個程式,使其接受本節所示的選項;輸出傳遞給main函數的實參内容。
#include <iostream>

int main(int argc, char const *argv[])
{
	std::string s;

	for(int i = 0; i != argc; ++i)
		s += std::string(argv[i]) + " ";
	std::cout << s << std::endl;

	return 0;
}
           
$ ./ex26 -d -o ofile data0
./ex26 -d -o ofile data0 
$ ./ex26 
./ex26 
           

練習6.27

編寫一個函數,它的參數是initializer_list類型的對象,函數的功能是計算清單中所有元素的和。
#include <iostream>
#include <initializer_list>

int counter_int(std::initializer_list<int> il)
{
	int cnt_i = 0;
	for(auto e : il)
		cnt_i += e;
	return cnt_i;
}

int main(int argc, char const *argv[])
{
	std::cout << counter_int({1,2,3,4,5}) << std::endl;

	return 0;
}
           

練習6.28

在error_msg函數的第二個版本中包含ErrCode類型的參數,其中循環内的elem是什麼類型?

const std::string&

練習6.29

在範圍for循環中使用initializer_list對象時,應該将循環控制變量聲明成引用類型嗎?為什麼?

如果拷貝代價小,則無需設定成引用類型;如果拷貝代價大,可以設定成引用類型。

練習6.30

編譯第200頁的str_subrange函數,看看你的編譯器是如何處理函數中的錯誤的。

g++

錯誤#1:error: return-statement with no value, in function returning ‘bool’ [-fpermissive]
錯誤#2:檢查不出,傳回true
           

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

合法,傳回數組ia[0]-ia[9]

練習6.33

編寫一個遞歸函數,輸出vector對象的内容。
#include <iostream>
#include <vector>

void read_vi(std::vector<int>::const_iterator iterator_begin, std::vector<int>::const_iterator iterator_end)
{
	if(iterator_begin != iterator_end)
	{
		std::cout << *iterator_begin << " ";
		return read_vi(++iterator_begin, iterator_end);
	}else
	{
		std::cout << std::endl;
		return;
	}
}

int main()
{
	std::vector<int> v{1,2,3,4,5};

	read_vi(v.begin(), v.end());

	return 0;
}
           

練習6.34

如果factorial 函數的停止條件如下所示,将發生什麼?

如果實參為大于等于0,函數将會多乘以一個1,比如factorial(5),等價于5 * 4 * 3 * 2 * 1 * 1;

如果實參小于0,函數将會不斷地調用它自身直到程式棧空間耗盡為止。

練習6.35

在調用

factorial

函數時,為什麼我們傳入的值是

val-1

而非

val--

val--

會傳回未修改的

val

内容,使程式陷入無限循環;

val--

會修改

val

的内容,使程式運作結果不符合預期。

練習6.36

編寫一個函數聲明,使其傳回數組的引用并且該數組包含10個string對象。不用使用尾置傳回類型、decltype或者類型别名。

練習6.37

為上一題的函數再寫三個聲明,一個使用類型别名,另一個使用尾置傳回類型,最後一個使用decltype關鍵字。你覺得哪種形式最好?為什麼?
using ARRS = std::string[10];
ARRS &fun(ARRS &arrs);
 
auto fun(std::string (&arrs)[10]) -> std::string (&)[10]);
 
std::string arrs1[10];
decltype(arrs1) &fun(decltype(arrs1) &arrs);
           

個人覺得using最好,最簡潔。

練習6.38

修改arrPtr函數,使其傳回數組的引用。
decltype(arrStr)& arrPtr(int i)
{
          return (i % 2) ? odd : even;
}
           

練習6.39

說明在下面的每組聲明中第二條語句是何含義。如果有非法的聲明,請指出來。
(a) int calc(int, int);
	int calc(const int, const int);
(b) int get();
	double get();
(c) int *reset(int *);
	double *reset(double *);
           

(a)合法,重複聲明可以,重複定義不行;

(b)非法,僅傳回值不同;

(c)合法。

練習6.40

下面的哪個聲明是錯誤的?為什麼?
(a) int ff(int a, int b = 0, int c = 0);
(b) char *init(int ht = 24, int wd, char bckgrnd);		
           

(a)正确;

(b)錯誤。

練習6.41

下面的哪個調用是非法的?為什麼?哪個調用雖然合法但顯然與程式員的初衷不符?為什麼?
char *init(int ht, int wd = 80, char bckgrnd = ' ');
(a) init();
(b) init(24,10);
(c) init(14,'*');
           

(a)非法,函數第一個形參沒有預設實參,必須給實參;

(b)合法;

(c)合法,但與初衷不符,char '*'轉換成整形了。

練習6.42

給make_plural函數的第二個形參賦予預設實參’s’, 利用新版本的函數輸出單詞success和failure的單數和複數形式。
#include <iostream>
#include <string>

using std::string;

string make_plural(size_t ctr, const string &word, const string &ending = "s")
{
	return (ctr > 1) ? word + ending : word;
}

int main()
{
	std::cout << make_plural(2, "success", "es") << std::endl;
	std::cout << make_plural(2, "failure") << std::endl;
}
           

練習6.43

你會把下面的哪個聲明和定義放在頭檔案中?哪個放在源檔案中?為什麼?
(a) inline bool eq(const BigInt&, const BigInt&) {...}
(b) void putValues(int *arr, int size);
           

(a)放在頭檔案中,内聯函數在程式中可以多次定義,它的多個定義必須完全一緻,是以放在頭檔案中比較好;

(b)放在頭檔案中,聲明放在頭檔案中。

練習6.44

将6.2.2節的isShorter函數改寫成内聯函數。
#include <iostream>
#include <string>

using std::string;

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

int main()
{
	string s1("aabb"), s2("aabbcc");

	std::cout << isShorter(s1, s2) << std::endl;

	return 0;
}
           

練習6.45

回顧在前面的練習中你編寫的那些函數,它們應該是内聯函數嗎?如果是,将它們改寫成内聯函數;如果不是,說明原因。

6.38和6.42應該是内聯函數;6.4不應該是,規模不小,調用不頻繁。

練習6.46

能把isShorter函數定義成constexpr函數嗎?如果能,将它改寫成constxpre函數;如果不能,說明原因。

不能,因為std::string::size(); 不是一個constexpr函數,s1.size() == s2.size(); 不是一個常量表達式。

練習6.47

改寫6.3.2節練習中使用遞歸輸出vector内容的程式,使其有條件地輸出與執行過程有關的資訊。例如,每次調用時輸出vector對象的大小。分别在打開和關閉調試器的情況下編譯并執行這個程式。
#include <iostream>
#include <vector>
#include <cassert>

// #define NDEBUG	//this is not work for assert(), we should use $ g++ -o ex47 ex47.cpp -D NDEBUG -std=c++11

void read_vi(std::vector<int>::const_iterator iterator_begin, std::vector<int>::const_iterator iterator_end)
{
	#ifndef NDEBUG
		std::cerr << iterator_end - iterator_begin << __func__ << " " << __FILE__ << " " 
		<< __LINE__ << " " << __TIME__ << " " << __DATE__ << std::endl;
	#endif
	assert(0);

	if(iterator_begin != iterator_end)
	{
		std::cout << *iterator_begin << " ";
		return read_vi(++iterator_begin, iterator_end);
	}else
	{
		std::cout << std::endl;
		return;
	}
}

int main()
{
	std::vector<int> v{1,2,3,4,5};

	read_vi(v.begin(), v.end());

	return 0;
}
           

練習6.48

說明下面這個循環的含義,它對assert的使用合理嗎?
string s;
while (cin >> s && s != sought) { } //空函數體
assert(cin);
           

合理,當輸入結束時終止程式。

練習6.49

什麼是候選函數?什麼是可行函數?

候選函數具備兩個特征:一是與被調用的函數同名,二是其聲明在調用點可見。

可行函數是從候選函數中選出的,有兩個特征:一是其形參數量與本次調用提供的實參數量相等,二是每個實參的類型與對應的形參類型相同,或者能轉換成形參的類型。

練習6.50

已知有第217頁對函數 f 的聲明,對于下面的每一個調用列出可行函數。其中哪個函數是最佳比對?如果調用不合法,是因為沒有可比對的函數還是因為調用具有二義性?
(a) f(2.56, 42)
(b) f(42)
(c) f(42, 0)
(d) f(2.56, 3.14)
           

(a)二義性;(b)最佳比對void f(int);(c)最佳比對void f(int, int);(d)最佳比對void f(double, double = 3.14)。

練習6.51

編寫函數f的4版本,令其各輸出一條可以區分的消息。驗證上一個練習的答案,如果你的回答錯了,反複研究本節内容直到你弄清自己錯在何處。
#include <iostream>
using std::cout; using std::endl;

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

練習6.52

已知有如下聲明:
void manip(int ,int);
double dobj;
           

請指出下列調用中每個類型轉換的等級。

(a) manip('a', 'z');
(b) manip(55.4, dobj);
           

(a)3等級,通過類型提升實作的比對;

(b)4等級,通過算數類型轉換。

練習6.53

說明下列每組聲明中的第二條語句會産生什麼影響,并指出哪些不合法(如果有的話)。
(a) int calc(int&, int&); 
	int calc(const int&, const int&); 
(b) int calc(char*, char*);
	int calc(const char*, const char*);
(c) int calc(char*, char*);
	int calc(char* const, char* const);
           

(a)合法,實參可以為const int;

(b)合法,實參可以為const char*;

(c)合法,頂層const,聲明重複(可以重複聲明,不可重複定義)。

練習6.54

編寫函數的聲明,令其接受兩個int形參并傳回類型也是int;然後聲明一個vector對象,令其元素是指向該函數的指針。
vector<int (*)(int, int)> vf;

//others:
int func(int a, int b);

using pFunc1 = decltype(func) *;
typedef decltype(func) *pFunc2;
using pFunc3 = int (*)(int a, int b);
using pFunc4 = int(int a, int b);
typedef int(*pFunc5)(int a, int b);
using pFunc6 = decltype(func);

std::vector<pFunc1> vec1;
std::vector<pFunc2> vec2;
std::vector<pFunc3> vec3;
std::vector<pFunc4*> vec4;
std::vector<pFunc5> vec5;
std::vector<pFunc6*> vec6;
           

練習6.55

編寫4個函數,分别對兩個int值執行加、減、乘、除運算;在上一題建立的vector對象中儲存指向這些函數的指針。
int add(int a, int b) { return a + b; }
int subtract(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; }
           

練習6.56

調用上述vector對象中的每個元素并輸出結果。
#include <iostream>
#include <string>
#include <vector>

using std::string;
using std::cout;
using std::endl;
using std::vector;

int add(int a, int b) { return a + b; }
int subtract(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; }

int main()
{
	vector<int (*)(int, int)> vf{add, subtract, multiply, divide};

	for(const auto &e : vf) cout << e(4, 2) << endl;

	return 0;
}
           

繼續閱讀