C++知识总结(1)--变量和基本类型
最近打算看看《C++ primer》,重新復習C++的一些知識點,同時會添加部分在做牛客網編程題目時候記錄的知識點。
變量和基本類型
- endl操縱符的效果是結束當前行,并將與設備關聯的緩沖區中的內容刷到設備中。緩沖刷新操作可以保證到目前為止程序所產生的所有輸出都真正寫入輸出流中,而不是僅停留在內存中等待寫入流。
- 注釋界定符不能嵌套。它是以/*開始,以*/結束的。因此,一個注釋不能嵌套在另一個注釋之內。
基本類型
算術類型
算術類型所能表示的數據范圍如下:
基本的字符類型是char,一個char的空間應確保可以存放機器基本字符集中任意字符對應的數字值。也就是說一個char的大小和一個機器字節一樣。
其他字符類型用于擴展字符集,如wchar_t, char16_t, char32_t。其中wchar_t確保可以存放機器最大拓展字符集中的任意一個字符。而后兩種字符類型則是為Unicode字符集服務。
C++標準指定了一個浮點數有效位數的最小值,但是大多數編譯器都實現了更高的精度。通常,float以1個字(32比特)來表示,double以2個字(64比特)來表示,long double以3或4個字(96或128比特)來表示。此外,一般float和double分別有7和16個有效位。
除了布爾型和擴展的字符型之外,其他整型可以分為帶符號的和無符號的。類型int、short、long和long long都是帶符號的,在它們前面加上unsigned則可以得到無符號類型。其中類型unsigned int可以縮寫為unsigned。
字符型則分成3種:char、signed char 和 unsigned char。并且,char和signed char并不一樣,而且字符的表現形式同樣是兩種,帶符號和無符號,因為char類型會表現為這兩種形式中的一種,具體是由編譯器決定具體形式。
具體類型的選擇建議如下:
類型轉換
類型轉換的過程如下:
注意,不能混合使用有符號數和無符號數,如果一個表達式中即包含有符號數和無符號數,那么有符號數會轉換成無符號數來進行計算,如果這個有符號數還是負數,那么會得到異常結果。如下例子所示:
unsigned u = 10; int i = -42; // 輸出-84 std::cout << i+i << std::endl; // 混合了無符號數和有符號數,如果int占32位,輸出4294967264 std::cout << u+i << std::endl;上述例子最后一個輸出說明了一個負數和一個無符號數相加是有可能得到異常結果的。32位的無符號數范圍是0到4294967295。
static_cast 的用法
static_cast < type-id > ( expression )
該運算符把expression轉換為type-id類型,但沒有運行時類型檢查來保證轉換的安全性。它主要有如下幾種用法:
①用于類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換。
進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;
進行下行轉換(把基類指針或引用轉換成派生類表示)時,由于沒有動態類型檢查,所以是不安全的。
②用于基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
③把空指針轉換成目標類型的空指針。
④把任何類型的表達式轉換成void類型。
注意:static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性。
C++中的static_cast執行非多態的轉換,用于代替C中通常的轉換操作。因此,被做為顯式類型轉換使用。
C++中的reinterpret_cast主要是將數據從一種類型的轉換為另一種類型。所謂“通常為操作數的位模式提供較低層的重新解釋”也就是說將數據以二進制存在形式的重新解釋。
dynamic_cast<>用于C++類繼承多態間的轉換,分為:
1.子類向基類的向上轉型(Up Cast)
2.基類向子類的向下轉型(Down Cast)
其中向上轉型不需要借助任何特殊的方法,只需用將子類的指針或引用賦給基類的指針或引用即可,dynamic_cast向上轉型其總是肯定成功的。**而向下轉換時要特別注意:dynamic_cast操作符,將基類類型的指針或引用安全的轉換為派生類的指針或引用。**dynamic_cast將一個基類對象指針(或引用)cast到繼承類指針,dynamic_cast會根據基類指針是否真正指向繼承類指針來做相應處理。這也是dynamic_cast與其他轉換不同的地方,dynamic_cast涉及運行時類別檢查,如果綁定到引用或指針的對象不是目標類型的對象,則dynamic_cast失敗。如果是指針類型失敗,則dynamic_cast的返回結果為0,如果是引用類型的失敗,則拋出一個bad_cast錯誤。
注意:dynamic_cast在將父類cast到子類時,父類必須要有虛函數。因為dynamic_cast運行時需要檢查RTTI信息。只有帶虛函數的類運行時才會檢查RTTI。
字面值常量
定義:形如42的值被稱為字面值常量。
我們可以將整型字面值寫作十進制數、八進制數和十六進制數。其中以0開頭的代表八進制數,以0x或者0X開頭的代表十六進制數。默認情況下,十進制字面值是帶符號數,而八進制和十六進制可以是帶符號也可以是無符號數。它們的類型都是選擇可以使用的類型中尺寸最小的,并且可以容納下當前數值的類型。如十進制可以使用的是int, long和long long,而八進制和十六進制還可以使用無符號類型的unsigned int,unsigned long 和unsigned long long。
有兩類字符是程序員不能直接使用的:一類是不可打印的字符,如退格或其他控制字符,因為它們沒有可視的圖符;另一類是在C++語言中有特殊含義的字符,如單引號、雙引號、問號、反斜線,這些情況下需要用到轉義序列,轉義序列均以反斜線開始,C++語言規定的轉義序列包括:
轉義序列被當做一個字符使用。
對于字面值類型可以通過添加一些前綴和后綴來改變其默認類型,如下例子所示:
注意 ,指定一個長整型字面值時使用大寫字母L來標記,這是由于小寫字母l和數字1容易混淆。
變量
在C++11新標準中,變量初始化除了使用傳統的如int a=2;這種方式外,還可以使用花括號進行初始化,這種初始化的形式稱為列表初始化,形式如int a{2};,需要注意的是,對于內置類型的變量,如果使用列表初始化且初始值存在丟失信息的風險,則編譯器將報錯,例如:
long double ld = 3.14159; // 錯誤: 轉換沒有執行,因為存在丟失信息的危險 int a{ld}, b = {ld}; // 正確:轉換執行,但確實丟失了部分值 int c(ld), d = ld;當然,如果定義變量時沒有指定初值,變量將被默認初始化,會被賦予默認值,而默認值會由變量類型決定,并且定義變量的位置也會有影響。如果是內置類型的變量未被顯式初始化,定義在任何函數體之外的變量被初始化為0。但是定義在函數體內部的內置類型變量將不被初始化,如果試圖拷貝或以其他形式訪問此類未定義的值將引發錯誤。
變量聲明規定了變量的類型和名字,在這一點上定義與之相同,但是定義還申請存儲空間,也可能會為變量賦一個初始值。如果想聲明一個變量而非定義它,可以在變量名前添加關鍵字extern,而且不要顯式地初始化變量,如下所示:
// 聲明i而非定義i extern int i; // 聲明并定義j int j;任何包含顯式初始化的聲明即成為定義,即使添加了關鍵字extern。此外,如果在函數體內部,試圖初始化一個由extern關鍵字標記的變量,將引發錯誤。
變量能且只能被定義一次,但是可以被多次聲明。
關鍵概念:靜態類型
C++是一種靜態類型語言,其含義是在編譯階段檢查類型。其中,檢查類型的過程稱為類型檢查。
C++的標識符由字母、數字和下劃線組成,其中必須以字母或下劃線開頭。標識符沒有長度限制,但對大小寫字母敏感。下面是一些變量命名的規范:
- 標識符要能體現實際含義;
- 變量名一般用小寫字母,如index;
- 用戶自定義的類名一般以大寫字母開頭,如Sales_item;
- 如果標識符由多個單詞組成,則單詞間應有明顯區分,如student_loan或studentLoan。
復合類型
復合類型是指基于其他類型定義的類型,如引用和指針。
引用
引用為對象起了另一個名字,其形式如int &refVal = ival;,其中ival是一個初始化的int類型變量,這里需要注意引用必須被初始化。
定義一個引用后,對其進行的所有操作都是在與之綁定的對象上進行的,比如為引用賦值,同樣會改變與引用綁定的對象。
引用只能綁定在對象上,而不能與字面值或某個表達式的計算結果綁定在一起,如int &ref = 10;這就是一個錯誤的例子。
引用傳遞不可以改變原變量的地址,但可以改變原變量的內容。
引用的類型必須與其所引用對象的類型一致。但在初始化常量引用時允許用任意表達式作為初始值,只有該表達式的結果能轉換成引用的類型即可。如:
int i = 42; const int &r1 = i; // 允許將常量引用綁定到一個普通int對象上 const int &r2 = 42; // 正確:r2是一個常量引用 const int &r3 = r1 * 2; // 正確: r3是一個常量引用 int &r4 = r1 * 2; // 錯誤:r4只是一個普通的非常量引用指針
指針是“指向”另外一種類型的復合類型。定義指針類型的方法是在變量名前使用*。
空指針不指向任何對象,在試圖使用一個指針之前代碼可以首先檢查它是否為空。下面是幾個生成空指針的方法:
int *p1 = nullptr; // 等價于 int *p1 = 0; int *p2 = 0; // 直接將p2初始化為字面常量0 // 需要首先#include cstdlib int *p3 = NULL; // 等價于int *p3 = 0;第一種方法中使用字面值nullptr來初始化指針,這是C++11新標準引入的一種方法。第三種方法就是使用一個名為NULL的預處理變量來給指針賦值,這個變量在頭文件cstdlib中定義,它的值就是0。
void* 是一種特殊的指針類型,可用于存放任意對象的地址。利用該指針能做的事比較有限:拿它和別的指針比較、作為函數的輸入或輸出,或者賦給另外一個void*指針。但是不能直接操作器指向的對象,因為并不知道這個對象是什么類型。
引用本身不是一個對象,因此不能定義指向引用的指針,但指針時對象,所以存在對指針的引用:
int i=42; int *p; // p是一個int型指針 int *&r = p; // r是一個對指針p的引用 r = &i; // r引用了一個指針,因此給r賦值&i就是令p指向i *r = 0; // 解引用r得到i,也就是p指向的對象,將i的值改為0要理解r的類型到底是什么,最簡單的辦法就是從右向左閱讀r的定義。離變量名最近的符號對變量的類型有最直接的影響,此例中是&r的符號&,所以r是一個引用,而聲明符的其余部分用以確定r引用的類型是什么,此例中的符號*說明r引用的是一個指針。
- 野指針是指向未分配或者已釋放的內存地址
- 使用free釋放掉一個指針內容后,必須手動設置指針為NULL,否則會產生野指針。
兩個指針之間的運算
只有指向同一個數組的兩個指針變量之間才能進行運算,否則運算毫無意義。
(1) 指針變量的相減
兩指針變量相減所得之差是兩個指針所指數組元素之間相差的元素個數,實際上是兩個指針值(地址)相減之差再除以該數組元素的長度(字節數).
例如pf1和pf2是指向同一浮點數組的兩個指針變量,設pf1的值為2010H,pf2的值為2000H,而浮點數組每個元素占4個字節,所以pf1-pf2的結果為(2000H-2010H)/4=4,表示pf1和 pf2之間相差4個元素。
注意:兩個指針變量不能進行加法運算。例如,pf1+pf2是什么意思呢?毫無實際意義。
(2) 兩指針變量進行關系運算
指向同一數組的兩指針變量進行關系運算可表示它們所指數組元素之間的關系。例如:
- pf1 = pf2 表示pf1和pf2指向同一數組元素;
- pf1 > pf2 表示pf1處于高地址位置;
- pf1 < pf2 表示pf2處于低地址位置。
指針變量還可以與0比較。設p為指針變量,則p==0表明p是空指針,它不指向任何變量;p!=0表示p不是空指針。
空指針是由對指針變量賦予0值而得到的。例如:
#define NULL 0 int *p = NULL;對指針變量賦0值和不賦值是不同的。指針變量未賦值時,值是隨機的,是垃圾值,不能使用的,否則將造成意外錯誤。而指針變量賦0值后,則可以使用,只是它不指向具體的變量而已。
常量指針和指針常量
- const在*的左測,指針所指向的內容不可變,即*p不可變,是常量指針。
- const在*的右側,指針不可變,即p++不被允許,是一個指針常量。
const 限定一個對象為只讀屬性。
先從一級指針說起吧:
(1)const char p 限定變量p為只讀。這樣如p=2這樣的賦值操作就是錯誤的。
(2)const char *p p為一個指向char類型的指針,const只限定p指向的對象為只讀。這樣,p=&a或 p++等操作都是合法的,但如*p=4這樣的操作就錯了,因為企圖改寫這個已經被限定為只讀屬性的對象。
(3)char *const p 限定此指針為只讀,這樣p=&a或 p++等操作都是不合法的。而*p=3這樣的操作合法,因為并沒有限定其最終對象為只讀。
(4)const char *const p 兩者皆限定為只讀,不能改寫。
有了以上的對比,再來看二級指針問題:
(1)const char **p p為一個指向指針的指針,const限定其最終對象為只讀,顯然這最終對象也是為char類型的變量。故像**p=3這樣的賦值是錯誤的,而像*p=? p++這樣的操作合法。
(2)const char * const *p 限定最終對象和 p指向的指針為只讀。這樣 *p=?的操作也是錯的。
(3)const char * const * const p 全部限定為只讀,都不可以改寫。
指針數組和數組指針
區分int *p[n]; 和int (*p)[n];就要看運算符的優先級了。
int *p[n]; 中,運算符[]優先級高,先與p結合成為一個數組,再由int*說明這是一個整型指針數組。
int (*p)[n]; 中()優先級高,首先說明p是一個指針,指向一個整型的一維數組。
例子:
int *s[8]; //定義一個指針數組,該數組中每個元素是一個指針,每個指針指向哪里就需要程序中后續再定義了。int (*s)[8]; //定義一個數組指針,該指針指向含8個元素的一維數組(數組中每個元素是int型)。
對于一個數組,如int a[10];,對其數組名進行加減,如果有取地址符和沒有是有區別的:
// 有取地址符,相當于每次增加整個數組的長度的倍數 &a + i = a + i*sizeof(a); // 沒有使用取地址符,只是數組名,則增加數組中元素的長度的倍數 a + i = a + i*sizeof(a[0]);
const限定符
const對象一旦創建后其值就不能再改變,所以const對象必須初始化。
默認情況下,const對象被設定為僅在文件內有效。當多個文件出現了同名的const變量,其實等同于在不同文件中分別定義了獨立的變量。
但如果想在多個文件之間共享const對象,必須在變量的定義之前添加extern關鍵字。
由于常量引用僅對引用可參與的操作做出了限定,對于引用的對象本身是否是一個常量未作限定。因此對象可能是一個非常量,可以通過其他途徑改變它的值,如:
int i = 42; int &r1 = i; // 引用r1綁定對象i const int &r2 = i; // r2也綁定對象i,但是不允許通過r2修改i的值 r1 = 0; // r1不是常量,所以可以修改i的數值 r2 = 0; // 錯誤,r2是一個常量引用頂層const可以表示任意的對象是常量,而底層const表示指針指向的對象是常量。當執行對象的拷貝操作時,常量是頂層const還是底層const區別明顯。其中,頂層const不受什么影響。而對于底層const則是有所限制的,拷入和拷出的對象必須具有相同的底層const資格,或者兩個對象的數據類型必須能夠轉換。一般來說,非常量可以轉換成常量,反之則不行。
常量表達式是指值不會改變并且在編譯過程中就能得到計算結果的表達式。
在C++11新標準中,規定了允許將變量聲明為constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式,并且聲明為這種類型的變量必須用常量表達式來初始化。
聲明constexpr時用到的類型被稱為字面值類型,前面介紹的算術類型、引用和指針都屬于字面類型。注意,引用和指針可以定義成constexpr,但其初始值卻受到嚴格限制,一個constexpr指針的初始值必須是nullptr或者0,或者是存儲于某個固定地址中的對象。此外,constexpr聲明中如果定義了一個指針,則該限定符僅作用于指針本身,而與指針所指對象無關,它會將指針變成一個頂層const,即指針本身是一個常量。而constexpr指針與其他常量指針類似,既可以指向常量也可以指向一個非常量。
類型別名
類型別名是一個名字,它是某種類型的同義詞。
有兩種方法可用于定義類型別名。傳統的方法是使用關鍵字typedef:
typedef double wages; // wages是double的同義詞C++11新標準規定了一個新的方法,使用別名聲明來定義類型的別名:
using SI = Sales_item; // SI是Sales_item的同義詞這種方法用關鍵字using作為別名聲明的開始,后面緊跟別名和等號,作用是將等號左側的名字規定成等號右側類型的別名。
auto類型說明符
C++11新標準引入了auto類型說明符,用它就能讓編譯器替我們去分析表達式所屬的類型。它是讓編譯器通過初始值來推算變量的類型,所以,auto定義的變量必須有初始值。
使用auto也能在一條語句中聲明多個變量。因為一條聲明語句只能有一個基本數據類型,所以該語句中所有變量的初始基本數據類型必須一樣:
auto i = 0, *p = &i; // 正確:i是整數,p是整型指針 auto sz=0, pi = 3.14; // 錯誤:sz和pi的類型不一致auto一般會忽略掉頂層const,同時底層const會保留下來。如果希望推斷出的auto類型是一個頂層const,需要明確指出:const auto f = ci; // ci的推演類型是int,f是const int。
decltype類型指示符
有時候希望從表達式的類型推斷出要定義的變量的類型,但不想用該表達式的值初始化變量。為了滿足這要求,C++11引入了第二種類型說明符decltype,其作用是選擇并返回操作數的數據類型。如:
decltype(f()) sum = x; // sum的類型就是函數f的返回類型decltype處理頂層const和引用的方式與auto有些不同。如果其使用的表達式是一個變量,則會返回該變量的類型,包括頂層const和引用在內:
如果decltype使用的表達式不是一個變量,則返回表達式結果對應的類型。如果表達式的內容是解引用操作,如decltype(*p) c;,則返回的是引用類型。
注意:如果decltype的表達式加上了括號,即如decltype((i));,其得到的結果是引用類型。
總結
以上是生活随笔為你收集整理的C++知识总结(1)--变量和基本类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中文文本分类
- 下一篇: 用心整理,1000行MySQL命令,很实