C++ primer 第六章習題

chapter6 函數

練習

6.1 節練習

練習6.1

  • 實參和形參的區別是什麼?

形參是函數內部被使用的參數。實參是形參的初始值,實參用於初始化形參。

練習6.2

  • 請指出下列函數哪個有錯誤,爲什麼?應該如何修改這些錯誤呢?
(a) int f() {
    string s;
    //...
    return s;}    //返回值與函數返回類型不同,將函數返回類型從int改爲string。
(b) f2(int i) { /*...*/ }    //沒有定義函數返回類型。
(c) int calc(int v1, int v1) /*...*/ }    //形參不能同名,函數體沒有被花括號包裹。
(d) double square(double x) return x * x;    //函數體沒有被花括號包裹。

練習6.3

  • 編寫你自己的fact函數,上機檢查是否正確。
#include <iostream>
using namespace std;
int fact(int val) {
	int ret = 1;
	while (val > 1)
		ret *= val--;
	return ret;
}
int main()
{
	cout << fact(5);
}

練習6.4

  • 編寫一個與用戶交互的函數,要求用戶輸入一個數字,計算生成該數字的階乘。在main函數中調用該函數。
#include <iostream>
using namespace std;
int fact1() {
	cout << "Please enter an integer:" << endl;
	int val;
	cin >> val;
	cout << "The factor of " << val;
	int ret = 1;
	while (val > 1)
		ret *= val--;
	cout << " is " << ret << endl;
	return 0;
}
int main()
{
	fact1();
}

練習6.5

  • 編寫一個函數輸出其實參的絕對值。
#include <iostream>
using namespace std;
int abso(int val) {
	if (val > 0)
		return val;
	else return -val;
}
int main()
{
	cout << "Please enter an integer:" << endl;
	int val;
	cin >> val;
	cout << "The absolute value of " << val << " is " << abso(val) << endl;
}

6.1.1 節練習

練習6.6

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

形參是一種在函數內部被初始化和使用的參數,是一種局部變量。形參和函數體內部定義的變量統稱爲局部變量。局部靜態變量能夠在函數調用結束後仍不被銷燬,存在至整個程序結束後才被銷燬。

#include <iostream>
using namespace std;
int fact(int val) {    //形參
	static int ret = 1;    //局部靜態變量
	int fac = 1;           //局部變量
	while (val > 1)
		fac *= val;
	ret += fac;
	return ret;
}

練習6.7

  • 編寫一個函數,當它第一次被調用時返回0,以後每次被調用返回值加1。
int ret1() {
	static int val = -1;
	return ++val;
}

6.1.2 節練習

練習6.8

  • 編寫一個名爲Chapter6.h 的頭文件,令其包含6.1節練習(第184頁)中的函數聲明。
#ifndef CHAPTER6_H
#define CHAPTER6_H
int fact1();
#endif

6.1.3 節練習

練習6.9

  • 編寫你自己的fact.cc 和factMain.cc ,這兩個文件都應該包含上一小節的練習中編寫的 Chapter6.h 頭文件。通過這些文件,理解你的編譯器是如何支持分離式編譯的。
//fact.cpp
#include "Chapter6.h"
#include <iostream>
using namespace std;
int fact1() {
	cout << "Please enter an integer:" << endl;
	int val;
	cin >> val;
	cout << "The factor of " << val;
	int ret = 1;
	while (val > 1)
		ret *= val--;
	cout << " is " << ret << endl;
	return 0;
}
//factMain.cpp
#include "Chapter6.h"
using namespace std;
int main() {
	fact1();
}

6.2.1 節練習

練習6.10

  • 編寫一個函數,使用指針形參交換兩個整數的值。在代碼中調用該函數並輸出交換後的結果,以此驗證函數的正確性。
#include <iostream>
using namespace std;
int change(int *p, int *q) {
	int temp = *p;
	*p = *q;
	*q = temp;
	return 0;
}
int main() {
	int i = 5, j = 10;
	int *p = &i, *q = &j;
	cout << *p << ", " << *q;
	change(p, q);
	cout << " been changed as "<< *p << ", "<< *q;
}

6.2.2 節練習

練習6.11

  • 編寫並驗證你自己的reset函數,使其作用於引用類型的參數。
#include <iostream>
using namespace std;
void reset(int &i) {
	i = 1;
}
int main() {
	int i = 5;
	reset(i);
	cout << i;
}

練習6.12

  • 改寫6.2.1節中練習6.10(第188頁)的程序,使其引用而非指針交換兩個整數的值。你覺得哪種方法更易於使用呢?爲什麼?
#include <iostream>
using namespace std;
int change(int &i, int &j) {
	int temp = i;
	i = j;
	j = temp;
	return 0;
}
int main() {
	int i = 5, j = 10;
	cout << i << ", " << j;
	change(i, j);
	cout << " been changed as " << i << ", " << j;
}

引用。引用就是變量的別名,不容易出錯,而指針是地址,容易出錯。引用可讀性可更強。

練習6.13

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

void f(T)是傳值形參,T的值被拷貝給了形參。void f(&T)是引用形參,T被引用爲形參。

練習6.14

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

將一個字符串內的每個字符變成兩個。理論上什麼情況下都可以是引用類型,如果不能改變實參的參數,也僅需使用常量引用。

練習6.15

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

s是字符串常量引用,因爲它不需要被改變,因此是常量,而如果使用傳值形參,那麼可能會遇到字符串過大的情況,將消耗過多資源用於拷貝字符串。

c是字符,它僅作爲比較的對象用,只需要它的字面值,因此用傳值形參即可。

occurs是整型引用,它用作計算字符c出現的次數,可能被改變,則不能是常量引用。必須是引用,以在函數外部保留記數信息。

c不會被改變,不必引用。s避免拷貝運算,故用引用。occurs可能被改變,故用引用。

s是普通引用,則s可能被改變,容易引發不必要的錯誤。

occurs是常量引用的話,將無法起到統計字符c出現的次數的功能,因爲它的值將無法被改變。

6.2.3 節練習

練習6.16

  • 下面的這個函數雖然合法,但是不算特別有用。指出它的侷限性並設法改善。
bool is_empty(string& s)
{ 
    return s.empty(); 
}

普通引用形參能獲取的參數太少了,如常量對象,字面值對象,需要類型轉換的對象均無法被傳遞給普通的引用形參。改爲常量引用。

練習6.17

  • 編寫一個函數,判斷string對象中是否含有大寫字母。編寫另一個函數,把string對象全都改寫成小寫形式。在這兩個函數中你使用的形參類型相同嗎?爲什麼?
bool upper_in_string(const string & str) {
	bool tag = false;
	for (char c : str) {
		tag = isupper(c);
		if (tag == true) break;
	}
	return tag;
}
void string_to_lower(string & str) {
	for (char &c : str) c = tolower(c);
}

使用的形參不同,因爲前者無需改變string對象內的元素,故使用常量引用。而後者需要將string對象中的字符改寫爲小寫形式,不能使用常量引用,只能使用普通引用。

練習6.18

  • 爲下面的函數編寫函數聲明,從給定的名字中推測函數具備的功能。
  • (a) 名爲 compare 的函數,返回布爾值,兩個參數都是 matrix 類的引用。
  • (b) 名爲 change_val 的函數,返回vector的迭代器,有兩個參數:一個是int,另一個是vector的迭代器。
(a) bool (const matrix &m1, const matrix &m2);
比較兩個matrix對象是否相同或是否前者比後者大。
(b) vector<int>::iterator change_val(int i, vector<int>::iterator);
改變指定向量指定位置的值,將其重新複製爲i。

練習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) 合法。cd均可由數據類型轉換完成調用。

練習6.20

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

當引用的參數過大,不易於複製時,可以使用常量形參,避免拷貝帶來的計算量和空間佔用。

會有部分數據類型無法被傳入引用中。會給使用者帶來一種函數會修改引用的實參的誤導。

6.2.4 節練習

練習6.21

  • 編寫一個函數,令其接受兩個參數:一個是int型的數,另一個是int指針。函數比較int的值和指針所指的值,返回較大的那個。在該函數中指針的類型應該是什麼?
int compare(const int i, const int *p){
	if (i > *p) return i;
	return *p;
}

指向常量整型的指針(const int *)。

練習6.22

  • 編寫一個函數,令其交換兩個int指針。
void swap(int *p, int *q){
	int *temp = p;
	p = q;
	q = temp;
}

練習6.23

  • 參考本節介紹的幾個print函數,根據理解編寫你自己的版本。依次調用每個函數使其輸入下面定義的i和j:
  • int i = 0, j[2] = { 0, 1 };
#include <iostream>
using namespace std;
void print(const int *beg, const int *end) {
	while (beg != end) cout << *beg++ << endl;
}

void print(const int *arr, size_t size) {
	for (size_t i = 0; i != size; i++)
		cout << *(arr + i) << endl;
}
int main() {
	int i = 0, j[2] = { 0, 1 };
	print(&i, &i + 1);
	print(begin(j), end(j));
	print(&i, 1);
	print(j, 2);
}

練習6.24

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

函數希望將一個包含10個整數的數組一一打印。

ia[10]實際上是指向ia首個數字的指針,不包含數組元素數量的信息。應改爲

void print(const int (&ia)[10])

6.2.5 節練習

練習6.25

  • 編寫一個main函數,令其接受兩個實參。把實參的內容連接成一個string對象並輸出出來。
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
	for (size_t i = 1; i < argc; ++i)
	{
		cout << argv[i];
	}
	cout << endl;
}

練習6.26

  • 編寫一個程序,使其接受本節所示的選項;輸出傳遞給main函數的實參的內容。
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
	for (size_t i = 1; i < argc; ++i)
	{
		cout << argv[i] << ' ';
	}
	cout << endl;
}

6.2.6 節練習

練習6.27

  • 編寫一個函數,它的參數是initializer_list類型的對象,函數的功能是計算列表中所有元素的和。
int sum(initializer_list<int> il) {
	int sum = 0;
	for (const int i : il) sum += i;
	return sum;
}

練習6.28

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

const string.

練習6.29

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

視情況而定。在變量類型是字符串等大量佔用空間的類型時,可以通過引用減少複製的開銷。其他情況下,引用與否區別不大,因爲initializer_list對象的元素都是常量,即使引用了也無法修改其值。

6.3.2 節練習

練習6.30

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

error C2561: “str_subrange”: 函數必須返回值。

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

合法,因爲返回的引用是對指針解引用後的值的引用,解引用後的值是非局部變量。

練習6.33

  • 編寫一個遞歸函數,輸出vector對象的內容。
void print(vector<int>::iterator vi, vector<int>::iterator end) {
	if (vi != end) { 
		cout << *vi << endl;
		print(++vi, end);
	}
}

練習6.34

  • 如果factorial 函數的停止條件如下所示,將發生什麼情況?
if (val != 0)

當輸入負數實參時,死循環,函數棧溢出。

練習6.35

  • 在調用factorial 函數時,爲什麼我們傳入的值是 val-1 而非 val–?

val--將返回val,不符合階乘的邏輯,對函數而言會造成死循環,函數棧溢出。可以用--val代替。

6.3.3 節練習

練習6.36

  • 編寫一個函數的聲明,使其返回數組的引用並且該數組包含10個string對象。不用使用尾置返回類型、decltype或者類型別名。
string (&func())[10];

練習6.37

  • 爲上一題的函數再寫三個聲明,一個使用類型別名,另一個使用尾置返回類型,最後一個使用decltype關鍵字。你覺得哪種形式最好?爲什麼?
using arr = string(&)[10];
arr func();
auto func() -> string(&)[10];
string arr[10];
decltype(arr) &func();

尾置返回類型,直觀。

練習6.38

  • 修改arrPtr函數,使其返回數組的引用。
decltype(odd) &arrPtr(int i)

6.4 節練習

練習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)calc()形參常量版。非法,頂層const形參無法與沒有頂層const的形參區分開來。

(b)get()返回類型double版。非法,與原版僅有返回類型不同。

(c)reset()double類型版。

6.5.1 節練習

練習6.40

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

(b)錯誤,因爲一旦某個形參被賦予了默認值,那麼它後面的所有形參都必須有默認值。

練習6.41

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

(a)是非法的,缺少實參。

©調用合法但與程序員初衷不符。因爲*字符將被轉化爲int,賦值給wd,而不是bckgrnd。

練習6.42

  • 給make_plural函數(參見6.3.2節,第201頁)的第二個形參賦予默認實參’s’, 利用新版本的函數輸出單詞success和failure的單數和複數形式。
#include <iostream>
#include <string>
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 << make_plural(1, "success") << '\n' << make_plural(2, "success", "es") << '\n'
		<< make_plural(1, "failure") << '\n' << make_plural(2, "failure");
}

6.5.2 節練習

練習6.43

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

(a) 聲明和定義都放在頭文件中。內聯函數的多個定義必須完全一致,因此通常放在頭文件中。

(b)聲明放在頭文件中,定義放在源文件中。可以讓編譯器協助檢查。

練習6.44

  • 將6.2.2節(第189頁)的isShorter函數改寫成內聯函數。
inline bool isShorter(const string &s1, const string &s2) {
	return s1.size() < s2.size();
}

練習6.45

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

如果是規模較小、流程直接、頻繁調用的函數,應該是內聯函數。否則不應改爲內聯函數,因爲過大的函數在原地內聯展開,容易出現錯誤等,其減少的計算消耗相比函數本身的消耗也不重要,因此不必改爲內聯函數。

練習6.46

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

不能。constexpr函數要求形參和返回類型都得是字面值類型。而s1.size() < s2.size()不構成一個常數表達式,故也不能返回字面值類型。

6.5.3 節練習

練習6.47

  • 改寫6.3.2節(第205頁)練習中使用遞歸輸出vector內容的程序,使其有條件地輸出與執行過程有關的信息。例如,每次調用時輸出vector對象的大小。分別在打開和關閉調試器的情況下編譯並執行這個程序。
#include <iostream>
#include <vector>
#define NDEBUG
using namespace std;
void print(vector<int>::iterator vi, vector<int>::iterator end) {
	if (vi != end) {
		cout << *vi << endl;
		print(++vi, end);
	}
}
int main()
{
	vector<int> v1 = { 1,2,3,4,5,6,7 };
#ifndef NDEBUG    //if not define
	cout << "vector have " << v1.size() << "elements." << endl;
#endif
	print(v1.begin(), v1.end());
}

練習6.48

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

持續讀取cin到s中,直到輸入字符串爲sought爲止。

不合理,當assert讀取到cin時,cin只可能是空或sought。那麼assert將毫無意義。

6.6 節練習

練習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 namespace std;
void f()
{cout << "調用" << __func__ << "()" << endl;}
void f(int)
{cout << "調用" << __func__ << "(int)" << endl;}
void f(int, int)
{cout << "調用" << __func__ << "(int, int)" << endl;}
void f(double, double = 3.14)
{cout << "調用" << __func__ << "(double, double = 3.14)" << endl;}
int main()
{
	//f(2.56, 42);
	f(42);
	f(42, 0);
	f(2.56, 3.14);
}

6.6.1 節練習

練習6.52

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

請指出下列調用中每個類型轉換的等級(參見6.6.1節,第219頁)。

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

(a)通過類型提升實現的匹配。

(b)通過算數類型轉換實現的匹配。

練習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)對函數重載。

(b)對函數重載。

(c)不合法,擁有頂層const的形參和沒有頂層const的形參無法被區分。

6.7 節練習

練習6.54

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

練習6.55

  • 編寫4個函數,分別對兩個int值執行加、減、乘、除運算;在上一題創建的vector對象中保存指向這些函數的指針。
#include <vector>
using namespace std;
int fun1(int i, int j) { return i + j; }
int fun2(int i, int j) { return i - j; }
int fun3(int i, int j) { return i * j; }
int fun4(int i, int j) { return i / j; }
int main()
{
	vector<int(*)(int, int)> v = {fun1, fun2, fun3, fun4};
}

練習6.56

  • 調用上述vector對象中的每個元素並輸出結果。
#include <iostream>
#include <vector>
using namespace std;
int fun1(int i, int j) { return i + j; }
int fun2(int i, int j) { return i - j; }
int fun3(int i, int j) { return i * j; }
int fun4(int i, int j) { return i / j; }
int main()
{
	vector<int(*)(int, int)> v = {fun1, fun2, fun3, fun4};
	for (auto i : v)
		cout << i(10, 5) << endl;
}

15
5
50
2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章