一、關於一般常量
聲明或定義的格式如下:
const <類型說明符> <變量名> = <常量或常量表達式>; [1]
<類型說明符> const <變量名> = <常量或常量表達式>; [2]
[1]和[2]的定義是完全等價的。
例如:
整形int(或其他內置類型:float,double,char)
const int bufSize = 512;
或者
int const bufSize = 512;
因為常量在定義後就不能被修改,所以定義時必須初始化。
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。
例如:
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_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成員變量進行初始化
}