C++ Primer, Fourth Edition (中英文)下載下傳位址:http://download.csdn.net/detail/ace_fei/4165568
以下内容截取自該書籍,都是一些基礎而又容易忽略的知識點。
初窺輸入/輸出
endl 是一個特殊值,稱為操縱符,将它寫入輸出流時,具有輸出換行的效果,并重新整理與裝置相關聯的緩沖區。通過重新整理緩沖區,使用者可立即看到寫入到流中的輸出。
比如下面這段程式可以看出,如果沒有cout << endl;重新整理緩沖區, 那麼要等10秒後,程式結束時,才能列印出字元串。
#include <iostream>
using namespace std;
int main()
{
cout <<"Sleep!";
// cout << endl;
sleep(10);
return 0;
}
複制
程式員經常在調試過程中插入輸出語句,這些語句都應該重新整理輸出流。忘記重新整理輸出流可能會造成輸出停留在緩沖區中,如果程式崩潰,将會導緻程式錯誤推斷崩潰位置。
使用内置算術類型
對于 unsigned 類型來說,負數總是超出其取值範圍。unsigned 類型的對象可能永遠不會儲存負數。有些語言中将負數賦給 unsigned 類型是非法的,但在 C++ 中這是合法的。
C++ 中,把負值賦給 unsigned 對象是完全合法的,其結果是該負數對該類型的取值個數求模後的值。是以,如果把 -1 賦給8位的 unsignedchar,那麼結果是 255,因為 255 是 -1 對 256 求模後的值。
若在某機器上short 類型占16 位,那麼可以賦給short類型的最大數是2的15次方-1,即32767;而unsignedshort 類型的最大數為2的16次方-1,即65535。
當将超過取值範圍的值賦給 signed 類型時,由編譯器決定實際賦的值。在實際操作中,很多的編譯器處理signed 類型的方式和 unsigned 類型類似。也就是說,指派時是取該值對該類型取值數目求模後的值。然而我們不能保證編譯器都會這樣處理 signed 類型。
如果您要處理的隻是非負整數,那麼應該優先使用unsigned 打頭的那些整數類型。如果您要處理的整數超出了int所能表示的範圍,并且您的編譯器中,long的表示範圍比int大,那就使用long。不過,若非必要,盡量不要用long,因為它可能會降低程式運作效率。有一點要注意:如果您的編譯器中,long和int都是32位的,并且您需要使用32位整數,那麼應該用long,而不要用int。隻有這樣,我們的程式才可以安全地移植到16位的計算機,因為16位的計算機,int一般也是16位的。類似地,如果您需要使用64位整數,那就用long long。如果int是32位的話,那麼使用short可以節省空間,不過您得確定您要處理的整數不會超出short的表示範圍。這種“節省”對記憶體大的計算機來說,是沒什麼意義的。
決定使用哪種浮點型進行運算:使用 double 類型基本上不會有錯。 在 float 類型中隐式的精度損失是不能忽視的,而 double 類型精度代價相對于 float 類型精度代價可以忽略。事實上,有些機器上,double 類型比 float 類型的計算要快得多。long double 類型提供的精度通常沒有必要,而且還需要承擔額外的運作代價。
建議:不要依賴未定義行為
使用了未定義行為的程式都是錯誤的,即使程式能夠運作,也隻是巧合。未定義行為源于編譯器不能檢測到的程式錯誤或太麻煩以至無法檢測的錯誤。
不幸的是,含有未定義行為的程式在有些環境或編譯器中可以正确執行,但并不能保證同一程式在不同編譯器中甚至在目前編譯器的後繼版本中會繼續正确運作,也不能保證程式在一組輸入上可以正确運作且在另一組輸入上也能夠正确運作。
程 序不應該依賴未定義行為。同樣地,通常程式不應該依賴機器相關的行為,比如假定 int 的位數是個固定且已知的值。我們稱這樣的程式是不可移植的。當程式移植到另一台機器上時,要尋找并更改任何依賴機器相關操作的代碼。在本來可以運作的程式 中尋找這類問題是一項非常不愉快的任務。
關鍵概念:強靜态類型
C++ 是一門靜态類型語言,在編譯時會作類型檢查。一些程式設計語言,特别是 Smalltalk 和 Python,在運作時才檢查語句中對象的類型。
在大多數語言中,對象的類型限制了對象可以執行的操作。如果某種類型不支援某種操作,那麼這種類型的對象也就不能執行該操作。
在 C++ 中,操作是否合法是在編譯時檢查的。當編寫表達式時,編譯器檢查表達式中的對象是否按該對象的類型定義的使用方式使用。如果不是的話,那麼編譯器會提示錯誤,而不産生可執行檔案。
随着程式和使用的類型變得越來越複雜,我們将看到靜态類型檢查能幫助我們更早地發現錯誤。靜态類型檢查使得編譯器必須能識别程式中的每個實體的類型。是以,程式中使用變量前必須先定義變量的類型( 寫慣perl 、shell、python這些腳本的人需要注意 )。
什麼是變量
變量提供了程式可以操作的有名字的存儲區。C++中的每一個變量都有特定的類型,該類型決定了變量的記憶體大小和布局、能夠存儲于該記憶體中的值的取值範圍以及可應用在該變量上的操作集。C++ 程式員常常把變量稱為“變量”或“對象(object)”。
術語:什麼是對象?
C++ 程式員經常随意地使用術語對象。一般而言,對象就是記憶體中具有類型的區域。說得更具體一些,計算左值表達式就會産生對象。
嚴格地說,有些人隻把術語對象用于描述變量或類類型的值。有些人還差別有名字的對象和沒名字的對象,當談到有名字的對象時一般指變量。還有一些人區分對象和值,用術語對象描述可被程式改變的資料,用術語值描述隻讀資料。
在本書中,我們遵循更為通用的用法,即對象是記憶體中具有類型的區域。我們可以自由地使用對象描述程式中可操作的大部分資料,而不管這些資料是内置類型還是類類型(也就是說int i;這裡的i也稱之為對象),是有名字的還是沒名字的,是可讀的還是可寫的。
初始化
C++ 支援兩種初始化變量的形式:複制初始化和直接初始化。複制初始化文法用等号(=),直接初始化則是把初始化式放在括号中
intival(1024); // direct-initialization
int ival =1024; // copy-initialization
複制
這裡,了解“初始化不是指派”是必要的。初始化指建立變量并給它賦初始值,而指派則是擦除對象的目前值并用新值代替。記住:當初始化類類型對象時,直接初始化文法更靈活且效率更高。對内置類型來說,複制初始化和直接初始化幾乎沒有差别。
變量初始化規則
内置類型變量是否自動初始化取決于變量定義的位置。在函數體外定義的變量都初始化成 0(全局變量和static變量都會被自動初始化為0),在函數體裡定義的内置類型變量不進行自動初始化(編譯器一般會配置設定給它一個随機值)。除了用作指派操作符的左操作數,未初始化變量用作任何其他用途都是沒有定義的。未初始化變量引起的錯誤難于發現。正如我們在之前勸告的,永遠不要依賴未定義行為。
警告:未初始化的變量引起運作問題
使用未初始化的變量是常見的程式錯誤,通常也是難以發現的錯誤。雖然許多編譯器都至少會提醒不要使用未初始化變量,但是編譯器并未被要求去檢測未初始化變量的使用。而且,沒有一個編譯器能檢測出所有未初始化變量的使用。
有時我們很幸運,使用未初始化的變量導緻程式在運作時突然崩潰。一旦跟蹤到程式崩潰的位置,就可以輕易地發現沒有正确地初始化變量。
但有時,程式運作完畢卻産生錯誤的結果。更糟糕的是,程式運作在一部機器上時能産生正确的結果,但在另外一部機器上卻不能得到正确的結果。添加代碼到程式的一些不相關的位置,會導緻我們認為是正确的程式産生錯誤的結果。
問題出在未初始化的變量事實上都有一個值。編譯器把該變量放到記憶體中的某個位置,而把這個位置的無論哪種位模式都當成是變量初始的狀态。當被解釋成整型值時,任何位模式(位模式:計算機中所有二進制的0、1代碼所組成的數字串。)都是合法的值——雖然這個值不可能是程式員想要的。因為這個值合法,是以使用它也不可能會導緻程式崩潰。可能的結果是導緻程式錯誤執行和/或錯誤計算。(條款04 :确定對象被使用前已先被初始化)
聲明和定義
變量的定義用于為變量配置設定存儲空間,還可以為變量指定初始值。在一個程式中,變量有且僅有一個定義。
聲明用于向程式表明變量的類型和名字。定義也是聲明:當定義變量時我們聲明了它的類型和名字。可以通過使用extern關鍵字聲明變量名而不定義它。不定義變量的聲明包括對象名、對象類型和對象類型前的關鍵字extern:
extern int i; // declares but does not define i
int i; // declares and defines i
複制
extern 聲明不是定義,也不配置設定存儲空間。事實上,它隻是說明變量定義在程式的其他地方。程式中變量可以聲明多次,但隻能定義一次。
隻有當聲明也是定義時,聲明才可以有初始化式,因為隻有定義才配置設定存儲空間。初始化式必須要有存儲空間來進行初始化。如果聲明有初始化式,那麼它可被當作是定義,即使聲明标記為 extern:
extern double pi =3.1416; // definition
複制
雖然使用了 extern ,但是這條語句還是定義了 pi,配置設定并初始化了存儲空間。隻有當 extern 聲明位于函數外部時,才可以含有初始化式。
因為已初始化的 extern 聲明被當作是定義,是以該變量任何随後的定義都是錯誤的:
extern double pi =3.1416; // definition
double pi; // error: redefinition of pi
複制
同樣,随後的含有初始化式的 extern 聲明也是錯誤的:
extern double pi =3.1416; // definition
extern doublepi; // ok: declaration notdefinition
extern double pi =3.1416; // error: redefinition of pi
複制
任何在多個檔案中使用的變量都需要有與定義分離的聲明。在這種情況下,一個檔案含有變量的定義,使用該變量的其他檔案則包含該變量的聲明(而不是定義)。
在變量使用處定義變量
一般來說,變量的定義或聲明可以放在程式中能擺放語句的任何位置。變量在使用前必須先聲明或定義。
Best practice: 通常把一個對象定義在它首次使用的地方是一個很好的辦法。(條款26:盡可能延後變量定義式的出現時間)
定義 const 對象
因為常量在定義後就不能被修改,是以定義時必須初始化。
const 對象預設為檔案的局部變量
在全局作用域裡定義非 const 變量時,它在整個程式中都可以通路。我們可以把一個非 const 變更定義在一個檔案中,假設已經做了合适的聲明,就可在另外的檔案中使用這個變量:
// file_1.cc
int counter; // definition (非 const 變量預設為extern)
// file_2.cc
extern intcounter; // uses counter from file_1
++counter; // increments counter defined infile_1
複制
與其他變量不同,除非特别說明,在全局作用域聲明的 const 變量是定義該對象的檔案的局部變量(這樣設定預設情況的原因在于允許const 變量定義在頭檔案中)。此變量隻存在于那個檔案中,不能被其他檔案通路。
通過指定 const 變更為 extern,就可以在整個程式中通路 const 對象:
// file_1.cc
// defines andinitializes a const that is accessible to other files
externconst int bufSize = fcn();
// file_2.cc
externconst int bufSize; // uses bufSize from file_1
// uses bufSizedefined in file_1
for (int index =0; index != bufSize; ++index)
複制
Note:非 const 變量預設為extern。要使 const 變量能夠在其他的檔案中通路,必須地指定它為 extern。
引用
引用就是對象的别名。在實際程式中,引用主要用作函數的形式參數。
引用是一種複合類型(另外兩種複合類型:指針和數組),通過在變量名前添加“&”符号來定義。複合類型是指用其他類型定義的類型。在引用的情況下,每一種引用類型都“關聯到”某一其他類型。不能定義引用類型的引用,但可以定義任何其他類型的引用。
引用必須用與該引用同類型的對象初始化:(差別于:const 引用)
int ival = 1024;
int &refVal =ival; // ok: refVal refers to ival
int&refVal2; // error: a referencemust be initialized
int &refVal3 =10; // error: initializer must be anobject
複制
Note:當引用初始化後,隻要該引用存在,它就保持綁定到初始化時指向的對象。不可能将引用綁定到另一個對象。
(條款21:必須傳回對象時,别妄想傳回其reference)
const 引用
Note:非 const 引用隻能綁定到與該引用同類型的對象。
const 引用則可以綁定到不同但相關的類型的對象或綁定到右值。
const 引用可以初始化為不同類型的對象或者初始化為右值,如字面值常量:
int i = 42;
// legal for const references only
const int &r =42;
const int &r2= r + i;
double dval =3.14;
const int &ri= dval;
複制
typedef 名字
typedef 通常被用于以下三種目的:
為了隐藏特定類型的實作,強調使用類型的目的。
簡化複雜的類型定義,使其更易了解。
允許一種類型用于多個目的,同時使得每次使用該類型的目的明确。
枚舉成員是常量
可以為一個或多個枚舉成員提供初始值,用來初始化枚舉成員的值必須是一個常量表達式。常量表達式是編譯器在編譯時就能夠計算出結果的整型表達式。整型字面值常量是常量表達式,正如一個通過常量表達式自我初始化的const 對象也是常量表達式一樣。
枚舉成員值可以是不唯一的。
枚舉類型的對象的初始化或指派,隻能通過其枚舉成員或同一枚舉類型的其他對象來進行。
#include<iostream>
#define x 1
const int y = 2; //constant expression
enum Points
{
point1 = x,
point2 = y,
point3,
point4 = 3,
};
int main()
{
Points p1 = point3; // ok: point3d is a Points enumerator
//Points pt2w = 3; // error: pt2w initialized with int
Points p2;
//pt2w = polygon; // error: polygon is not a Points enumerator
p2 = p1; // ok: both are objects of Points enum type
return 0;
}
複制
類類型
每個類都定義了一個接口和一個實作。接口由使用該類的代碼需要執行的操作組成。實作一般包括該類所需要的資料。實作還包括定義該類需要的但又不供一般性使用的函數。
程式設計新手經常會忘記類定義後面的分号,這是個很普遍的錯誤!
用 class 和struct 關鍵字定義類的唯一差别在于預設通路級别:預設情況下,struct 的成員為 public,而class 的成員為 private。
頭檔案用于聲明而不是用于定義
頭檔案一般包含類的定義、extern 變量的聲明和函數的聲明。對于頭檔案不應該含有定義這一規則,有三個例外。頭檔案可以定義類、值在編譯時就已知道的const 對象(即const變量是用常量表達式初始化)和inline 函數。
設計頭檔案不能太大,程式員可能不願意承受包含該頭檔案所帶來的編譯時代價。
因為頭檔案包含在多個源檔案中,是以不應該含有變量或函數的定義。(因為程式中定義隻能出現一次,如果含有定義頭檔案包含在多個源檔案之中,就會出現重複定義)
一些 const 對象定義在頭檔案中
如果 const 變量不是用常量表達式初始化,那麼它就不應該在頭檔案中定義。
當我們在頭檔案中定義了 const 變量後,每個包含該頭檔案的源檔案都有了自己的 const 變量,其名稱和值都一樣。當該 const 變量是用常量表達式初始化時,可以保證所有的變量都有相同的值。在實踐中,大部分的編譯器在編譯時都會用相應的常量表達式替換這些 const 變量的任何使用。是以,在實踐中不會有任何存儲空間用于存儲用常量表達式初始化的const 變量。
以下是來自網上的一段代碼,解釋的很好:
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此時并未将Pi放入ROM中 ......
double i=Pi; //此時為Pi配置設定記憶體,以後不再配置設定!
double I=PI; //編譯期間進行宏替換,配置設定記憶體
double j=Pi; //沒有記憶體配置設定
double J=PI; //再進行宏替換,又一次配置設定記憶體!
const定義常量從彙編的角度來看,隻是給出了對應的記憶體位址,而不是象#define一樣給出的是立即數,是以,const定義的常量在程式運作過程中隻有一份拷貝,而#define定義的常量在記憶體中有若幹個拷貝。
避免多重包含
頭檔案應該含有保護符,即使這些頭檔案不會被其他頭檔案包含。編寫頭檔案保護符并不困難,而且如果頭檔案被包含多次,它可以避免難以了解的編譯錯誤。
在編寫頭檔案之前,我們需要引入一些額外的預處理器設施。 預處理器允許我們自定義變量。預處理器變量有兩種狀态:已定義或未定義。定義預處理器變量和檢測其狀态所用的預處理器訓示不同。#define 訓示接受一個名字并定義該名字為預處理器變量。#ifndef 訓示檢測指定的預處理器變量是否未定義。如果預處理器變量未定義,那麼跟在其後的所有訓示都被處理,直到出現#endif。
可以使用這些設施來預防多次包含同一頭檔案:
#ifndef SALESITEM_H
#define SALESITEM_H
// Definition of Sales_itemclass and related functions goes here
endif
複制