c++primer第二章變數和基本型別
2.1 基本內建型別
2.1.1算術型別
算術型別分兩類:整型(integral type)和浮點型。
算術型別的尺寸在不同機器上有所差別。某一類所佔的位元數不同,它所能表示的資料範圍也不樣。
型別 | 最小尺寸 |
---|---|
bool | 未定義 |
char | 8位 |
wchar_t | 16位 |
char16_t | 16位 |
char32_t | 32位 |
short | 16位 |
int | 32位 |
long | 32位 |
long long | 64位 |
float | 6位有效數字 |
double | 10位有效數字 |
long double | 10位有效數字 |
基本的字元型別是char,一個char的大小和一個機器位元組一樣。
大多數計算機以2的整數次冪個位元作為塊來處理記憶體,可定址的最小記憶體塊稱為“位元組”,儲存的基本單元稱為“字”,它通常由幾個位元組組成。大多數機器的位元組由8位元組成,字則由32或64位元組成,也就是4或8位元組。
為了賦予記憶體中某個地址明確的含義,必須首先知道儲存在該地址的資料的型別。型別決定了資料所佔的位元數以及該如何解釋這些位元的內容。
無符號型別
無符號型別僅能表示大於等於0的值。
帶符號型別
int short long 和longlong都是帶符號型別。
字元型別
分為char signed char unsigned char。無符號中所有位元都用來儲存值,例如,8位元的unsigned char 可以用來表示0至255之間的數
執行浮點數運算選用double,這是因為float通常精度不夠而且雙精度浮點數和單精度浮點數的計算代價相差無幾。
2.1.2 型別轉換
大多數型別的物件
當在程式的某處我們使用了一種型別其實應該取另一種物件時,系統會自動進行型別轉換。
例如:
bool b=41;//b為真
int i=b;//i的值為 1
i=3.14;//i的值為3
double pi=i;//pi的值為3.0
unsigned char c=-1;//假設char佔8位元,c的值為255
signed char c1=256;//假設cahr佔8位元,c1的值是未定義的
當我們把非布林型別的算術值賦給布林型別是,初始值為0則結果為false,否則結果為1;
把布林賦給非布林時,初始值為false為0,true時為1;
把浮點型賦給整型時,結果只保留浮點數中小數點之前的部分。
把整型賦給浮點型時,小數部分記為0.如果該整數所佔空間超過了浮點型別的容量,精度可能有損失。
當我們賦給無符號型別一個超出它表示範圍的值時,結果是初始值對無符號型別表示數值總數取模後的餘數。
當我們賦給一個帶符號型別一個超出它表示範圍的值是,結果是未定義的。
如果表示式中既有帶符號的又有無符號的,當帶符號取值為負時會出現一場結果,這是因為帶符號數自動轉換成無符號數。例如在一個形如a*b的式子中,如果a=-1,b=1,而且a和b都是int,則表示式值為-1,但是如果b是unsigned,則結果在我們的環境裡是4294967295.
2.1.3 字面值常量
一個形如42的值被稱為字面值常量。每個字面值常量對應一種資料型別。
整型和浮點型字面值
以0開頭的是八進位制數,以0x或0X開頭的是16進位制。例如我們用不同的方法表示20:
20//十進位制 024//八進位制 0x14//十六進位制
預設情況下十進位制是帶符號的,八進位制和十六進位制可能是帶符號也可能是不帶符號的。
字元和字串字面值
‘a’//字元字面值
“hollow world”字串字面值
編譯器在每個字串結尾處新增一個空字元(‘\0’),因此字串字面值的實際長度要比他的內容多1。
轉義序列
轉義序列均以反斜線作為開始,c++語言規定的轉義序列包括:
換行符\n 橫向製表符\t 報警符\a
縱向製表符\v 退格符\b 雙引號\"
反斜線\\ 問號\? 單引號\'
回車符\r 進紙符\r
指定字面值的型別
字首 含義 型別
u Unicode16字元 char16_t
U Unicode32字元 char32_t
L 寬字元 wchar_t
u8 UTF-8 char
字尾 整型字面值 浮點字面值 型別
u or U unsigned f or F float
l or L long l or L long double
ll or LL long long
布林字面值和指標字面值
true和false是布林型別的字面值:
bool test=false;
nullptr是指標字面值。
2.2 變數
2.2.1 變數定義
型別說明符緊跟一個或多個變數名組成的列表,其中變數名以逗號分割,最後以分號結束。
int sum=0,value, sold=0;
Sale_item item;//item是Sale_item型別
string book(“0-201-78345-X”);//book 通過一個string字面值初始化
通常情況下,物件是指一塊能儲存資料並具有某種型別的記憶體空間。
初始值
物件建立時被賦予一個特定的值,我們說這個物件被初始化了。初始化可以是任意複雜的表示式。
double price=3.14, discount=price*0.98;
double saleprice=函式返回值;
列表初始化
無論是初始化物件還是為某些物件賦新值。
int sold=0;
int sold={0};
int sold(0)
int sold{0};
花括號初始化僅僅在某些受限的場合下才能使用
long double ld=3.1415926536;
int a{ld},b={ld};//錯誤轉換為執行,存在丟失資訊的風險
int c(ld),d=ld;//正確,確實丟失了部分值
預設初始化
1.如果是內建型別的變數未被顯式初始化,它的值由定義的位置決定。
2.定義域任何函式體之外的變數被初始化為0,。
一個未初始化的變數的值是未定義的,如果試圖拷貝或者以其他形式訪問此型別將引發錯誤。建議初始化沒一個內建型別的變數。
2.2.2 變數宣告和定義的關係
c++語言支援分離式編譯機制。該機制允許將程式分割為若干個檔案,每個檔案可獨立編譯。
宣告是的名字為程式所知。定義負責建立與名字關聯的實體。
如果想宣告一個變數而非並非定義它,就在該變數前新增關鍵字extern,而不要顯式的初始化變數:
extern int i;//宣告i而非定義i
int j;//宣告並定義j
變數能且只能被定義一次,但能可以被多次宣告。
如果要在多個檔案中使用同一個變數。就必須將宣告和定義分離。此時變數的定義必須出現在且只能出現在一個檔案中,而其他用到該變數的檔案必須對其進行宣告,卻絕對不能重複定義。
2.2.3識別符號
c++的識別符號有字母,數字下劃線組成,其中必須以字母或下劃線開頭。識別符號的長度沒有限制,但是對大小寫字母敏感:
int somename,someName,SomeName,SOMENAME;
使用者自定義的識別符號不能連續出現兩個下畫線,也不能以下畫線緊連大寫字母開頭。此外,定義在函式體外的識別符號不能以下畫線開頭。
重要命名規範
1.識別符號要能體現實際含義。
2.變數名一般用小寫字母,如index,不要使用Index或INDEX。
3.使用者自定義的類名一般以大寫字母開頭,如Sale_item。
4.如過識別符號有多個單次組成,則單次間應有明顯區分,圖student_loan或StudentLoan,不要使用studentloan
名字的作用域
名字的有效區域始於名字的宣告語句,以宣告語句所在的作用域末端為結束。
一般來說,在物件第一次使用附近定義它是一種好的選擇,因為這樣更容易地找到變數的定義。
巢狀作用域
作用域能彼此包含,被包含的作用域稱為內層作用域。包含著別的作用域的作用域稱為外層作用域。
作用域中一旦宣告某個名字。它所巢狀這的所有作用域中都能訪問該名字。同時允許在內城定義域中重新定義外層作用域已有的名字。
2.3複合型別
複合型別是指基於其他型別定義的型別。
一條簡單的宣告語句有一個數據型別和緊隨其後的一個變數名列表組成。每個宣告符命名了一個變數並指定該變數與基本資料型別有關的某種型別。
2.3.1引用
引用為物件起另一個名字。
int ival=1024;
int &refval=ival;//refval指向ival
int &refval2;//錯誤引用必須初始化
引用是和他的初始值繫結在一起,而不是將初始值拷貝給引用。引用無法重新繫結到另一個物件,所以必須初始化。
定義了引用後在其上的所有操作都是在與之繫結的物件上進行的。
refal=2;
int li=refal;//等價於li=ival
int &refal2=refal;//正確refal3繫結到了與refval繫結的物件上
int i=refval;//正確,此時i被初始化為refval的值
允許在一條語句定義多個引用
int i=12, i1=55;
int &a=i,&b=i1;
引用只能繫結在物件上,不能與字面值或某個表示式的結果繫結在一起。
引用的型別必須和繫結的型別相同
double i=12;
int &refval=i;//錯誤,型別不一致
2.3.2指標
指標是指向另外一種型別的複合型別。指標本身就是一個物件,允許對指標賦值和拷貝,而且在指標的生命週期內它可以先後指向幾個不同的物件。指標無需再定義時賦值。
int *p,*q;//p q是指向int型物件的指標。
獲取物件的地址
要想獲得某個物件的地址,需要使用取地址符&
int ival=42;
int *p=&ival;//p存放變數ival的地址,或者說p指向ival;
除了兩種例外,指標要與指向的物件的型別嚴格匹配。
指標值
指標的值應屬下列4中狀態之一:
1.指向一個物件。
2.指向緊鄰物件所斬空間的下一個位置
3.空指標
4.無效指標,也就是上述之外的其他指標。
利用指標訪問物件
如果指標指向一個物件,允許使用解引用符*來訪問該物件:
int ival=42;
int *p=&ival;
cout<<*p;//輸出平指向物件ival的值
空指標
空指標不指向任何物件。
int *p1=nullptr;
int *p2=0;
int *p3=NULL;//等價於*p3=0;
建議初始化所有指標
賦值和指標
指標本身就是一個物件,給指標賦值就是令它存放一個新地址,從而指向一個新物件:
int i=42;
int *p1=0;
int *p2=&i;//初始化p2,存有i的地址
int *p3;//如果p3定義域塊內,那麼p3的值是無法確定的
p3=p2;//p3和p2指向同一個物件i
p2=0;//現在p2不指向任何物件
又是後不好弄清楚到底改變的是指標的值還是指標所指物件的值,最好的方法是記住賦值永遠改變等號左邊的值。
其他指標操作
int ival=1024;
int *p=0;
int *p2=&ival;
if(p1)//條件為false
//.....
if(p2)//條件未true
//....
void 指標*
void* 指標是一類特殊的指標,可用於存放任意物件的地址。我們隊該地址中到底是個什麼型別的物件並不瞭解。
double obj=3.14,*pd=&obj
void *pv=&obj;//obj可以是任意型別物件
pv=pd;
不能直接操作void指標所指的物件。
以void指標的視角來看記憶體空間也就僅僅是記憶體空間,沒辦法訪問記憶體空間中所存的物件。
2.3.3理解複合內省的宣告
一條語句可以定義不同型別的變數
int i,*p=&i,&r=i;
定義多個變數
有一種錯誤觀點,在定義語句時,類修飾符(&和*)作用域本次定義的全部變數。
int* p1,p2;//p1是指標p2是int型變數
指向指標的指標
指標是記憶體中的物件,想其他物件一樣也有自己的地址,因此允許把指標的地址在存到另一個指標當中。
通過*的個數可以區分指標的級別。
int ival=1024;
int *p1=&ival;
int **p2=&p1;//p2指向一個int型指標
cout<<ival<<*p1<<**p2<<endl;//三種方式輸出ival
指向指標的引用
不能定義指向引用的指標,能定義指向指標的引用。
int i=42;
int *p;
int *&r=p;//r是一個對指標p的引用
r=&i;//r引用了一個指標,因此給r賦值&i就是令p指向i
*r=0;//解引用r得到i,也就是p指向的物件,將i的值改為0
要理解 r的型別到底是什麼最簡單的方法是從右向左閱讀 r的定義 ,離變數名最近的符號對變數有最直接的影響 ,因此 r是一個引用,宣告符的其餘部分可以確定 r引用的型別是什麼。此例子中的符號 * 說明 r引用的是一個指標。
2.4 const限定符
可以用const來定義一個值不能改變的變數。
const int bufsize=512;
bufsize=512;//錯誤:試圖對const物件寫值
const物件必須初始化
const int k;//錯誤,k是一個未經初始化的常量
初始化和const
如果用一個物件去初始化另一個物件,那它們是不是const都無關緊要:
int i=42;
const int ci=i;
int j=ci;
ci的常量特徵僅僅在執行改變ci的操作時才會發揮作用。
預設狀態下,const物件僅在檔案內有效
預設情況下,const物件盡在檔案內有效,當多個檔案中出現了同名的const變數時,其實等同於在多個檔案中分別定義了獨立的變數。
有時我們想在一個檔案中定義const,而在多個檔案中宣告並使用它。解決方式是,對於const變數不管是宣告還是定義都新增extern。
//file_1.cc
extern const int bufsize=512;
//file_1.h
extern const int buffsize;//與file_1.cc中是同一個
2.4.1 const的引用
可以吧引用繫結到const物件上,稱之為對常量的引用 簡稱常量引用
常量引用不能用作修改它所繫結的物件:
const int ci=1024;
const int &r1=ci;
r1=42;//錯誤r1是常量引用
int &r2=ci;//錯誤試圖讓一個非常量引用指向一個常量引用
初始化和對const的引用
1.初始化常量引用是允許任意表達式作為初始值,只要改表示式的結果能轉換成引用的型別即可。
2.允許為一個變數引用非常量的物件,字面值,甚至一般表示式。
int i=42;
const int &r1=i;
const int &r2=42;
const int &r3=r1*2;
int &r4=r1*2;//錯誤:r4是一個普通的非常量引用
常量引用和引用物件的型別必須匹配
double dval=3.14;
const int &r1=dval;//錯誤
對const的引用可能引用一個並非const的物件
常量引用僅對引用可參與的的操作做出了限定,對於引用的物件本身是不是一個變數未做限定。
int i=42;
int &r1=i;
const int &r2=i;
r1=0;
r2=0;//錯誤,不能通過r2改變i的值
2.4.2指標和const
指向常量的指標不能用於改變其所指物件的值。要想存放常量物件的地址,只能使用指向常量的指標。
const double pi=3.14;
double *ptr=π//錯誤:ptr是一個普通指標
const double *cptr=π
*cptr=42;//錯誤不能給*ptr賦值
允許一個指向常量的指標指向一個非常量物件:
double dval=3.14;
cptr=&dval;
const指標
常量指標必須初始化,而且一旦初始化了,則它的值就不能改變了
int errnumb=0;
int &const curerr=&errnumb;//curerr將一直指向errnumb
const ouble pi=3.14159;
const double *const pip=π//pip是指向一個常量物件的常量指標
頂層const
用名詞頂層const表示指標本身是個常量,而用名詞底層const表示指標所指的物件是一個常量
int i=0;
int *const pi=&i;//這是一個頂層cosnt
const int ci=42;
cosnt int *p2=&ci;//這是一個底層const
2.4.4 constexpr和常量表達式
常量表達式是指不會改變並且在編譯過程就能得到結果的表示式。
一個物件是不是常量表達式由它的資料型別和初始值共同決定:
const int max_files=20;//max_files是常量表達式
const int limit=max_files+1;//常量表達式
int staff_size=25;//不是常量表達式
constexpr物件
允許將變數宣告為constexpr型別以便於編譯器來驗證變數的值是否是一個常量表達式。
一般來說如果你認定變數時一個常量表達式,那就把它宣告為constexpr型別。
指標和constexpr物件
在constexpr宣告中如果定義了一個指標,限定符constexpr僅對指標有效
constexpr int *p=nullptr;//p是一個指向整數的常量指標
2.5處理型別
2.5.1 型別表明
類型別名是一個名字,他是某種型別的同義詞。
1.typedefdef
typedef double wages;//wages是double的同義詞
typedef wages base,*p;//p是double*的同義詞
2.使用別名生命來定義型別的別名
using SI=Sales_item;//SI是Sale_items的同義詞
using作為別名宣告的開始,把等號左側的名字規定為等號右側型別的別名
指標,常量和類型別名
typedef char *pstring;
const pstring cstr=0;//cstr是指向char的常量指標
const pstring *ps;//ps是一個指標,它的物件是指向char的常量指標
pstring實際上是指向char的指標,因此const pstring就是指向char的常量指標,而非指向常量型別的指標
const char *cstr=0;//是對 const pstring cstr=0的錯誤理解
2.5.2 auto型別說明符
c++引入了auto型別說明符,用它就能讓編譯器替我們去分析表示式所屬的型別。
auto型別說明符必須有初始值
auto item=val1+val2;//item初始化為val1和val2相加的結果
auto也能在一條語句宣告多個變數。
auto i=0,*p=&i;//i是整數,p是整型指標
複合型別常量和auto
編譯器推斷出來的auto型別有時候和初始值的型別並不一樣,編譯器會適當的改變結果型別使其更符合初始化規則。
int i=0,&r=i;
auto a=r;//a是一個整數
auto一般會忽略頂層const但是會保留底層const:
const int i,&cr=ci;
auto b=ci;//b是整數
auto c=cr;//c是整數
auto d=&i;//d是整數指標
auto e=&ci;//e是指向整數常量的指標
2.5.3decltype型別指示符
希望從表示式的型別推斷出要定義的變數的型別,但是不想用該變數的值初始化變數,c++第二種型別說明符decltype,它的作用是選擇並返回運算元的資料型別。
decltype(f())sum=x;//sum的型別就是函式f的返回型別
如果decltype使用的是一個變量表達式,則decltype返回該變數的型別
const int c0,&cj=ci;
decltype(ci) x=0;//x是const int 型別
decltype(cj)y=x;//y的型別是const int &
decltype和引用
有些表示式將想decltype返回一個引用型別:
int i=42,*p=&i,&r=i;
decltype(r+o) b;//b是一個int型別
decltype(*p) c;//錯誤c是int&,必須初始化
因為r是一個引用,因此decltype(r)的結果是引用型別,如果想讓結果是r所指的型別,可以吧r當做表示式的一部分。
如果表示式的內容是解引用操作,則decltype將得到引用型別。
解引用可以得到指標所指的物件,而且還能給這個物件賦值因此decltype(*p)得到的結果型別就是int&而非int;
如果decltype使用的是一個不加括號的變數,則得到的結果就是該變數的型別,如果給變數接上一層或多層括號,編譯器會把它當成一個表示式。
decltype((i))d;//錯誤:d是int&,必須初始化
decltype(i) e;//正確:e是一個int
預處理概述
前處理器是在編譯之前執行的一段程式,可以部分的改變我們所寫的程式。
c++還可能用到的一項功能是標頭檔案保護符,標頭檔案保護符依賴於預處理變數。
預處理變數有兩種狀態:已定義和未定義。#define指令把一個名字設定為預處理變數,另外兩個指令分別檢查某個指令的預處理變數是否定義:#ifdef當且僅當變數已定義時為真,#ifdef當且僅當變數未定義時為真,一旦檢查結果為真,則執行後續操作直至遇到#endif指令為止。
#ifdef SALES_DATE_H
#define SALE_DATE_H
#include<string>
struct Sale_date{
string bookNo;
undigned units_sold=0;
double revenue=0.0;
}
#endif
整個程式中的預處理變數包括標頭檔案保護符必須唯一,通常的做法是基於標頭檔案的類名來構建保護符的名字,以確保其唯一性。一般為了避免與程式中的其他實體發生名字衝突,預處理變數的名字全部大寫。