Chapter 2 Variables and Basic Types
2.1 基本內(nèi)置類型
2.1.1 算術(shù)類型
算術(shù)類型分為兩類:整型(integral type,包括字符和布爾類型)和浮點(diǎn)型顽馋。
? 基本字符類型是 char敞峭,一個(gè) char 的空間應(yīng)確卑跄。可以存放機(jī)器基本字符集中任意字符對(duì)應(yīng)的數(shù)字值崎溃。也就是說(shuō)运敢,一個(gè) char 的大小和一個(gè)機(jī)器字節(jié)一樣邑遏。
? 其他字符類型用于擴(kuò)展字符類型,如 wchar_t困曙、char16_t表伦、char32_t、wchar_t 類型用于確笨独觯可以存放機(jī)器最大擴(kuò)展字符集中的任意一個(gè)字符蹦哼,類型 char16_t 和 char32_t 則為 Unicode 字符集服務(wù)。
? 浮點(diǎn)型可表示單精度盈魁,雙精度和擴(kuò)展精度值翔怎。通常,float 以一個(gè)字(32 bits)來(lái)表示杨耙,double 以兩個(gè)字(64 bits)來(lái)表示赤套,long double 以 3 或 4 個(gè)字(96 or 128 bits)來(lái)表示。一般來(lái)說(shuō)珊膜,類型 float 和 double 分別有 7 個(gè)和 16 個(gè)有效位容握。
? 除去布爾型和擴(kuò)展的字符型之外,其他整型可劃分為 signed 和 unsigned 兩種车柠。而字符型被分為了三種:char剔氏、signed char 和 unsigned char。類型 char 實(shí)際表現(xiàn)為哪一種竹祷,具體是由編譯器來(lái)決定的谈跛。
Type | Meaning | Minimum Size |
---|---|---|
bool | boolean | NA |
char | character | 8 bits |
wchar_t | wide character | 16 bits |
char16_t | Unicode character | 16 bits |
char32_t | Unicode character | 32 bits |
short | short integer | 16 bits |
int | integer | 16 bits |
long | long integer | 32 bits |
long long | long integer | 64 bits |
float | single-precision floating-point | 6 significant digits |
double | double-precision floating-point | 10 significant digits |
long double | extend-precision floating-point | 10 significant digits |
2.1.2 類型轉(zhuǎn)換
# include <iostream>
int main() {
unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl; // 32
std::cout << u - u2 << std::endl; // 4294967264
int i = 10, i2 = 42;
std::cout << i2 - i << std::endl; // 32
std::cout << i - i2 << std::endl; // -32
std::cout << i - u << std::endl; // 0
std::cout << u - i << std::endl; // 0
return 0;
}
2.1.3 字面值常量
我們的整型字面值寫作十進(jìn)制數(shù)、八進(jìn)制數(shù)塑陵、十六進(jìn)制數(shù)的形式感憾。以 0 開頭的數(shù)代表八進(jìn)制數(shù),以 0x 或 0X 開頭的代表十六進(jìn)制數(shù)令花。
默認(rèn)情況下阻桅,是十進(jìn)制字面值是帶符號(hào)數(shù)凉倚,八進(jìn)制和十六進(jìn)制不確定。十進(jìn)制字面值是 int嫂沉、long稽寒、long long 中尺寸最小的那個(gè),前提是這種類型可容納當(dāng)前的值趟章。
有單引號(hào)括起來(lái)的一個(gè)字符稱為 char 型字面值杏糙,雙括號(hào)括起來(lái)的零個(gè)或多個(gè)字符則構(gòu)成字符型字面值。
'a' // 字符字面值
'Hello World!' // 字符串字面值
轉(zhuǎn)義序列
自己查書去尤揣。
2.2 變量
2.2.1 變量定義
初始化不是賦值搔啊,初始化的含義是創(chuàng)建變量時(shí)賦予其一個(gè)初始值,而賦值的含義是把對(duì)象的當(dāng)前值擦除北戏,而以一個(gè)新值來(lái)替代。
如果內(nèi)置類型的變量未被顯示初始化漫蛔,它的值由定義的位置決定嗜愈。定義于任何函數(shù)體外的變量被初始化為 0,定義在函數(shù)體內(nèi)部的內(nèi)置變量類型不被初始化莽龟,訪問這類值將引發(fā)錯(cuò)誤蠕嫁。類的對(duì)象如果沒有顯示地初始化,則其值由類確定毯盈。
2.2.2 變量聲明和定義的關(guān)系
分離式編譯機(jī)制(separate compilation):允許將程序分割成若干個(gè)文件剃毒,每個(gè)文件可悲獨(dú)立編譯。
聲明(declaration)使得名字為程序所知搂赋,而定義(definition)負(fù)責(zé)創(chuàng)建與名字關(guān)聯(lián)的實(shí)體赘阀。
如果像聲明一個(gè)變量而非定義它,就在變量名錢添加關(guān)鍵字 extern脑奠,而不要顯式地初始化變量:
extern int i; // 聲明 i 而非定義 i
int j; // 聲明并定義 j
extern double pi = 3.1416 // 定義
變量能且只能被定義一次基公,但是可以被多次聲明。
2.2.3 標(biāo)識(shí)符
C++ 的標(biāo)識(shí)符由字母宋欺,數(shù)字和下劃線組成轰豆,其中必須以字母或下劃線開頭。標(biāo)識(shí)符的長(zhǎng)度沒有限制齿诞,但對(duì)大小寫字母敏感酸休。
用戶自定義的標(biāo)識(shí)符不能連續(xù)出現(xiàn)兩個(gè)下劃線,也不能以下劃線緊連大寫字母開頭祷杈。此外斑司,定義在函數(shù)體外的標(biāo)識(shí)不能以下劃線開頭。
變量命名規(guī)范
- 標(biāo)識(shí)符要能體現(xiàn)實(shí)際含義
- 變量名一般用小寫字母
- 用戶自定義的類名一般以大寫字母開頭
- 如果標(biāo)識(shí)符由多個(gè)單詞組成吠式,則單詞間應(yīng)有明顯區(qū)分
2.3 復(fù)合類型
2.3.1 引用
引用為對(duì)象起了另外一個(gè)名字陡厘,將聲明符寫成 & 的形式來(lái)定義引用類型抽米,其中 d 是聲明的變量名。定義引用時(shí)糙置,程序把引用和它的初始值綁定在一起云茸,而不是拷貝。一旦綁定谤饭,則一直綁定标捺,無(wú)法重新綁定到新對(duì)象,因此必須初始化揉抵。
引用并非對(duì)象亡容,只是一個(gè)已經(jīng)存在的對(duì)象所起的另外一個(gè)名字。
引用本身不是一個(gè)對(duì)象冤今,所以不能定義引用的引用闺兢。
int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl;
// 10 10
2.3.2 指針
指針本身就是一個(gè)對(duì)象,允許對(duì)指針賦值拷貝戏罢,而且在指針的生命周期內(nèi)它可以先后指向幾個(gè)不同的對(duì)象屋谭。
指針無(wú)須在定義時(shí)賦初值。和其他內(nèi)置類型一樣龟糕,在塊作用域內(nèi)定義的指針如果沒有被初始化桐磁,也將擁有一個(gè)不確定的值。
定義指針類型的方法將聲明符寫成 *d 的形式讲岁,其中 d 是變量名我擂。
獲取對(duì)象的地址
取地址符 &
double dval;
double *pd = &dval; // 正確:初始值是 double 型對(duì)象的地址
double *pd2 = pd; // 正確:初始值是指向 double 對(duì)象的指針
int *pi = pd; // 錯(cuò)誤:指針 pi 的類型和 pd 的類型不匹配
pi = &dval; // 錯(cuò)誤:試圖把 double 型對(duì)象的地址賦給 int 型指針
指針值
指針的值(即地址)應(yīng)屬于下列4種狀態(tài)之一:
指向一個(gè)對(duì)象。
指向緊鄰對(duì)象所占空間的下一個(gè)位置缓艳。
空指針校摩,意味著指針沒有指向任何對(duì)象。
-
無(wú)效指針郎任,也就是上述情況之外的其他值秧耗。
試圖拷貝或以其他的形式訪問無(wú)效指針的值都將引發(fā)錯(cuò)誤。盡管第2中和第3種形式的指針是有效的舶治,但使用同樣受到限制分井,試圖訪問此類指針(假定的)對(duì)象的行為不被允許。
利用指針訪問對(duì)象
如果指針指向了一個(gè)對(duì)象霉猛,則允許用解引用符(*)來(lái)訪問該對(duì)象:
int ival = 42;
int *p = &ival; // p 存放著變量 ival 的地址尺锚;p 是指向變量 ival 的指針
cout << *p; // 由符號(hào)*得到指針 p 所指的對(duì)象,輸出42
*p = 0; // 由符號(hào)*得到指針 p 所指的對(duì)象惜浅, 即經(jīng)過(guò) p 為 ival 賦值
cout << *p; // 輸出 0
對(duì)指針解引用會(huì)得出所指的對(duì)象瘫辩,因此如果給解引用的結(jié)果賦值,實(shí)際上也就是給指針?biāo)傅膶?duì)象賦值。
空指針
int *p1 = nullptr; // 等價(jià)于 int *p1 = 0;
int *p2 = 0; // 直接將 p2 初始化為字面常量 0
// 需要首先 #include cstdlib
int *p3 = NULL; // 等價(jià)于 int *p3 = 0;
2.4 const 限定符
const 對(duì)象一旦創(chuàng)建后就不能再改變伐厌,所以 const 對(duì)象必須初始化承绸,且初始值可以是任意復(fù)雜的表達(dá)式。
初始化和 const
默認(rèn)狀態(tài)下挣轨, const 對(duì)象僅在文件內(nèi)有效军熏。當(dāng)多個(gè)文件中出現(xiàn)了同名的 const 變量時(shí),其實(shí)等同于在不同文件中分別定義了獨(dú)立的變量卷扮。
如果想在多個(gè)文件之間共享 const 對(duì)象荡澎,必須在變量的定義之前添加 extern 關(guān)鍵字。
2.4.1 const 的引用
可以把引用綁定到 const 對(duì)象上晤锹,就像綁定到其他對(duì)象上一樣摩幔,稱之為對(duì)常量的引用。與普通引用不同的是鞭铆,對(duì)常量的引用不能被用作修改它所綁定的對(duì)象:
const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are const
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object
因?yàn)椴辉试S直接為 ci 賦值或衡,當(dāng)然也不能通過(guò)引用去改變 ci。因此车遂,對(duì) r2 的初始化時(shí)錯(cuò)誤的薇宠。假設(shè)該初始化合法,則可以通過(guò) r2 來(lái)改變它引用的對(duì)象的值艰额,顯然不正確。
初始化和對(duì) const 的引用
引用的類型必須與其所引用對(duì)象的類型一致椒涯,但是有兩個(gè)例外柄沮。第一種例外是在初始化常量引用時(shí)允許用任意表達(dá)式作為初始值,只要該表達(dá)式的結(jié)果能轉(zhuǎn)換成引用的類型即可废岂。尤其祖搓,允許作為一個(gè)常量引用綁定非常量的對(duì)象,字面值湖苞,甚至是一個(gè)表達(dá)式拯欧。
int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1 * 2; // ok: r3 is a reference to const
int &r4 = r * 2; // error: r4 is a plain, non const reference
對(duì) const 的引用可能引用一個(gè)并非 const 的對(duì)象
常量引用并不限定引用的對(duì)象本身是不是一個(gè)常量。
int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const
2.4.2 指針和 const
指向常量的指針不能用于改變其所指對(duì)象的值财骨。要想存放常量對(duì)象的地址镐作,只能使用指向常量的子指針:
const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = π // error: ptr is a plain pointer
const double *cptr = π // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr
2.3.2 提到,指針的類型必須與其所指對(duì)象的類型一致隆箩,但是有兩個(gè)例外该贾。第一種情況似乎允許令一個(gè)指向常量的指針指向一個(gè)非常量對(duì)象:
double dval = 3.14; // dval is a double; its value can be changed
cptr = &dval; // ok: but can't change dval through cptr
Tip:
It may be helpful to think of pointers and references to const as pointers or
references “that think they point or refer to const.”
const 指針
指針是對(duì)象而引用不是,因此允許把指針本身定為常量捌臊。常量指針必須初始化杨蛋,而一旦初始化,它的值(就是那個(gè)地址)不能再改變。
2.4.3 頂層 const
術(shù)語(yǔ)頂層 const(top-level const)表示指針本身是個(gè)常量逞力,而底層 const(low-level const)表示指針?biāo)傅膶?duì)象是一個(gè)常量曙寡。
頂層 const 可以表示任意的對(duì)象是常量,如算術(shù)類型寇荧、類举庶、指針等。而底層 const 則與指針和引用等復(fù)合類型部分有關(guān):
int i = 0;
int *const p1 = &i; // we can't change the value of p1; const is top-level
const int ci = 42; // we cannot change ci; const is top-level
const int *p2 = &ci; // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci; // const in reference types is always low-level
2.4.4 constexpr 和常量表達(dá)式
常量表達(dá)式是指值不會(huì)改變并且在編譯過(guò)程中得到計(jì)算結(jié)果的表達(dá)式砚亭。一個(gè)對(duì)象是不是常量表達(dá)式是由它的數(shù)據(jù)類型個(gè)初始值共同決定的:
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression
constexpr 變量
C++11 新標(biāo)準(zhǔn)規(guī)定灯变,允許將變量聲明為 constexpr 類型以便由編譯器來(lái)驗(yàn)證變量的值是否是一個(gè)常量表達(dá)式。聲明為 constexpr 的變量一定是一個(gè)常量捅膘,而且必須用常量表達(dá)式初始化:
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
字面值類型
常量表達(dá)式的值需要在編譯時(shí)得到計(jì)算添祸,因此對(duì)聲明 constexpr 時(shí)用到的類型必須有所限制。因?yàn)檫@些類型一般比較簡(jiǎn)單寻仗,值也顯而易見刃泌,就稱為“字面值類型”。
指針和 constexpr
在 constexpr 聲明中如果定義了一個(gè)指針署尤,限定符 constexpr 僅僅對(duì)指針有效耙替,與指針?biāo)傅膶?duì)象無(wú)關(guān):
const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int
constexpr 把它所定義的對(duì)象置為了頂層 const。
constexpr int *np = nullptr; // np is a constant pointer to int that is null
int j = 0;
constexpr int i = 42; // type of i is const int
// i and j must be defined outside any function
constexpr const int *p = &i; // p is a constant pointer to the const int i
constexpr int *p1 = &j; // p1 is a constant pointer to the int j
2.5 處理類型
2.5.1 類型別名
有兩種方法可以定義類型別名曹体。傳統(tǒng)的方法時(shí)使用關(guān)鍵字 typedef:
typedef duoble wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*
含有 typedef 的聲明語(yǔ)句定義的不再是變量而是類型別名俗扇。新標(biāo)準(zhǔn)規(guī)定了一個(gè)新的方法,使用別名聲明來(lái)定義類型的別名:
using SI = Sales_item; // SI is a synonym for Sales_item
用 using 關(guān)鍵字作為別名聲明的開始箕别,其后緊跟別名和等號(hào)铜幽,其作用是把等號(hào)左側(cè)的名字規(guī)定成等號(hào)右側(cè)類型的別名。
指針串稀、常量和類型別名
typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char
const char *cstr = 0; // wrong interpretation of const pstring cstr, cstr use a pointerto const char
類型 pstring 是類型 char* 的別名除抛。
2.5.2 auto 類型說(shuō)明符
C++ 新標(biāo)準(zhǔn)引入了 auto 類型說(shuō)明符,讓編譯器通過(guò)初始值來(lái)推算變量的類型母截。顯然到忽,auto 定義的變量必須有初始值。
復(fù)合類型清寇、常量和 auto
編譯器推斷出得 auto 類型有時(shí)候和初始值得類型并不完全一樣喘漏,編譯器會(huì)適當(dāng)?shù)馗淖兘Y(jié)果類型使其更符合初始化規(guī)則。
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
auto 一般會(huì)忽略掉頂層 const颗管,同時(shí)底層 const 則會(huì)保留下來(lái)陷遮。如若希望推斷出的 auto 類型是一個(gè)頂層 const,需要明確指出:
const auto f = ci; // deduced type of ci is int; f has type const int
要在一條語(yǔ)句中定義多個(gè)變量垦江,切記帽馋,符號(hào) & 和 * 只從屬于某個(gè)聲明符搅方,而非基本數(shù)據(jù)類型的一部分,因此初始值必須是同一類型:
auto k = ci, &l = i; // k is int; l is int&
auto &m = ci, *p = &ci; // m is a const int&;p is a pointer to const int
auto &n = i, *p2 = &ci; // error: type deduced from i is int; type deduced from &ci is const int
2.5.3 decltype 類型提示符
C++ 11 新標(biāo)準(zhǔn)引入了第二種類型說(shuō)明符 decltype绽族,它的作用是選擇并返回操作數(shù)的數(shù)據(jù)類型姨涡。在此過(guò)程中,編譯器分析表達(dá)式并得到它的類型吧慢,卻并不計(jì)算表達(dá)式的值:
decltype(f()) sum = x; // sum has whatever type f returns
2.6 自定義數(shù)據(jù)結(jié)構(gòu)
從最基本的層面理解涛漂,數(shù)據(jù)結(jié)構(gòu)是把一組相關(guān)的數(shù)據(jù)元素組織起來(lái)然后使用它們的策略和方法。
2.6.1 定義 Sales_data 類型
初步定義 Sales_data 如下:
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
這個(gè)類以關(guān)鍵字 struct 開始检诗,緊跟著類名和類體(其中類體部分可以為空)匈仗。類體由花括號(hào)包圍形成了一個(gè)新的作用域。類內(nèi)部定義的名字必須唯一逢慌,但是可以與類外的名字重復(fù)悠轩。
類體右側(cè)表示結(jié)束的花括號(hào)后必須寫一個(gè)分毫,因?yàn)轭愺w后面緊跟變量名以示對(duì)該類型對(duì)象的定義攻泼,所以分號(hào)必不可少:
struct Sales_data { /* ... */ } accum, trans, *salesptr;
// equivalent, but better way to define these objects
struct Sales_data { /* ... */ };
Sales_data accum, trans, *salesptr;
2.6.2 使用 Sales_data 類
2.6.3 編寫自己的頭文件
為了確保各個(gè)文件中類的定義一致火架,類通常被定義在頭文件中,而且類所在頭文件的名字應(yīng)該與類的名字一樣忙菠。例如何鸡,把 Sales_data 類定義在名為 Sales_data.h 的頭文件中。
頭文件一旦被改變牛欢,相關(guān)的源文件必須重新編譯以獲取更新過(guò)的聲明骡男。
預(yù)處理器概述
? 確保頭文件多次包含仍能安全工作的技術(shù)是預(yù)處理器(preprocessor),在編譯之前執(zhí)行傍睹,比如預(yù)處理功能 #include洞翩。
? 還有一項(xiàng)預(yù)處理功能是頭文件保護(hù)符(header guard),頭文件保護(hù)符依賴于預(yù)處理變量(2.3.2 節(jié)焰望,p48)。預(yù)處理變量有兩種狀態(tài):已定義和未定義已亥。#define 指令把一個(gè)名字設(shè)定為預(yù)處理變量熊赖,另外兩個(gè)指令分別檢查某個(gè)指定的預(yù)處理變量是否已經(jīng)定義:#ifdef 當(dāng)且僅當(dāng)變量已定義時(shí)為真,#ifndef 當(dāng)且僅當(dāng)變量未定義時(shí)為真虑椎。一旦檢查結(jié)果為真震鹉,則執(zhí)行后續(xù)操作直至遇到 #endif 指定為止。
? 使用這些功能就能有效地防止重復(fù)包含的發(fā)生:
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif
預(yù)處理變量無(wú)視 C++ 語(yǔ)言中關(guān)于作用域的規(guī)則捆姜。