天天看點

C++知識總結(1)--變量和基本類型

最近打算看看《C++ primer》,重新複習C++的一些知識點,同時會添加部分在做牛客網程式設計題目時候記錄的知識點。

變量和基本類型

  • endl

    操縱符的效果是結束目前行,并将與裝置關聯的緩沖區中的内容刷到裝置中。緩沖重新整理操作可以保證到目前為止程式所産生的所有輸出都真正寫入輸出流中,而不是僅停留在記憶體中等待寫入流。
  • 注釋界定符不能嵌套。它是以

    /*

    開始,以

    */

    結束的。是以,一個注釋不能嵌套在另一個注釋之内。

基本類型

算術類型

算術類型所能表示的資料範圍如下:

C++知識總結(1)--變量和基本類型

基本的字元類型是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類型會表現為這兩種形式中的一種,具體是由編譯器決定具體形式。

具體類型的選擇建議如下:

C++知識總結(1)--變量和基本類型
類型轉換

類型轉換的過程如下:

注意,不能混合使用有符号數和無符号數,如果一個表達式中即包含有符号數和無符号數,那麼有符号數會轉換成無符号數來進行計算,如果這個有符号數還是負數,那麼會得到異常結果。如下例子所示:

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++語言規定的轉義序列包括:

C++知識總結(1)--變量和基本類型

轉義序列被當做一個字元使用。

對于字面值類型可以通過添加一些字首和字尾來改變其預設類型,如下例子所示:

注意 ,指定一個長整型字面值時使用大寫字母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和引用在内:

繼續閱讀