C++自修入門實境秀、C++ Primer 5版研讀秀 37/ ~v6函式-本章總結-20190826_182416





練習6.46

5:16:47

其參數型別為string& ,而參考是一個字面值(literal)型別;但是:

Severity Code Description

Error (active) E2392 a constexpr function cannot have a parameter of nonliteral type "std::string"

拿掉參考才會出錯:

Severity Code Description

Error C3615 constexpr function 'isShorter' cannot result in a constant expression

此即一個函式若不能推導出常值運算式(constant expression),就不能是constexpr function

constexpr就是constant expression的縮寫

傳回不是常值,就是本書中所說的

一個constexpr函式能夠回傳不是常數的一個值(頁239)

可能須用char來改寫。string.size()比較大小傳回的也不會是constant



無法寫成constexpr的原因應是:

一個constexpr函式的主體可以含有其他的述句,只要那些述句不會在執行時期產生任何動作就行。舉例來說,一個constexpr函式可以含有null述句、型別別名(§ 2.5.1 ),或是 using宣告。(頁239)

而string.size()是會在執行階段做出動作的。

0:21

不見題目只見關鍵字

只要能回傳constant expression的函式就是constexpr函式

所以我改寫的原則是不變原題的string型別

只要能回傳constexpr就好:

//constexpr bool isShorter(const string &s1, const string & s2)

constexpr bool isShorter(size_t sz1, size_t sz2)//是不是constexpr函式與參數是不是const無關,乃是與計算出的結果與傳回值是不是constant expression有關

{

//return s1.size() < s2.size();

//return const_cast<const size_t&>(sz1) ;

return sz1<sz2;

}

int main() {

//const char s1[] ="一切法從真實心中作", s2[] = "凡所有相,皆是虛妄" ;

string s1="一切法從真實心中作",s2= "凡所有相,皆是虛妄";

//constexpr size_t sz1 = s1.size(), sz2 = s2.size();//這行用constexpr來測試就知道.size()計算出來的結果不是常值運算式(constant expression)

cout<< (isShorter(s1.size(),s2.size())?"true":"false")<<endl;//因為.size()計算產出的不會是constant expression

//所以改用在呼叫端來傳值

}

結論:即使是constexpr函式,也未必都回傳常值。若在必須使用常值的地方用到它的回傳值,就會是錯誤,即使該函式在編譯或執行時都正常,也順利傳回值,然而其傳回的值卻未必是常值;即使它是常值運算式(constant expression)函式

一個constexpr Functions只是說它「可以」傳回常值,但不保證傳回常值

30:00要將constexpr改成const才能保證回傳的是常值型

6.5.3. Aids for Debugging

6.5.3用於除錯的輔助功能

31:20

35:00

The assert Preprocessor Macro

assert前置處理器巨集



assert是一種前置處理器巨集(preprocessor macro)。前置處理器巨集是行為有點類似inline函式的一種前置處理器變數。

可見assert就是一種前置處理器變數(preprocessor variable)

前置處理器巨集就是一種前置處理器變數。 2:7:00

assert 是前置處理器巨集(preprocessor macro)而 NDEBUG 則是前置處理器變數(preprocessor variable)

而前置處理器巨集又是其變數的一種,只是這種變數之行為有點像一個inline函式

頁241

41:00 前置處理器的前,就是在編譯器之前 ⑧找對主詞 誰的前?!



46:00 assert這個名字盡量不要用,即使我們沒有include cassert標頭檔



50:00 assert巨集通常是用來檢查一些不可能發生的情況(條件)

The NDEBUG Preprocessor Variable

2:10:40 NDEBUG有沒有被定義,決定了assert會不會被執行,如果NDEBUG被定義了,則assert不會有任何反應。而要assert執行,也要看它的條件運算式中的值是否為false,若是true則不執行任何動作。



NDEBUG被定義,assert就不會有作用:

If NDEBUG is defined, assert does nothing. By default, NDEBUG is not defined, so, by default, assert performs a run-time check.

52:48 55:00

We can “turn off” debugging by providing a #define to define NDEBUG .

2:16:20關掉除錯,就是略過assert,讓它不起作用。

只要寫下這一行,就是定義NDEBUG完成了

#define NDEBUG

或藉由命令提示字元來下達如下的參數:

$ CC -D NDEBUG main.C # use /D with the Microsoft compiler

2:19:99

Therefore, assert should be used only to verify things that truly should not be possible.

因此,asserter應該只被用來驗證真的不應該是可能的東西。

→因此,assert應該只被用來驗證那些真的不可能發生的事情。



1:3:30

In addition to using assert , we can write our own conditional debugging code using NDEBUG .

If NDEBUG is not defined, the code between the #ifndef and the #endif is executed. If NDEBUG is defined, that code is ignored:

#ifndef可以寫在函式裡耶

2:24:30#ifndef #endif 來寫,就類似 用assert(expr)一樣,所以是「in addition to」,且也有conditional。



頁242

1:15:00

void print(const int ia[], size_t size)

{

#ifndef NDEBUG

// _func_ is a local static defined by the compiler that holds the function's name

cerr << _func_ << ": array size is " << size << endl;

#endif

// ...

}

int main() {

int ia[]{2,3,4};

print(ia,size(ia));

}

__func__底線要連起來,不能用半形空格

它傳回的是包裹它的函式名稱(不是全名)

1:20:00編譯器在每個函式都會定義一個__func__,是編譯器自行定義的變數

It is a local static array of const char that holds the name of the function.

它的型別是靜態的區域常值字元陣列

另外,前置處理器(preprocessor)也定義了4個有用的變數:

1._FILE_ 字串字面值(string literal),傳回檔名

2. _LINE_ 整數字面值,傳回行號

3. _TIME_ 字串字面值,檔案被編譯的時間

4. _DATE_ 字串字面值,檔案被編譯的日期

原文作「constants」常數?

1:57:00

void print(const int ia[], size_t size,string word="ss",size_t threshold=3)//故意讓word.size() < threshold為真(發生) 2:30:00

{

#ifndef NDEBUG

// _func_ is a local static defined by the compiler that holds the function's name

cerr << _func_ << ": array size is " << size << endl;

#endif

// ...

if (word.size() < threshold)//這裡反而用if,而不是用assert。if的條件運算式和assert的邏輯正好相反,if是在expr為true時反應,而assert是false時才有反應。

cerr << "Error: " << _FILE_

<< " :in function " << _func_

<< " at line " << _LINE_ << endl

<< "Compiled on " << _DATE_

<< " at " << _TIME_ << endl << "Word read was \"" << word <<"\": Length too short" << endl;



}

int main() {

int ia[]{2,3,4};

print(ia,size(ia));

}



1:47:30

6.6. Function Matching

6.6函式匹配



頁243

練習6.47

1:50:50 2:32:10

2:48:20 assert會做的事只有:

assert writes a message and terminates the program.(頁241)

所以不能用它來作判斷而印出訊息,要改用 #ifndef 才行



vector<string>::const_iterator print_vector(vector<string>::const_iterator vsi, vector<string>::const_iterator visend) {

//debugging turn on with assert

assert(false);//觸發強制(斷言、決定是)終止

……

//debugging turn off with assert

assert(true);//不觸發強制(斷言、決定是)終止

//在Visual Studio 2019 在此行run to cursor 也會跳過此行,可見是完全略過(忽略不計)了

……

下式實際測試的結果是,assert仍會被觸發,即使NDEBUG已定義亦然:

vector<string>::const_iterator print_vector(vector<string>::const_iterator vsi, vector<string>::const_iterator visend) {

#define NDEBUG

#ifndef NDEBUG

cerr << visend - vsi << endl;//唯此行被略過

#endif

assert(false); //此行仍被執行

3:32:00

vector<string>::const_iterator print_vector(vector<string>::const_iterator vsi, vector<string>::const_iterator visend) {

//debugging turn off with NDEBUG and #ifndef and assert

#define NDEBUG

assert((visend - vsi) > 0);

#ifndef NDEBUG

cerr << visend - vsi << endl;

#endif

……





vector<string>::const_iterator print_vector(vector<string>::const_iterator vsi, vector<string>::const_iterator visend) {

//debugging turn on with NDEBUG and #ifndef and assert

//#define NDEBUG

assert((visend - vsi) > 0);

#ifndef NDEBUG

cerr << visend - vsi;// << endl;

#endif

cout << *vsi << " ";

++vsi;//要寫一個遞迴(recursion)應該是先寫好函式的本體,再呼叫它本身的遞迴部分邏輯才清楚,不會紊亂、打結 6:37:00

if (vsi != visend) {

return print_vector(vsi, visend);//其實遞迴(recursion)就是寫一個非while do_while for的迴圈嘛

}

cout << endl;

return vsi;

}

int main() {

vector<string> vs{ "王之渙","登鸛雀樓","白日依山盡","黃河入海流","欲窮千里目","更上一層樓" };

//print_vector(vs.begin(), vs.end()-vs.size());//此即會觸發assert,只要「-」後大於vs.size()在此例為6,即會觸發。大於6 則是觸發編譯錯誤,超出vector範圍

print_vector(vs.begin(), vs.end());

}

印出來的結果就是:

6王之渙 5登鸛雀樓 4白日依山盡 3黃河入海流 2欲窮千里目 1更上一層樓

請按任意鍵繼續 . . .



練習6.48

3:37:40

string s, sought="x";

while (cin >> s && s != sought) {} // empty body

assert(cin);//cin=fasle=0 才會觸發,送出訊息並且中止程式

//cin是讀取失敗時才會是false

//只要沒有讀取成功,或讀不到sought(此例假設為「x」),就會觸發assert。若作為一定要有sought才能執行後續的程式碼時,是適用的。作為一個把關的,適合。

We have solutions for your book!(練習解答)

sought是變數,不應是字串,已見前,「s != sought」怎麼會是錯的?可見解答也未必可靠:

We also can define an empty block by writing a pair of curlies with no statements. An empty block is equivalent to a null statement:

while (cin >> s && s != sought)

{ } // empty block(seeCompound Statements (Blocks),中文版頁173)

4:14:50

函式匹配

即在多載函式中如何找到正確要套用呼叫調用的函式

Determining the Candidate and Viable Functions

判斷候選函式與合用的函式

4:40:10 找出匹配又合用的函式

找出合格又合用的函式



candidate functions .候選函式

函式匹配的步驟:

1.函式名稱與有效性(可見到、生命週期)比對

2.參數暨引數比對(配對)

.符合以上的就是viable functions 合用的函式

3.接下來(頁244)函式匹配就會找到最符合呼叫端要求的函式。就是逐一核對引數與參數是否一一匹配



winnow

即林占梅〈地震歌〉的「籠篩」

頁244

4:41:50

Finding the Best Match, If Any

如果可能,找到最速配的

4:42:28



Function Matching with Multiple Parameters

多重參數的函式匹配(function matching)



函式匹配應是會選擇引數值損耗率最小的那個來作匹配

4:57:00 5:11:00

非也!(以上所預測者非也)

The compiler will reject this call because it is ambiguous:(頁245)

編譯器不會妥協。呵呵。不想被編譯器「駁回」就要將型不合的引數在呼叫前先明確轉型。

5:0:30

overall 應即佛門裡愛用的「究竟」,∴

There is an overall best match if there is one and only one function for which

如果有一個而且只有一個符合下列條件的函式存在,那就是整體而言的最佳匹配:

應翻成「那就是究竟最佳的匹配」更適合中文語境。

exact match 完全吻合,完美配對,最速配



頁245

練習6.49

5:24:10

名稱與呼叫端一樣的重載函式是候選函式:

The first step of function matching identifies the set of overloaded functions considered for the call. The functions in this set are the candidate functions.

合用函式就是經過引數評估後,堪可執行的函式:

The second step selects from the set of candidate functions those functions that can be called with the arguments in the given call. The selected functions are the viable functions.

練習6.50

5:29:59

//(a)

f(2.56, 42);// Error(active) E0308 more than one instance of overloaded function "f" matches the argument list

// (b)

f(42);//f(int);

// (c)

f(42, 0);//void f(int, int) ;

// (d)

f(2.56, 3.14);//void f(double, double = 3.14) ;

練習6.51

5:38:40

Write all four versions of f. Each function should print a distinguishing message.

這就是所謂的實作implement。

6.6.1. Argument Type Conversions

6.6.1引數型別之轉換

轉型的等級

1.最速配,又分三級:

(1)引數與參數型別完全相同

(2)引數是可以轉成指標(pointer)的陣列或函式

(3)頂層const可以被忽略

2.可由csont轉型

3.經由值的提昇(整數提升(integral promotion)

4.藉由算術或指標轉型

5.類別型別(class type)的轉型

頁246

5:59:15

Matches Requiring Promotion or Arithmetic Conversion

須經由值的提昇和算術型別轉換的函式匹配

需要提升或算術轉換的匹配



幸好,設計良好的系統很少會包含參數之間關係像接下來的範例那麼接近的函式。

→幸好,設計良好的系統很少會像接下來的範例中那些函式有著關係那麼接近的參數。

關係那麼接近就是型別那麼類似

6:38:40 意思就是下面的



6:15:00 如何用命令列指令來重啟小小輸入法



函式調用時會,引數型別若不吻合,會有值的提昇(整數提昇)

In order to analyze a call, it is important to remember that the small integral types always promote to int or to a larger integral type.

如short型別就只會被short型別的引數呼叫。若引數不是short型別,要呼叫short型別參數的函式,就必須先將該引數轉型為short才行



同是算術型別轉換,只要有兩種可能,多載函式的呼叫就會是歧義之誤(並不會傾向哪一個優先。因為各個算術型別間的轉換都是平等的,沒有誰比誰優先)



Function Matching and const Arguments

函式匹配和常值引數

常值的引數就調用常值參數的函式

We cannot bind a plain reference to a const object.

因為普通的參考(plain reference)默認就是想對它所參考的物件作更動。

用非常值去初始化一個常值參考一樣要經過轉型:

However, initializing a reference to const from a non const object requires a conversion.

7:8:00

頁247

練習6.52

7:9:40

given在此可翻成「像」

rank排位(與「牌位」同音,不若翻成「位次」)

//what is the rank(§ 6.6.1, p. 245) of each conversion in the following calls ?

//(a)

manip('a', 'z');//整數提升(integral promotion) rank is 3

//(b)

manip(55.4, dobj);//算術型別轉換 rank is 4

練習6.53

7:13:59

7:30:30

////(a)

int calc(int&, int&) { return 3; }

int calc(const int&, const int&) { return 3; }//const int and constexpr int argument call this

////(b) 7:35:00

int calc(char*, char*) { return 3; }

int calc(const char*, const char*) { return 3; }//const char* and constexpr char* call this

//(c) illegal because tho top-level const could be omited

int calc(char*, char*) {};

int calc(char* const, char* const) {};//Error C2084 function 'int calc(char *,char *)' already has a body

6.7. Pointers to Functions

6.7指向函式的指標

A function pointer is just that—a pointer that denotes a function rather than an object.

指標(pointer)是指向一個特定的型別。即指標(pointer)是複合型別(compound type)

函式像陣列,也是一種型別,這種型別是由其傳回值之型別與其參數的型別來組成的:

A function’s type is determined by its return type and the types of its parameters.

函式之名並非其型別之一部分

頁248

7:51:27

Using Function Pointers

使用函式指標

前面是宣告函式指標(function pointer)

就像陣列一樣,在我們把函式當作一個值來使用的時候,此函式會自動被轉型成指標(pointer):

When we use the name of a function as a value, the function is automatically converted to a pointer.



pf = lengthCompare; // pf now points to the function named lengthCompare

pf = &lengthCompare; // equivalent assignment: address-of operator is optional

& 取址運算子可以省略(注意,函式名後並不加()函式呼叫運算子)

要調用函式指標指向和的函式可以直接調用,不必解參考後才行

bool b1 = pf("hello", "goodbye"); // calls lengthCompare

bool b2 = (*pf)("hello", "goodbye"); // equivalent call

bool b3 = lengthCompare("hello", "goodbye"); // equivalent call

這樣看來,函式指標很像函式的「別名」alias



指向不同函式型別的函式指標間不存在轉型的可能。所以一定要是同型的函式型別,才能指定給該型的函式指標。

string::size_type sumLength(const string&, const string&);

bool cstringCompare(const char*, const char*);

pf = 0; // ok: pf points to no function

pf = sumLength; // error: return type differs

pf = cstringCompare; // error: parameter types differ

pf = lengthCompare; // ok: function and pointer types match exactly

函式與函式指標的型別一定要完全吻合。

函式的型別則由其參數型別與回傳型別構成

Pointers to Overloaded Functions

多載函式的指標

指向多載函式的指標

對重載函式的指標

8:14:00

也就是指向多載函式的指標只能指向多載中的一個函式,它的型別要和此指標的型別完全相符

頁249

8:19:10

Function Pointer Parameters

函式指標參數(指參數是函式指標型別:即「函式指標的參數」)

8:23:00

參數型別不能是函式型別(function type),但卻可以是函式指標型別。

8:28:40

當我們直接把函式當引數傳遞時,其實這個函式早已被轉型成了函式指標

// automatically converts the function lengthCompare to a pointer to function

useBigger(s1, s2, lengthCompare);

注意:這裡的函式都只取其名,沒有函式呼叫運算子()。有點像VBA中,當使用一個函式不回傳值時,就不要用(),此時就時將它當「方法」(method)在用。


8:39:00

利用 type alias 和 decltype來簡化對函式指標的使用,尤其當其作為參數型別定義時

typedef是取型別別名的傳統方式,新標準是用using……=……

以下這四種其實都是一樣的,因為函式型別在操作時也是直接自動被轉型為函式指標的:

// Func and Func2 have function type

typedef bool Func(const string&, const string&);

typedef decltype(lengthCompare) Func2; // equivalent type

// FuncP and FuncP2 have pointer to function type

typedef bool(*FuncP)(const string&, const string&);

typedef decltype(lengthCompare) *FuncP2; // equivalent type

8:51:00 可見 decltype是回傳精確的型別,沒有被隱含或自動轉型前的:

It is important to note that decltype returns the function type; the automatic conversion to pointer is not done.

可以理解為「宣告時(declaration)的型別(type)」就不會被後來的轉到給改變了。

而「typedef」則是「type definition」,定義可以覆寫宣告(override declaration),應該是這樣的邏輯。

9:0:50 前面是說函式型別或函式指標作為函式式的參數,接下來要講函式如何傳回函式型別或函式指標

Returning a Pointer to Function

傳回一個函式指標

回傳對函式的指標

和陣列一樣,無法回傳函式型別:

As with arrays (§ 6.3.3 , p. 228 ), we can’t return a function type but can return a pointer to a function type.

這時候(回傳值時)反而不會自動轉成指標了,而須我們自己指定型別:(這是和函式型別作為參數時不同的特性 9:25:00。當它作參數時,是會自動轉型為函式指標的)

頁250

we must write the return type as a pointer type; the compiler will not automatically treat a function return type as the corresponding pointer type. 9:24:00

The thing to keep in mind is that, unlike what happens to parameters that have function type, the return type is not automatically converted to a pointer type.

9:18:40 可見在取別名時,別名的本尊對象都不要給名字:

using F = int(int*, int); // F is a function type, not a pointer

using PF = int(*)(int*, int); // PF is a pointer type

別名才取名。

別名宣告(alias declaration)(頁68)

就像陣列一樣,回傳是不能回傳陣列的,也無法回傳函式型別,只能回傳對陣列或函式型別的指標。

9:32:00

int (*f1(int))(int*, int);

1. f1有參數int,∴ f1是個函式

2. f1這個函式回傳的是一個指標

3. 這個指標是指向一個有參數列(int*, int) 的,∴ 因為有參數列,可知是一個函式

4. 而這個函式回傳值是int



9:50:20

For completeness, it’s worth noting that we can simplify declarations of functions that return pointers to function by using a trailing return (§ 6.3.3 , p. 229 ):

為了完整性起見,值得一提的是,我們可以使用一個尾端回傳(§6.3.3)來簡化會回傳對函 式指標的函式之宣告:

「For completeness」應翻為「最後」,即本段小結。謂至此論述如何傳回函式指標的部分才完整也。這「最後」的部分都提了,就沒有缺漏遺憾了。



9:40:00

尾端回傳型別(trailing return type)

auto f1(int) -> int (*)(int*, int);

這也是不需要指名的,只要指明是指標即可

9:47:48有尾端就有首端,所謂首尾兩端 呵呵

首端就是一般將回傳值型別置於函式宣告或定義前端

10:8:59

Using auto or decltype for Function Pointer Types

為函式指標型別使用auto或decltype

如果我們知道我們要傳回的是哪一、或哪些函式,那麼我們就可以用decltype來簡化我們對回傳函式指標型別的指定

這裡沒有談到auto啊,又怎麼依據參數值來決定指向的是哪個函式的指標,也沒交代清楚:

// depending on the value of its string parameter,

// getFcn returns a pointer to sumLength or to largerLength

也沒交代清楚

練習6.54

10:21:38

//int calc( int& p1, int& p2) { return p1; }

int calc( int p1, int p2) { return p1; } // & 通常是要去操作引數,才用參考

vector<decltype(calc)*> vecFP;//declare a vector whose elements have this function pointer type.

10:47:50

練習6.55

前一題是宣告,這一題是定義(實作)

int calc( int p1, int p2) { return p1; }

vector<decltype(calc)*> vecFP;//declare a vector whose elements have this function pointer type.

//函式名不重要,只要參數和傳回型別一致,其函式型別即一。

//A function’s type is determined by its return type and the types of its parameters.(頁247)

//所以calc和以下4個型別一致,只要用calc去decltype就知其函式型別為何了

int add(int p1, int p2) { return p1 + p2; }

int subt(int p1, int p2) { return p1 - p2; }

int mult(int p1, int p2) { return p1 * p2; }

int divi(int p1, int p2) { return p1 / p2; }



int main() {

vecFP.push_back(add);

vecFP.push_back(subt);

vecFP.push_back(mult);

vecFP.push_back(divi);

cout <<vecFP[3](12,2) << endl;//result should be 6(divi is called)

}

練習6.56

10:52:48

//int calc( int p1, int p2) { return p1; }

int calc( int, int) ; //宣告不必取名,只要函式參數及回傳之型別俱即可。

vector<decltype(calc)*> vecFP;//declare a vector whose elements have this function pointer type.

//函式名不重要,只要參數和傳回型別一致,其函式型別即一。

//A function’s type is determined by its return type and the types of its parameters.(頁247)

//所以calc和以下4個型別一致,只要用calc去decltype就知其函式型別為何了

int add(int p1, int p2) { return p1 + p2; }

int subt(int p1, int p2) { return p1 - p2; }

int mult(int p1, int p2) { return p1 * p2; }

int divi(int p1, int p2) { return p1 / p2; }



int main() {

vecFP.push_back(add);

vecFP.push_back(subt);

vecFP.push_back(mult);

vecFP.push_back(divi);

//for (auto fnc :vecFP)

using fncT = int(*)(int, int);

for ( fncT fnc : vecFP)

{

cout << fnc(12, 2) << " ";//result should be 14 10 24 6

}//()即函式呼叫運算子(頁166)

cout << endl;

}

11:1:20

11:7:00

頁251

Chapter Summary

函式是一個具名的計算單位,是構成程式的必要元件:

Functions are named units of computation and are essential to structuring even modest programs.

list of parameters = 參數列(parameters list)

11:18:30

When a function is called, the arguments passed to the function must be compatible with the types of the corresponding parameters.

參數與引數:一個蘿蔔一個坑

只要參數的數量或型別上有所不同,同一名稱可以定義一組多載/重載函式

Defined Terms

11:22:00 也可翻成「相關的專有名詞」

ambiguous call

含混的呼叫

多個函式對一個呼叫的滿足是不分軒輊的



11:35:30

constexpr function

A constexpr function is implicitly inline .

11:37:00

executable file

File, which the operating system executes, that contains code corresponding to our program.

11:38:00

function

Callable unit of computation.

函式最簡明扼要的定義



initializer_list 串列初始器

initializer_list Library class that represents a comma-separated list of objects of

a single type enclosed inside curly braces.

頁252

inline function

Request to the compiler to expand a function at the point of call,if possible. Inline functions avoid the normal function-calling overhead.

行內函式可以省去一般函式呼叫的繁文縟節

link

link (連結)把多個目的檔(object files)放在一起形成一個可執行程式的編譯步驟。

11:49:40

object code

Format into which the compiler transforms our source code.

object code (目的碼)編譯器會將我們的原始碼轉為這種格式。

object file

File holding object code generated by the compiler from a given source file. An executable file is generated from one or more object files after the files are linked together.

11:53:20

Global objects are created during program startup.

are destroyed被撤銷(掉)



preprocessor macro

Preprocessor facility that behaves like an inline function. Aside from assert , modern C++ programs make very little use of preprocessor macros.

留言

熱門文章