天天看點

c++基礎之變量和基本類型

之前我寫過一系列的c/c++ 從彙編上解釋它如何實作的博文。從彙編層面上看,确實c/c++的執行過程很清晰,甚至有的地方可以做相關優化。而c++有的地方就隻是一個文法糖,或者說并沒有轉化到彙編中,而是直接在編譯階段做一個文法檢查就完了。并沒有生成彙編代碼。也就是說之前寫的c/c++不能涵蓋它們的全部内容。而且抽象層次太低,在應用上很少會考慮它的彙編實作。而且從c++11開始,加入了很多新特性,給人的感覺就好像是一們新的程式設計語言一樣。對于這塊内容,我覺得自己的知識還是有欠缺了,是以我決定近期重新翻一翻很早以前買的《c++ primer》 學習一下,并整理學習筆記

背景介紹

為什麼會想到再次重新學習c++的基礎内容呢?目前來看我所掌握的并不是最新的c++标準,而是“c with class” 的内容,而且很明顯最近在關注一些新的cpp庫的時候,發現它的文法我很多地方都沒見過,雖然可以根據它的寫法來大緻猜到它到底用了什麼東西,或者說在實作什麼功能,但是要自己寫,可能無法寫出這種文法。而且明顯感覺到新的标準加入了很多現代程式設計語言才有的内容,比如正規表達式、lambda表達式等等。這些都讓寫c++變得容易,寫出的代碼更加易讀,使其脫離了上古時期的烙印更像現代的程式設計語言,作為一名靠c++吃飯的程式員,這些東西必須得會的。

看書、學程式設計總少不了寫代碼并編譯運作它。這次我把我寫代碼的環境更換到了mac平台,在mac平台上使用 vim + g++的方式。這裡要提一句,在mac 的shell中,g++和gcc預設使用的是4.8的版本,許多新的c++标準并不被支援,需要下載下傳最新的編譯器并使用替換環境中使用的預設編譯器,使其更新到最新版本

gcc / g++ 使用

在shell環境中,不再像visual studio開發環境中那樣,隻要點選build就一鍵幫你編譯連結生成可執行程式了。shell中所有一切都需要你使用指令行來搞定,好在gcc/g++的使用并不複雜,記住幾個常用參數就能解決日常80%的使用場景了,下面羅列一些常用的指令

  • -o 指定生成目标檔案位置和名稱
  • -l 指定連接配接庫檔案名稱,一般庫以lib開頭但是在指定名稱時不用加lib字首,例如要連結libmath.o 可以寫成-lmath
  • -L 指定庫所在目錄
  • -Wall 列印所有警告,一般編譯時打開這個
  • -E 僅做預處理,不進行編譯
  • -c 僅編譯,不進行連結
  • -static 編譯為靜态庫
  • -share 編譯為動态庫
  • -Dname=definition 預定義一個值為definition的,名稱為name的宏
  • -ggdb -level 生成調試資訊,level可以為1 2 3 預設為2
  • -g -level 生成作業系統本地格式的調試資訊 -g相比于-ggdb 來說會生成額外的資訊
  • -O0/O1/O2/O3 嘗試優化
  • -Os 對生成的檔案大小進行優化

常用的編譯指令一般是 ​

​g++ -Wall -o demo demo.cpp​

​ 開啟所有警告項,并編譯demo.cpp 生成demo程式

基本資料類型與變量

算術類型

這裡說的基本資料類型主要是算術類型,按占用記憶體空間從小到大排序 char、bool(這二者應該是相同的)、short、wchar_t、int、long、longlong、float、double、long double。當然它們有的還有有符号與無符号的差別,這裡就不單獨列出了

一般來說,我們腦袋中記住的它們的大小好像是固定,比如wchar_t 占2個位元組,int占4個位元組。單實際上c++ 并沒有給這些類型的大小都定義死,而是固定了一個最小尺寸,而具體大小究竟定義為多少,不同的編譯器有不同的實作,比如我嘗試的wchar_t 類型在vc 編譯環境中占2個位元組,而g++編譯出來的占4一個位元組。下面的表是c++ 規定的部分類型所占記憶體空間大小

類型 含義 最小尺寸
bool 布爾類型 未定義
char 字元 8位
wchar_t 寬字元 16位
char16_t Unicode字元 16位
char32_t Unicode字元 32位
short 短整型 16位
int 整型 32位
long 長整型 32位
longlong 長整型 64位
float 單精度浮點數 32位
double 雙精度浮點數 64位

另外c++的标準還規定 一個int類型至少和一個short一樣大,long至少和int一樣大、一個longlong至少和一個long一樣大。

有符号數與無符号數

數字類型分為有符号和無符号的,預設上述都是有符号的,在這些類型中加入unsigned 表示無符号,而char分為 signed char、char、unsigned char 三種類型。但是實際使用是隻能選有符号或者無符号的。根據編譯器不同,char的表現不同。

一般在使用這些資料類型的時候有如下原則

  1. 明确知曉數值不可能為負的情況下使用unsigned 類型
  2. 使用int進行算數運作,如果數值超過的int的表示範圍則使用 longlong類型
  3. 算術表達式中不要使用char或者bool類型
  4. 如果需要使用一個不大的整數,必須指定是signed char 還是unsigned char
  5. 執行浮點數運算時使用double

類型轉化

當在程式的某處我們使用了一種類型,而實際對象應該取另一種類型時,程式會自動進行類型轉化,類型轉化主要分為隐式類型轉化和顯示類型轉化。

數值類型進行類型轉化時,一般遵循如下規則:

  1. 把數字類型轉化為bool類型時,0值會轉化為false,其他值最後會被轉化為true
  2. 當把bool轉化為非bool類型時,false會轉化為0,true會被轉化為1
  3. 把浮點數轉化為整型時,僅保留小數點前面的部分
  4. 把整型轉化為浮點數時,小數部分為0;如果整數的大小超過浮點數表示的範圍,可能會損失精度
  5. 當給無符号類型的整數指派一個超過它表示範圍的數時,會發生溢出。實際值是指派的數對最大表示數取餘數的結果
  6. 當給有符号的類型一個超出它表示範圍的值時,具體結果會根據編譯器的不同而不同
  7. 有符号數與無符号數混用時,結果會自動轉化為無符号數 (使用小轉大的原則,盡量不丢失精度)

由于bool轉化為數字類型時非0即1,注意不要在算術表達式中使用bool類型進行運算

下面是類型轉化的具體例子

bool b = 42; // b = true
int i = b; // i = 1
i = 3.14; // i = 3;
double d = i; // d = 3.0
unsigned char c = -1; // c = 256
signed char c2 = c; // c2 = 0 gcc 中 255在記憶體中的表現形式為0xff,+1 變為0x00 并向高位溢出,是以結果為0      

上述代碼的最後一個語句發生了溢出,對于像溢出這種情況下。不同的編譯器有不同的處理方式,得到的結果可能不經相同,在編寫代碼時需要避免此類情況的出現

盡管我們知道不給一個無符号數賦一個負數,但是經常會在不經意間犯下這樣的錯誤,例如當一個算術表達式中既有無符号數,又有有符号數的時候。例如下面的代碼

unsigned u = 10;
int i = -42;
printf("%d\r\n", u + i); // -32
printf("%u\r\n", u + i); //4294967264      

那麼該如何計算最後的結果呢,這裡直接根據它們的二進制值來進行計算,然後再轉化為具體的10進制數值,例如u = 0x0000000A,i = 0xffffffd6;二者相加得到 0xffffffEO, 如果轉化為int類型,最高位是1,為負數,其餘各位取反然後加一得到0x20,最終的結果就是-32,而無符号,最後的值為4294967264

字面值常量

一般明确寫出來數值内容的稱之為字面值常量,從彙編的角度來看,能直接寫入代碼段中數值。例如32、0xff、"hello world" 這樣内容的數值

整數和浮點數的字面值

整數的字面值可以使用二進制、8進制、10進制、16進制的方式給出。而浮點數一般習慣上以科學計數法的形式給出

  1. 二進制以 0b開頭,八進制以0開頭,十六進制以0x開頭
  2. 數值類型的字面值常量最終會以二進制的形式寫入變量所在記憶體,如何解釋由變量的類型決定,預設10進制是帶符号的數值,其他的則是不帶符号的
  3. 十進制的字面值類型是int、long、longlong中占用空間最小的(前提是類型能容納對應的數值)
  4. 八進制、十六進制的字面值類型是int、unsigned int、long、unsigned long、longlong和unsigned longlong 中尺寸最小的一個(同樣的要求對應類型能容納對應的數值)
  5. 浮點數的字面值用小數或者科學計數法表示、指數部分用e或者E标示

字元和字元串的字面值常量

由單引号括起來的一個字元是char類型的字面值,雙引号括起來的0個或者多個字元則構成字元串字面值常量。字元串實際上是一個字元數組,數組中的每個元素存儲對應的字元。這個數組的大小等于字元串中字元個數加1,多出來一個用于存儲結尾的\0

有兩種類型的字元程式員是不能直接使用的,一類是不可列印的字元,如回車、換行、倒退等格式控制字元,另一類是c/c++語言中有特殊用途的字元,例如單引号表示字元、雙引号表示一個字元串,在這些情況下需要使用轉義字元.

  1. 轉義以\開頭,後面隻轉義僅接着的一個字元
  2. 轉義可以以字元開始,也可以以數字開始,數字在最後會被轉化為對應的ASCII字元
  3. \x後面跟16進制數、\後面跟八進制數、八進制數隻取後面的3個;十六進制數則隻能取兩個數值(最多表示一個位元組)
'\\' // 表示一個\字元
"\"" //表示一個"
"\155" //表示一個 155的8進制數,8進制的155轉化為10進制為109 從acsii表中可以查到,109對應的是M
"\x6D"      

一般來講我們很難通過字面值常量知道它到底應該是具體的哪種類型,例如 15既可以表示short、int、long、也是是double等等類型。為了準确表達字面值常量的類型,我們可以加上特定的字首或者字尾來修飾它們。常用的字首和字尾如下表所示:

字首 含義
L'' 寬位元組
u8"" utf-8字元串
42ULL unsgined longlong
f 單精度浮點數
3L long類型
3.14L long double
3LL longlong
u'' char16_t Unicode16字元
U'' char32_t Unicode32字元

變量

變量為程式提供了有名的,可供程式操作的記憶體空間,變量都有具體的資料類型、所在記憶體的位置以及存儲的具體值(即使是未初始化的變量,也有它的預設值)。變量的類型決定它所占記憶體的大小、如何解釋對應記憶體中的值、以及它能參與的運算類型。在面向對象的語言中,變量和對象一般都可以替換使用

變量的定義與初始化

變量的定義一般格式是類型說明符其後緊随着一個或者多個變量名組成的清單,多個變量名使用逗号隔開。最後以分号結尾。

一般在定義變量的同時指派,叫做變量的初始化。而指派語句結束之後,在其他地方使用指派語句對其進行指派,被稱為指派。從彙編的角度來看,變量的初始化是,在變量進入它的生命有效期時,對那塊記憶體執行的記憶體拷貝操作。而指派則需要分解為兩條語句,一個尋址,一個值拷貝。

c++11之後支援初始化清單進行初始化,在使用初始化清單進行初始化時如果出現初始值存在精度丢失的情況時會報錯

c++11之後的清單初始化語句,支援使用指派運算幅、指派運算符加上{}、或者直接使用{}、直接使用()

int i = 3.14; //正常
int i(3.14); //正常
int i{3.14}; //報錯,使用初始化清單進行初始化時,由double到int可能會發生精度丢失
int i(3.14); //正常      

如果變量在定義的時候未給定初始值,則會執行預設初始化操作,全局變量會被指派為0,局部變量則是未初始化的狀态;它的值是不确定的。這個所謂的預設初始化操作,其實并不是真的那個時候執行了什麼初始化語句。全局變量被初始化為0,主要是因為,在程式加載之初,作業系統會将資料段的記憶體都初始化為0,而局部變量,則是在進入函數之後,初始化棧,具體初始化為何值,根據平台的不同而不同

聲明與定義的關系

為了允許把程式拆分為多個邏輯部分來編寫,c++支援分離式編譯機制,該機制允許将程式分割為若幹個檔案,每個檔案可被獨立編譯。

如果将程式分為多個檔案,則需要一種在檔案中共享代碼的方法。c++中這種方法是将聲明與定義區分開來。在我之前的部落格中,有對應的說明。聲明隻是告訴編譯器這個符号可以使用,它是什麼類型,占多少空間,但前對它執行的這種操作是否合法。最終會生成一個符号表,在連結的時候根據具體位址,再轉化為具體的二進制代碼。而定義則是真正為它配置設定記憶體空間,以至于後續可以通過一個具體的位址通路它。

聲明隻需要在定義語句的前面加上extern關鍵字。如果extern 關鍵字後面跟上了顯式初始化語句,則認為該條語句是變量的定義語句。變量可以聲明多次但是隻能定義一次。另外在函數内部不允許初始化一個extern聲明的變量

int main()
{
    extern int i = 0; //錯誤
    return 0;
}      

一個好的規範是聲明都在放在對應的頭檔案中,在其他地方使用時引入該頭檔案,後續要修改,隻用修改頭檔案的一個地方。一個壞的規範是,想用了,就在cpp檔案中使用extern聲明,這樣會導緻聲明有多份,修改定義,其他聲明都得改,項目大了,想要找起來就不那麼容易了。

變量作用域

變量的作用域始于聲明語句,終結于聲明語句所在作用域的末端

  1. 局部變量在整個函數中有效
  2. 普通全局變量在整個程式中都有效果
  3. 花括号中定義的變量僅在這對花括号中有效

作用域可以存在覆寫,并且以最新的定義的覆寫之前的

int i = 10;
void func()
{
    int i = 20;
    {
        string i = "hello world";
        cout << i <<endl; //輸出 "hello world"
    }
    cout << i << endl; //輸出 20
}

cout << i << endl; //輸出 10      

複合類型

複合類型是基于其他類型定義的類型,c++中的複合類型主要有指針、引用、結構體、類、共用體等等。這裡介紹指針和引用這兩種

引用類型

引用是對象的一個别名,從彙編的角度來看引用就是指針,但是使用引用比指針安全,也容易了解

使用引用類型時需要注意以下幾點:

  1. 引用必須指向對象
  2. 引用必須初始化
  3. 引用一旦初始化後,後續不能修改它的指向
  4. 引用本身不是對象,是以不能有指向引用的引用
  5. 可以多個引用指向同一個對象
int i = 0;
int &j = i;
int &value; //錯誤,引用必須初始化
int &k = 0; //錯誤,引用必須與變量綁定      

指針類型

指針是一個特殊的類型,它本身是一個對象,對象中存儲的值是另一個對象的位址。指針本身應該是一個無符号的整數,指針大小與程式中位址所占記憶體空間一緻,32位程式中指針是4位元組,64位程式,指針大小為8位元組

使用指針時的限制比引用要寬泛的多

  1. 指針可以指向對象,也可以指向另一個指針
  2. 指針不需要初始化,而且後續可以随意更改指向(當然必須指向同一資料類型)
  3. 可以多個指針指向同一個對象

指針隻能指向對象,指針本身也是一個對象。基于這兩點可以知道,不能有指向引用的指針,但是可以有指向指針的引用

int i = 0;
int *pi = &i; //定義一個指針指向i
int& ri = i; //定義一個引用指向i
int* &rp = pi; //定義一個引用,它指向一個指針
int& *pr = &ri; //試圖定義一個指向引用的指針,錯誤      

對于這種在變量定義中既有指針又有引用的情況下,想要知道它到底是指針還是引用,可以從右至左了解。例如rp中與變量名結合最緊密的是&,它表明變量是一個引用,而引用所指向的對象類型是一個int* 也就是定義了一個引用,它所指向的對象是一個指針,該指針指向一個int類型的變量

const 限定符

有時候我們希望定義一種變量,它的值不能被改變。針對這種需求我們可以使用const關鍵字

const修飾的變量無法被修改(但是隻在文法層面上,可以通過類型轉換的方式強制将其指針修改為非const的)

const對象無法修改,是以const對象需要初始化

預設狀态下const 對象僅在該檔案中有效:編譯器在編譯const對象時會在使用到const變量的位置直接用它的初始值進行替換。在多檔案中,為了完成這一個操作,必須事先知道它的值,也就是在編譯階段知道它的值。但是編譯階段各個檔案是獨立的,換句話說在其他檔案中定義的變量,在本檔案被編譯的過程中是隻能根據聲明知道它的類型,而不知道它的值。是以c++規定const類型變量各個檔案是獨立的。即使在多個檔案出現多個const變量重複定義,也會被認定為不同的const變量

//file1.cpp
const int g_i = 10;

//file2.cpp
const int g_i = 20;
int main()
{
    cout << g_i << endl; //輸出20
}      

上述代碼如果是使用普通變量則會報錯,報重複定義的錯誤,但是const變量,每個檔案獨立一份,是以不會有問題

如果想要定義的const變量在所有檔案中都有效,不管在其定義還是聲明語句前都加上extern關鍵字

//1.cpp
extern const int g_i = 10; //在定義的同時也加上extern

//1.h
extern const int g_i; //在聲明時也加上extern,後面需要使用時直接包含該頭檔案即可      

const 引用

指向const類型對象的引用被稱之為const引用。

const int i = 10;
const int& ref =i;      

const引用可以指向非const類型的變量,使用const引用後,不能通過引用修改對象的值

非const 引用無法指向const類型的變量

const與指針

指向const 類型變量的指針是一個指向常量指針,它的本質還是一個普通的指針,隻是它不允許通過指針修改變量的值, 這種類型的指針被稱之為指針常量

指向const類型變量的指針與const引用相似,也是可以指向非const類型變量,但是不允許普通指針指向const類型變量

由于指針本身允許後續修改指向,是以針對指針本身也可以進行const修飾,這種一般稱為const類型指針,這種情況下指針本身是const的,一旦指向一個變量,後續不允許修改指向,但是可以通過指針修改所指向的值。這種類型被稱之為常量指針

int i = 10;
const int *p = &i; //定義一個指向常量的指針
int * const pi = &i; //定一個常量指針
const int * const pci = &i;      

如何區分指針常量和常量指針呢,一般來說關注最後兩個字,它表示的是const修飾的具體内容,常量指針最後兩個字是指針,也就是說const修飾的是指針,指針本身是一個不能修改的const值;而指針常量,最後兩個字是常量,也就說const修飾的是一個常量。指針指向的是一個常量。

在閱讀代碼的時候,使用的方式仍然是從右到左的方式解讀。比如上面代碼中p 與之緊密結合的是,表示它是一個指針,指向一個const int對象;與pi結合最緊密的是const,也就是說pi本身有const屬性,是一個常量,後面的int表示它是一個指向int型對象的指針;而與pci緊密結合的是const,表明它自身是const類型,後面就是對它類型的修飾,const int * 表示它是一個指針類型,指向的是一個int的常量對象。

頂層const和底層const

頂層const 表示指針本身是一個常量,底層const表示所指向的對象是一個const。可以這樣了解,指針指向的是一個對象,通過指針修改對象是隐式的修改,更加偏向底層。是以頂層則是對指針本身進行修飾,底層則是對它所指向的對象的修飾。

  1. 指針既可以是頂層const也可以是底層const
  2. 引用後續無法修改其指向,也就不存在頂層const
  3. 底層const可以修飾const變量或者非const變量
  4. 底層const後續無法通過指針或者引用來修改變量的值
  5. 非底層const可以指派給底層const,而底層const無法指派稱為非底層const
  6. 在執行拷貝操作時,源對象可以是const或者非const、而目标對象隻能是非const

constexpr 和常量表達式

常量表達式是指那些不用運作,在編譯時期就能确定其值并且後續不會發生更改的表達式。

一個表達式是否是常量表達式是根據它的類型和初始值共同決定。例如:

const int i = 10; //是常量表達式,字面值在編譯時就能确定值,而const保證了後續變量值不會修改
int i = 10; //不是常量表達式,字面值在編譯時就能确定,但是這裡定義的是變量,後續可能會有代碼對其進行修改

const int j = i + 20; // 是常量表達式,根據前面的代碼,i是常量表達式,這個表達式中的i會在編譯時進行替換,也就說j在編譯時也能确定初始值

const int sz = i + get_size(); //雖然i是常量,但是函數隻有運作時才能擷取傳回值,是以這裡不是常量表達式      

上述代碼都比較簡單,比較好辨認處是否是常量表達式,但是在實際工程代碼中,可能情況比較複雜,無法确定是否是常量表達式,我們可以在需要定義常量表達式的情況下使用 constexpr關鍵字,該關鍵字是用來修飾一個常量表達式,如果對應的語句不是一個常量表達式,編譯器會報錯,可以根據這個報錯進行修改。

指針中的constexpr隻對指針本身有效,對它所指向的對象無效

int i = 10;
const int *p = &i; //指向整型常量的指針
constexpr int *p = nullptr; //指向整型的常量指針
constexpr int *p = &i //錯誤      

constexpr類型的指針無法指向具體的局部變量,但是它可以指向全局變量, 常量表達式的要求之一就是要在編譯期就知道它的具體值,局部變量是在函數開始執行的時候為它配置設定記憶體,也就是說局部變量無法在編譯期就得到它的位址,而全局變量是在程式加載的時候得到它的記憶體位址,複合常量表達式的要求

另外要注意,constexpr 不存在底層和頂層的現象,隻能寫在語句開頭

類型處理

随着程式越來約複雜,用到的資料類型也會越來約複雜,這個複雜主要展現在兩個方面,一個是難以拼寫,最典型的就是 類似于 ​

​namespace1::namespace2::container<namespacespace3::namespace4::type> value;​

​, 甚至還有更加複雜的。另一種就是語句過于複雜,從語句上無法推斷出它的傳回到底該用哪種類型來接收。針對第一種,c++中定義了别名;針對第二種,定義auto和decltype關鍵字

别名

類型别名就是給一個類型另外取一個名字,它讓複雜的類型書寫起來變得更加簡單,易于了解和使用。

在c語言中定義别名的方式一般是typedef,c++ 中新增加了using的方式

typedef const char* LPCSTR; 
using LPCSTR = const char*; 

LPCSTR lpStr = "Hello World";      

别名在與常量的使用中,需要額外注意,并不是簡單的進行替換就行了,它修飾的其實是變量本身,例如

typedef char* LPSTR;
// using LPSTR = char*

const LPSTR str = "hello world";      

上述代碼中const修飾的其實是str這個變量自身無法修改,也就是說這個const其實是一個頂層const。并不是簡單的替換。

const char* str; //錯誤了解,這裡并不是簡單的替換
char* const str; //這個才是正确的了解,它修飾的是變量本身      

auto

auto 關鍵字能根據表達式傳回的值類型,自動推斷變量的類型。

有auto關鍵字并不能說明c++是動态類型的語言,動态類型是指,在運作過程中能随意改變變量所存儲的資料的類型。例如在python中

s = 1; #此時s存儲的是int類型
s = "hello" # 這個時候s存儲的是字元串類型,同一個變量可以随意更改它所存儲的資料的類型      
auto i = 1; //根據表達式結果推斷出i應該是int
i = "hello world"; //i是int,隻能存儲int類型的資料,不能存儲字元串資料      

當初教科書上說的是在編譯期就決定類型的是靜态語言,運作期就決定類型的是動态語言。這個導緻我了解有些偏差,我一直以為是明确給出變量類型的是靜态。是以當初知道auto這個用法後,我一度以為c++要朝着動态類型語言這塊發展。

編譯器推斷出來的類型有時候跟初始值類型并不完全一樣,編譯器會适當的改變結果類型,時期更符合初始化規則。

  1. 使用引用對象來給auto指派時,auto會被推斷為被引用的對象類型
  2. auto一般會忽略頂層const,而底層const則會保留下來。也就是說auto會自動忽略掉變量自身的const屬性
int i = 10;
const int ci = i;
const int& cr = ci;

auto b = i; // auto 類型為int
auto c = cr; //auto 類型為 int (cr是ci的别名、此時應該使用ci的類型,而ci本身是一個頂層const,會被忽略掉)
auto d = &i; // auto類型為 int*
auto e = &ci; // auto類型為 const int* (ci 自身是一個const,是以指針指向的應該是一個int型常量,但是指針本身應該不帶有const屬性,是以類型應該是const int*)      

如果希望變量自身帶有頂層const屬性,可以在auto前加上一個const修飾變量

const auto f = &ci; //此時f的類型為 const int const*      

decltype

有了auto就可以很友善的推斷出類型了,為什麼還有整出一個新的關鍵字呢?auto有一個問題,那就是必須用表達式的值來初始化變量,但是有些時候我隻想用這個表達式值的類型來決定我變量的類型,我不想用這個值來初始化我的變量。或者我不想對變量初始化。

int i = 10;
auto j = i; // 如果這個時候我不想用i的值來初始化,我想用其他的。      

基于這個需求,c++11标準提出了新的關鍵字 decltype ,編譯器會分析表達式并得到它的類型,但是并不計算表達式的值,也不使用表達式的值對變量進行初始化

int i = 10;
const int j = 10;
int fn();
decltype(fn()) j; //這裡可以不對j進行初始化
int& ri = i;
decltype(ri) rz; //錯誤 rz是一個引用,必須初始化
decltype(j) k; //錯誤k 是一個const類型的變量,需要初始化      
  1. auto 會自動忽略掉頂層const,而decltype 則會傳回變量的完整類型,包括頂層const
  2. c++ 中的引用一般會被當作變量的同義詞使用,使用引用的表達式可以自動替換成使用該變量,但是在decltype中例外,引用得到的也是引用類型
  1. decltype中如果是一個表達式,則類型是表達式計算結果的類型。
  2. 如果變量又額外用括号括起來了,編譯器會将其作為一個表達式,得到的結果是一個引用。多層括号的結果永遠是引用類型
  3. 表達式如果是解引用操作,得到的也是引用
const int i = 10;
decltype(i + 10) j; //由于i + 10 得到的是一個int類型,是以這裡j也是int類型
const int *p = &i;
decltype(*p) k; //錯誤,k的類型為const int& ,是一個引用類型,需要初始化      
下一篇: 關聯容器

繼續閱讀