第六章 分支語句和邏輯運算子
測試條件發生的強制類型轉換
只要是使用到這些關於真假的判斷,系統將強制轉換成bool型,所以對於一般的實值型別這個轉換時有系統自動完成的,然而對於我們自訂的類類型或者是結構就需要通過重載bool強制類型轉換運算子來實現這個功能。所以直接將cin>>num放置到if判定中也是可行的,這將會進行一個強制轉換來顯示上一次的讀取是否成功。
條件預算符合錯誤防範
variable == value 進行一個反轉能夠有效預防錯誤的發生,因為後者是將一個變數賦值給一個常量,這在編譯時間就會發生錯誤。
新的順序點
C++規定,||、&&運算子均為順序點,冒號和逗號運算子也是。順序點就是當遇到順序點時,所有的副作用都必須要要產生,而對於其他未定義的交給各個編譯器自行決定。
其他表示方式
並不是所有的鍵盤都提供這些邏輯運算子,所以C++擁有and、or和not這三個保留字,這意味著這三個單詞不能夠被表示為變數名等。C語言添加iso646.h即可將其用作運算子。
使用標準庫cctype
雖然我們可以使用 ‘a’<=ch && ch <= ‘z’ 來表示一個輸入是否是小寫字母,但這依賴於這些字元的ascii碼是連續的,且我們不能簡單的想象 ’a’<=ch && ch <=’Z’來表示輸入是否為一個字母。所以這種比較依賴於編碼方式的判斷有時候是不靠譜的,因此使用系統必須實現的庫函數來解決就簡單多了。
isalnum() 判定是否是字母或者數字
isalpha() 判定是否為字母
iscntrl() 判定是否為控制字元
isdigit() 判定是否為數字
isgraph() 判定是否除空格之外的列印字元
islower() 判定是否是小寫字母
isprint() 判定是否是列印字元(包括空格)
ispunct() 判定是否是標點符號
isspace() 判定是否是標準空白字元,如空格、進紙、分行符號、斷行符號,水平/垂直定位字元
isupper() 判定是否是大寫字母
isxdigit() 判定是否是16進位數字0-9、a-f、A-F
tolower() 如果是大寫字母則返回其小寫,否則返回本身
toupper() 如果是小寫字母則返回其大寫,否則返回本身
switch語句的特性
由於沒有自動的break處理,因此可以對統一語句實現多標籤。如果既可以使用if...else if語句,也可以使用switch語句的話,那麼當選項不少於3個時,應使用switch語句,從代碼長度和執行速度而言,switch語句的效率更高。
第七章 函數——C++的編程模組
要使用C++函數,必須完成如下工作:
提供函數定義;
提供函數原型;
調用函數。
庫函數已經定義並編譯好,同時可以使用標準庫標頭檔提供其原型,因此只需要正確地調用這種函數即可。
C++傳回值
一個函數能夠返回除了數群組類型之外的其他任何類型。
整型的格式控制符
%d用來讀取十進位數,%o用來讀取八位元,%x用來讀取十六進位數,%i用來根據格式自動識別並讀取。例如:如果使用%d,即時輸入010,那麼前置0忽略,但是%i則會當做八進位的數處理,十進位輸出結果分別是10,8。
為什麼需要原型
這樣可使得編譯器的工作更有效率,否則需要停下當前的工作去尋找某個函數的定義,這樣效率將變得很低,而且可能這些定義還有可能不在同一個檔案下面。原型的功能如下:
編譯器正確處理函數傳回值;
編譯器檢查使用的參數數目是否正確;
編譯器檢查使用的參數類型是否正確。如果不正確,則轉換為正確的類型(如果可能的話)。
僅當有意義時,原型化才會導致類型轉換。例如,原型不會將整數轉換為結構或者指標。
在編譯階段進行的原型化被稱為靜態類型型檢查(static type checking)。可以看出,靜態類型檢查可捕獲許多在運行階段非常難以捕獲的錯誤。
關於原型是否要給定一個參數的名字,我們知道原型的參數名僅僅是一個預留位置,但是為了更好的理解函數的參數性質,最好在類型相同且不容易弄清楚具體參數時應該給定一個易懂的名字。我們注意到庫函數中大部分是使用了類型重定義後的類型名,這確實是一個折中的好辦法。
函數參數是傳值的
對於這點我們要注意的在傳遞數組名時,其實傳遞的是一個指標(數組名),就像函數並沒有辦法返回一個數組一樣。當然可以將一個數組使用結構體進行封裝,那麼就能夠傳遞一個數組。
這裡要再次進行的一個說明的是:假如聲明int array[3][4]這樣一個二維數組。
數組的地址是&array, 類型是int (*)[3][4],一個指向二維數組的指標,array+1就等於加上了48(以一個int型佔4個位元組計算),其實也就是通過數值上加上sizeof(*(&array)),理解起來就是當前指標通過一次指標解除的大小,恰好sizeof(array)就是48。
那麼array是什麼呢,它的類型是int (*)[4],一個指向一維數組的指標,array+1就等於加上了16,sizeof(*array)也就是16。
判定一個指標是多少維是根據其需要通過多少次指標解除才能得到最終的基本類型決定的,因此array是一個二維指標,&array是一個三維指標。
函數如何使用指標來處理數組
若且唯若在函數表示形參時,int *arr 和 int arr[]的意義時是一樣的。但是理解起來就稍微有點不同,後者顯示的告訴我們傳遞將是一個數組,而前者可能是一個普通的指向當個值的指標。
一般情況下,如果要傳遞一個數組的話,我們只需要傳遞一個數組名就可以了。當然如果硬是要體現出這個傳遞的參數的全部的資訊,那麼可以對這個數組名取一次地址,這樣在形參中就要詳細的寫出所有的維數的資訊了。如果這樣的話,這個函數的可重複利用率就大大減低了,所以使用兩個形參搭配非常好,例如對於一個一維數組而言int array[10]:
void fun(int arr[], 10) 相比 void fun(int (*arr)[10])的通用性更好。雖然後者更加詳細的介紹這個數組的性質,STL中更多的使用數組的開始位置和結束位置來表示一個數組。
傳遞數組名的話,我們就只能夠控制數組的最高維,其他維在這個數組名中已經隱含了。
const在指標中的應用
首先說說為什麼只在指標中使用const(不牽涉到後面學習到的引用),因為所有的在指標看來的基本類型都是在同樣是值傳遞的過程中,本身就產生了一個副本,也就根本不會影響到實參的值了,所以也就沒有必要聲明這個變數是唯讀。
對於指標類型就很有必要,加了const不僅能夠使得我們的代碼在不應該修改值的地方發生錯誤操作能夠在編譯階段就發現(相比在運行階段去尋找要簡單很多),並且能夠使得讀代碼的人明白寫代碼的人更多心意。
以前在第四章複合類型討論過一般的變數是將值作為本身的量,而地址作為派生量。而指標則剛好相反,將地址作為本身的量,而將值作為派生的量(對於指標的地址我們暫時不予考慮)。那麼對於const而言,對於實值型別來說,地址肯定是const的,因此 const int num和int const num是沒有區別的。而對於指標類型而言const強調的是這個本身的量還是派生的量呢,定義如下:
const int * ptr; // 表示指向常整型的指標變數,const強調的是派生的量
int * const ptr; // 表示指向整型的常指標變數,const強調的是本身的量
其實也就是看ptr先與誰結合,先於 * 結合表示就是一個普通的指標,先與const結合說明是一個常指標。其餘部分都是對派生量的描述。
一條規定:假如涉及的是一級間接關係,則將非const指標賦給const指標是可以的。然而,進入兩級間接關係時,與一級間接關係一樣將const和非const混合的指標賦值方式將不再安全,如果允許這樣做,則可以編寫這樣的代碼:
const int **pp2;
int *p1;
const int n =13;
pp2 = &p1; // 不允許,但是如果允許的話
*pp2 = &n; // 允許,都是const類型
*p1 = 10; // 允許,但是改變了n的值
當然造成這種情況的可能還有就是人為的將const型實值型別的地址強制轉化為非const類型。
函數指標
函數指標是一個比較冷僻的知識點,因為平時做題也很少用到,其實每一個函數名就是這個函數的地址,也就是其類型就是一個函數指標,不需要通過取地址操作,它就是個指標。
聲明一個函數指標就是照著某個函數的原型抄一遍,然後將函數名改成(* valname)即可。例如:double fun(int, double) 那麼定義指標就是 double (*p_fun)(int, double); 之後就能夠完成p_fun = fun; 或者是*p_fun = fun; 這兩條語句了,這兩條語句也完全等價,只是理解的角度不同,前者認為函數名同樣是地址能夠直接使用,所有指標也直接用,後者認為既然是指標,當然使用一次解除引用也是理所當然的。
這裡之所以使用括弧將p_fun括起來就是應該後面那個括弧(寫參數列表)的優先順序高於前面的 * 號,因此p_fun先於後面的參數列表結合,函數指標申明就變成了返回一個double指標的函式宣告了。
C++11提供的自動類型推斷功能能夠很好的解決這類複雜的賦值問題。auto p_fun = fun; 要比前兩者簡單多了。
當然typedef也能夠解決一定的問題,對於typedef的使用就是定義好一個變數後,在前面加上一個typedef就可以了,以後就可以直接使用這個變數名去定義其他變數了。