##C++ Primer 學習筆記(第二章:變量和基本類型)安全
[TOC]數據結構
###2.1 基本內置類型函數
wchar_t
(16位)類型用於確保能夠存放機器最大擴展字符集中的任意一個字符,char16_t
和char32_t
則爲Unicode
字符集服務(Unicode
表示全部天然語言中字符的標準)。學習
一般,float
以1個字(32比特)來表示,double
以2個字(64比特)來表示,longdouble
以3或4個字(96或128比特)來表示。指針
整型類型(int
,short
,long
,longlong
)都是帶符號(省略signed
),加上unsigned
就是無符號類型。unsigned int
能夠縮寫爲unsigned
。code
字符型有三種:char
、signedchar
、unsigned char
。但字符型的表現只有兩種,有符號和無符號,而char到底有沒有符號要根據編譯器決定。對象
類型選擇經驗: (1)明確知曉不可能爲負時選用無符號類型。 (2)使用int
執行整數運算。short
過小而long
通常和int
具備相同尺寸。若是範圍超過int
表示範圍,再選用longlong
。 (3)執行浮點數運算使用double
。由於float
一般精度不夠,而雙精度和單精度在計算代價上相差無幾。long double
通常狀況下也無必要。生命週期
關於類型轉換:當賦給無符號類型一個超出它表示範圍的值時,結果是初始值對無符號類型表示數值總數取模以後的餘數;當賦給帶符號類型一個超出它表示範圍的值時,結果是未定義的(undefined
)。ip
整型字面值:以0開頭的整數表明八進制,以0x
或0X
開頭的表明十六進制數。內存
字符字面值:'A'
,只有一個單獨字符;字符串字面值:"A"
,有兩個字符,一個A
,一個空字符'\0'
。
若是兩個字符串字面值位置緊鄰且僅由空格、縮進和換行符分隔,則他們其實是一個總體。即當書寫的字符串字面值較長,一行寫不下,可採起分開書寫的方式。
###2.2變量
初始化不是賦值,初始化的含義是建立變量時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,而以一個新值來替代。
C++11
新標準:用花括號初始化變量:
int unit=0; int unit={0}; int unit{0}; int unit(0);
被稱爲列表初始化,不管是初始化對象仍是某些時候爲對象賦新值均可以用花括號。
若是內置類型的變量未被顯式初始化,它的值由定義的位置決定。定義於任何函數體以外的變量被初始化爲0;定義在函數體內部的內置類型變量不被初始化,其值是未定義的(undefined
)。
爲了支持分離式編譯,C++
語言將聲明和定義區分開來。若是想聲明一個變量而非定義它,就在變量名前添加關鍵字extern
,並且不要顯式地初始化變量:
extern int i;//聲明而非定義 int j;//聲明並定義j
變量能且只能被定義一次,可是能夠被屢次聲明。若是要在多個文件中使用同一個變量,就必須將定義和聲明分離,變量的定義必須出如今一個文件中,而其餘用到該變量的文件必須對其進行聲明,卻絕對不能重複定義。
用戶自定義的標識符中不能連續出現兩個下劃線,也不能如下劃線緊連大寫字母開頭。定義在函數體外的標識符不能如下劃線開頭。
若是函數內部定義了一個與全局變量同名的新變量,則直接訪問變量名時,局部變量將會覆蓋全局變量;若想顯式訪問全局變量須要在變量名前使用做用域操做符(::
)。(注:函數內部不宜定義相同變量名)
###2.3 複合類型(引用和指針)
定義引用時(&d
),程序把引用和它的初始值綁定在一塊兒,而不是將初始值拷貝給引用。一旦初始化完成,引用將和它的初始值綁定在一塊兒。引用必須初始化。
引用並不是對象,它只是一個已經存在的對象所起的另一個名字,因此不能定義引用的引用。
容許在一條語句中定義多個引用,其中每一個引用標識符都必須以符號&
開頭。
全部引用的類型要和綁定的對象嚴格匹配(僅是初始化時嚴格匹配,實際上引用也沒法從新綁定到另外一個對象上),且引用只能綁定在對象上,而不能與字面值或某個表達式的計算結果綁定在一塊兒。
指針和引用的區別:指針自己就是一個對象,容許對指針賦值和拷貝,並且在指針的生命週期內它能夠前後指向幾個不一樣的對象(而引用非對象,一旦定義就沒法綁定到另外對象上);指針無須在定義時賦初值。
指針的類型要和它指向的對象嚴格匹配。
指針的解引用符(*
),若是給解引用的結果賦值,就至關於給指針所指對象賦值。
空指針的表示形式有三種:
int *p1 = nullptr;(推薦) int *p2 = 0; int *p3 = NULL;(#include<cstdlib>)
把int
變量直接賦給指針是錯誤的操做,即便int變量的值剛好等於0也不行。
兩個指針存放的地址值相同有三種可能: 都爲空;都指向一個對象;都指向同一對象的下一地址。 也有多是一個指針指向某對象,另外一指針指向另外對象的下一地址。
void*
是一種特殊指針類型,它能夠存聽任意對象的地址。 利用void*
只能作:和別的指針比較;做爲函數的輸入輸出;賦給另一個void*
指針。 不能直接操做void*
指針所指的對象,由於咱們並不知道它是什麼類型,也沒法肯定它能作哪些操做。
指針是內存中的對象,像其餘對象同樣也有本身的地址,所以容許把指針的地址再存放到另外一個指針當中(即**
表示指向指針的指針)
引用不是對象,不能定義指向引用的指針;但指針是對象,因此存在對指針的引用:
int i = 42, *p; int *&r = p; r = &i; *r = 0;
###2.4 const限定符(const
、constT*
、constT&
)
const
對象一旦建立後其值不可改變,因此const
對象必須初始化。能夠運行初始化,也可編譯初始化。const int i = get_size(); const int i = 52;
默認狀況下,const
對象被設定爲僅在文件內有效。當多個文件中出現了同名的const
變量時,其實等同於在不一樣文件中分別定義了獨立的變量。
只在一個文件中定義const
,在其餘多個文件中聲明並使用它:對於const
變量無論是聲明仍是定義都添加extern
關鍵字,並只需定義一次。
extern const int i = fcn();//file.cc extern const int i;//file.h
若是想在多個文件中共享const
對象,必須在變量的定義以前添加extern
關鍵字。
const T&
(只讀引用):初始化常量引用時容許用任意表達式做爲初始值,只要該表達式的結果能轉換成引用的類型便可。 const auto&
的做用是:避免對元素的拷貝;避免對象的寫操做。int i1 = 42;double i2 = 3.14; const int &r1 = i1; const in t&r2 = i2;
但當常量引用被綁定到另一種類型上時,它只綁定到了一個臨時量對象,若是改變被綁定對象或其另一個同類型引用的值時(注意此處的值與常量引用的類型不一樣),常量引用的值將不會改變。而若是常量引用和綁定類型一致時,則常量引用可當作一個只讀功能,也會對被綁定對象的值改變。 可是須要注意:要想綁定一個常量必須用常量引用。
儘管常量引用的初始化要求比較寬鬆,但不能忘記很是量引用嚴格要求類型匹配(表達式不行,常量同一類型也不行,必須是同類型變量)。其緣由能夠理解爲:既然定義了很是量引用,就但願經過引用修改綁定對象的值,但實際上只要類型稍有不一樣就必定存在類型轉換,存在類型轉換就須要編譯器臨時建立一個臨時量對象用於很是量引用的綁定,而這種綁定將致使對引用的修改沒法反映到原始的對象上(只反映到臨時量對象,而這個對象毫無心義,也沒法訪問)。因此C++
禁止這種類型不用的引用綁定。
指向常量的指針(const T*
)不能用於改變其所指對象的值。要想存放常量對象的地址,只能使用指向常量的指針:
const double pi = 3.14; const double *cptr = π//這裏不能隨隨便便定義一個很是量指針double*來存放常量的地址
存放常量對象的指針必須使用指向常量的指針,但並不表明指向常量的指針不能指向很是量。(見第7條)
double dval = 3.14; const double *cptr = &dval;
指向常量的指針也僅僅要求不能經過該指針改變對象的值,而沒有規定那個對象的值不能經過其餘途徑改變。 指向常量的指針能夠隨時改變其所指對象,只要不改變值便可。
T *const
)必須初始化,且一旦初始化完成,它的值(即存放的地址)就不能再改變了,即不變的指針自己的值(即存放的地址)而非指向的那個值。(可是能夠經過常量指針修改所指對象的值)int errNumb = 0; int *const curRrr = &errNumb; const double pi = 3.14159; const double* const pip = π//指向常量對象的常量指針
指向常量的指針(const T *name
)Vs
常量指針(T *const name
): 從右向左閱讀,const
離誰近,誰不能變(「誰」包括指向的地址和所指對象的值)。const T1 * const T2
就都不能變。
頂層const
和底層const
: 頂層const
表示指針自己是個常量,而底層const
表示指針所指的對象是一個常量。通常的,頂層const
能夠表示任意的對象是常量,對任何數據類型都適用;而底層const
和指針引用等複合類型的基本類型部分有關。而指針類型既能夠有頂層const
也能夠有底層const
。 當執行拷貝操做時,常量的頂層const
不影響。而若是是底層const
,拷入和拷出對象必須具備相同的底層const
資格,或者兩個對象的數據類型必須可以轉換。通常來講很是量能夠轉化爲常量。 即兩條準則:(1)頂層const
不影響拷貝;若是被拷貝的有底層const
,必須具備相同的底層const
資格才能拷貝;(2)很是量能夠轉化爲常量,反之不行。 例:
int i= 0; int *const p1 = &i;//頂層const const int ci = 42;//頂層const const int *p2 = &ci;//底層const const int *const p3 = p2;//靠右的是頂層const,靠左的是底層const const int &r = ci;//用於聲明引用的都是底層const //頂層const不影響拷貝: i = ci;//ci是一個頂層const //底層const: int *p = p3;//錯誤,p3有底層const,而p沒有 p2 = p3;//正確,p2和p3都有底層const,這裏p3的頂層const不受影響 p2 = &i;//正確,很是量能夠轉化爲常量 int &r = ci;//錯誤,普通的int&不能綁定到int常量上,即常量不能夠轉化爲很是量 const int &r2 = i;//正確,至關於制度,即很是量能夠轉化爲常量
const
對象也是常量表達式。const int max_files = 20;//是 const int limit = max_files + 1;//是 int staff = 27;//不是 const int sz = get_size();//不是
C++11
容許將變量聲明爲constexpr
類型以便編譯器來驗證變量的值是不是一個常量表達式。聲明爲constexpr
的變量必定是一個常量,並且必須用常量表達式來初始化。constexpr int sz = size();//size()必須是一個constexpr類型函數。
通常來講,若是認定一個變量是常量表達式,那就把它聲明成constexpr
類型。
constexpr
用到的類型稱爲「字面值類型」,這些類型通常比較簡單,值也顯而易見、容易獲得。算數類型、引用和指針屬於字面值類型。類、IO
庫、string
不屬於。###2.5 處理類型
typedef
和使用別名聲明。typedef double wages; typedef wages base, *p;//p是double*的同義詞 using SI = Sales_item;//C++11
typedef char *pstring; const pstring cstr = 0;//cstr是指向char的常量指針
const pstring
是指向char
的常量指針,而非指向常量char
的指針。
C++11
引入auto
類型說明符,讓編譯器經過初始值來推算變量的類型。而auto
的變量必須有初始值。
auto
能夠在一條語句中聲明多個變量,但一條聲明語句只能有一個基本類型。
當auto
的初始值爲引用時,參與初始化的是其引用對象的值;auto
通常會忽略掉頂層const
,同時底層const
則會留下來:(即auto
忽略引用,忽略頂層const
)
int i = 0, &r = i; auto a = r;//a是一個整數 const int ci = i, &cr = ci; auto b = ci;//整型,忽略頂層const auto c = cr;//整型,cr和ci無差異 auto d = &i;//整型指針 auto e = &ci;//指向整型常量的指針(對整數常量取地址是一種底層const) //若是但願推斷出的auto類型是一個頂層const則須要明確給出const: const auto f = ci; //也能夠將引用類型設爲auto,此時初始化規則仍然適用: auto &g = ci;//g是引用,綁定到ci auto &h = 42;//不能爲很是量引用綁定字面值 const auto &j = 42;//能夠爲常量引用綁定字面值
C++
定義了類型說明符decltype
來選擇並返回操做數的數據類型。此過程當中編譯器分析表達式並獲得它的類型,卻不實際計算表達式的值。decltype(f()) sum = x;//sum的類型就是f函數返回的類型
auto
不一樣,若是decltype
使用的表達式是一個變量,則decltype
返回該變量的類型(包括頂層const
和引用在內):const int ci = 0, &cj = ci; decltype(ci) x = 0;//x是const int decltype(cj) y = x;//y是const int&,並綁定到x上 decltype(cj) z;//錯誤,z是一個引用,必須初始化
引用歷來都做爲其所指對象的同義詞出現,只有用在decltype
處是例外。
decltype
使用的表達式若是是一個解引用操做,則decltype
將獲得引用類型而不是解引用類型:int i = 42, *p = &i, &r = i; decltype(r + 0) b;//正確,加法的結果是int,b是一個未初始化的int decltype(*p) c;//錯誤,c是int&,必須初始化
decltype
使用的是一個不加括號的變量,則獲得的結果就是該變量的類型;若是給變量加上了一層或多層括號,編譯器就會把它當成一個表達式,而這樣的decltype
就會獲得引用類型。int i = 42; decltype((i)) d;//錯誤,d是一個int&,必須初始化 decltype(i) e;//正確,e是一個未初始化int
即decltype((variable))
的結果永遠是引用,而decltype(variable)
的結果只有當variable
自己就是一個引用時纔是引用。
i = x
的類型是int &
。###2.6 自定義數據結構
struct
)定義時,最好不要把對象的定義和類的定義放到一塊兒。struct Sales_data{/*...*/}; Sales_data accum, *saleptr;
C++11
新標準規定能夠爲數據成員提供一個類內初始值。建立對象時,類內初始值將用於初始化數據成員。沒有初始值的成員將被默認初始化。
類一般定義在頭文件中,並且類所在頭文件的名字應與類的名字同樣。 頭文件一般包含那些只能被定義一次的實體,如類、const
和constexpr
變量等。
預處理器:確保頭文件屢次包含仍能安全工做的經常使用技術。 #include
:用指定的頭文件內容代替#include
頭文件保護符:依賴於預處理變量。 預處理變量有兩種狀態:已定義和未定義。#define
指令把一個名字設爲預處理變量,#ifdef
和#ifndef
分別檢查某個指定的預處理變量是否已經定義。#ifdef
當且僅當變量已定義時爲真,#ifndef
當且僅當變量未定義時爲真。一旦檢查結果爲真,則執行後續操做直到遇到#endif
指令爲止。 例如某個頭文件:
#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
預處理變量包括頭文件保護符必須惟一,一般的作法是基於文件中類的名字來構造,預處理變量的名字所有大寫。