C++基礎:變量和基本類型

基本內置類型

一、基本內置類型

       C++定義了一套包括算術類型(arithmetic type) 和空類型(void) 在內的基本數據類型。其中算術類型包含了字符、整型數、布爾值和浮點數。空類型不對應具體的值,僅用於一些特殊的場合,例如最常見的是,當函數不返回任何值時使用空類型作爲返回類型。

二、算術類型

       算術類型分爲兩類:整型(integral type,包括字符布爾類型在內)和浮點型

帶符號類型和無符號類型
       除去布爾型和擴展的字符型之外,其他整型可以劃分爲帶符號的(signed)無符號的(unsigned) 兩種。帶符號類型可以表示正數、負數或0無符號類型則僅能表示大於等於0的值
       類型int、short、 long 和long long 都是帶符號的,通過在這些類型名前添加unsigned就可以得到無符號類型,例如unsigned long。 類型unsigned int 可以縮寫爲unsigned。
       與其他整型不同,字符型被分爲了三種: char、signed char和unsigned char。特別需要注意的是:類型char和類型signed char並不一樣。類型char實際上會表現爲上述兩種形式中的一種,具體是哪種由編譯器決定。

建議:如何選擇類型

       和C語言一樣,C++的設計準則之一也是儘可能地接近硬件。C++的算術類型必須滿足各種硬件特質,所以它們常常顯得繁雜而令人不知所措。事實上,大多數程序員能夠(也應該)對數據類型的使用做出限定從而簡化選擇的過程。以下是選擇類型的一些經驗準則:

  • 當明確知曉數值不可能爲負時,選用無符號類型
  • 使用int執行整數運算。在實際應用中,short常常顯得太小而long一般和int有一樣的尺寸。如果你的數值超過了int的表示範圍,選用long long
  • 算術表達式中不要使用 char或bool只有在存放字符或布爾值時才使用它們。因爲類型char在一些機器上是有符號的,而在另一些機器上又是無符號的,所以如果使用char進行運算特別容易出問題。如果你需要使用一個不大的整數,那麼明確指定它的類型是signed char 或者unsigned char。
  • 執行浮點數運算選用double,這是因爲float通常精度不夠而且雙精度浮點數和單精度浮點數的計算代價相差無幾。事實上,對於某些機器來說,雙精度運算甚至比單精度還快。long double 提供的精度在一般情況下是沒有必要的,況且它帶來的運行時消耗也不容忽視。

三、類型轉換

       對象的類型定義了對象能包含的數據和能參與的運算,其中- -種運算被大多數類型支持,就是將對象從一-種給定的類型轉換(convert)爲另一種相關類型。

  • 當我們把一個非布爾類型的算術值賦給布爾類型時初始值爲0則結果爲false,否則結果爲true
  • 當我們把一個布爾值賦給非布爾類型時,初始值爲false則結果爲0,初始值爲true則結果爲1。
  • 當我們把一個浮點數賦給整數類型時,進行了近似處理。結果值將僅保留浮點數中小數點之前的部分。
  • 當我們把一個整數值賦給浮點類型時,小數部分記爲0。如果該整數所佔的空間超過了浮點類型的容量,精度可能有損失。
  • 當我們賦給無符號類型一個超出它表示範圍的值時,結果是初始值對無符號類型表示數值總數取模後的餘數。例如,8比特大小的unsigned char 可以表示0至255區間內的值,如果我們賦了一個區間以外的值,則實際的結果是該值對256取模後所得的餘數。因此,把-1賦給8比特大小的unsigned char 所得的結果是255。
  • 當我們賦給帶符號類型一個超出它表示範圍的值時,結果是未定義的(undefined)。此時,程序可能繼續工作、可能崩潰,也可能生成垃圾數據。
     

提示:切勿混用帶符號類型和無符號類型
        如果表達式裏既有帶符號類型又有無符號類型,當帶符號類型取值爲負時會出現異常結果,這是因爲帶符號數會自動地轉換成無符號數。例如,在一個形如a*b的式子中,如果a=-1,b=1,而且a和b都是int,則表達式的值顯然爲-1。然而,如果a是int,而b是unsigned,則結果須視在當前機器上int所佔位數而定。在我們的環境裏,結果是4294967295。

四、字面值常量

       一個形如42的值被稱作字面值常量(literal),這樣的值一望而知。每個字面值常量都對應一種數據類型, 字面值常量的形式和值決定了它的數據類型。
1、整型和浮點型字面值
       我們可以將整型字面值寫作十進制數、八進制數或十六進制數的形式。以0開頭的整數代表八進制數,以0x或0X開頭的代表十六進制數。例如,我們能用下面的任意一種形式來表示數值20:
                                                   20 /*十進制*/                     024/*八進制*/                      0x14 /* 十六進制*/

2、字符和字符串字面值
       由單引號括起來的一個字符稱爲char型字面值,雙引號括起來的零個或多個字符則構成字符串型字面值
                                                            'a'    //字符字面值                     "Hello World!" // 字符串字面值

3、轉義序列

4、指定字面值的類型
通過添加如下表中所列的前綴和後綴,可以改變整型、浮點型和字符型字面值的默認類型。

       對於一個整型字面值來說,我們能分別指定它是否帶符號以及佔用多少空間。如果後綴中有U,則該字面值屬於無符號類型,也就是說,以U爲後綴的十進制數、八進制數或十六進制數都將從unsigned int、unsigned long和unsigned long long中選擇能匹配的空間最小的一個作爲其數據類型。如果後綴中有L,則字面值的類型至少是long;如果後綴中有LL,則字面值的類型將是long long和unsigned long long中的一種。顯然我們可以將U與L或LL合在一起使用。例如,以UL爲後綴的字面值的數據類型將根據具體數值情況或者取unsigned long,或者取unsigned long long。

  • L' a'            //寬字符型字面值,類型是wchar_ t
  • u8"hi!"        // utf-8字符串字面值(utf-8用8位編碼一個Unicode字符)
  • 42ULL        //無符號整型字面值,類型是unsigned long long
  • 1E- 3F        //單精度浮點型字面值,類型是float
  • 3.14159L    //擴展精度浮點型字面值,類型是long double

5、布爾字面值和指針字面值

true和false是布爾類型的字面值:bool test = false;

nullptr是指針字面值。

變量

一、變量

       變量提供一個具名的、可供程序操作的存儲空間。C++中的每個變量都有其數據類型,數據類型決定着變量所佔內存空間的大小和佈局方式、該空間能存儲的值的範圍,以及變量能參與的運算。對C++程序員來說,“變量(variable)” 和“對象( object)”一般可以互換使用。

二、變量定義

☆ 注意:初始化不是賦值,初始化的含義是創建變量時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,而以一個新值來替代。
1、列表初始化
       C++語言定義了初始化的好幾種不同形式,這也是初始化問題複雜性的一個體現。 例如,要想定義一個名爲units_ sold 的int變量並初始化爲0,以下的4條語句都可以做到這一點:
int units_ sold = 0;
int units_ sold = {0} ;
int units_ sold{0};
int units_ sold(0) ;

2、默認初始化
       如果定義變量時沒有指定初值,則變量被默認初始化(default initialized), 此時變量被賦予了“默認值”。默認值到底是什麼由變量類型決定,同時定義變量的位置也會對此有影響。
       建議初始化每一個內置類型的變量。雖然並非必須這麼做,但如果我們不能確保初始化後程序安全,那麼這麼做不失爲一種簡單可靠的方法。

三、變量聲明和定義的關係

       爲了允許把程序拆分成多個邏輯部分來編寫,C++語言支持分離式編譯( separate compilation)機制,該機制允許將程序分割爲若千個文件,每個文件可被獨立編譯。
       爲了支持分離式編譯,C++語言將聲明和定義區分開來。聲明(declaration) 使得名字爲程序所知,一個文件如果想使用別處定義的名字則必須包含對那個名字的聲明。而定義(definition)負責創建與名字關聯的實體。

extern int i;                                 // 聲明i而非定義 i
int j;                                            // 聲明並定義 j
extern double pi =3.1416;          // 定義

      變量能且只能被定義一次,但是可以被多次聲明。
      聲明和定義的區別看起來也許微不足道,但實際上卻非常重要。如果要在多個文件中使用同-一個變量,就必須將聲明和定義分離。此時,變量的定義必須出現在且只能出現在一個文件中,而其他用到該變量的文件必須對其進行聲明,卻絕對不能重複定義。

四、標識符

       C++的標識符(identifier) 由字母、數字和下畫線組成,其中必須以字母或下畫線開頭。標識符的長度沒有限制,但是對大小寫字母敏感。

變量命名有許多約定俗成的規範,下面的這些規範能有效提高程序的可讀性:

  1. 標識符要能體現實際含義。
  2. 變量名一般用小寫字母,如index,不要使用Index或INDEX。
  3. 用戶自定義的類名一般以大寫字母開頭,如Sales_ item。
  4. 如果標識符由多個單詞組成,則單詞間應有明顯區分,如student_ loan或studentLoan,不要使用studentloan。
     

五、名字的作用域

       作用域(scope)是程序的一部分, 在其中名字有其特定的含義。C++語言中大多數作用域都以花括號分隔。

       名字main定義於所有花括號之外,它和其他大多數定義在函數體之外的名字-樣擁有全局作用域( global scope)。 一旦聲明之後,全局作用域內的名字在整個程序的範圍內都可使用。

       名字sum定義於main函數所限定的作用域之內,從聲明sum開始直到main函數結束爲止都可以訪問它,但是出了main函數所在的塊就無法訪問了,因此說變量sum擁有塊作用域(block scope)。 名字val定義於for語句內,在for語句之內可以訪問val,但是在main函數的其他部分就不能訪問它了。

       作用域能彼此包含,被包含(或者說被嵌套)的作用域稱爲內層作用域(innerscope),包含着別的作用域的作用域稱爲外層作用域(outer scope)。作用域中一旦聲明瞭某個名字,它所嵌套着的所有作用域中都能訪問該名字。同時,允許在內層作用域中重新定義外層作用域已有的名字。
      !!!如果函數有可能用到某全局變量,則不宜再定義一個同名的局部變量。

複合類型

一、複合類型

       複合類型(compound type)是指基於其他類型定義的類型。C++語言有幾種複合類型,將介紹其中的兩種:引用和指針

二、引用

       引用(reference) 爲對象起了另外一個名字,引用類型引用(refers to)另外一種類型。通過將聲明符寫成&d的形式來定義引用類型,其中d是聲明的變量名:
int ival = 1024;
int &refVal = ival;      // refVal指向ival (是ival的另一個名字)
int &refVal2;              //報錯:引用必須被初始化

       引用並非對象,相反的,它只是爲一個已經存在的對象所起的另外一個名字。

1、引用的定義
允許在一條語句中定義多個引用,其中每個引用標識符都必須以符號&開頭:

inti=1024,i2=2048;          //i和i2都是int
int&r=i,r2=i2;               //r是一個引用,與i綁定在一起,r2是int
int i3 = 1024,&ri = i3;     //i3是int, ri是一個引用,與i3綁定在一起
int&r3=i3,&r4=i2;            //r3和r4都是引用

有引用的類型都要和與之綁定的對象嚴格匹配。而且,引用只能綁定在對象上,而不能與字面值或某個表達式的計算結果綁定在一起。

int &refVa14 = 10;     錯誤:引用類型的初始值必須是一個對象
double dval = 3.14;
int &refVal5 = dval;   錯誤:此處引用類型的初始值必須是int型對象

三、指針

       指針(pointer) 是“指向(point to)” 另外一一種類型的複合類型。

       與引用類似,指針也實現了對其他對象的間接訪問。然而指針與引用相比又有很多不同點。

  • 其一,指針本身就是一個對象,允許對指針賦值和拷貝,而且在指針的生命週期內它可以先後指向幾個不同的對象。
  • 其二,指針無須在定義時賦初值。和其他內置類型一樣, 在塊作用域內定義的指針如果沒有被初始化,也將擁有-一個不確定的值。
     

       定義指針類型的方法將聲明符寫成*d的形式,其中d是變量名。如果在一條語句中定義了幾個指針變量,每個變量前面都必須有符號*:
int*ip1,*ip2;         //  ipl和ip2都是指向int型對象的指針
double dp, *dp2;    // dp2是指向double型對象的指針,dp是double型對象

1、獲取對象的地址
        指針存放某個對象的地址,要想獲取該地址,需要使用取地址符( 操作符&)
int ival = 42;
int *p = &ival;    // p存放變量ival的地址,或者說p是指向變量ival的指針
       第二條語句把p定義爲一個指向int的指針,隨後初始化p令其指向名爲ival的int對象。因爲引用不是對象,沒有實際地址,所以不能定義指向引用的指針。

double dval;
double *pd = &dval;    正確:初始值是double型對象的地址
double *pd2 = pd;      正確:初始值是指向double對象的指針

int *pi = pd;          錯誤:指針pi的類型和pd的類型不匹配
pi = &dval;            錯誤:試圖把double型對象的地址賦給int型指針

2、指針值
指針的值(即地址)應屬下列4種狀態之一:

  1. 指向一個對象。
  2. 指向緊鄰對象所佔空間的下一個位置。
  3. 空指針,意味着指針沒有指向任何對象。
  4. 無效指針,也就是上述情況之外的其他值。
     

3、利用指針訪問對象
如果指針指向了一個對象,則允許使用解引用符(操作符*)來訪問該對象:

int ival = 42;
int *p = &ival;    // p存放着變量ival的地址,或者說p是指向變量ival的指針
cout << *p;        // 由符號*得到指針p所指的對象,輸出42

對指針解引用會得出所指的對象,因此如果給解引用的結果賦值,實際上也就是給指針所指的對象賦值:

*p=0;         // 由符號*得到指針p所指的對象,即可經由p爲變量ival賦值
cout << *p;   // 輸出0

如上述程序所示,爲*p賦值實際上是爲p所指的對象賦值。

int i= 42;
int &r=i;    // &緊隨類型名出現,因此是聲明的一部分,r是一個引用
int *p;      // *緊隨類型名出現,因此是聲明的一部分,p是一個指針
P= &i;       // &出現在表達式中,是一個取地址符
*p=i;        // *出現在表達式中,是一個解引用符
int &r2=p;   // &是聲明的一部分,*是一個解引用符

4、空指針
       空指針(null pointer)不指向任何對象,在試圖使用一個指針之前代碼可以首先檢查它是否爲空。以下列出幾個生成空指針的方法:

int *pl = nullptr;    等價於int*p1=0;
int *p2 = 0;
直接將p2初始化爲字面常量0
需要首先#include cstdlib
int *p3 = NULL;
等價於int *p3 = 0;

       得到空指針最直接的辦法就是用字面值nullptr來初始化指針,這也是C++11新標準剛剛引入的一種方法。 nullptr是一種特殊類型的字面值,它可以被轉換成任意其他的指針類型。

建議:初始化所有指針
       使用未經初始化的指針是引發運行時錯誤的一大原因。和其他變量一樣,訪問未經初始化的指針所引發的後果也是無法預計的。通常這一行爲將造成程序崩潰,而且一旦崩潰,要想定位到出錯位置將是特別棘手的問題。在大多數編譯器環境下,如果使用了未經初始化的指針,則該指針所佔內存空間的當前內容將被看作一個地址值。訪問該指針,相當於去訪問一個本不存在的位置上的本不存在的對象。糟糕的是,如果指針所佔內存空間中恰好有內容,而這些內容又被當作了某個地址,我們就很難分清它到底是合法的還是非法的了。因此建議初始化所有的指針,並且在可能的情況下,儘量等定義了對象之後再定義指向它的指針。如果實在不清楚指針應該指向何處,就把它初始化爲nullptr或者0,這樣程序就能檢測並知道它沒有指向任何具體的對象了。

5、賦值和指針
       指針和引用都能提供對其他對象的間接訪問,其中最重要的一點就是引用本身並非一個對象。 一旦定義了引用,就無法令其再綁定到另外的對象,之後每次使用這個引用都是訪問它最初綁定的那個對象。
       指針和它存放的地址之間就沒有這種限制了。和其他任何變量(只要不是引用)一樣,給指針賦值就是令它存放一個新的地址從而指向一個新的對象

inti=42;
int *pi = 0;    //pi被初始化,但沒有指向任何對象
int *pi2 = &i;  //pi2被初始化,存有i的地址
int *pi3;       //如果pi3定義於塊內,則pi3的值是無法確定的
pi3 = pi2;      //pi3和pi2指向同一個對象i
pi2 = 0;        //現在pi2不指向任何對象了

6、其他指針操作
只要指針擁有一個合法值,就能將它用在條件表達式中。和採用算術值作爲條件,遵循的規則類似,如果指針的值是0,條件取false:

int ival = 1024;
int *pi = 0;      // pi合法,是一個空指針
int *pi2 = &ival ;// pi2是一個合法的指針,存放着ival的地址
if (pi)   // pi的值是0,因此條件的值是false
// ...
if (pi2)  // pi2指向ival, 因此它的值不是0,條件的值是true

任何非0指針對應的條件值都是true。

7、void*指針
       void*是一種特殊的指針類型,可用於存放任意對象的地址。一個void*指針存放着一個地址,這一點和其他指針類似。不同的是,我們對該地址中到底是個什麼類型的對象並不瞭解:

double obj = 3.14, *pd = &obj;//正確:void*能存放任意類型對象的地址
void *pv = &obj;  //obj可以是任意類型的對象
PV = pd;          //pv可以存放任意類型的指針

       利用void*指針能做的事兒比較有限:拿它和別的指針比較、作爲函數的輸入或輸出,或者賦給另外一個void* 指針不能直接操作void* 指針所指的對象,因爲我們並不知道這個對象到底是什麼類型,也就無法確定能在這個對象上做哪些操作。

四、理解複合類型的聲明

定義語句中,雖然基本數據類型只有-一個,但是聲明符的形式卻可以不同。也就是說,一條定義語句可能定義出不同類型的變量:

// i是一個int型的數,p是一個int型指針,r是一個int型引用
inti=1024,*p=&i,&r=i;

1、指向指針的指針
      通過*的個數可以區分指針的級別。也就是說,** 表示指向指針的指針,*** 表示指向指針的指針的指針,以此類推:

int ival = 1024;
int *pi = &ival; // pi指向一個int型的數
int **ppi = &pi; // ppi 指向一個int型的指針

此處pi是指向int型數的指針,而ppi是指向int型指針的指針,下圖描述了它們之間的關係。

cout << "The value of ival\n"
     << "direct value: " << ival << "\n"
     << "indirect value: " << *pi << "\n"
     << "doubly indirect value: " << **ppi
     << endl ;

該程序使用三種不同的方式輸出了變量ival的值:

  • 第一種直接輸出;
  • 第二種通過int型指針pi輸出;
  • 第三種兩次解引用ppi,取得ival的值。
     

2、指向指針的引用
引用本身不是一個對象,因此不能定義指向引用的指針。但指針是對象,所以存在對指針的引用:

inti=42;
int *p;         p是一個int型指針
int *&r = p;    r是一個對指針p的引用
r=&i;           r引用了一個指針,因此給r賦值&i就是令p指向i
*r=0;           解引用r得到i,也就是p指向的對象,將i的值改爲0

面對一條比較複雜的指針或引用的聲明語句時,從右向左閱讀有助於弄清楚它的真實含義

const限定符

一、const限定符

1、初始化和const

inti=42;
const int ci = i;//正確: i的值被拷貝給了ci
intj=ci;         //正確: ci的值被拷貝給了j

2、默認狀態下,const對象僅在文件內有效

如果想在多個文件之間共享const對象,必須在變量的定義之前添加extern關鍵字。

二、const的引用

       可以把引用綁定到const對象上,就像綁定到其他對象上一樣,我們稱之爲對常量的引用(reference to const)。與普通引用不同的是,對常量的引用不能被用作修改它所綁定的對象:

const int ci = 1024;
const int &r1 = ci;      //正確:引用及其對應的對象都是常量
r1 = 42;                 //錯誤: r1是對常量的引用
int &r2 = ci;            //錯誤:試圖讓一個非常量引用指向一個常量對象

1、初始化和對const的引用

inti=42;●
const int &r1 = i;   允許將const int&綁定到一個普通int對象上
const int &r2 = 42;  正確: r1是一個常量引用
constint&r3=r1*2;    正確: r3是一個常量引用
int&r4=r1★2;        錯誤: r4是一個普通的非常量引用,注意這裏和普通引用的區別

2、對const的引用可能引用一個並非const的對象
       必須認識到,常量引用僅對引用可參與的操作做出了限定,對於引用的對象本身是不是一個常量未作限定。因爲對象也可能是個非常量,所以允許通過其他途徑改變它的值:

inti=42;
int &r1 = i;          引用ri綁定對象i
const int &r2 = i;    r2也綁定對象i,但是不允許通過r2修改i的值
r1=0;                 rl並非常量,i的值修改爲0
r2=0;                 錯誤: r2是一個常量引用

三、指針和const

       與引用一樣,也可以令指針指向常量或非常量。類似於常量引用,指向常量的指針(pointer to const)不能用於改變其所指對象的值。要想存放常量對象的地址,只能使用指向常量的指針:

const double pi = 3.14;    // pi是個常量,它的值不能改變
double *ptr = &pi;         // 錯誤: ptr是一個普通指針
const double *cptr = &pi;  // 正確: cptr可以指向一個雙精度常量
*cptr = 42;                // 錯誤:不能給*cptr賦值

       指針的類型必須與其所指對象的類型一致, 但是有兩個例外。第一種例外情況是允許令一個指向常量的指針指向一個非常量對象:

double dval = 3.14;  // dval是一個雙精度浮點數,它的值可以改變
cptr = &dval;        // 正確:但是不能通過cptr改變dval的值

       和常量引用一樣,指向常量的指針也沒有規定其所指的對象必須是一個常量。所謂指向常量的指針僅僅要求不能通過該指針改變對象的值,而沒有規定那個對象的值不能通過其他途徑改變。
       TIP試試這樣想吧:所謂指向常量的指針或引用,不過是指針或引用“自以爲是”罷了,它們覺得自己指向了常量,所以自覺地不去改變所指對象的值。
1、const指針
       指針是對象而引用不是,因此就像其他對象類型一樣,允許把指針本身定爲常量。常量指針(const pointer)必須初始化,而且一旦初始化完成,則它的值(也就是存放在指針中的那個地址)就不能再改變了。把*放在const關鍵字之前用以說明指針是一個常量,這樣的書寫形式隱含着一層意味,即不變的是指針本身的值而非指向的那個值:

int errNumb = 0;
int *const curErr = &errNumb ;     // curErr 將一直指向errNumb
const double pi = 3.14159;
const double *const pip = &pi;     // pip是一個指向常量對象的常量指針

       指針本身是一個常量並不意味着不能通過指針修改其所指對象的值,能否這樣做完全依賴於所指對象的類型。例如,pip是一個指向常量的常量指針,則不論是pip所指的對象值還是pip自己存儲的那個地址都不能改變。相反的,curErr指向的是一一個一般的非常量整數,那麼就完全可以用curErr去修改er rNumb的值。

四、頂層const 

       如前所述,指針本身是一個對象,它又可以指向另外一個對象。因此,指針本身是不是常量以及指針所指的是不是一個常量就是兩個相互獨立的問題。用名詞頂層const(top-level const)表示指針本身是個常量,而用名詞底層const (low-level const)表示指針所指的對象是一個常量。
       更一般的,頂層const可以表示任意的對象是常量,這一點對任何數據類型都適用,如算術類型、類、指針等。底層const則與指針和引用等複合類型的基本類型部分有關。比較特殊的是,指針類型既可以是頂層const也可以是底層const,,這一點和其他類型相比區別明顯:

inti=0;
int *const p1 = &i;  //不能改變pl的值,這是一個頂層const
const int ci = 42;   //不能改變ci的值,這是一個頂層const
const int *p2 = &ci; //允許改變p2的值,這是一個底層const
const int *const p3 = p2; //靠右的const是頂層const,靠左的是底層const
const int &r = ci;        //用於聲明引用的const都是底層const

        當執行對象的拷貝操作時,常量是頂層const還是底層const區別明顯。其中,頂層const不受什麼影響:

i=ci;   //正確:拷貝ci的值,ci是一個頂層const,對此操作無影響
p2 = p3;//正確: p2和p3指向的對象類型相同,p3頂層const的部分不影響

       另一方面,底層const的限制卻不能忽視。當執行對象的拷貝操作時,拷入和拷出的對象必須具有相同的底層const資格,或者兩個對象的數據類型必須能夠轉換。一般來說,非常量可以轉換成常量,反之則不行:

int *p = p3;       //錯誤: p3包含底層const的定義,而p沒有
p2 = p3;           //正確:p2和p3都是底層
const p2 = &i;     //正確: int*能轉換成const int*
int &r = ci;       //錯誤:普通的int&不能綁定到int常量上
const int &r2 = i; //正確: const int&可以綁定到一個普通int上

      p3既是頂層const也是底層const,拷貝p3時可以不在乎它是-一個頂層const,但是必須清楚它指向的對象得是一一個常量。因此,不能用p3去初始化p,因爲p指向的是一個普通的(非常量)整數。另-方面,p3的值可以賦給p2,是因爲這兩個指針都是底層const,儘管p3同時也是一個常量指針(頂層const), 僅就這次賦值而言不會有什麼影響。

五、constexpr和常量表達式

       常量表達式( const expression)是指值不會改變並且在編譯過程就能得到計算結果的表達式。顯然,字面值屬於常量表達式,用常量表達式初始化的const對象也是常量表達式。後面將會提到,C++語言中有幾種情況下是要用到常量表達式的。
       一個對象(或表達式)是不是常量表達式由它的數據類型和初始值共同決定,例如:

const int max_ files = 20;        // max_files是常量表達式
const int limit = max_ files + 1; // limit 是常量表達式
int staff_ size = 27;             // staff_ size不是常量表達式
const int sz = get_ size();       // sz不是常量表達式.

1、constexpr變量
       在一個複雜系統中,很難(幾乎肯定不能)分辨一個初始值到底是不是常量表達式。當然可以定義一個const變量並把它的初始值設爲我們認爲的某個常量表達式,但在實.際使用時,儘管要求如此卻常常發現初始值並非常量表達式的情況。
       C++11新標準規定,允許將變量聲明爲constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式。聲明爲constexpr的變量一定是一個常量,而且必須用常量表達式初始化:

constexpr int mf = 20;         // 20是常量表達式
constexpr int limit = mf + 1;  // mf +1是常量表達式
constexpr int sZ = size() ;
//只有當size是一個constexpr函數時
//纔是一條正確的聲明語句

      一般來說,如果你認定變量是一個常量表達式,那就把它聲明成constexpr類型。

2、字面值類型

       常量表達式的值需要在編譯時就得到計算,因此對聲明constexpr時用到的類型必須有所限制。因爲這些類型一般比較簡單, 值也顯而易見、容易得到,就把它們稱爲“字面值類型”(literal type)。
3、指針和constexpr
       必須明確一點,在constexpr聲明中如果定義了-一個指針,限定符constexpr僅對指針有效,與指針所指的對象無關:

const int *P = nullptr;          // p是一個指向整型常量的指針
constexpr int *q = nullptr;      // q是一個指向整數的常量指針

       p和q的類型相差甚遠,p是一個指向常量的指針,而q是一個常量指針,其中的關鍵在於constexpr把它所定義的對象置爲了頂層const。
       與其他常量指針類似,constexpr指針既可以指向常量也可以指向一個非常量:

constexpr int *np = nullptr; // np是一個指向整數的常量指針,其值爲空
intj=0;
constexpr int i = 42;        // i的類型是整型常量
// i和j都必須定義在函數體之外
constexpr const int *p = &i; // p是常量指針,指向整型常量i
constexpr int *p1 = &j;      // p1是常量指針,指向整數j

處理類型

一、類型別名

類型別名(type alias)是一個名字,它是某種類型的同義詞。

有兩種方法可用於定義類型別名。

① 傳統的方法是使用關鍵字typedef:

typedef double wages;   //wages 是double的同義詞
typedef wages base, *p; //base 是double的同義詞,p是double*的同義詞

       其中,關鍵字typedef作爲聲明語句中的基本數據類型的一部分出現。含有typedef的聲明語句定義的不再是變量而是類型別名。和以前的聲明語句一樣,這裏的聲明符也可以包含類型修飾,從而也能由基本數據類型構造出複合類型來。
② 新標準規定了一種新的方法,使用別名聲明( alias declaration)來定義類型的別名:

using SI = Sales item; // SI是Sales item 的同義詞

二、auto類型說明符

        C++11新標準引入了auto類型說明符,用它就能讓編譯器替我們去分析表達式所屬的類型。和原來那些只對應一種特定類型的說明符(比如double)不同,auto 讓編譯器通過初始值來推算變量的類型。顯然,auto 定義的變量必須有初始值

//由vall和val2相加的結果可以推斷出item的類型
auto item = val1 + val2; // item 初始化爲vall和val2相加的結果
autoi=0,*p=&i;          //正確: i是整數、p是整型指針
autosz=0,pi=3.14;       //錯誤:sz和pi的類型不--.致

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章