C語言探究 - 什麼時候使用const?

C語言 CSDN 程序員 Allen5G 2019-06-01

原文首發於同名微信公號「Allen5G」,歡迎大家搜索關注,歡迎轉發!


今天有個讀者看了我之前寫的一篇文章介紹const,問他不喜歡用const,用了害怕錯,不用也能寫程序,索性就不用了。

《指針本質剖析》原文鏈接:(我的很多技術文章基本都在csdn上,後續會慢慢整理到公眾號)

https://blog.csdn.net/super828/article/details/80439812

一時我也沒有總結const的好處,然後我就找搜索總結了下。


const傳奇

原作:Rahul Singh 翻譯:zhigang

[譯者注]有些地方按原文解釋不通,譯者根據自己的理解作了適當修改。如有不妥之處,請告知[email protected]或參考原文。

原文來自www.codeproject.com

簡介

當我自己寫程序需要用到const的時候,或者是讀別人的代碼碰到const的時候,我常常會停下來想一會兒。許多程序員從來不用const,理由是即使沒用const他們也這麼過來了。本文僅對const的用法稍作探討,希望能夠對提高軟件的源代碼質量有所幫助。

常變量

變量用const修飾,其值不得被改變。任何改變此變量的代碼都會產生編譯錯誤。Const加在數據類型前後均可。

例如


void main(void)
{
const int i = 10; //i,j都用作常變量
int const j = 20;
i = 15; //錯誤,常變量不能改變
j = 25; //錯誤,常變量不能改變
}

常指針

Const跟指針一起使用的時候有兩種方法。

const可用來限制指針不可變。也就是說指針指向的內存地址不可變,但可以隨意改變該地址指向的內存的內容。


void main(void)
{
char* const str = "Hello, World"; //常指針,指向字符串
*str = ''M''; //可以改變字符串內容
str = "Bye, World"; //錯誤,如能改變常指針指向的內存地址
}

const也可用來限制指針指向的內存不可變,但指針指向的內存地址可變。


void main(void)
{
const char* str = "Hello, World"; //指針,指向字符串常量
*str = ''M''; //錯誤,不能改變字符串內容
str = "Bye, World"; //修改指針使其指向另一個字符串
*str = ''M''; //錯誤,仍不能改變字符串內容
}

看完上面的兩個例子,是不是糊塗了?告訴你一個訣竅,在第一個例子中,const用來修飾指針str,str不可變(也就是指向字符的常指針);第二個例子中,const用來修飾char*,字符串char*不可變(也就是指向字符串常量的指針)。

這兩種方式可以組合起來使用,使指針和內存內容都不可變。


void main(void)
{
const char* const str = "Hello, World"; //指向字符串常量的常指針
*str = ''M''; //錯誤,不能改變字符串內容
str = "Bye, World"; //錯誤,不能改變指針指向的地址
}

Const和引用

引用實際上就是變量的別名,這裡有幾條規則:

聲明變量時必須初始化

一經初始化,引用不能在指向其它變量。

任何對引用的改變都將改變原變量。

引用和變量本身指向同一內存地址。

下面的例子演示了以上的規則:


void main(void)
{
int i = 10; //i和j是int型變量
int j = 20;
int &r = i; //r 是變量i的引用
int &s; //錯誤,聲明引用時必須初始化
i = 15; //i 和 r 都等於15
i++; //i 和 r都等於16
r = 18; //i 和r 都等於18
printf("Address of i=%u, Address of r=%u",&i,&r); //內存地址相同
r = j; //i 和 r都等於20,但r不是j的引用
r++; //i 和 r 都等於21, j 仍等於20
}

用const修飾引用,使應用不可修改,但這並不耽誤引用反映任何對變量的修改。Const加在數據類型前後均可。

例如:


void main(void)
{
int i = 10;
int j = 100;
const int &r = i;
int const &s = j;
r = 20; //錯,不能改變內容
s = 50; //錯,不能改變內容
i = 15; // i和r 都等於15
j = 25; // j和s 都等於25
}

Const和成員函數

聲明成員函數時,末尾加const修飾,表示在成員函數內不得改變該對象的任何數據。這種模式常被用來表示對象數據只讀的訪問模式。

例如:


class MyClass
{
char *str ="Hello, World";
MyClass()
{
//void constructor
}

~MyClass()
{
//destructor
}
char ValueAt(int pos) const //const method is an accessor method
{
if(pos >= 12)
return 0;
*str = ''M''; //錯誤,不得修改該對象
return str[pos]; //return the value at position pos
}
}

Const和重載

重載函數的時候也可以使用const,考慮下面的代碼:

class MyClass
{
char *str ="Hello, World";
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
char ValueAt(int pos) const //const method is an accessor method
{
if(pos >= 12)
return 0;
return str[pos]; //return the value at position pos
}
char& ValueAt(int pos) //通過返回引用設置內存內容
{
if(pos >= 12)
return NULL;
return str[pos];
}
}

在上面的例子中,ValueAt是被重載的。Const實際上是函數參數的一部分,在第一個成員函數中它限制這個函數不能改變對象的數據,而第二個則沒有。這個例子只是用來說明const可以用來重載函數,沒有什麼實用意義。

實際上我們需要一個新版本的GetValue。如果GetValue被用在operator=的右邊,它就會充當一個變量;如果GetValue被用作一元操作符,那麼返回的引用可以被修改。這種用法常用來重載操作符。String類的operator[]是個很好的例子。(這一段譯得很爛,原文如下:In reality due to the beauty of references just the second definition of GetValue is actually required. If the GetValue method is used on the the right side of an = operator then it will act as an accessor, while if it is used as an l-value (left hand side value) then the returned reference will be modified and the method will be used as setter. This is frequently done when overloading operators. The [] operator in String classes is a good example.)

class MyClass
{
char *str ="Hello, World";
MyClass()
{
//void constructor
}

~MyClass()
{
//destructor
}
char& operator[](int pos) //通過返回引用可用來更改內存內容
{
if(pos >= 12)
return NULL;
return str[pos];
}
}
void main(void)
{
MyClass m;
char ch = m[0]; //ch 等於 ''H''
m[0] = ''M''; //m的成員str變成:Mello, World
}

Const的擔心

C/C++中,數據傳遞給函數的方式默認的是值傳遞,也就是說當參數傳遞給函數時會產生一個該參數的拷貝,這樣該函數內任何對該參數的改變都不會擴展到此函數以外。每次調用該函數都會產生一個拷貝,效率不高,尤其是函數調用的次數很高的時候。

例如:


class MyClass
{
public:
int x;
char ValueAt(int pos) const //const method is an accessor method
{
if(pos >= 12)
return 0;
return str[pos]; //return the value at position pos
}
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
MyFunc(int y) //值傳遞
{
y = 20;
x = y; //x 和 y 都等於 20.
}
}
void main(void)
{
MyClass m;
int z = 10;
m.MyFunc(z);
printf("z=%d, MyClass.x=%d",z,m.x); //z 不變, x 等於20.
}

通過上面的例子可以看出,z沒有發生變化,因為MyFunc()操作的是z的拷貝。為了提高效率,我們可以在傳遞參數的時候,不採用值傳遞的方式,而採用引用傳遞。這樣傳遞給函數的是該參數的引用,而不再是該參數的拷貝。然而問題是如果在函數內部改變了參數,這種改變會擴展到函數的外部,有可能會導致錯誤。在參數前加const修飾保證該參數在函數內部不會被改變。


class MyClass
{
public:
int x;
MyClass()
{
//void constructor
}
~MyClass()
{
//destructor
}
int MyFunc(const int& y) //引用傳遞, 沒有任何拷貝
{
y =20; //錯誤,不能修改常變量
x = y
}
}
void main(void)
{
MyClass m;
int z = 10;
m.MyFunc(z);
printf("z=%d, MyClass.x=%d",z,m.x); //z不變, x等於10.
}

如此,const通過這種簡單安全機制使你寫不出那種說不定是什麼時候就會掉過頭來咬你一口的代碼。你應該儘可能的使用const引用,通過聲明你的函數參數為常變量(任何可能的地方)或者定義那種const method,你就可以非常有效確立這樣一種概念:本成員函數不會改變任何函數參數,或者不會改變任何該對象的數據。別的程序員在使用你提供的成員函數的時候,不會擔心他們的數據被改得一塌糊塗。


相關推薦

推薦中...