一. const
的基本功能與用法
1.將限定符聲明為只讀
使用方法如下,在類型前/後加上關鍵字const
,該變量必須被初始化,否則編譯錯誤;該變量不能被重新賦值,否則也編譯錯誤。
舉例:
const int i = 50; // 編譯正確
const int j; // 編譯錯誤
int k = 0;
i = k; // 編譯錯誤
k = i; // 編譯正確
2.用於修飾函數形參,保護參數使其不被修改
用法1:若形參為const A* a
,則不能改變函數所傳遞的指針內容,這樣對指針所指向的內容起到保護作用,這裡需要注意的是,該修飾不能改變指針所指向地址所存儲的內容,但是指針a
所指向的地址可以被改變,具體例子如下:
void Test(const int *a)
{
*a = 1; //錯誤,*a不能被賦值
a = new int(10086); //正確,為指針a開辟新的空間,並令*a=2
}
int main()
{
int *a = new int(10000);
Test(a);
return 0;
}
用法2:若形參為const A& a
,則改變函數傳遞進來的引用對象,從而保護了原對象的屬性。
對於自定義的數據類型,用引用傳遞速度較快,如果不想改變原值,就可以用const
來保護參數,如以下例子:
void Test(const int &a) //保護L7中的a不會被改變
{
a = 2;//錯誤,a不能給常量賦值
}
int main()
{
int a = 3;
Test(a);
return 0;
}
事實上對於內置型數據類型(如以上例子中的int
類型),用引用傳遞不會使速度更快。如果是用引用傳參,一般是為了改變參數值;如果不想改變參數的值,直接值傳遞即可,不必使用const
修飾。而對於自定義數據類型的輸入參數,為了提高速度和效率,應使用“const
+ 引用傳遞”代替值傳遞。例如:
將函數 void Test(A a)
改為-> void Test(const A &a)
3.用於修飾函數返回值
用法1:用const
修飾返回值為對象本身(非引用和指針)的情況多用於二目操作符重載函數並產生新對象的時候
舉例:
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
Rational a,b;
Radional c;
(a*b) = c;//錯誤
用法2:不建議用const
修飾函數的返回值類型為某個對象或對某個對象引用的情況。原因如下:如果返回值為某個對象為const(const A test = A 實例)
或某個對象的引用為const(const A& test = A實例)
,則返回值具有const
屬性,則返回實例只能訪問類A中的公有(保護)數據成員和const
成員函數,並且不允許對其進行賦值操作,這在一般情況下很少用到,具體例子如下:
class A
{
public:
int y;
A(int y):x(x),y(y){};
void Sety(int y){this->y = y;}
};
const A Test1(A a)
{
return a;
}
const A& Test2(A &a)
{
return a;
}
int main()
{
A a(2);
Test1(a).Sety(3);//錯誤,因為Test1(a)的返回值是個const,不能被Sety(3)修改
Test2(a).Sety(3);//錯誤,因為Test1(a)的返回值是個const,不能被Sety(3)修改
return 0;
}
用法3:如果給采用“指針傳遞”方式的函數返回值加const
修飾,那麼函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const
修飾的同類型指針。例子如下:
const char * GetString(void){}
int main()
{
char *str1=GetString();//錯誤
const char *str2=GetString();//正確
return 0;
}
用法4:函數返回值采用“引用傳遞”的場合不多,這種方式一般只出現在類的賦值函數中,目的是為了實現鏈式表達。例子如下:
class A
{
// 以下賦值函數的返回值加const修飾,該返回值的內容不允許修改
A &operate = (const A &other);
}
A a, b, c; // a,b,c為A的對象
a = b = c; // 正確
(a = b) = c; // 錯誤,a = b的返回值不允許被再賦值
4.在類成員函數的函數體後加關鍵字const
在類成員函數的函數體後加關鍵字const
,形如:void fun() const;
在函數過程中不會修改數據成員。如果在編寫const
成員函數時,不慎修改了數據成員,或者調用了其他非const
成員函數,編譯器將報錯,這大大提高了程序的健壯性。
如果不是在類的成員函數,沒有任何效果,void fun() const;
和void func();
是一樣的。
5.在另一連接文件文件中引用常量
方法:在類型前添加關鍵字extern const
,表示引用的是常量,因此該常量不允許被再次賦值,舉例如下:
extern const int i; // 正確
extern const int j = 10; // 錯誤,常量不可以被再次賦值
二.const
常量與#define
的區別
1.const
常量有數據類型,而宏常量沒有數據類型
宏常量只進行簡單的字符替換,沒有類型安全檢查,並且在字符替換時可能會產生意料不到的錯誤,如:
#define I = 10
const long &i = 10;
// 由於編譯器的優化,使得在const long i=10時i不被分配內存
// 而是已10直接代入以後的引用中,以致在以後的代碼中沒有錯誤
//一旦你關閉所有優化措施,即使const long i = 10也會引起後面的編譯錯誤。
char h = I; // 正確
char h = i; // 編譯警告,可能由於數的截短帶來錯誤賦值
2.使用const可以避免不必要的內存分配
從匯編的角度來看,const
定義常量只是給出了對應的內存地址, 而不是象#define
一樣給出的是立即數,所以,const
定義的常量在程序運行過程中只有一份拷貝,而#define
定義的常量在內存中有若干個拷貝。例子如下:
#define k Hello world!
const char pk[]=Hello world!;
printf(k); // 為k分配了第一次內存
printf(pk); // 為pk一次分配了內存,以後不再分配
printf(k); // 為k分配了第二次內存
printf(pk);
三. 使用const
的一些注意事項
1.修改const
所修飾的常量值
以下例子中,i
為const
修飾的變量,可以通過對i
進行類型強制轉換,將地址賦給一個新的變量,對該新的變量再作修改即可以改變原來const
修飾的常值。
const int i=0;
int *p=(int*)&i;
p=100;
2.構造函數不能被聲明為const
3.const數據成員的初始化只能在類的構造函數的初始化表中進行
class A
{
public:
const int a;
A(int x):a(x)//正確
{
a = x;//錯誤
}
};
4.在參數中使用const
應該使用引用或指針,而不是一般的對象實例
合理利用const
在成員函數中的三種用法(參數、返回值、函數),一般來說,不要輕易的將函數的返回值類型定為const
;另外,除了重載操作符外一般不要將返回值類型定為對某個對象的const
引用。
5.對於使用const
修飾來指針的情況
對於以下情況,const
放在變量聲明符的前後位置效果是一樣的,這種情況下不允許對指針a
的內容進行更改操作:
int i;
const int *a = &i;
int const*a = &i;
但是,如果const
位於星號的左側,則const
就是用來修飾指針所指向的變量,即該指針指向一個地址,該地址的內容不可變;如果const
位於星號的右側,const
就是修飾指針本身,即指針本身是常量:
int i;
// 以下一行表示a是一個指針,可以任意指向int常量或者int變量
// 它總是把它所指向的目標當作一個int常量
// 也可以寫成int const* a
const int *a = &i;
// 以下一行表示a是一個指針常量,
// 初始化的時候必須固定指向一個int變量
// 之後就不能再指向別的地方了
int *const a = &i;
6.指針本身是常量,而指針所指向的內容不是常量,這種情況下不能對指針本身進行更改操作,如以下例子中a++是錯誤的:
int *const a = &i;
a++; // 錯誤,a指針本身是常量,不能再指向別的地方
7.當指針本身和指針所指向的內容均為常量時
這種情況下可寫為:
const int * const a = &i;
8.const成員函數返回的引用,也是const
#include
using namespace std;
class A
{
public:
int x;
void set(int x){this->x = x;}
// const成員函數返回的引用也是const,a
// 如果把A&前面的const去掉會出錯
// 因為返回的是一個const的對象,返回類型卻不是const
// 返回的內容和返回的類型不符
const A& Test1()const
{
// 錯誤。這是const成員函數的特點
x = 2;
// 不限於*this。不管返回的是什麼,哪怕是一個定義為非const的對象,結果也是一樣的
return *this;
}
};
int main()
{
A a, b;
// 正確,雖然返回的是一個const,卻用另一個非const來接收
b = a.Test1();
// 錯誤,既然是別名,那麼別名的類型要與原來的類型相同
A &c = a.Test1();
// 正確雖然在a.Test1()中a不能改變,但是這裡已經出了這個成員函數的作用域
a.set(2);
// 正確,b接收了a.Test1()返回的數據的內容,但是它不是const
b.set(2);
// 錯誤。a.Test1()是一個對象,這個對象是它的返回值
// 雖然沒有名字,但是它就是a.Test1()的返回值
// 值是a.Test1()返回的值,類型是a.Test1()返回的類型
a.Test1().set(2);
return 0;
}
9.mutable
將數據聲明為可變數據成員
在C++語言中,mutable
是使用較少的關鍵字,它的作用是:如果一個函數被const
修飾,那麼它將無法修改其成員變量的,但是如果一個成員變量是被mutable
修飾的話,則可以修改。
mutable
可以用來指出,即使成員函數或者類變量為const
,其某個成員也可以被修改。反過來說,可變數據成員永遠不能成為const
,即使它是const
對象的成員。
class A
{
public:
int x;
mutable int y;
A(int a, int b):x(a),y(b){}
};
int main()
{
const A a(0, 0); // const對象必須初始化
a.x = 1; // 錯誤
a.y = 2; // 正確,mutable修飾使得成員可被修改,即使對象a為const
return 0;
}