const in C++

一 關於通常常量

聲明或定義的格式以下:數組

const <類型說明符> <變量名> = <常量或常量表達式>; [1]
<類型說明符> const <變量名> = <常量或常量表達式>; [2]
[1]和[2]的定義是徹底等價的。

例如:函數

整形int(或其餘內置類型:float,double,char)this

const int bufSize = 512;

或者編碼

int const bufSize = 512;

由於常量在定義後就不能被修改,因此定義時必須初始化。spa

bufSize = 128;   // error:attempt to write to const object
const string cntStr = "hello!"; // ok:initialized
const i, j = 0; // error: i is uninitialized const

非const變量默認爲extern。指針

const 對象默認爲文件的局部變量。要使const變量可以在其餘的文件中訪問,必須顯式地指定它爲extern。code

例如:對象

const int bufSize = 512;        // 做用域只限於定義此變量的文件
extern const int bufSize = 512; // extern用於擴大做用域,做用域爲整個源程序(只有extern 位於函數外部時,才能夠含有初始化式)

二 關於數組及結構體

聲明或定義的格式以下:內存

const <類型說明符> <數組名>[<大小>]…… [1]
<類型說明符> const <數組名>[<大小>]…… [2]
[1]和[2]的定義是徹底等價的。

例如:作用域

整形int(或其餘內置類型:float,double,char)

const int cntIntArr[] = {1,2,3,4,5};

或者

int const cntIntArr[] = {1,2,3,4,5};
struct SI
{
    int i1;
    int i2;
};
const SI s[] = {{1,2},{3,4}};
// 上面的兩個const都是變量集合,編譯器會爲其分配內存,因此不能在編譯期間使用其中的值(例如:int temp[cntIntArr[2]],這樣的話編譯器會報告不能找到常量表達式)

三 關於引用

聲明或定義的格式以下:

const <類型說明符> &<變量名> = …… [1]
<類型說明符> const &<變量名> = …… [2]
[1]和[2]的定義是徹底等價的。

例如:

const int i = 128;
const int &r = i;(或者 int const &r = i;)

const 引用就是指向const 對象的引用。

普通引用不能綁定到const 對象,但const 引用能夠綁定到非const 對象。

const int ii = 456;
int &rii = ii; // error
int jj = 123;
const int &rjj = jj; // ok

非const 引用只能綁定到與該引用同類型的對象。

const 引用則能夠綁定到不一樣但相關的類型的對象或綁定到右值。

例如:

1.const int &r = 100;     // 綁定到字面值常量
2.int i = 50;
const int &r2 = r + i; // 引用r綁定到右值
3.double dVal = 3.1415;
const int &ri = dVal;   // 整型引用綁定到double 類型

編譯器會把以上代碼轉換成以下形式的編碼:

int temp = dVal;      // create temporary int from double
const int &ri = temp; // bind ri to that temporary

四 關於指針

1.指向const 對象的指針(指針所指向的內容爲常量)

聲明或定義的格式以下(定義時能夠不初始化):

const <類型說明符> *<變量名> …… [1]
<類型說明符> const *<變量名> …… [2]
[1]和[2]的定義是徹底等價的。

例如:

const int i = 100;
const int *cptr = &i;

或者

int const *cptr = &i; [cptr 是指向int 類型的const 對象的指針]
容許把非const 對象的地址賦給指向const 對象的指針,例如:
double dVal = 3.14;          // dVal is a double; its value can be change
const double *cdptr = &dVal; // ok;but can't change dVal through cdptr

不能使用指向const 對象的指針修改基礎對象。然而若是該指針指向的是一個沒const 對象(如cdptr),可用其餘方法修改其所指向的對象。

如何將一個const 對象合法地賦給一個普通指針???

例如:

const double dVal = 3.14;
double *ptr = &dVal; // error
double *ptr = const_cast<double*>(&dVal);
// ok: const_cast是C++中標準的強制轉換,C語言使用:double *ptr = (double*)&dVal;

2.const 指針(指針自己爲常量)

聲明或定義的格式以下(定義時必須初始化):

<類型說明符> *const <變量名> = ……

例如:

int errNumb = 0;
int iVal = 10;
int *const curErr = &errNumb; [curErr 是指向int 型對象的const 指針]
指針的指向不能被修改。
curErr = &iVal; // error: curErr is const
指針所指向的基礎對象能夠修改。
*curErr = 1; // ok:reset value of the object(errNumb) which curErr is bind

3.指向const 對象的const 指針(指針自己和指向的內容均爲常量)

聲明或定義的格式以下(定義時必須初始化):

const <類型說明符> *const <變量名> = ……

例如:

const double pi = 3.14159;
const double dVal = 3.14;
const double *const pi_ptr = &pi; [pi_ptr 是指向double 類型的const 對象的const 指針]
指針的指向不能被修改。
pi_ptr = &dVal; // error: pi_ptr is const
指針所指向的基礎對象也不能被修改。
*pi_ptr = dVal; // error: pi is const

五 關於通常函數

1.修飾函數的參數

class A;
void func1(const int i); // i不能被修改
void func3 (const A &rA);        // rA所引用的對象不能被修改
void func2 (const char *pstr); // pstr所指向的內容不能被修改

2.修飾函數的返回值

返回值:const int func1(); // 此處返回int 類型的const值,意思指返回的原函數裏的變量的初值不能被修改,可是函數按值返回的這個變量被製成副本,能不能被修改就沒有了意義,它能夠被賦給任何的const或非const類型變量,徹底不須要加上這個const關鍵字。
[*注意*]但這隻對於內部類型而言(由於內部類型返回的確定是一個值,而不會返回一個變量,不會做爲左值使用,不然編譯器會報錯),對於用戶自定義類型,返回值是常量是很是重要的(後面在類裏面會談到)。

返回引用:const int &func2(); // 注意千萬不要返回局部對象的引用,不然會報運行時錯誤:由於一旦函數結束,局部對象被釋放,函數返回值指向了一個對程序來講再也不有效的內存空間。

返回指針:const int *func3(); // 注意千萬不要返回指向局部對象的指針,由於一旦函數結束,局部對象被釋放,返回的指針變成了指向一個再也不存在的對象的懸垂指針。

六 關於類

class A
{
public:
    void func();
    void func() const;
    const A operator+(const A &) const;
private:
    int num1;
    mutable int num2;
    const size_t size;
};

1.修飾成員變量

const size_t size; // 對於const的成員變量,
[1]必須在構造函數裏面進行初始化;
[2]只能經過初始化成員列表來初始化;
[3]試圖在構造函數體內對const成員變量進行初始化會引發編譯錯誤。

例如:

A::A(size_t sz):size(sz) // ok:使用初始化成員列表來初始化
{
}
A::A(size_t sz)


2.修飾類成員函數

void func() const; // const成員函數中不容許對數據成員進行修改,若是修改,編譯器將報錯。若是某成員函數不須要對數據成員進行修改,最好將其聲明爲const 成員函數,這將大大提升程序的健壯性。

const 爲函數重載提供了一個參考

class A
{
public:
    void func();        // [1]:一個函數
    void func() const; // [2]:上一個函數[1]的重載
    ……
};
A a(10);
a.func(); // 調用函數[1]
const A b(100);
b.func(); // 調用函數[2]

如何在const成員函數中對成員變量進行修改???

下面提供幾種方式(只提倡使用第一種,其餘方式不建議使用)

(1)標準方式:mutable

class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i){ m_data = i; }
private:
    mutable int m_data; // 這裏處理
};


(2)強制轉換:static_cast

class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { static_cast<int>(m_data) = i; } // 這裏處理
private:
    int m_data;
};

(3)強制轉換:const_cast

class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { const_cast<A*>(this)->m_data = i; } // 這裏處理
private:
    int m_data;
};

(4)使用指針:int *

class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { *m_data = i; } // 這裏處理
private:
    int *m_data;
};

(5)未定義的處理方式

class A
{
public:
    A::A(int i):m_data(i){}
    void SetValue(int i)
    { int *p = (int*)&m_data; *p = i } // 這裏處理
private:
    int m_data;
};
注意:這裏雖說能夠修改,但結果是未定義的,避免使用!

3.修飾類對象

const A a; // 類對象a 只能調用const 成員函數,不然編譯器報錯。

4.修飾類成員函數的返回值

const A operator+(const A &) const; // 前一個const 用來修飾重載函數operator+的返回值,可防止返回值做爲左值進行賦值操做。
例如:
A a;
A b;
A c;
a + b = c; // errro: 若是在沒有const 修飾返回值的狀況下,編譯器不會報錯。

七 使用const的一些建議

1.要大膽的使用const,這將給你帶來無盡的益處,但前提是你必須搞清楚原委;

2.要避免最通常的賦值操做錯誤,如將const變量賦值,具體可見思考題;

3.在參數中使用const應該使用引用或指針,而不是通常的對象實例,緣由同上;

4.const在成員函數中的三種用法(參數、返回值、函數)要很好的使用;

5.不要輕易的將函數的返回值類型定爲const;
6.除了重載操做符外通常不要將返回值類型定爲對某個對象的const引用;

八 cons有什麼主要的做用?

1.能夠定義const常量,具備不可變性。
例如:

const int Max=100;
int Array[Max];


2.便於進行類型檢查,使編譯器對處理內容有更多瞭解,消除了一些隱患。

例如:

void f(const int i) { .........}
編譯器就會知道i是一個常量,不容許修改;

3.能夠避免意義模糊的數字出現,一樣能夠很方便地進行參數的調整和修改。
同宏定義同樣,能夠作到不變則已,一變都變!如(1)中,若是想修改Max的內容,只須要:

const int Max=you want;

便可!


4.能夠保護被修飾的東西,防止意外的修改,加強程序的健壯性。
仍是上面的例子,若是在函數體內修改了i,編譯器就會報錯;
例如: 

void f(const int i) { i=10;//error! }

5.爲函數重載提供了一個參考。

class A
{
     ......
    void f(int i)       {......} file://一個函數
    void f(int i) const {......} file://上一個函數的重載
    ......
};


6.能夠節省空間,避免沒必要要的內存分配。

例如:
#define PI 3.14159         file://常量宏
const doulbe Pi=3.14159; file://此時並未將Pi放入ROM中
......
double i=Pi;               file://此時爲Pi分配內存,之後再也不分配!
double I=PI;               file://編譯期間進行宏替換,分配內存
double j=Pi;               file://沒有內存分配
double J=PI;               file://再進行宏替換,又一次分配內存!

const定義常量從彙編的角度來看,只是給出了對應的內存地址,而不是象#define同樣給出的是當即數,因此,const定義的常量在程序運行過程當中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。


7.提升了效率。

編譯器一般不爲普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操做,使得它的效率也很高。

{
    size = sz; // error:試圖在構造函數體內對const成員變量進行初始化
}
相關文章
相關標籤/搜索