C++Primer学习笔记:第2章 变量和基本类型
-
空類型不對應具體的值,僅用于一些特殊的場合
-
long的長度為32位,float有7個有效位,double有16個有效位
-
如果數值超過了int的范圍,應該用long long而不是long,long一般和int一樣大
-
在算術表達式中不要使用char或bool,只有在存放字符或布爾值時才是使用它們。因為類型char在一些機器上是有符號的,而在另一些機器上又是無符號的,所以使用char進行運算特別容易出現問題。如果需要使用一個不大的整數,那么明確指明它的類型是signed char或unsigned char。
-
執行浮點運算時用double。這是因為float通常精度不夠而且雙精度和單精度浮點數的計算代價相差無幾(甚至在一些機器上double更快)。long double提供的精度一般是沒有必要的,而且計算帶來的運行消耗也不容忽視。
-
當我們把一個非布爾類型的算術值賦給布爾類型時,初始值為0則結果為false,否則為true;當我們把一個布爾值賦給非布爾類型是,初始值為false則結果為0,初始值為true則結果為1;當我們把一個浮點數賦給整數類型時會截取整數部分;當我們賦給無符號類型一個超出它范圍的值時,結果是初始值對無符號類型表示數值總數取模后的余數,而當賦予帶符號類型一個超出它表示范圍的值時,結果是未定義的
-
程序應該盡量避免依賴于實現環境的行為(例如int的尺寸),否則會導致程序的不可移植性
-
不要混用帶符號類型和無符號類型,帶符號數會自動轉化為無符號數
-
在涉及無符號數的表達式中不要出現負數,例如下面這個很容易產生錯誤的代碼:
for (unsigned u = 10; u >= 0; --u)std::cout << u << std::endl;這個代碼看起來很簡單,但是實際上會出現死循環,因為當u==-1的時候因為u是無符號數的緣故 -1將會自動轉化成一個合法的無符號數,無法跳出循環
-
以0開頭的整數表示八進制數,以0x開頭的整數代表十六進制數
-
如果我們使用了一個負十進制字面值,那么負號并不在字面值之內,它的作用僅僅是對字面值取負值。
-
單引號括起來的一個字符稱為char型字面值,雙引號括起來的零個或多個字符則構成字符串字面值。字符串字面值的類型實際上由常量字符構成的數組。編譯器在每個字符串的結尾處添加一個空字符(\0),因此,字符串字面值的實際長度比它的內容多1。如果兩個字符串字面值位置緊鄰且僅由空格、縮進和換行符分隔,則他們實際上是一個整體
-
退格符\b會刪除輸出緩沖區中的一個字符(如果沒有就無效),進紙符\f和縱向制表符\v效果相同,都會在下一行的當前位置的后一個位置開始輸出,回車符\r會在緩沖區的開始位置開始覆蓋,報警符\a會響一下
-
我們可以使用泛化的轉義序列,\x后緊跟一個或者多個十六進制數字,或者\后緊跟1個,2個或3個(最多)八進制數字
-
通過添加前綴或者后綴可以改變整型、浮點型和字符型字面值的默認類型。如:對整型字面值添加LL后綴意味著字面值為long long類型,對浮點型字面值添加L意味著字面值為long double類型。在字符或者字符串字面值前面添加L表示寬字符
-
通常情況下,對象是指一塊能存儲數據并具有某種類型的內存空間(意義同我們經常說的變量
-
當一次定義了兩個或者多個變量時,對象的名字隨著定義也可以馬上使用了。因此在同一條定義語句中,可以用先定義的變量值去初始化后定義的其他變量
-
在C++中,初始化和賦值是兩個完全不同的操作。兩者的區別在于:初始化的含義是創建變量的時候賦予其一個初始值,而賦值的含義是把對象的當前值擦除,而以一個新值來代替。
-
初始化格式:
int a = 0; int b = {0}; int c{0}; int c(0);上面四種方式都是允許的,其中使用{}進行初始化的方式被稱為列表初始化,在C++11標準被全面使用。使用列表初始化的一個重要特點:如果我們使用列表初始化且初始值存在丟失信息的風險,那么編譯器會報錯。例如double a = 1.5; int b{a};將不被允許
-
如果定義變量的時候沒有指定初始值,則變量被默認初始化。默認值到底是什么由變量類型決定,同時定義變量的位置也會對此產生影響。對于內置類型,如果定義在任何函數體之外的變量被默認初始化為0,定義在函數內部的內置類型將不被初始化。使用一個未初始化的內置將產生未定義的行為。建議初始化每一個內置類型的變量
-
為了允許把程序拆分成多個邏輯部分來編寫,C++語言支持分離式編譯機制,該機制允許將程序分割為若干個文件,每個文件可被獨立編譯。為了支持分離式編譯,C++語言將聲明和定義區分開。聲明使得名字為程序所知,一個文件如果想使用別處定義的名字則必須包含對那個名字的聲明,而定義負責創建與名字關聯的實體
-
如果想要聲明一個變量而非定義它,就在變量名前面添加關鍵字extern,而且不要顯式地初始化變量。任何包含了顯式初始化的聲明即成為定義,即使使用extern。在函數內部,如果試圖初始化一個由extern關鍵字標記的變量將引發錯誤
-
變量能且只能定義一次,但是可以被多次聲明
-
C++是一種靜態類型語言,其含義是在編譯階段檢查類型。其中檢查類型的過程稱為類型檢查。對象的類型決定了對象能夠參與的運算。
-
C++標識符由字母、數字和下劃線組成,其中必須以字母和下劃線開頭(不能以數字開頭)。標識符的長度沒有限制,但是對大小寫字母敏感。用戶自定義的標識符中不能連續出現兩個下劃線,也不能以下劃線緊跟大寫字母開頭。此外,定義在函數體外的標識符不能以下劃線開頭。變量名一般用小寫字母,用戶自定義的類名一般以大寫字母開頭
-
名字的有效區域始于名字的聲明語句,以聲明語句所在的作用域末端為結束
-
一般來說,在對象第一次被使用的地方定義他是一種好的選擇,因為這樣可以更容易找到變量的定義,也容易賦給他一個比較合理的初始值。
-
作用域中一旦聲明了某個名字,那么他所嵌套著的所有作用域中都能訪問該名字,同時允許在內層作用域中重新定義外層作用域中已有的名字
-
可以使用作用域操作符來覆蓋默認的作用域規則,因為全局作用域本身沒有名字,所以當作用與操作符的左側為空時,向全局作用域發出請求獲取作用域操作符右側名字對應的變量
-
復合類型是基于其他類型定義的類型(例如[左值]引用和指針)。一條聲明語句由一個基本數據類型和緊隨其后的一個聲明符列表組成。每個聲明符命名了一個變量并指定該變量為與基本類型有關的某種類型。
-
引用為對象起了另一個名字。在定義引用時,程序把引用和他的初始值綁定在一起,而不是將初始值拷貝給引用。一旦初始化完成,引用將和他的初始值對象一直綁定在一起。因為無法另引用重新綁定到另一個對象,所以引用必須被初始化。因為引用本身不是一個對象,因此不能定義引用的引用。引用類型必須和要綁定的對象類型匹配。而且引用只能綁定在對象上,而不能與字面值或者某個表達式的計算結果綁定在一起。
int a = 10; int &b = a; //b指向a -
指針是指向另外一種類型的復合類型,存放指定對象的地址。和引用的區別:
指針本身就是一個對象,允許對指針復制和拷貝,而且在指針的生命周期內它可以先后指定幾個不同的對象,而引用綁定的對象不會發生改變。
指針無須在定義時賦初值(雖然最好這樣做)。
因為引用不是對象,沒有實際地址,所以不能定義指向引用的指針。但是可以定義指向指針的引用。
因為在聲明語句中指針的類型實際上被用于指定它所指向對象的類型,因此兩者必須匹配。
-
試圖拷貝或以其他方式訪問無效指針的值都將引發錯誤,編譯器不負責檢查此類錯誤。
-
空指針定義
int *p1 = nullptr; //推薦,C++11標準 int *p2 = 0; //#include<cstdlib> int *p3 = NULL把int變量直接賦給指針是錯誤的操作,即使int變量的值恰好等于0也不行
-
void*是一種特殊的指針類型,可用于存放任意對象的地址。
作用: - 和別的指針比較
- 作為函數的輸入或輸出
- 賦給另外一個void*指針
-
代碼規范:把類型修飾符(* 或者 &)與變量名寫在一起,與數據類型分開
int a = 0, *b = &a, *&c = b;當聲明語句比較復雜的時候,從右往左閱讀有助于弄清楚他的真實含義。
-
const對象一旦創建后其值就不能再改變,所以const對象必須進行初始化,可以用另一個對象去初始化一個const對象,對變量的初始化不算改變變量的內容。(這里也能看出初始化和賦值的區別)
-
使用const變量以后,編譯器在編譯過程中用到該變量的地方都替換成對應的值。為了執行上述操作,編譯器必須知道變量的初始值,因此就必須在每一個用到const變量的文件中有該變量的定義。為了避免重復定義,默認情況下,const對象被設定為僅在文件內有效。如果想要在多個文件之間共享const對象,必須在變量的定義(當然聲明也要加)之前添加extern關鍵字,使其被其他文件使用。
-
對常量對象的引用也必須用const修飾,表示該引用所指向的是一個常量。
const int a = 1; const int &b = a; //b是a的引用 a = 2; //錯誤 int &c = a; //錯誤,試圖讓一個非常量引用指向一個常量對象需要注意的是引用定義中的const不是表示引用本身的常量屬性,引用變量b本身就具有常量屬性(不能解除與a的綁定),而是說b對引用的變量的操作的常量屬性(如果引用的對象是一個const對象,則操作必須是對常量的操作,后面還有其他情況的說明)。我們一般稱這種引用為常量引用
-
一般情況下,引用的類型必須和其所引用的對象保持一致,但是對于常量引用的初始化允許用任意表達式作為初始值,只要該表達式的結果能夠轉化為引用的類型即可。尤其,允許為一個常量引用綁定非常量的對象、字面值,甚至是一個表達式。
int a = 1; const int &b = a; //正確 const int &c = 2; //正確 const int &d = b * 2;//正確 int &e = r1 * 2; //錯誤,普通的引用必須綁定在一個對象上 double e = 3.14; const int &f = e; //正確想要理解上面這些奇怪的行為,就要明白對常量引用初始化的過程,為了完成對f的初始化,編譯器會將最后一行代碼翻譯為:
const int tmp = e; const int &f = tmp;在這種情況下,f綁定了一個臨時量對象(編譯器需要一個空間來暫存表達式的求值結果時臨時創建的一個未命名的對象)。其他初始化語句同理。
-
必須要認識到,常量引用僅僅限定了引用可參與的操作,對于引用的變量本身是不是一個常量未作限定,我們仍舊可能通過其他方式對常量引用所指向的對象進行修改:
int i = 1; int &r1 = i; const int &r2 = i; const int &r3 = i * 3.14; r1 = 2; //i == 2 //r1 == 2 //r2 == 2 //r3 == 3再次說明了數據類型對數據可以進行的操作進行了限定。
-
指向常量的指針不能用于改變其所指對象的值,想要存放常量對象的地址,只能使用指向常量的指針。在這一點上,對常量的引用和指向常量的指針類似。然而兩者最大的不同點在于,指向常量的指針本身不是一個常量,可以發生變化。而對常量的引用卻因為是一個引用一旦初始化便不能再發生改變
const double pi = 3.14; double *p1 = π //錯誤 const double *p2 = π //正確 *p2 = 4; //錯誤,不能改變指向常量的指針所指向隨想的值 double a = 4.0; p2 = &a; //正確 a = 5.0; //正確 -
指針的類型必須和所指對象的類型一致,但是允許一個指向常量的指針指向一個非常量對象(但是數據類型必須一致,這一點和對常量的引用不同)。如上面代碼所示,指向常量的指針也沒有規定所只想的對象必須是一個常量,我們仍可能通過其他方式改變所指向對象的值。
-
常量指針必須初始化,一旦初始化完成,常量指針所指向的地址就不能再發生變化。我們把*放在const之前用來說明指針是一個常量(聲明的閱讀法則:從右往左看)??赡芡ㄟ^常量指針改變所指向的值(如果指向的不是一個常量的話)。
int i = 1; const int &r1 = i * 3.14; const int *temp = &r1; const int **const &tmp = &temp; //**tmp等于3 *tmp = &i; //**tmp等于1上面的代碼是可以運行通過的(實測),tmp是一個指向整型常量的常量二級指針的引用。但是我們是可以改變*tmp的值的,因為const只修飾了最近的那個*,如果需要*tmp的值也不能改變,我們就要將聲明改為:const int *const *const &tmp = &temp(屬實是我寫過最長的聲明了)。
-
用名詞頂層const表示指針本身是一個常量,用名詞底層const表示指針所指的對象是一個常量。頂層const可以表示任意的對象是常量,這一點對任何數據類型都適用。底層const則與指針和引用等復合類型的基本類型部分有關。其中用于聲明引用的const都是底層const(其實很好理解,因為引用本身不能更改,相當于本身就具有頂層const的屬性)
-
當執行拷貝操作時,常量是頂層const還是底層const區別明顯,其中頂層const不受什么影響,但是底層const的限制卻不能忽視,當執行對象的拷貝操作時,拷入和拷出的對象必須具有相同的底層const資格,或者兩個對象的數據類型必須能夠轉換(非常量可以轉換為常量)
-
對頂層cosnt和底層const的理解:這只是一個人為理解的概念,我認為對程序本身沒有什么影響。頂層const是指對象的值的不可更改的性質,底層const表示對所指向對象的操作權限比較有限的性質。雖然書中沒有說,但是我認為把非復合類型的數據類型聲明中的const應該同時看作頂層和底層const比較好理解,因為這個時候對象和指向的對象是相同的,這樣就可以推廣上面的規則:任何一個底層const必須初始化/賦值給一個底層const(給基本數據類型的拷貝不算),否則報錯:
- 一個基本數據類型(非復合)常量只能初始化/賦值給常量引用和指向常量的指針
- 一個常量引用只能初始化給一個常量引用,一個指向常量的指針只能初始化/賦值給一個指向常量的指針
-
常量表達式是指值不會改變并且在編譯過程中就能得到計算結果的表達式。顯然字面值屬于常量表達式,用常量表達式初始化的const對象也是常量表達式。特征:常量+在編譯時就能確定結果
-
在C++11新標準規定,允許將變量聲明為constexpr類型以便編譯器來驗證變量的值是否是一個常量表達式,聲明為constexpr的變量一定是一個常量,而且必須用常量表達式初始化
constexpr int i = 20; 20是常量表達式 constexpr int limit = i +1; i+1是常量表達式 constexpr int sz = size(); 只有size()是一個constexpr函數時才是一條正確的聲明語句 -
聲明constexpr類型的必須是字面值類型:算數類型、引用和指針。一個constexpr指針必須是nullprt或0或存儲在某個固定地址中的對象。定義于所有函數體之外的對象其地址固定不變,能用來初始化constexpr指針。需要注意的是constexpr會把變量聲明為頂層const
constexpr const int *p = &i; 要求i的地址固定不變(例如在函數體外) -
關鍵字typedef作為聲明語句中的基本數據類型中的一部分,含有typedef的聲明語句定義的不再是變量而是類型別名
typedef double db; db是double的同義詞 typedef db base, *p; base是double的同義詞,p是double *的同義詞新標準規定了一種新的方法,使用類型別名來定義類型的別名:
using SI = Sales_item; SI是Sales_item的同義詞 -
使用類型別名以后,類型別名成為了新的基本類型,不能錯誤嘗試把類型別名替換成它原本該的樣子來進行理解。
typedef char * cp; const cp p = nullptr; p是一個指向char的指針常量,如果錯誤地將將cp替換成char *以后p是一個指向常量char的指針 -
C++11標注引入了auto類型說明符,編譯器會代替我們去分析表達式所屬的類型,通過初始值來推算變量的類型,顯然,auto定義的變量必須有初始值。使用auto也能在一條語句中聲明多個變量,因為一條聲明語句只能有一個基本數據類型,所以該語句中的所有變量的初始基本數據類型必須一樣
-
編譯器推斷出來的auto類型有時候和初始值的類型并不完全一樣,編譯器會適當地改變結果類型使得其更符合初始化規則:
-
用引用進行初始化的時候編譯器會把引用的對象的類型作為auto類型
-
auto會忽略頂層const,但是底層const會保留下來。
int i = 1;const int ci = i, &cr = ci;auto a = ci; a是整型auto b = cr; b是整型auto c = &i; c是整型指針auto d = &ci; d是指向整型常量的指針 -
如果希望推斷出的auto類型是一個頂層const,需要明確指出:
const auto e = ci; //e是整型常量 -
可以將引用的類型設置為auto,原來的規則依然適用,初始值中的頂層const依然保留(如果我們給初始值綁定一個引用,則由引用所綁定的對象決定):
auto &f = ci; f是一個整型常量引用 auto &g = 42; 錯誤,不能為非常量引用綁定字面值 const auto &g = 42; 正確 -
在一條語句中定義多個auto的引用時,符號&和*只從屬于某個聲明符,而非基本數據類型的一部分。
auto &h = ci, *p = &ci; //h是對整型常量的引用,p是指向整型常量的指針//錯誤寫法 auto &n = i, *p2 = &ci; //n是對整型的引用,p2是指向整型常量的指針auto在不同聲明表現出對頂層const的不同處理方式可能令人費解,在普通聲明中會忽略頂層const,但是在引用的聲明中不會忽略頂層const,我認為從另一個方向理解可能更好理解,在普通數據類型賦值給復合類型的時候,普通數據類型的const同時具有頂層和底層const屬性。更底層的理解方法是復合類型往往意味著對已有對象的操作,但是我們不能放大對對象的操作權限(縮小可以)。
-
-
如果我們希望從表達式的類型推斷出要定義的變量的類型,但是不想用該表達式的值初始化該變量,可以用decltype類型說明符,在此過程中,編譯器分析表達式并得到它的類型,卻不實際計算表達式的值。decltype處理頂層const和引用的方式與auto不同,如果decltype使用的表達式是一個變量,則decltype返回該變量的類型,包括頂層const和引用在內。
const int ci = 0, &cr = ci; decltype(ci) x = 0; x是const int decltype(cr) xr = x; xr 是 const int &引用一般都是作為其所指對象的同義詞出現,只有在decltype處是一個例外
-
decltype的結果類型與表達式形式密切相關。如果decltype的表達式是一個變量,則聲明就是變量的類型,如果給這個變量加上括號,則聲明是引用類型。因為加上括號以后比編譯器會把變量當做一種可以作為賦值語句左值的特殊表達式,所以這樣的decltype就會使引用類型
int i = 0; decltype(i) e; //正確,e是int decltype((i)) d; //錯誤,d是int & -
類以關鍵字struct開始,緊跟類名和類體(類體可以為空)。類體由花括號包圍形成了一個新的作用域。類體右側的表示結束的花括號后必須寫一個分號,這是因為類體后面可以緊跟變量名以表示對該類型對象的定義,所以分號必不可少。但是最好不要把對象的定義和類的定義放在一起。
-
C++11新標準規定可以為數據成員提供一個類內初始值,沒有初始值的成員將會被默認初始化。記住不能使用圓括號進行初始化,可以使用花括號或者等號進行初始化。
-
盡管可以在函數體內部定義類,但是這樣的類會收到一定的限制。因此類一般不定義在函數體中,當在函數體外部定義類時,在各個指定的源文件中可能只有一處該類的定義。而且如果要在多個文件中使用同一個類,類的定義就必須保持一致。為了確保各個文件中類的定義一致,類通常被定義在頭文件中,而且類所在頭文件的名字應該與類的名字一樣。
-
為了確保頭文件多次包含仍能夠安全工作,我們使用預處理器中的頭文件保護符進行處理,對于頭文件Sales_data.h,文件的格式應該如下:
#ifndef __SALES_DATA_H__ #define __SALES_DATA_H__ //定義部分 #endif預處理變量無視C++語言中關于作用域的規則
-
太感動了,我終于看完第二章了。
局限:不能直接操作void*指針所指的對象
總結
以上是生活随笔為你收集整理的C++Primer学习笔记:第2章 变量和基本类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么这个程序会一直输出 财富值1
- 下一篇: 男性不孕不育症的治疗