基本內(nèi)置類型
- 算術(shù)類型
- 字符
- 整型
- 布爾值
- 浮點(diǎn)數(shù)
- 空類型(void)
算術(shù)類型
類型 | 說明 | 最小尺寸 | 測試尺寸 |
---|---|---|---|
bool | 布爾類型(true哪廓、false) | 未定義 | 1 Byte |
char | 字符 | 1 Byte | 1 Byte |
wchar_t | 寬字符 | 2 Byte | 4 Byte |
char16_t | Unicode 字符 | 2 Byte | 未測試 |
char32_t | Unicode 字符 | 4 Byte | 未測試 |
short | 短整型 | 2 Byte | 2 Byte |
int | 整型 | 2 Byte | 4 Byte |
long | 長整型 | 4 Byte | 8 Byte |
long long | 長整型(C++ 11) | 8 Byte | 8 Byte |
float | 單精度浮點(diǎn)數(shù) | 6位有效數(shù)字 | 4 Byte,一般7位有效數(shù)字 |
double | 雙精度浮點(diǎn)數(shù) | 10位有效數(shù)字 | 8 Byte稍刀,一般16位有效數(shù)字 |
long double | 擴(kuò)展精度浮點(diǎn)數(shù) | 10 位有效數(shù)字 | 16 Byte撩独,具體實(shí)現(xiàn)不同精度不同 |
-
帶符號類型和無符號類型
- int、short账月、long综膀、 long long都是帶符號的,通過類型前面添加unsigned 得到無符號類型局齿,如 unsigned int剧劝,可以縮寫為unsigned。
- char 類型 帶符號的 signed char 和不帶符號的unsigned char 抓歼,char 類型具體為哪一種和編譯器有關(guān)讥此。
- 無符號類型所有bit 都被用來表示數(shù)字,有符號的最高位為符號位
-
如何選擇類型
- 當(dāng)數(shù)值不可能為負(fù)時 谣妻,選用無符號類型
- 使用int執(zhí)行整數(shù)運(yùn)算萄喳。
- 算術(shù)表達(dá)中不要 使用char 或bool杯矩,只有在存放字符或布爾值才使用据块。如果需要一個不大的整數(shù)杉辙,明確指出類型是signed char 還是unsigned char
- 執(zhí)行浮點(diǎn)運(yùn)算使用double肩刃,因?yàn)閒loat精度不夠而且單雙精度浮點(diǎn)運(yùn)算代價相差無幾孤紧。
類型轉(zhuǎn)換淺談
當(dāng)在程序中我們使用一種類型而其實(shí)對象應(yīng)該取另一種類型時凌那,程序會自動進(jìn)行類型轉(zhuǎn)換唁情,當(dāng)我們像下面這樣把一種算術(shù)類型的值賦給另外一種類型時:
bool b=42; //b 位true
int i= b; //i 為1
i =3.14 ; //i 為3
double pi=i ;//pi 為3.0
unsigned char c =-1姻僧; //char 占一個字節(jié)時辈灼,c 值為255
signed char c2=256 ;//char 占一個字節(jié)時份企, c 未定義
類型所能表示的值得范圍決定了轉(zhuǎn)換的過程:
- 非布爾類型賦值給布爾類型時,初始值為0 巡莹,結(jié)果為false 司志,否則結(jié)果為true。
- 布爾類型賦值給非布爾類型時榕莺,初始值為false 俐芯,結(jié)果為0,初始值為true 則結(jié)果為1钉鸯。
- 浮點(diǎn)數(shù)賦值給整型時吧史,將僅保留浮點(diǎn)數(shù)中小數(shù)點(diǎn)之前的整數(shù)部分。
- 整數(shù)賦值給浮點(diǎn)數(shù)時,小數(shù)部分記為0贸营,若整數(shù)所占的空間超過浮點(diǎn)類型的容量吨述,精度可能損失。
- 當(dāng)我們賦給無符號類型一個超出它表示范圍的值時钞脂,結(jié)果是初始值對無符號類型表示數(shù)值總數(shù)取模后的余數(shù)揣云。
- 當(dāng)我們賦給帶符號類型一個超出它表示范圍的值時,結(jié)果是未定義的冰啃。
含有無符號類型的表達(dá)式:
- 盡管我們不會故意給無符號對象賦一個負(fù)值邓夕,卻可能寫出這樣的代碼
- 當(dāng)一個表達(dá)式中既有無符號數(shù)又有int值時,int值會被轉(zhuǎn)為為無符號數(shù)阎毅。
unsigned u=10;
int i= -42;
std::cout <<i+i<<std::endl; //-84
std::cout <<u+i<<std::endl; //如果int 占32 位焚刚,輸出4294967264
注意: 不要混用帶符號類型和無符號類型
字面值常量
什么是字面值常量? 形如42這樣的值就是扇调,一看就知道是多少矿咕,每種字面值常量對應(yīng)一種數(shù)據(jù)類型,字面值常量的形式和值決定了它的數(shù)據(jù)類型狼钮。
-
整型和浮點(diǎn)型字面值
整型字面值可以寫作十進(jìn)制數(shù)碳柱,八進(jìn)制數(shù)或者16進(jìn)制的形式,以0開頭的代表八進(jìn)制數(shù)熬芜,以0x或0X開頭的代表十六進(jìn)制數(shù)莲镣。- 十進(jìn)制字面值的類型是int ,long,long long,中尺寸最小的那個(例如涎拉,三者當(dāng)中最小是int)剥悟,當(dāng)然前提是這種類型要能容納下當(dāng)前的值。
- 八進(jìn)制和十六進(jìn)制字面值類型是能夠容納其數(shù)值的int曼库,unsigned int,long 略板,unsigned long毁枯, long long和unsigned long long 中的最小者。如果一個字面值連與之相連的最大數(shù)據(jù)類型都放不下叮称,將產(chǎn)生錯誤种玛。
- 盡管整型字面值可以存儲在帶符號數(shù)據(jù)類型中,但嚴(yán)格來說瓤檐,十進(jìn)制字面值不會是負(fù)數(shù)赂韵,它的作用僅僅是對字面值取負(fù)值而已。
- 浮點(diǎn)型字面值是一個double
字符和字符串字面值
由單引號括起來的一個字符稱為char型字面值挠蛉,雙引號括起來的0個或多個字符則構(gòu)成字符串型字面值祭示。
'a' //字符字面值
“hello world!” //字符串字面值
字符串字面值的類型實(shí)際上是由常量字符構(gòu)成的數(shù)組(array)谴古,編譯器在每個字符串的結(jié)尾處添加一個空字符('\0')轉(zhuǎn)義序列
有兩類字符程序員不能直接使用:一類是不可打印字符质涛,如退格或其他控制字符稠歉,因?yàn)樗麄儧]有可視的圖符,另一類是語言中有特殊含義的字符汇陆,(單引號怒炸,雙引號,問好毡代,阅羹。。)教寂。在這些情況下需要用到轉(zhuǎn)義序列:
名稱 | 形式 | 名稱 | 形式 | 名稱 | 形式 |
---|---|---|---|---|---|
換行符 | \n | 橫向制表符 | \t | 報警(響鈴)符 | \a |
縱向制表符 | \v | 退格符 | \b | 雙引號 | " |
反斜線 | |問號 | ? | 單引號 | \’ | |
回車符 | \r | 進(jìn)紙符 | \f |
在程序中捏鱼,轉(zhuǎn)義序列被當(dāng)做一個字符使用。
- 指定字面值的類型
字符和字符串字面值
前綴 | 含義 | 類型 |
---|---|---|
u | Unicode16字符 | char16_t |
U | Unicode32字符 | char32_t |
L | 寬字符 | wchar_t |
u8 | UTF-8 | char |
整型字面值
后綴 | 最小匹配類型 |
---|---|
u or U | unsigned |
l or L | long |
ll or LL | long long |
浮點(diǎn)型字面值
后綴 | 最小匹配類型 |
---|---|
f or F | float |
l or L | long double |
ex:
L'a' // 寬字符型字面值孝宗,類型時wchar_t
u8"hi!" //utf-8 用8位編碼一個unicode 字符
42ULL //無符號整型字面值穷躁,類型是unsigned long long
1E-3F // 單精度浮點(diǎn)型字面值,類型是float
變量
變量對應(yīng)一個可供操作的存儲空間因妇,C++中的每個變量都有其數(shù)據(jù)類型问潭,數(shù)據(jù)類型決定著變量所占內(nèi)存空間的大小和布局方式,該空間能存儲的值得范圍婚被,以及變量能參與的運(yùn)算狡忙。對C++程序員來說,“變量”和“對象”一般可以互換使用址芯。
變量的定義
基本形式: 類型說明符 隨后緊跟一個或多個變量名組成的列表灾茁,以逗號分隔,分號結(jié)束定義谷炸。列表中每個變量名的類型都由類型說明符指定北专,定義時還可以為一個或多個變量賦初值:
int a=0,b,c=0; // abc 都為int 類型
Sales_item item; //item 的類型是sales_item 自定義類型
std::string book("0-12-33-x"); //book 通過一個string 字面值初始化,string 庫類型
初始值
當(dāng)對象(變量)在創(chuàng)建時獲得了一個特定的值旬陡,我們說這個對象被初始化了拓颓。用于初始化變量的值可以是任意復(fù)雜的表達(dá)式。當(dāng)一次定義了兩個或多個變量時描孟,對象的名字隨著定義也就馬上可以使用了驶睦。因此在同一條定義語句中,可以用先定義的變量值去初始化后定的其他變量匿醒。
在C++中场航,初始化是一個異常復(fù)雜的問題,很多程序員對于使用等號來初始化變量的方式倍感困惑廉羔,這種方式容易讓人認(rèn)為初始化是賦值的一種溉痢。事實(shí)上,初始化和復(fù)制是兩個完全不同的操作,然而在很多編程語言中二者的區(qū)別幾乎可以忽略不計(jì)适室,即使在C++中有時這種區(qū)別也無關(guān)緊要嫡意,所以人們特別容易吧二者混為一談。但是捣辆,這是兩種不同的操作蔬螟,這個概念很重要。
- 初始化:創(chuàng)建變量時賦予其一個初始值汽畴。
- 賦值: 把對象(變量)的當(dāng)前值擦除旧巾,以一個新值來替代。
列表初始化
C++語言定義了初始化的好幾種不同形式忍些,這也是初始化問題復(fù)雜性的一個體現(xiàn)鲁猩。例如,要想定義一個名為units_sold 的int變量并初始化為0罢坝,以下的4條語句都可以做到這一點(diǎn):
int units_sold = 0;
int units_sold = {0} ;
int units_sold{0};
int units_sold(0);
作為C++ 11 新標(biāo)準(zhǔn)用花括號來初始化變量得到了全面應(yīng)用廓握,而在此之前,這種初始化的形式僅在某受限的場合下才能使用嘁酿。這種初始化的形式被稱為列表初始化∠度現(xiàn)在無論是初始化對象還是某些時候?yàn)閷ο筚x新值,都可以使用這樣一組由花括號括起來的初始值了闹司。
當(dāng)用于內(nèi)置類型的變量時娱仔, 這種初始化形式有一個重要特點(diǎn):如果我們使用列表初始值存在丟失信息的風(fēng)險,則編譯器將報錯:
long double ld =3.1415925;
int a{ld}, b={ld}; //錯誤: 轉(zhuǎn)換未執(zhí)行游桩,因?yàn)榇嬖趤G失信息的危險
int c(ld), d=ld; //正確: 轉(zhuǎn)換執(zhí)行牲迫,且確實(shí)丟失了部分值
使用long double 值初始化int變量時可能丟失數(shù)據(jù),所以編譯器拒絕了a 和 b 的初始化請求借卧。其中盹憎,至少ld的小數(shù)部分會丟失掉,而且int也可能存不下ld的整數(shù)部分铐刘。
默認(rèn)初始化
如果定義變量時沒有指定初值脚乡,則變量被默認(rèn)初始化(default initialized),此時變量被賦予了“默認(rèn)值”滨达。默認(rèn)值到底是什么由變量類型決定,同時定義變量的位置也會對此有影響俯艰。
如果是內(nèi)置類型的變量未被顯示初始化捡遍,它的值由定義的位置決定。定義任何函數(shù)體之外的變量被初始化為0竹握。然而画株,一種例外情況是,定義在函數(shù)體內(nèi)部的內(nèi)置類型變量將不被初始化。一個未被初始化的內(nèi)置類型變量的值是未定義的谓传,如果試圖拷貝或以其他形式訪問此類值將引發(fā)錯誤蜈项。
每個類各自決定其初始化對象的方式。而且续挟,是否允許不經(jīng)初始化就定義對象也由類自己決定紧卒。如果類允許這種行為,他將決定對象的初始值到底是什么诗祸。
絕大多數(shù)類都支持無需顯示初始化而定義對象跑芳,這樣的類提供了一個合適的默認(rèn)值:
std::string empty; //empty 非顯式地初始化為一個空串
Sales_item item; // 被默認(rèn)初始化的Sales_item 對象
一些類要求每個對象都顯式初始化,此時如果創(chuàng)建了一個該類的對象而未對其做明確的初始化操作直颅,將引發(fā)錯誤博个。
變量聲明和定義的關(guān)系
為了允許把程序拆成多個邏輯部分來編寫,c++語言支持分離式編譯(separate compilation) 機(jī)制功偿,該機(jī)制允許將程序分割為若干個文件盆佣,每個文件可被獨(dú)立編譯。
如果將程序分為多個文件械荷,則需要在文件間共享代碼的方法共耍。 例如,一個文件的代碼可能需要使用另一個文件中定義的變量养葵。
為了支持分離式編譯征堪,c++語言將聲明和定義區(qū)分開來,聲明使得名字為程序所知关拒,一個文件如果想使用別處定義的名字則必須包含對那個名字的聲明佃蚜。而定義負(fù)責(zé)與名字關(guān)聯(lián)的實(shí)體。
變量聲明規(guī)定了變量的類型和名字着绊,在這一點(diǎn)上定義與之相同谐算,但是除此之外,定義還申請存儲空間归露,也可能會為變量賦一個初始值洲脂。如果想聲明一個變量而非定義它,就在變量名前添加關(guān)鍵字 extern,而且不要顯式地初始化變量:
extern int i; //聲明 i 而非定義 i
int j; //聲明并定義 j
任何包含了顯式初始化的聲明即成為定義剧包。我們能給由extern 關(guān)鍵字標(biāo)記的變量賦一個初始值恐锦,但是這么做也就抵消了 extern 的作用。extern 語句如果包含初始值就不再是聲明疆液,而變成定義了:
extern double pi =3.1416; // 定義
在函數(shù)體內(nèi)部一铅,如果試圖初始化一個由extern 關(guān)鍵字標(biāo)記的變量,將引發(fā)錯誤堕油。
變量能且只能被定義一次潘飘,但是可以被多次聲明肮之。
注意:c++是一種靜態(tài)類型語言,含義是在編譯階段檢查類型卜录,其中戈擒,檢查類型的過程稱為類型檢查。
標(biāo)識符
C++ 的標(biāo)識符由字母艰毒,數(shù)字和下劃線組成筐高,其中必須以字母或下劃線開頭。標(biāo)識符的長度沒有限制现喳,但是對大小寫字母敏感:
// 定義4個不同的int 變量
int somename , someName ,SomeName,SOMENAME;
用戶自定義的標(biāo)識符中不能連續(xù)出現(xiàn)兩個下劃線凯傲,也不能以下劃線緊連大寫字母開頭。此外嗦篱,定義在函數(shù)體外的標(biāo)識符不能以下劃線開頭冰单。
變量命名規(guī)范
- 標(biāo)識符要能體現(xiàn)實(shí)際含義
- 變量名一般用小寫字母,如 index灸促,不要使用Index 或INDEX
- 用戶自定義的類名一般以大寫字母開頭诫欠,如Sale_item
- 如果標(biāo)識符由多個單詞組成,則單詞之間應(yīng)有明顯區(qū)分浴栽,如 student_loan 或 studenLoan ,不要使用studentloan
名字的作用域
不論是在程序的什么位置荒叼,使用到的每個名字都會指向一個特定的實(shí)體: 變量、函數(shù)典鸡、類型等被廓。然而,同一個名字如果出現(xiàn)在程序的不同位置萝玷,也可能指向的是不同的實(shí)體
作用域是程序的一部分嫁乘,在其中名字有其特定的含義。C++語言中大多數(shù)作用域都以花括號分隔球碉。
同一個名字在不同的作用域中可能指向不同的實(shí)體蜓斧,名字的有效區(qū)域始于名字的聲明語句,以聲明語句所在的作用域末端為結(jié)束
#include <iostream>
int main()
{
int sum=0;
//sum 用于存放從1 到10 所有數(shù)的和
for(int val =1 ;val <=10;++val)
sum+=val; //等價于sum =sum+val
std::cout <<"Sum of 1 to 10 inclusive is "
<<sum <<std::endl;
return 0;
}
這段程序定義了3個名字:main睁冬、sum 和 val 挎春,同時使用了命名空間名字std,該空間提供了2個名字cout 和 cin 供程序使用。
名字main 定義所有花括號之外豆拨,他和其他大多數(shù)定義在函數(shù)體之外的名字一樣擁有全局作用域直奋,一旦聲明之后,在整個程序的范圍內(nèi)都可以使用施禾。名字sum定義在mian函數(shù)所限定的作用域之內(nèi)脚线,從聲明sum開始直到main函數(shù)結(jié)束為止都可以訪問它,但是除了main函數(shù)所在的塊就無法訪問了拾积,因此說變量sum擁有塊作用域殉挽,名字val定義在for語句內(nèi),在for語句內(nèi)可以訪問val拓巧,但是在main函數(shù)的其它部分就不能訪問它了斯碌。
建議:第一次使用變量時再去定義它
嵌套的作用域
作用域能彼此包含,被包含(或者說被嵌套)的作用域稱為內(nèi)層作用域肛度,包含著別的作用域的作用域稱為外層作用域傻唾。
作用中一旦聲明名了某個名字,它所嵌套著的所有作用域都能訪問該名字承耿。同時冠骄,允許在內(nèi)層作用域中重新定義外層作用域已有的名字:
#include <iosteam>
//該程序僅用于說明:函數(shù)內(nèi)部不宜定義與全局變量同名的新變量
int reused = 42; //reused 擁有全局作用域
int main()
{
int unique =0 ;//unique 擁有塊作用域
std::cout <<reused <<" "<<unique <<std::endl;
int reused =0;//新建局部變量reused
std::cout <<reused <<" "<<unique <<std::endl;//局部變量
std::cout <<::reused <<" "<<unique<<std::endl;
return 0;
}
第一次輸出全局變量reused值,第二次輸出局部變量reused值加袋,第三次輸出全局變量reused值凛辣。 因?yàn)槿肿饔糜虮旧聿]有名字,所以职烧,當(dāng)作用域操作符的左側(cè)為空是扁誓,向全局作用域發(fā)出請求獲取作用于操作符右側(cè)名字對應(yīng)的變量。
Note:如果函數(shù)有可能用到某個全局變量蚀之,則不宜再定義一個同名的局部變量
復(fù)合類型
復(fù)合類型 是指基于其他類型定義的類型蝗敢。C++中有好幾種復(fù)合類型,本章將介紹其中的兩種: 引用和指針
與我們已經(jīng)掌握的變量聲明相比足删,定義復(fù)合類型的變量要復(fù)雜很多寿谴。
引用
引用為對象起了另外一個名字,引用類型引用(refers to)另外一種類型失受。通過將聲明符寫成&d的形式來定義引用類型讶泰,其中d就是聲明的變量名;
int ival =1024;
int &refval=ival; //refvel 指向ival(是ival 的另一種類型)
int &refval2; //錯誤: 應(yīng)用必須被初始化
一般在初始化變量時贱纠,初始值就會被拷貝到新建的對象中峻厚,然而定義引用時,程序把引用和其初始值綁定在一起谆焊,而不是將初始值拷貝給引用惠桃,而且無法令引用重新綁定到另一個對象,因此引用必須初始化辖试。
引用即別名:就是為變量新起了一個名字
引用的定義
允許在同一條語句內(nèi)定義多個引用辜王,其中每個引用標(biāo)識符都必須以符號&開頭
int i=1024,i2=2048; //i 和i2 都是int
int &r = i,r2= i2; //r 是一個引用,與 i綁定在一起罐孝,r2 int
int i3=1024,&ri=i3;
int &r3=i3,&r4 = i2;
引用的類型要和與之綁定對象嚴(yán)格匹配呐馆,引用只能綁定在對象上,不能與字面值或某個表達(dá)式的計(jì)算結(jié)果綁定在一起莲兢,
int &refval4=10; //error
double dval=22.22;
int &ff=dval; //error
指針
指針(pointer)是 "指向"另外一種類型的復(fù)合類型汹来。與引用類似续膳,指針也實(shí)現(xiàn)了對其他對象的間接訪問
指針與引用相比有很多不同點(diǎn):
- 指針是一個對象,允許對指針賦值和拷貝收班,而且在指針的生命周期內(nèi)可以先后指向多個不同的對象
- 指針無需再定義時賦初值
- 和其他內(nèi)置類型一樣坟岔,在塊作用域內(nèi)定義的指針如果沒有初始化,也將擁有一個不確定的值摔桦。
定義指著類型的方法將聲明符寫成d的形式社付,其中d是變量名。如果在一條語句定義了多個指針變量邻耕,則每個變量前面都必須有符號:
int *p1,*p2; //聲明了兩個int型對象的指針
double dp,*dp2;//dp 是double 對象鸥咖,dp2 是指向double類型的指針
獲取對象的地址
指針存放某個對象的地址,要想獲取該地址兄世,需要使用取地址符(&)
int vial =42;
int *p=&vial; // p 存放變量ival 的地址
由于引用不是對象啼辣,沒有自己的地址,所以不能定義指向引用的指針碘饼。
指針的類型都要和其所指向的對象嚴(yán)格匹配熙兔,除了一些特殊情況,譬如 void * 艾恼。
指針的值應(yīng)是下面幾種狀態(tài)之一:
- 指向一個對象
- 指向緊鄰對象所占空間的下一個位置
- 空指針住涉,沒有指向任何對象
- 無效指針,上述情況之外的其他值
試圖拷貝或以其他方式訪問無效指針的值都將引發(fā)錯誤钠绍,編譯器并不負(fù)責(zé)檢查此類錯誤舆声,這一點(diǎn)和試圖使用未經(jīng)初始化的變量是一樣的。訪問無效指針的后果無法對象柳爽。
利用指針訪問對象
如果指針指向了一個對象媳握,則允許使用解引用符()*來操作對象:
int ival =42;
int *p =&ival; //p 存放著變量ival 的地址,或者說p 是指向變量ival 的指針
count << *p; // 42
對指針解引用將會得出所指的對象磷脯,因此如果將解引用的結(jié)果賦值蛾找,實(shí)際上也就是給指針?biāo)傅膶ο筚x值:
*p = 0; //由符號*得到指針p 所指的對象,即可經(jīng)由 P為變量ival 賦值
count <<*p ; //0
由此可以得出: 某些符號具有多重含義,如 & 赵誓。
空指針
空指針 不指向任何對象打毛,在試圖使用一個指針之前代碼可以首先檢查它是否為空。下面列出幾個生成空指針的方法:
int *p1 =nullptr; //c++ 新標(biāo)準(zhǔn)剛引入的一種方法俩功,nullptr 是一種特殊類型的字面值幻枉。可以被轉(zhuǎn)化成任意其他的指針類型
int *p2=0;
int *p3 = NULL; //需要 #include cstdlib
初始化所有指針:使用未經(jīng)初始化的指針是引發(fā)運(yùn)行時錯誤的一大原因
賦值和指針
指針和引用都能提供對其他對象的簡介訪問诡蜓,然而在具體實(shí)現(xiàn)細(xì)節(jié)上二者有很大不同熬甫,其中最重要的一點(diǎn)就是引用本身并非一個對象。
有時候想要搞清楚一條賦值語句到底是改變了指針的值還是改變了指針?biāo)笇ο蟮闹挡惶菀茁#詈玫霓k法就是記住賦值永遠(yuǎn)改變的是等號左側(cè)的對象椿肩。
pi=&val; // pi的值改變瞻颂,pi 指向了ival
* pi =0; //ival 的值改變,指針pi并沒有改變
其他指針操作
只要指針擁有一個合法值郑象,就能將他用在條件表達(dá)式中蘸朋,和采用算術(shù)值作為條件遵循的規(guī)則類似,如果指針的值為0扣唱,條件取false。
void * 指針
void* 指針是一種特殊的指針類型团南,可用于存放任意對象的地址噪沙,一個 void* 指針存放著一個地址,這一點(diǎn)和其他指針類似吐根,但不同的是正歼,我們對該地址中到底是個什么類型的對象并不了解:
double obj =3.14, * pd=&obj;
void *pv=&obj; //void 指針類型可以存放任意類型的對象地址
pv=pd;
由于不知道void * 指針?biāo)娴膶ο蟮木唧w類型,所以無法直接操作void 指針?biāo)傅膶ο罂介伲瑥膙oid * 的視角來看內(nèi)存空間也就僅僅是內(nèi)存空間局义,沒辦法訪問內(nèi)存空間中所存的對象。
理解復(fù)合類型的聲明
如前所述冗疮,變量的定義包括一個基本數(shù)據(jù)類型 (base type) 和一組聲明符萄唇。在同一條定義語句中,雖然基本數(shù)據(jù)類型只有一個术幔,但是聲明符的形式卻可以不同另萤。也就是說,一條定義語句可能定義出不同類型的變量:
// i int , p int 型指針诅挑,r int 型引用
int i=1024,* p= &i, &r =i;
很多程序員容易迷惑于基本數(shù)據(jù)類型和類型修飾符的關(guān)系四敞,其實(shí)后者不過是聲明符的一部分罷了。
定義多個變量
經(jīng)常有一種觀點(diǎn)會誤以為拔妥,在定義語句中忿危,類型修飾符(* 或 &)作用與本次定義的全部變量。原因之一是由于可以把空格寫在類型修飾符和變量名之間:
int * p; //合法但是容易產(chǎn)生誤導(dǎo)
這種寫法可能產(chǎn)生誤導(dǎo)是因?yàn)閕nt * 放在一起好像是這條語句中所有變量共同的類型一樣没龙。其實(shí)恰恰相反铺厨,基本數(shù)據(jù)類型是int 而非 int * 。 * 僅僅是修飾了 P 而已兜畸,對該聲明語句中的其他變量努释,它并不產(chǎn)生任何副作用:
int * p1, p2; //p1 int 型指針,p2 int
涉及指針或引用的聲明咬摇,一般有兩種寫法伐蒂,第一種把修飾符和變量標(biāo)識符寫在一起:
int *p1, *p2; //強(qiáng)調(diào)變量具有的復(fù)合類型
第二種把修飾符和類型名寫在一起,并且每條語句只定義一個變量:
int* p1;
int* p2; //著重強(qiáng)調(diào)本次聲明定義了一種復(fù)合類型肛鹏;
tips:上面兩種寫法沒有什么誰對誰錯逸邦,關(guān)鍵是選擇并堅(jiān)持其中一種寫法恩沛,不要老是變來變?nèi)?/p>
指向指針的指針
一般來說, 聲明符中修飾符的個數(shù)并沒有限制缕减,當(dāng)有多個修飾符連寫在一起時雷客,按照其邏輯關(guān)系加以解釋即可。以指針為例桥狡,指針是內(nèi)存中的對象搅裙,像其他對象一樣也有自己的地址,因此允許把指針的地址再存放到另一個指針當(dāng)中裹芝。
通過*的個數(shù)可以區(qū)分指針的級別部逮,也就是說,兩個表示指向指針的指針嫂易,三個表示指向指針的指針的指針兄朋,以此類推。
int ival=1024;
int *pi=&ival;
int **ppi=π
指向關(guān)系如下:
ppi>>>pi>>>ival(1024)
cout <<"The Value of ival \n"
<<"direct value :" <<ival <<"\n"
<<"indirect value:" <<*pi<<"\n"
<<"doubly indirect value: " << **ppi
<< endl;
上面使用三種不同方式輸出了變量ival的值怜械。
指向指針的引用
引用本身不是一個對象颅和,因此不能定義指向引用的指針,但是指針是對象缕允,所以存在對指針的引用:
int i=42;
int *p; //p 是一個int 型指針
int * &r=p; //r 是一個對指針p的引用
r =&i; //r 引用了一個指針峡扩,因此給r賦值&i就是p指向i
*r =0; //*p=0,i=0
const 限定符
有時候我們希望定義這樣一種變量,它的值不能被改變障本。為了滿足這一要求有额,可以用關(guān)鍵字const 來對變量的類型加以限定。
const int buffsize =512 ; // 輸入緩沖區(qū)大小
這樣就把bufsize定義為了一個常量彼绷,任何試圖為bufsize 賦值的行為都將引發(fā)錯誤:
bufsize =512; //錯誤巍佑,試圖向const 對象寫值
因?yàn)閏onst對象一旦創(chuàng)建后其值就不能在改變,所以const 對象必須初始化寄悯。一如既往萤衰,初始化可以是任意復(fù)雜的表達(dá)式:
const int i=get_size(); // 正確 運(yùn)行時初始化
const int j=42; //編譯時初始化,正確
const int k; //error ,k 是一個未經(jīng)初始化的常量
初始化和const
正如之前所反復(fù)提到的猜旬,對象的類型決定了其上的操作脆栋。與非const 類型所能參與的操作相比,const類型的對象能完成其中大部分洒擦,但也不是所有的操作都適合椿争,主要的限制就是只能在const 類型的對象上執(zhí)行不改變內(nèi)容的操作。 例如熟嫩, const int 和普通的int一樣都能參與算術(shù)運(yùn)算秦踪,也都能轉(zhuǎn)換成一個布爾值。。椅邓。柠逞。。景馁。
注意: 默認(rèn)情況下const 對象僅僅在本文件內(nèi)有效
當(dāng)以編譯時初始化的方式定義一個const 對象時板壮,就如對bufsize的定義一樣:
const int bufsize =512; // 輸入緩沖區(qū)大小
編譯器將在編譯過程中把用到的該變量的地方都替換成相應(yīng)的值,也就是說合住,編譯器會找到代碼中所有用到bufsize的地方绰精,然后用512 替換。
為了執(zhí)行上述轉(zhuǎn)換透葛,編譯器必須知道變量的初始值茬底,如果包含多個文件,就必須在每一個用到變量的文件中都有對它的定義获洲。為了支持這一用法,同時避免對同一個變量的重復(fù)定義殿如,默認(rèn)情況下贡珊,const 對象被設(shè)定為僅在本文件內(nèi)有效。當(dāng)多個文件中同時出現(xiàn)了同名的const 變量時涉馁,其實(shí)等同與在不同文件中分別定義了獨(dú)立的變量门岔。
有時候有這樣一種const 變量,它的初始值不是一個常量表達(dá)式烤送,但又確實(shí)有必要在文件間共享寒随。這種情況下,我們不希望編譯器為每個文件分別生成獨(dú)立的變量帮坚。相反妻往,我們想讓這種const 對象像其他(非常量)對象一樣工作,也就是說试和,只在一個文件內(nèi)定義const讯泣,而在其他多個文件中聲明并使用它。
解決的方法是:對于const 變量不管是聲明還是定義都要添加** extern ** 關(guān)鍵字阅悍,這樣只需要定義一次就可以了好渠;
//file_1.cc 定義并初始化了一個常量,該常量能被其他文件訪問
extern const int bufsize =fcn();
// file_1.h 頭文件
extern const int bufsize ; //與上面定義的bufsize是同一個
如上述程序所示节视,file_1.cc 定義并初始化了bufsize,因?yàn)檫@條語句包含了初始值拳锚,所以它是一次定義,然而寻行,因?yàn)閎ufsize 是一個常量霍掺,必須用extern 加以限定才能被其他文件使用。
file_1.h 頭文件中的聲明也由extern 做了限定,其作用是指明bufsize 并非本文件獨(dú)有抗楔,它的定義將在別處出現(xiàn)棋凳。
如果想要在多個文件之間共享const 對象,必須在變量的定義之前添加extern關(guān)鍵字
const 的引用
可以把引用綁到const 對象上连躏,就像綁定到其他對象一樣剩岳,稱之為對常量的引用,與普通引用不同之處在于入热,對常量的引用不能用作修改它所綁定的對象: 就是說不能更改
const int ci =104;
const int &refci =ci ;
refci =42; //error 不能改
int &refci2 =ci; //錯誤: 試圖讓一個非常量引用指向一個常量對象
常量引用是對const 的引用
初始化和對cosnt 的引用
前面所述拍棕,引用的類型必須與其所引用對象的類型一致植,但是有兩個例外
- 第一種例外是在初始化常量引用時允許用任意表達(dá)式作為初始值勺良,只要該表達(dá)式的結(jié)果能轉(zhuǎn)換成引用的類型即可绰播,尤其允許為一個常量引用綁定非常量的對象,字面值或是個一般表達(dá)式
int i=42尚困;
const int &r1 =i ;// 允許將const int& 綁定到一個普通int 對象上
const int &r2 =42蠢箩; // ok
const int &r3 =r1*2; //ok
int &r4= r1* 32; //error
要想理解這種例外情況的原因事甜,最簡單的方式是弄清楚當(dāng)一個常量引用綁定到另外一個類型上時到底發(fā)生了什么:
double dval =3.14; //由雙精度浮點(diǎn)數(shù)生成一個臨時的整型常量
const int &ri =dval;
此時ri 引用了一個int 型的數(shù)谬泌,對ri的操縱應(yīng)該是整數(shù)運(yùn)算,但是dval 是一個雙精度浮點(diǎn)數(shù)而不是整數(shù)逻谦,因此為了確保讓ri綁定一個整數(shù)掌实,編譯器把上述代碼變成了如下形式:
const int temp =dval ; //由 雙精度浮點(diǎn)數(shù)生成一個臨時的整型變量
const int & ri =temp ;// ri 綁定這個臨時變量
在這種情況下邦马,ri 綁定了一個臨時量對象贱鼻,所謂臨時量 對象就是當(dāng)編譯器需要一個空間來暫存表達(dá)式的求值結(jié)果時臨時創(chuàng)建的一個未命名的對象。
接下來談?wù)摦?dāng)ri不是常量時滋将,如果執(zhí)行了類似于上面的初始化過程將帶來什么樣的后果邻悬,如果ri不是常量陕赃,就允許對ri賦值袜刷,這樣就會改變ri所引用對象的值。注意组题,此時綁定的對象是一個臨時量而非dval橱脸。程序員既然讓ri引用dval础米,就肯定想通過ri改變dval的值,否則干什么要給ri賦值呢添诉?如此看來屁桑,既然大家基本上不會想著把引用綁定到臨時變量上,C++ 語言也就把這種行為歸為非法栏赴。
對const 的引用可能引用一個并非const的對象
必須認(rèn)識到蘑斧,常量引用僅對引用可參與的操縱做出了限定,對于引用的對象本身是不是一個常量未做限定,因?yàn)閷ο笠部赡苁莻€非常量竖瘾,所以允許通過其他途徑改變它的值:
int i=42沟突;
int &ri=i;
const int &r2 =i;
r1=0;
r2=0; //錯誤,r2是一個而常量引用
指針和const
與引用一樣捕传,類比常量引用惠拭,指向常量的指針不能用于改變所指對象的值,要想存放常量對象的地址庸论,只能使用指向常量的指針:
const double pi =3.14; //pi 是個常量职辅,它的值不能改變
double * ptr =π //錯誤
const double *cptr =π //ok
* cptr =42; //error
如前文所說,指針的類型必須與其所指對象的類型一致聂示,但是有兩個例外域携,第一種例外情況是允許另一個指向常量的指針指向一個非常量對象:
double dval=3.14;
cptr=&dval; //right ,but can't modified dval by cptr;
和常量引用一樣,指向常量的指針也沒有規(guī)定其所指的對象必須是一個常量鱼喉,所謂指向常量的指針僅僅要求不能通過該指針改變對象的值秀鞭,而沒有規(guī)定那個對象的值不能通過其他途徑改變。
const 指針
指針是對象而引用不是扛禽,因此就像其他對象類型一樣锋边,允許把指針本身定為常量,常量指針必須初始化旋圆,一旦初始化,它的值就不能在改變了麸恍,把*放在const關(guān)鍵字之前用來說明指針是一個常量灵巧,這樣的書寫形式意味著指針本身的值是不變的而并非指向的那個值:
int errnumb=0;
int *const curerr =&errnumb ; //curerr 將一直指向errnumb
const double pi=3.14;
const double * coust pip=π //pip 是一個指向常量對象的常量指針
頂層const
如前所述抹沪,指針本是是一個對象刻肄,它又可以指向另外一個對象,因此融欧,指針本身是不是常量以及指針?biāo)傅氖遣皇且粋€常量就是兩個相互獨(dú)立的問題敏弃,用名詞頂層const和底層const來分別表示這兩個問題:
- 頂層const: 表示指針本身是個常量
- 底層const: 表示指針?biāo)傅膶ο笫且粋€常量
更一般的,頂層const可以表示任意的對象是常量噪馏,這一點(diǎn)對任何數(shù)據(jù)類型都適用麦到,如算術(shù)類型,類欠肾、指針等瓶颠。底層const則與指針和引用等復(fù)合類型的基本類型部分有關(guān)。比較特殊的是刺桃,指針類型既可以是頂層const 也可以是底層const粹淋,這一點(diǎn)和其他類型相比區(qū)別明顯:
int i=0;
int *const p1=&i; //頂層const
const int ci =42; //頂層const
const int *p2=&ci; //底層const
const int * const p3=p2;
const int &r =ci; //用于聲明引用的const 都是底層const
當(dāng)執(zhí)行對象的拷貝操作時,拷入和拷出的對象必須具有相同的底層const資格,或者兩個對象的類型必須能夠轉(zhuǎn)換桃移,一般來說屋匕,非常量可以轉(zhuǎn)化成常量,反值則不行:
int * p=p3; //錯誤:p3 包含底層const 定義借杰,p沒有
p2=p3; //ok
p2=&i; //ok
int &r=ci ;// error 普通的int& 不能綁定到int常量上
const int &r2 =i ; //正確: const int & 可以綁定到一個普通int 上
constexpr 和常量表達(dá)式
常量表達(dá)式是指值不會改變并且在編譯過程就能得到計(jì)算結(jié)果的表達(dá)式过吻,顯然字面值屬于常量表達(dá)式,用常量表達(dá)式初始化的const對象也是常量表達(dá)式第步。后面將會提到疮装,C++語言中有幾種情況下是要用到常量表達(dá)式的。
一個對象(表達(dá)式)是不是常量表達(dá)式由它的數(shù)據(jù)類型和初始值共同決定粘都,例如:
const int max_files =20 ;//是常量表達(dá)式
const int limit =max_files +1 ; //limit 是常量表達(dá)式
int staff_size =27 ; //staff_size 不是常量表達(dá)式
雖然不能使用普通函數(shù)作為constexpr 變量的初始值廓推,但是新標(biāo)準(zhǔn)定義了一種特殊的constexpr 函數(shù),這種函數(shù)應(yīng)該足夠簡單以使得編譯時可以計(jì)算器結(jié)果翩隧,這樣就能用constexpr函數(shù)去初始化cosntexpr變量了樊展。
字面值類型
常量表達(dá)式的值需要在編譯時就得到計(jì)算,因此對聲明constexpr時用到的類型必須有所限制堆生。因?yàn)檫@些類型一般比較簡單专缠,值也顯而易見,容易得到淑仆,就把它們稱為“字面值類型”(literal type)涝婉。
目前為止所接觸到的數(shù)據(jù)類型中,算術(shù)類型墩弯,引用和指針類型引矩,都屬于字面值類型茂翔,而自定義類sales_item、IO 庫等類型不屬于字面值類型槽地。
盡管指針和引用都能被定義為constexpr缅糟,但它們的初始值卻受到嚴(yán)格限制,一個consteptr指針的初始值必須是nullptr或0欺殿,或者是存儲于某個固定地址的變量(對象)(全局變量或者局部變量中的靜態(tài)變量(staitc 修飾的))帆阳。
函數(shù)體內(nèi)定義的變量一般并非固定的地址,因此constexpr指針不能指向這樣的變量屋吨,相反的蜒谤,定義與所有函數(shù)體之外的對象其地址固定不變,能用來初始化constexpr指針至扰,函數(shù)體內(nèi)定義一類有效范圍超出函數(shù)本身的變量鳍徽,這類變量和定義在函數(shù)體之外的變量一樣也有固定地址,因此敢课,constepr引用能綁定到這樣的變量上阶祭,cosntexpr指針也能指向這樣的變量绷杜。
指針和constexpr
在constexpr聲明中如果定義了一個指針,限定符constexpr進(jìn)對指針有效濒募,與指針?biāo)傅膶ο鬅o關(guān):
const int * p=nullptr; // p 是一個指向整型常量的指針
constexpr int * q =nullptr;// q 是一個指向整數(shù)的常量指針
處理類型###
類型別名
類型別名是一個名字鞭盟,有兩種方法可用于定義類型別名,傳統(tǒng)的方法是試用關(guān)鍵字typedef:
typedef double wages ; //wages 是double的同義詞
typedef wages base , *p ; //base 是double的同義詞瑰剃,p是double * 的同義詞
新標(biāo)準(zhǔn)規(guī)定一種新的方法齿诉,試用別名聲明來定義類型的別名:
using SI =Sales_item; //SI 是Sales_item 的同義詞
這種方法用關(guān)鍵字using作為別名聲明的開始,其后緊跟別名和等號晌姚,其作用是把等號左側(cè)的名字規(guī)定成等號右側(cè)類型的別名粤剧。
類型別名和類型名字等價,只要是類型的名字能出現(xiàn)的地方挥唠,就能使用類型別名抵恋。
指針、常量和類型別名
如果某個類型別名指的是復(fù)合類型或常量猛遍,那么把它用到聲明語句中就會產(chǎn)生想不到的后果馋记,如下:
typedef char * pstring ;
const pstring cstr =0 ;//cstr 是指向char 的常量指針
const pstring * ps ; //ps 是一個指針,它的對象是指向char 的常量指針
pstring 實(shí)際上是指向char的指針懊烤,因此梯醒,const pstring 就是指向char 的常量指針,而非指向常量字符的指針腌紧。
遇到一條使用了類型別名的聲明語句時茸习,人們往往會錯誤的嘗試將類型別名替換成它本來的樣子,來理解該語句的含義:
const char * cstr =0;// 是對const pstring cstr 的錯誤理解
再強(qiáng)調(diào)一遍壁肋,這種理解是錯誤的号胚,聲明語句中用到pstring 時,其基本數(shù)據(jù)類型是指針浸遗,可是用char *
重寫了聲明語句后猫胁,數(shù)據(jù)類型就變成了char, *
成為了聲明符的一部分,這樣改寫的結(jié)果是,const char 成了基本數(shù)據(jù)類型跛锌,前后兩種聲明含義截然不同弃秆,前者聲明了一個指向char的常量指針,改寫后的形式則聲明了一個指向const char的指針髓帽。
auto 類型說明符
C++11 新標(biāo)準(zhǔn)引用了auto類型說明符菠赚,用它就能讓編譯器替我們?nèi)シ治霰磉_(dá)式所屬的類型,顯然郑藏,auto定義的變量必須有初始值:
//由 val1 和val2 想加的結(jié)果就可以推斷出item的類型
auto item = val1+val2 ;//item 初始化為val1 和val2 想加的結(jié)果
此處編譯器將自己推斷item的類型衡查,使用auto也能在一條語句中聲明多個變量,因?yàn)橐粭l聲明語句只能有一個基本數(shù)據(jù)類型必盖,所以該語句中所有變量的初始基本數(shù)據(jù)類型都必須一樣:
auto i=0, * p=&i; //right: i is int ,p is int point
auto sz=0 ,pi=3.14 ;// error: sz 和 pi的類型不一致
復(fù)合類型拌牲、常量和auto
編譯器推斷出的auto 類型有時候會和初始值的類型并不完全一樣俱饿,編譯器會適當(dāng)?shù)母淖兘Y(jié)果類型使其更符合初始化規(guī)則。
int i=0,&r=i;
auto a =r ; //a is int
其次塌忽,auto 一般會忽略頂層const稍途,同時底層const則會保留下來,比如當(dāng)初始值是一個指向常量的指針時:
const int ci =i, &cr =ci ;
auto b =ci ; //b 是一個整數(shù) (ci 的頂層const 特性被忽略掉了)
auto c =cr ; //c 是一個整數(shù) (cr是ci 的別名砚婆,ci 本身是一個頂層const )
auto d =&i; //d 是一個整型指針(整數(shù)的地址就是指向整數(shù)的指針)
auto e =&ci ; //e 是一個指向整數(shù)常量的指針(對常量對象取地址是一種底層const )
如果希望推斷出的auto類型是一個頂層const 械拍,需明確指出:
const auto f=ci ;
decltype類型指示符
有時候遇到這種情況,希望從表達(dá)式的類型推斷出要定義的變量的類型装盯,但是不想用該表達(dá)式的值初始化變量坷虑,為了滿足這一要求,C++11 新標(biāo)準(zhǔn)引入了第二種類型說明符decltype埂奈,它的作用是選擇并返回操作數(shù)的數(shù)據(jù)類型迄损,在此過程中,編譯器分析表達(dá)式并得到它的類型账磺,卻不實(shí)際計(jì)算表達(dá)式的值:
decltype (f()) sum =x ; // sum 的類型就是函數(shù)f 的返回類型
編譯器并不實(shí)際調(diào)用函數(shù)f芹敌,而是使用當(dāng)調(diào)用發(fā)生時f的返回值類型作為sum的類型。decltype 處理頂層const 和引用的方式與auto有些許不同垮抗,如果decltype 使用的表達(dá)式是一個變量氏捞,則decltype 返回該變量的類型(包括頂層const 和引用在內(nèi)):
const int ci =0 ,&cj =ci ;
decltype(ci) x =0 ; //x 的類型是const int
decltype(cj) y=x; // y 的類型是const int& , y 綁定到變量x
decltype (cj) z; // error z 是一個引用,必須初始化
decltype 和引用
//decltype 的結(jié)果可以是引用類型
int i=42 冒版,* p=&i ,&r =i ;
decltype (r + 0) b; //正確: 加法的結(jié)果是int
decltype (*p ) c; //error : c是int& ,必須初始化
decltype 和 auto 的另一處重要區(qū)別是液茎,decltype 的結(jié)果類型與表達(dá)式形式密切相關(guān),有一種情況需要特別注意辞嗡,加上括號與不加括號的區(qū)別:
//decltype 的表達(dá)式如果是加上了括號的變量捆等,結(jié)果將是引用
decltype((i))d; //錯誤: d 是int & ,必須初始化
decltype(i) e ; //正確,e 是一個int