(1)可以定義 const 常量
(2)const 可以修飾函數的參數、返回值.
詳細內容:
1、什么是const?
常類型是指使用類型修飾符const說明的類型,常類型的變量或對象的值是不能被更新的。(當然,我們可以偷梁換柱進行更新:)
2、為什么引入const?
const 推出的初始目的,正是為了取代預編譯指令,消除它的缺點,同時繼承它的優點。
3、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) {......} //一個函數
void f(int i) const {......} //上一個函數的重載 ......
};
(6)可以節省空間,避免不必要的內存分配。例如:
#define PI 3.14159 //常量宏
const doulbe Pi=3.14159; //此時並未將Pi放入ROM中 ......
double i=Pi; //此時為Pi分配內存,以后不再分配!
double I=PI; //編譯期間進行宏替換,分配內存
double j=Pi; //沒有內存分配
double J=PI; //再進行宏替換,又一次分配內存!
const定義常量從匯編的角度來看,只是給出了對應的內存地址,而不是象#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。
(7)提高了效率。編譯器通常不為普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成為一個編譯期間的常量,沒有了存儲與讀內存的操作,使得它的效率也很高。
4、如何使用const?
(1)修飾一般常量一般常量是指簡單類型的常量。這種常量在定義時,修飾符const可以用在類型說明符前,也可以用在類型說明符后。例如:
int const x=2; 或 const int x=2;
(2)修飾常數組定義或說明一個常數組可采用如下格式:
int const a[5]={1, 2, 3, 4, 5};
const int a[5]={1, 2, 3, 4, 5};
(3)修飾常對象常對象是指對象常量,定義格式如下:
class A; const A a;
A const a; 定義常對象時,同樣要進行初始化,並且該對象不能再被更新,修飾符const可以放在類名后面,也可以放在類名前面。
(4)修飾常指針
const int *A; //const修飾指向的對象,A可變,A指向的對象不可變
int const *A; //const修飾指向的對象,A可變,A指向的對象不可變
int *const A; //const修飾指針A, A不可變,A指向的對象可變
const int *const A;//指針A和A指向的對象都不可變
(5)修飾常引用使用const修飾符也可以說明引用,被說明的引用為常引用,該引用所引用的對象不能被更新。其定義格式如下:
const double & v;
(6)修飾函數的常參數 const修飾符也可以修飾函數的傳遞參數,格式如下:
void Fun(const int Var); 告訴編譯器Var在函數體中的無法改變,從而防止了使用者的一些無意的或錯誤的修改。
(7)修飾函數的返回值: const修飾符也可以修飾函數的返回值,是返回值不可被改變,格式如下:
const int Fun1(); const MyClass Fun2();
(8)修飾類的成員函數: const修飾符也可以修飾類的成員函數,格式如下:
class ClassName {
public:
int Fun() const; .....
};這樣,在調用函數Fun時就不能修改類里面的數據
(9)在另一連接文件中引用const常量
extern const int i;//正確的引用
extern const int j=10;//錯誤!常量不可以被再次賦值另外,還要注意,常量必須初始化!例如: const int i=5;
5、幾點值得討論的地方:
(1)const究竟意味着什么?
說了這么多,你認為const意味着什么?一種修飾符?接口抽象?一種新類型?也許都是,在Stroustup最初引入這個關鍵字時,只是為對象放入ROM做出了一種可能,對於const對象,C++既允許對其進行靜態初始化,也允許對他進行動態初始化。理想的const對象應該在其構造函數完成之前都是可寫的,在析夠函數執行開始后也都是可寫的,換句話說,const對象具有從構造函數完成到析夠函數執行之前的不變性,如果違反了這條規則,結果都是未定義的!雖然我們把const放入ROM中,但這並不能夠保證const的任何形式的墮落,我們后面會給出具體的辦法。無論const對象被放入ROM中,還是通過存儲保護機制加以保護,都只能保證,對於用戶而言這個對象沒有改變。換句話說,廢料收集器(我們以后會詳細討論,這就一筆帶過)或數據庫系統對一個const的修改怎沒有任何問題。
(2)位元const V.S. 抽象const?
對於關鍵字const的解釋有好幾種方式,最常見的就是位元const 和抽象const。下面我們看一個例子: class A { public: ...... A f(const A& a); ...... }; 如果采用抽象const進行解釋,那就是f函數不會去改變所引用對象的抽象值,如果采用位元const進行解釋,那就成了f函數不會去改變所引用對象的任何位元。我們可以看到位元解釋正是c++對const問題的定義,const成員函數不被允許修改它所在對象的任何一個數據成員。為什么這樣呢?因為使用位元const有2個好處:最大的好處是可以很容易地檢測到違反位元const規定的事件:編譯器只用去尋找有沒有對數據成員的賦值就可以了。另外,如果我們采用了位元const,那么,對於一些比較簡單的const對象,我們就可以把它安全的放入ROM中,對於一些程序而言,這無疑是一個很重要的優化方式。(關於優化處理,我們到時候專門進行討論)當然,位元const也有缺點,要不然,抽象const也就沒有產生的必要了。首先,位元const的抽象性比抽象const的級別更低!實際上,大家都知道,一個庫接口的抽象性級別越低,使用這個庫就越困難。其次,使用位元const的庫接口會暴露庫的一些實現細節,而這往往會帶來一些負面效應。所以,在庫接口和程序實現細節上,我們都應該采用抽象const。有時,我們可能希望對const做出一些其它的解釋,那么,就要注意了,目前,大多數對const的解釋都是類型不安全的,這里我們就不舉例子了,你可以自己考慮一下,總之,我們盡量避免對const的重新解釋。
(3)放在類內部的常量有什么限制?
看看下面這個例子:
class A {
private:
const int c3 = 7; // ???
static int c4 = 7; // ???
static const float c5 = 7; // ??? ......
};
你認為上面的3句對嗎?呵呵,都不對!使用這種類內部的初始化語法的時候,常量必須是被一個常量表達式初始化的整型或枚舉類型,而且必須是static和const形式。這顯然是一個很嚴重的限制!那么,我們的標准委員會為什么做這樣的規定呢?一般來說,類在一個頭文件中被聲明,而頭文件被包含到許多互相調用的單元去。但是,為了避免復雜的編譯器規則,C++要求每一個對象只有一個單獨的定義。如果C++允許在類內部定義一個和對象一樣占據內存的實體的話,這種規則就被破壞了。
(4)如何初始化類內部的常量?
一種方法就是static 和 const 並用,在內部初始化,如上面的例子;另一個很常見的方法就是初始化列表:
class A {
public:
A(int i=0):test(i) {}
private:
const int i;
};還有一種方式就是在外部初始化,例如:
class A {
public:
A() {}
private:
static const int i;//注意必須是靜態的!
};
const int A::i=3;
(5)常量與數組的組合有什么特殊嗎?我們給出下面的代碼:
const int size[3]={10,20,50};
int array[size[2]];
有什么問題嗎?對了,編譯通不過!為什么呢?
Const可以用於集合,但編譯器不能把一個集合存放在它的符號表里,所以必須分配內存。在這種情況下,const意味着"不能改變的一塊存儲"。然而,其值在編譯時不能被使用,因為編譯器在編譯時不需要知道存儲的內容。自然,作為數組的大小就不行了:)你再看看下面的例子:
class A {
public:
A(int i=0):test[2]({1,2}) {}//你認為行嗎?
private:
const int test[2];
};
vc6下編譯通不過,為什么呢?關於這個問題,前些時間,njboy問我是怎么回事?我反問他:"你認為呢?"他想了想,給出了一下解釋,大家可以看看:我們知道編譯器堆初始化列表的操作是在構造函數之內,顯式調用可用代碼之前,初始化的次序依據數據聲明的次序。初始化時機應該沒有什么問題,那么就只有是編譯器對數組做了什么手腳!其實做什么手腳,我也不知道,我只好對他進行猜測:編譯器搜索到test發現是一個非靜態的數組,於是,為他分配內存空間,這里需要注意了,它應該是一下分配完,並非先分配test[0],然后利用初始化列表初始化,再分配test[1],這就導致數組的初始化實際上是賦值!然而,常量不允許賦值,所以無法通過。呵呵,看了這一段冠冕堂皇的話,真讓我笑死了!njboy別怪我揭你短呀:)我對此的解釋是這樣的:C++標准有一個規定,不允許無序對象在類內部初始化,數組顯然是一個無序的,所以這樣的初始化是錯誤的!對於他,只能在類的外部進行初始化,如果想讓它通過,只需要聲明為靜態的,然后初始化。這里我們看到,常量與數組的組合沒有什么特殊!一切都是數組惹的禍!
(6)this指針是不是const類型的?
this指針是一個很重要的概念,那該如何理解她呢?也許這個話題太大了,那我們縮小一些:this指針是個什么類型的?這要看具體情況:如果在非const成員函數中,this指針只是一個類類型的;如果在const成員函數中,this指針是一個const類類型的;如果在volatile成員函數中,this指針就是一個volatile類類型的。
(7)const到底是不是一個重載的參考對象?
先看一下下面的例子:
class A {
......
void f(int i) {......}//一個函數
void f(int i) const {......}//上一個函數的重載
......
}; 上面是重載是沒有問題的了,那么下面的呢?
class A {
......
void f(int i) {......}//一個函數
void f(const int i) {......}//?????
......
}; 這個是錯誤的,編譯通不過。那么是不是說明內部參數的const不予重載呢?再看下面的例子:
class A {
......
void f(int& ) {......}//一個函數
void f(const int& ) {......}//?????
......
}; 這個程序是正確的,看來上面的結論是錯誤的。為什么會這樣呢?這要涉及到接口的透明度問題。按值傳遞時,對用戶而言,這是透明的,用戶不知道函數對形參做了什么手腳,在這種情況下進行重載是沒有意義的,所以規定不能重載!當指針或引用被引入時,用戶就會對函數的操作有了一定的了解,不再是透明的了,這時重載是有意義的,所以規定可以重載。
(8)什么情況下為const分配內存?
以下是我想到的可能情況,當然,有的編譯器進行了優化,可能不分配內存。
A、作為非靜態的類成員時;
B、用於集合時;
C、被取地址時;
D、在main函數體內部通過函數來獲得值時;
E、const的 class或struct有用戶定義的構造函數、析構函數或基類時;。
F、當const的長度比計算機字長還長時;
G、參數中的const;
H、使用了extern時。不知道還有沒有其他情況,歡迎高手指點:)
(9)臨時變量到底是不是常量?
很多情況下,編譯器必須建立臨時對象。像其他任何對象一樣,它們需要存儲空間而且必須被構造和刪除。區別是我們從來看不到編譯器負責決定它們的去留以及它們存在的細節。對於C++標准草案而言:臨時對象自動地成為常量。因為我們通常接觸不到臨時對象,不能使用與之相關的信息,所以告訴臨時對象做一些改變有可能會出錯。當然,這與編譯器有關,例如:vc6、vc7都對此作了擴展,所以,用臨時對象做左值,編譯器並沒有報錯。
(10)與static搭配會不會有問題?假設有一個類:
class A {
public:
......
static void f() const { ......}
......
}; 我們發現編譯器會報錯,因為在這種情況下static不能夠與const共存!為什么呢?因為static沒有this指針,但是const修飾this指針,所以...
(11)如何修改常量?
有時候我們卻不得不對類內的數據進行修改,但是我們的接口卻被聲明了const,那該怎么處理呢?我對這個問題的看法如下:
1)標准用法:
mutable class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const { test=i; }
private: mutable int test;//這里處理!
};
2)強制轉換:
const_cast class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const {
const_cast (test)=i;
}//這里處理!
private:
int test;
};
3)靈活的指針:
int* class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const { *test=i; }
private:
int* test; //這里處理!
};
4)未定義的處理
class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const {
int *p=(int*)&test; *p=i;
}//這里處理!
private:
int test;
};注意,這里雖然說可以這樣修改,但結果是未定義的,避免使用!
5)內部處理:this指針
class A {
public:
A(int i=0):test(i) { }
void SetValue(int i)const {
((A*)this)->test=i;
}//這里處理!
private:
int test;
};
6)最另類的處理:空間布局
class A {
public:
A(int i=0):test(i),c('a') { }
private:
char c;
const int test;
};
int main()
{
A a(3);
A* pa=&a;
char* p=(char*)pa;
int* pi=(int*)(p+4);//利用邊緣調整
*pi=5;//此處改變了test的值!
return 0;
}
雖然我給出了6中方法,但是我只是想說明如何更改,但出了第一種用法之外,另外5種用法,我們並不提倡,不要因為我這么寫了,你就這么用,否則,我真是要誤人子弟了:)
(12)最后我們來討論一下常量對象的動態創建。既然編譯器可以動態初始化常量,就自然可以動態創建,例如:
const int* pi=new const int(10); 這里要注意2點:
1)const對象必須被初始化!所以(10)是不能夠少的。
2)new返回的指針必須是const類型的。 那么我們可不可以動態創建一個數組呢? 答案是否定的,因為new內置類型的數組,不能被初始化。 這里我們忽視了數組是類類型的,同樣對於類內部數組初始化我們也做出了這樣的忽視,因為這涉及到數組的問題,我們以后再討論。
c語言中的const關鍵字
1, 基本的const
1.1 const和變量的初始化
如果在定義const自動變量時沒有進行初始化,
那么就沒法直接進行初始化,而只能通過指針間接進行初始化。
int
main()
{
const int a;
a = 9;
//這里無法通過編譯
//錯誤:向只讀變量 'a' 賦值
//但如果寫成這樣 const int a = 9;將順利通過編譯。
return 0;
}
1.2 以下定義是相同的
int const a = 9;
const int a = 9;
記住:const和基本自動變量使用,定義時可以交換位置。
在和指針一起定義某個變量時,不能交換。
1.3 const並非無法修改
無法直接修改const的變量,但可以通過指針的方式
間接修改。
void
test1(void)
{
int *pi;
int const a = 9;
printf("int const a = %d\n", a);
pi = &a;
*pi = 2;
printf("haha, I change it a = %d\n", a);
}
2, const 和指針
const和指針在一起使用時,容易搞混,
在網上找到一個識別的方法覺得用起來很不錯:
舍棄法識破const。
1)首先舍棄const,得到一個普通的聲明;
2)若有多個const從最左邊的const開始去。
3)然后舍棄const右面的const標志以及其它的關鍵字;
4)接下來就是將const右面所有的*和變量用一個新變量代替,
單個字符不用被取代,那么這個新變量就是const作用對象。
下面先給出這幾個表達式的解釋,后面用例子說明。
const int *A; //修飾指向的對象,A可變,A指向的對象不可變
int const *A; //修飾指向的對象,A可變,A指向的對象不可變
int *const A; //修飾指針A, A不可變,A指向的對象可變
const int *const A; //指針A和A指向的對象都不可變
2.1 const int *A;和 int const *A;
按照上面的3條,這兩個的表達式的作用應該是一樣的。
const的作用對象應該是*A這個整體。也就是作用於A指向的變量的值。
61 void
62 test4(void)
63 {
64 int num=12;
65 const int *A=#
66 (*A)++; //error: 令只讀位置自增
67 printf("result=%d\n",*A);
68 }
61-68行可以看出,const int *A的確修飾的是*A這個整體,也就是
指針指向的值。
下面的這個例子說明了,雖然無法改變*A的值,但是可以改變A指針本身。
71 void
72 test5(void)
73 {
74 int a[] = {1,2,3,4,5};
75 const int *ar = &a;
76
77 *ar++; //ok
78 printf("result=%d\n",*ar); //result=2
79 }
2.2 int *const a; 和 const *int a;
const *int a; //這樣的定義格式是錯誤的,指針不知道是什么類型。
int *const a;
按照上面的規則我可以判斷,該const是修飾的a,而a被定義為一個指針。
所以他限定的對象是a這個指針。也就是說a的指針值不能修改。
#include<stdio.h>
int
main(void)
{
int a[] = {1,2,3,4,5};
int *const ar = a;
*ar++; //error
//若是這樣寫,就對了:
//*ar = 9; // ok
printf("result=%d\n",*ar); //result=2
return 0;
}
編譯無法通過,出現錯誤:
test2.c: In function 'main':
test2.c:9: 錯誤:令只讀變量 'ar' 自增
2.3 const int *const a; //指針A和A指向的對象都不可變
通過分析知道,該定義中const限制了*a和a。也就是這
兩個(作為整體)都不能改變。
1 #include<stdio.h>
2
3 int
4 main(void)
5 {
6 int a[] = {1,2,3,4,5};
7 const int *const ar = a;
8
9 //*ar++; //error
10 *ar = 9; // error
11 printf("result=%d\n",*ar); //result=2
12 return 0;
13 }
3, 作為參數時的const
17 void
18 testpc(const char *s, const int *a)
19 {
20 if (*a == 3) {
21 a = 0; //改變其地址允許
22 s[1] = "a"; //改變*s的值,出錯
23 }
24 }
根據規則,這樣的賦值是錯誤的,但如果改變其地址卻可以。
const int *a;的意思是不能改變 *a這個整體的值。
const是一個C語言的關鍵字,它限定一個變量不允許被改變。使用const在一定程度上可以提高程序的健壯性,另外,在觀看別人代碼的時候,清晰理解const所起的作用,對理解對方的程序也有一些幫助。
雖然這聽起來很簡單,但實際上,const的使用也是c語言中一個比較微妙的地方,微妙在何處呢?請看下面幾個問題。
問題:const變量 & 常量
為什么我象下面的例子一樣用一個const變量來初始化數組,ANSI C的編譯器會報告一個錯誤呢?
const int n = 5;
int a[n];
答案與分析:
1)、這個問題討論的是"常量"與"只讀變量"的區別。常量肯定是只讀的,例如5, "abc",等,肯定是只讀的,因為程序中根本沒有地方存放它的值,當然也就不能夠去修改它。而"只讀變量"則是在內存中開辟一個地方來存放它的值,只不過這個值由編譯器限定不允許被修改。C語言關鍵字const就是用來限定一個變量不允許被改變的修飾符(Qualifier)。上述代碼中變量n被修飾為只讀變量,可惜再怎么修飾也不是常量。而ANSI C規定數組定義時維度必須是"常量","只讀變量"也是不可以的。
2)、注意:在ANSI C中,這種寫法是錯誤的,因為數組的大小應該是個常量,而const int n,n只是一個變量(常量 != 不可變的變量,但在標准C++中,這樣定義的是一個常量,這種寫法是對的),實際上,根據編譯過程及內存分配來看,這種用法本來就應該是合理的,只是 ANSI C對數組的規定限制了它。
3)、那么,在ANSI C 語言中用什么來定義常量呢?答案是enum類型和#define宏,這兩個都可以用來定義常量。
問題:const變量 & const 限定的內容
下面的代碼編譯器會報一個錯誤,請問,哪一個語句是錯誤的呢?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
答案與分析:
問題出在p2++上。
1)、const使用的基本形式: const char m; 限定m不可變。
2)、替換1式中的m, const char *pm; 限定*pm不可變,當然pm是可變的,因此問題中p1++是對的。
3)、替換1式char, const newType m; 限定m不可變,問題中的charptr就是一種新類型,因此問題中p2不可變,p2++是錯誤的。
問題:const變量 & 字符串常量
請問下面的代碼有什么問題?
char *p = "i'm hungry!";
p[0]= 'I';
答案與分析:
上面的代碼可能會造成內存的非法寫操作。分析如下, "i'm hungry"實質上是字符串常量,而常量往往被編譯器放在只讀的內存區,不可寫。p初始指向這個只讀的內存區,而p[0] = 'I'則企圖去寫這個地方,編譯器當然不會答應。
問題:const變量 & 字符串常量2
請問char a[3] = "abc" 合法嗎?使用它有什么隱患?
答案與分析:
在標准C中這是合法的,但是它的生存環境非常狹小;它定義一個大小為3的數組,初始化為"abc"。注意,它沒有通常的字符串終止符'\0',因此這個數組只是看起來像C語言中的字符串,實質上卻不是,因此所有對字符串進行處理的函數,比如strcpy、printf等,都不能夠被使用在這個假字符串上。
問題5:const & 指針
類型聲明中const用來修飾一個常量,有如下兩種寫法,那么,請問,下面分別用const限定不可變的內容是什么?
1)、const在前面
const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可變
const (char *) pContent;//pContent是const,*pContent可變
char* const pContent; //pContent是const,*pContent可變
const char* const pContent; //pContent和*pContent都是const
2)、const在后面,與上面的聲明對等
int const nValue; // nValue是const
char const * pContent;// *pContent是const, pContent可變
(char *) const pContent;//pContent是const,*pContent可變
char* const pContent;// pContent是const,*pContent可變
char const* const pContent;// pContent和*pContent都是const
答案與分析:
const和指針一起使用是C語言中一個很常見的困惑之處,在實際開發中,特別是在看別人代碼的時候,常常會因為這樣而不好判斷作者的意圖,下面講一下我的判斷原則:
沿着*號划一條線,如果const位於*的左側,則const就是用來修飾指針所指向的變量,即指針指向為常量;如果const位於*的右側,const就是修飾指針本身,即指針本身是常量。你可以根據這個規則來看上面聲明的實際意義,相信定會一目了然。
另外,需要注意:對於const (char *) ; 因為char *是一個整體,相當於一個類型(如 char),因此,這是限定指針是const。
另=======
const用於函數時出現三個位置:
例如:
const returnVal function (const list_array)const;
第一個const意思是:返回值是常量
第二個const意思是:函數過程中不能修改list_array的值
第三個const意思是:函數過程不能隱式的修改function參數的值
===
zzhttp://publishblog.blogchina.com/blog/tb.b?diaryID=3217823
const char*, char const*, char*const的區別問題幾乎是C++面試中每次都會有的題目。
Bjarne在他的The C++ Programming Language里面給出過一個助記的方法:把一個聲明從右向左讀。
char * const cp; ( * 讀成 pointer to ) :cp is a const pointer to char
const char * p; :p is a pointer to const char;
char const * p;
同上因為C++里面沒有const*的運算符,所以const只能屬於前面的類型。
另:下面定義的一個指向字符串的常量指針:
char * const prt1 = stringprt1;
其中,ptr1是一個常量指針。因此,下面賦值是非法的。 ptr1 = stringprt2;
而下面的賦值是合法的: *ptr1 = "m";
因為指針ptr1所指向的變量是可以更新的,不可更新的是常量指針ptr1所指的方向(別的字符串)。
下面定義了一個指向字符串常量的指針:
const * ptr2 = stringprt1;
其中,ptr2是一個指向字符串常量的指針。ptr2所指向的字符串不能更新的,而ptr2是可以更新的。因此,
*ptr2 = "x"; 是非法的,而: ptr2 = stringptr2; 是合法的。
所以,在使用const修飾指針時,應該注意const的位置。定義一個指向字符串的指針常量和定義一個指向字符串常量的指針時,const修飾符的位置不同,前者const放在*和指針名之間,后者const放在類型說明符前。
const在C語言中算是一個比較新的描述符,我們稱之為常量修飾符,意即其所修飾
的對象為常量(immutable)。
我們來分情況看語法上它該如何被使用。
1、函數體內修飾局部變量。
例:
void func(){
const int a=0;
}
首先,我們先把const這個單詞忽略不看,那么a是一個int類型的局部自動變量,
我們給它賦予初始值0。
然后再看const.
const作為一個類型限定詞,和int有相同的地位。
const int a;
int const a;
是等價的。於是此處我們一定要清晰的明白,const修飾的對象是誰,是a,和int沒
有關系。const 要求他所修飾的對象為常量,不可被改變,不可被賦值,不可作為
左值(l-value)。
這樣的寫法也是錯誤的。
const int a;
a=0;
這是一個很常見的使用方式:
const double pi=3.14;
在程序的后面如果企圖對pi再次賦值或者修改就會出錯。
然后看一個稍微復雜的例子。
const int* p;
還是先去掉const 修飾符號。
注意,下面兩個是等價的。
int* p;
int *p;
其實我們想要說的是,*p是int類型。那么顯然,p就是指向int的指針。
同理
const int* p;
其實等價於
const int (*p);
int const (*p);
即,*p是常量。也就是說,p指向的數據是常量。
於是
p+=8; //合法
*p=3; //非法,p指向的數據是常量。
那么如何聲明一個自身是常量指針呢?方法是讓const盡可能的靠近p;
int* const p;
const右面只有p,顯然,它修飾的是p,說明p不可被更改。然后把const去掉,可以
看出p是一個指向 int形式變量的指針。
於是
p+=8; //非法
*p=3; //合法
再看一個更復雜的例子,它是上面二者的綜合
const int* const p;
說明p自己是常量,且p指向的變量也是常量。
於是
p+=8; //非法
*p=3; //非法
const 還有一個作用就是用於修飾常量靜態字符串。
例如:
const char* name=David;
如果沒有const,我們可能會在后面有意無意的寫name[4]='x'這樣的語句,這樣會
導致對只讀內存區域的賦值,然后程序會立刻異常終止。有了 const,這個錯誤就
能在程序被編譯的時候就立即檢查出來,這就是const的好處。讓邏輯錯誤在編譯
期被發現。
const 還可以用來修飾數組
const char s[]=David;
與上面有類似的作用。
2、在函數聲明時修飾參數
來看實際中的一個例子。
NAME
memmove -- copy byte string
LIBRARY
Standard C Library (libc, -lc)
SYNOPSIS
#include
void *
memmove(void *dst, const void *src, size_t len);
這是標准庫中的一個函數,用於按字節方式復制字符串(內存)。
它的第一個參數,是將字符串復制到哪里去(dest),是目的地,這段內存區域必須
是可寫。
它的第二個參數,是要將什么樣的字符串復制出去,我們對這段內存區域只做讀
取,不寫。
於是,我們站在這個函數自己的角度來看,src 這個指針,它所指向的內存內所存
儲的數據在整個函數執行的過程中是不變。於是src所指向的內容是常量。於是就
需要用const修飾。
例如,我們這里這樣使用它。
const char* s=hello;
char buf[100];
memmove(buf,s,6); //這里其實應該用strcpy或memcpy更好
如果我們反過來寫,
memmove(s,buf,6);
那么編譯器一定會報錯。事實是我們經常會把各種函數的參數順序寫反。事實是編
譯器在此時幫了我們大忙。如果編譯器靜悄悄的不報錯,(在函數聲明處去掉
const即可),那么這個程序在運行的時候一定會崩潰。
這里還要說明的一點是在函數參數聲明中const一般用來聲明指針而不是變量本身。
例如,上面的size_t len,在函數實現的時候可以完全不用更改len的值,那么是否
應該把len也聲明為常量呢?可以,可以這么做。我們來分析這么做有什么優劣。
如果加了const,那么對於這個函數的實現者,可以防止他在實現這個函數的時候修
改不需要修改的值(len),這樣很好。
但是對於這個函數的使用者,
1。這個修飾符號毫無意義,我們可以傳遞一個常量整數或者一個非常量整數過
去,反正對方獲得的只是我們傳遞的一個copy。
2。暴露了實現。我不需要知道你在實現這個函數的時候是否修改過len的值。
所以,const一般只用來修飾指針。
再看一個復雜的例子
int execv(const char *path, char *const argv[]);
着重看后面這個,argv.它代表什么。
如果去掉const,我們可以看出
char * argv[];
argv是一個數組,它的每個元素都是char *類型的指針。
如果加上const.那么const修飾的是誰呢?他修飾的是一個數組,argv[],意思就是
說這個數組的元素是只讀的。那么數組的元素的是什么類型呢?是char *類型的指
針.也就是說指針是常量,而它指向的數據不是。
於是
argv[1]=NULL; //非法
argv[0][0]='a'; //合法
3、全局變量。
我們的原則依然是,盡可能少的使用全局變量。
我們的第二條規則則是,盡可能多的使用const。
如果一個全局變量只在本文件中使用,那么用法和前面所說的函數局部變量沒有什
么區別。
如果它要在多個文件間共享,那么就牽扯到一個存儲類型的問題。
有兩種方式。
1.使用extern
例如
/* file1.h */
extern const double pi;
/* file1.c */
const double pi=3.14;
然后其他需要使用pi這個變量的,包含file1.h
#include file1.h
或者,自己把那句聲明復制一遍就好。
這樣做的結果是,整個程序鏈接完后,所有需要使用pi這個變量的共享一個存儲區域。
2.使用static,靜態外部存儲類
/* constant.h */
static const pi=3.14;
需要使用這個變量的*.c文件中,必須包含這個頭文件。
前面的static一定不能少。否則鏈接的時候會報告說該變量被多次定義。
這樣做的結果是,每個包含了constant.h的*.c文件,都有一份該變量自己的copy,
該變量實際上還是被定義了多次,占用了多個存儲空間,不過在加了static關鍵字
后,解決了文件間重定義的沖突。
壞處是浪費了存儲空間,導致鏈接完后的可執行文件變大。但是通常,這個,小小
幾字節的變化,不是問題。
好處是,你不用關心這個變量是在哪個文件中被初始化的。
最后,說說const的作用。
const 的好處,是引入了常量的概念,讓我們不要去修改不該修改的內存。直接的
作用就是讓更多的邏輯錯誤在編譯期被發現。所以我們要盡可能的多使用const。
但是很多人並不習慣使用它,更有甚者,是在整個程序編寫/調試完后才補
const。如果是給函數的聲明補const,尚好。如果是給全局/局部變量補const,那
么……那么,為時已晚,無非是讓代碼看起來更漂亮了。關於const的使用,曾有一
個笑話說,const 就像安全套,事前要記牢。如果做完后才想起來該用而忘了用,
呵呵……呵呵……
p.s.C++中的const與C語言還是有很大差別的。不寫了,用者自知吧。
const是一個C語言的要害字,它限定一個變量不答應被改變。使用const在一定程度上可以提高程序的健壯性,另外,在觀看別人代碼的時候,清楚理解const所起的作用,對理解對方的程序也有一些幫助。
雖然這聽起來很簡單,但實際上,const的使用也是c語言中一個比較微妙的地方,微妙在何處呢?請看下面幾個問題。
問題:const變量 & 常量
為什么我象下面的例子一樣用一個const變量來初始化數組,ANSI C的編譯器會報告一個錯誤呢?
const int n = 5;
int a[n];
答案與分析:
1)、這個問題討論的是"常量"與"只讀變量"的區別。常量肯定是只讀的,例如5, "abc",等,肯定是只讀的,因為程序中根本沒有地方存放它的值,當然也就不能夠去修改它。而"只讀變量"則是在內存中開辟一個地方來存放它的值,只不過這個值由編譯器限定不答應被修改。C語言要害字const就是用來限定一個變量不答應被改變的修飾符(Qualifier)。上述代碼中變量n被修飾為只讀變量,可惜再怎么修飾也不是常量。而ANSI C規定數組定義時維度必須是"常量","只讀變量"也是不可以的。
2)、注重:在ANSI C中,這種寫法是錯誤的,因為數組的大小應該是個常量,而const int n,n只是一個變量(常量 != 不可變的變量,但在標准C++中,這樣定義的是一個常量,這種寫法是對的),實際上,根據編譯過程及內存分配來看,這種用法本來就應該是合理的,只是ANSI C對數組的規定限制了它。
3)、那么,在ANSI C 語言中用什么來定義常量呢?答案是enum類型和#define宏,這兩個都可以用來定義常量。
更多內容請看C/C++進階技術文檔專題,或
問題:const變量 & const 限定的內容
下面的代碼編譯器會報一個錯誤,請問,哪一個語句是錯誤的呢?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
答案與分析:
問題出在p2++上。
1)、const使用的基本形式: const char m;
限定m不可變。
2)、替換1式中的m, const char *pm;
限定*pm不可變,當然pm是可變的,因此問題中p1++是對的。
3)、替換1式char, const newType m;
限定m不可變,問題中的charptr就是一種新類型,因此問題中p2不可變,p2++是錯誤的。
問題:const變量 & 字符串常量
請問下面的代碼有什么問題?
char *p = "i'm hungry!";
p[0]= 'I';
答案與分析:
上面的代碼可能會造成內存的非法寫操作。分析如下, "i'm hungry"實質上是字符串常量,而常量往往被編譯器放在只讀的內存區,不可寫。p初始指向這個只讀的內存區,而p[0] = 'I'則企圖去寫這個地方,編譯器當然不會答應。
問題:const變量 & 字符串常量2
請問char a[3] = "abc" 合法嗎?使用它有什么隱患?
答案與分析:
在標准C中這是合法的,但是它的生存環境非常狹小;它定義一個大小為3的數組,初始化為"abc",,注重,它沒有通常的字符串終止符'\0',因此這個數組只是看起來像C語言中的字符串,實質上卻不是,因此所有對字符串進行處理的函數,比如strcpy、PRintf等,都不能夠被使用在這個假字符串上。
問題5:const & 指針
類型聲明中const用來修飾一個常量,有如下兩種寫法,那么,請問,下面分別用const限定不可變的內容是什么?
1)、const在前面
const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可變
const (char *) pContent;//pContent是const,*pContent可變
char* const pContent; //pContent是const,*pContent可變
const char* const pContent; //pContent和*pContent都是const
2)、const在后面,與上面的聲明對等
int const nValue; // nValue是const
char const * pContent;// *pContent是const, pContent可變
(char *) const pContent;//pContent是const,*pContent可變
char* const pContent;// pContent是const,*pContent可變
char const* const pContent;// pContent和*pContent都是const
答案與分析:
const和指針一起使用是C語言中一個很常見的困惑之處,在實際開發中,非凡是在看別人代碼的時候,經常會因為這樣而不好判定作者的意圖,下面講一下我的判定原則:
沿着*號划一條線,const和誰在一邊,那么誰就是const,即const限定的元素就是它。你可以根據這個規則來看上面聲明的實際意義,相信定會一目了然。
另外,需要注重:對於const (char *) ; 因為char *是一個整體,相當於一個類型(如 char),因此,這是限定指針是const。
一、說明指針常量、指向常量的指針和指向常量的常量指針的含義和區別和共同點。
首先,以上三種概念的共同點就是都是說的指針。
指針也是一種變量,它存儲指定類型的變量的內存地址,如可char* 來聲明一個字符型指針變量,跟其它變量一樣,當其值不可改變時,該指針變量就成為了指針常量,既是常量,它當然一直指向同一個內存地址,而不能被改變。
指向常量的指針顧名思義就是說其指向的那個地址的值不是變量而是常量了,即其指向的內存地址的內容將不能被改變,而指針本身則可以改變。
指向常量的指針常量最好理解,結合以上兩者的含義即可知道,其特點就是指針本身的值(即指向的內存地址)和指針指向內存地址的內容均是常量,不能被改變。
二、const的應用舉例及說明
//指針常量
char str[5] = "abcd"; //聲明一個字符數組
char * const pStr = str; //讓pStr指針指向字符數組變量str;pStr該指針常量值(即指向的內存地址)不能被改變。
//分別執行以下語句的結果,可以體現它的特點
pStr = "asdfaf" ; //企圖讓pStr 指針常量指向另一個地址,結果報錯!
*pStr = 'd'; //修改pStr指針常量指向的內存地址中第一個字符的內容為d,結果正確,第一個字符被修改。
//指向常量的指針
char str[5] = "abcd"; //聲明一個字符數組
const char * pStr = str; //讓pStr指針指向字符數組常量str,該字符數組不能被改變;
//分別執行以下語句的結果,可以體現它的特點
pStr = "asdfaf" ; //讓pStr 指針常量指向另一個地址,結果正確,pStr不再指向str字符數組常量,而指向了字符串asdfaf的首地址!
*pStr = 'd'; //修改pStr指針常量指向的內存地址中第一個字符的內容為d,結果錯誤,常量不能被修改。
//指向常量的指針常量
char str[5] = "abcd"; //聲明一個字符數組
const char * const pStr = str; //讓pStr指針指向字符數組常量str,該字符數組不能被改變;
//分別執行以下語句的結果,可以體現它的特點
pStr = "asdfaf" ; //讓pStr 指針常量指向另一個地址,結果錯誤,此時是指針值不能被改變!
*pStr = 'd'; //修改pStr指針常量指向的內存地址中第一個字符的內容為d,結果錯誤,此時是內容不能被改變。
以上說明希望對初學者對const的理解和使用有所幫助!
const 用法總結(C++)
根據個人的學習和理解,下面我將從以下幾個分類來進行討論,如有錯誤之處,還請各位大蝦多多指教!(部分內容直接轉載,以供學習和參考)
一、關於一般常量
聲明或定義的格式如下:
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成員變量進行初始化
}
沿着*號划一條線,const和誰在一邊,那么誰就是const,即const限定的元素就是它。你可以根據這個規則來看上面聲明的實際意義,相信定會一目了然。
另外,需要注意:對於const (char *) ; 因為char *是一個整體,相當於一個類型(如 char),因此,這是限定指針是const。
int const nValue; // nValue是const char const * pContent;// *pContent是const, pContent可變 (char *) const pContent;//pContent是const,*pContent可變 char* const pContent;// pContent是const,*pContent可變 char const* const pContent;// pContent和*pContent都是const |
情景一:最簡單的const用法
#include<stdio.h> int main() { int const a; a=5; printf("a=%d\n",a); return 0; }如果編譯這個c文件,就會報錯: 1071.c: In function 'main': 1071.c:5: error: assignment of read-only variable 'a' 顯而易見,這是const在搞鬼。 因為聲明了const的變量是不能修改的! 如果將源代碼修改為如下這樣,就沒有問題了! #include<stdio.h> int main() { int const a=5; printf("a=%d\n",a); return 0; } |
總結:const聲明的變量必須要進行初始化賦值,如果錯過這個機會,以后再想給const的變量賦值,可就沒門了!切記~
PS:int const和const int是一回事,"顛倒寫"都是可以的。以后遇到了別犯暈,呵呵。但是,還是要留個心眼,當const和指針攙和到一起時,這個"顛倒寫"的規律可未必成立。
情景二:發明const為了什么?
在const誕生之前,開發者一直使用#define VAR 100來定義一些有特殊用途的類常量,不過這樣定義是存在一些劣勢的。因此const應運而生,之后開發者可以使用const int VAR=100;來定義類常量了。
至於為什么#define有其劣勢,還要讀者自己去google下。
情景三:const和指針的配合是噩夢!
你能分辨得清這些聲明么: const int *A; int const *A; int *const A; const int *const A const int *A; //修飾指向的對象,A可變,A指向的對象不可變 int const *A; //修飾指向的對象,A可變,A指向的對象不可變 int *const A; //修飾指針A, A不可變,A指向的對象可變 const int *const A; //指針A和A指向的對象都不可變 |
情景四:const int *A
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; const int *A=# printf("result=%d\n",*A); return 0; }編譯執行結果為: [rocrocket@wupengchong const_test]$ cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=12 接下來,我們動動手腳,在代碼中加入了(*A)++;這條語句: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; const int *A=# (*A)++; printf("result=%d\n",*A); return 0; }編譯這個c文件: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function 'main': test1.c:6: error: increment of read-only location '*A' 可以看到,報錯了,報錯的內容表示"*A"是只讀的,不能修改。 我們再修改一下源代碼為下面這樣: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; const int *A=# A=&tmp; printf("result=%d\n",*A); return 0; }編譯執行結果為: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=100 |
好了,如果你仔細看了這幾個小得不能再小的程序,你自己都可以給出結論了!
結論:如果聲明了const int *A,那么A值是可以修改的,而*A是不可以修改的。更通俗的說,A指針可以隨便指向一個整型,但只要被A盯上了的整型變量在使用*A引用時就不能修改了。
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; const int *A=# A=&tmp; tmp=3; printf("result=%d\n",*A); return 0; }編譯執行的結果為: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=3 |
結論2:即使A指向了tmp,我雖然不能修改*A,但是我仍然是可以用tmp來修改這個值的,完全不管*A的存在。呵呵
情景五:int *const A
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int *const A=# printf("result=%d\n",*A); return 0; }編譯執行結果為: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=12 我們稍微修改下源代碼: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; int *const A=# A=&tmp; printf("result=%d\n",*A); return 0; }編譯時報錯了: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function 'main': test1.c:7: error: assignment of read-only variable 'A' [rocrocket@wupengchong const_test]$ cat test1.c 可見A本身的值已經不能再變了。 繼續修改源代碼如下: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int *const A=# (*A)=100; printf("result=%d\n",*A); return 0; }編譯執行結果為: [rocrocket@wupengchong const_test]$ !cc cc test1.c [rocrocket@wupengchong const_test]$ ./a.out result=100 可以看出,(*A)是可以改變的。 |
結論又可以輕易推出了:int *const A; //const修飾指針A, A不可變,A指向的對象可變
情景六:const int *const A; //指針A和A指向的對象都不可變
[rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int const *const A=# (*A)=100; printf("result=%d\n",*A); return 0; }編譯會報錯: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function 'main': test1.c:6: error: assignment of read-only location '*A' 改下源代碼: [rocrocket@wupengchong const_test]$ cat test1.c #include<stdio.h> int main() { int num=12; int tmp=100; int const *const A=# A=&tmp; printf("result=%d\n",*A); return 0; }編譯仍然會報錯: [rocrocket@wupengchong const_test]$ !cc cc test1.c test1.c: In function 'main': test1.c:7: error: assignment of read-only variable 'A' |
呵呵,結論很明顯了,const int *const A; //指針A和A指向的對象都不可變
當然const int *const A;和int const *const A=#是等價的!
情景七:如果const用在函數形參里呢?是不是又要復雜很多?
答案是NO!一點也不復雜。
來看看這個函數投:int addnum(const int num, int a, int b);
這個函數聲明中的第一個形參是const int num,這就表明如果我調用了這個函數,那么第一個實參被傳到addnum函數里之后,就不能再做修改了!呵呵 就這么簡單。
給個例子吧,讓大家能更一目了然:
[rocrocket@wupengchong const_test]$ cat test2.c #include<stdio.h> int addto(const int num, int a, int b) { if(num==1){ return a+b; }else{ return 0; } }
int main(){ int num=100; int a=12,b=22; int res; num=1; res=addto(num,a,b); printf("res=%d\n",res); return 0; }編譯執行結果為: [rocrocket@wupengchong const_test]$ !cc cc test2.c [rocrocket@wupengchong const_test]$ ./a.out res=34 如果我修改一下,編譯就會出錯: [rocrocket@wupengchong const_test]$ cat test2.c #include<stdio.h> int addto(const int num, int a, int b) { if(num==1){ num=3; return a+b; }else{ return 0; } }
int main(){ int num=100; int a=12,b=22; int res; num=1; res=addto(num,a,b); printf("res=%d\n",res); return 0; }編譯報錯為: [rocrocket@wupengchong const_test]$ !cc cc test2.c test2.c: In function 'addto': test2.c:5: error: assignment of read-only location 'num' |
可見在函數里形參被聲明為const的變量也是不能修改的哦!呵呵~
const其實不難,把本文的幾個小例子看懂就OK了!
淺析C++"const"常用方法
C++的c*****t可以非常靈活的擺放,例如:c*****t int i、int c*****t i、c*****t int* p、int c*****t *p、int* c*****t p 等等,很多人會因此感到困惑,甚至於不使用c*****t,我也是這樣的。作為過來人,有必要說的是,其實c*****t常用的也就這么幾種(歡迎大家補遺):
1、修飾變量(variable)
c*****t放在最前面修飾變量,例如:c*****t int i、c*****t int* p,這表明變量的內容不能改變,對i和*p不能進行賦值,例如 i = 20 或 *p = 20,編譯器會報錯。
c*****t放在指針名稱前,例如:int* c*****t p,這表明指針的地址不能改變了,例如:++p是不允許的。
如果既想指針的內容無法改變,也想指針的指向的地址無法改變,則可以這么寫:c*****t int* c*****t p,這也是一種常見的用法。
2、修飾函數(method)
c*****t修飾函數常用在修飾返回值為指針的函數體上,例如:
int g = 0;
c*****t int* GetValuePtr()
{
return &g;
}
c*****t char* GetStr()
{
return "Hello";
}
這樣就表明返回的指針指向的內容,調用者不要去改動。對於調用方,也只能聲明用c*****t修飾的指針去調用,例如:
c*****t int* i = GetValuePtr();
c*****t char* p = GetStr();
如1所示,這樣聲明指針,當然也就無法修改指針內容了。
3、修飾類成員函數(method of class)
c*****t修飾非類的函數的果效同樣作用於類成員函數上,特殊的是類的成員函數還可以在聲明的末端用c*****t修飾,例如:
class CMyClass
{
public:
CMyClass();
~CMyClass();
public:
int GetValue() c*****t;
int* GetPtr();
private:
int m_iValue;
};
這樣的意思是,在GetValue這個函數體內,是不會有改變類成員變量和調用其它非c*****t修飾函數的舉動,例如:
int CMyClass::GetValue() c*****t
{
m_iValue = 0; //編譯器會報錯
GetPtr(); //編譯器會報錯
return m_iValue;
}
所以類的某個函數如果不會去改變類的成員變量,例如只返回某個成員變量的類函數,那么就應該在尾巴處修飾上c*****t。
4、修飾類(class)
例如:c*****t CMyClass o,這時的o對類成員變量和類函數的調用是有限制的:
a、能讀取類的成員變量而不能去修改它;
b、只能調用末端有c*****t修飾的函數。
class CMyClass
{
public:
CMyClass()
{
m_iValue = 0;
m_pPtr = NULL;
}
~CMyClass()
{
}
public:
int GetValue() c*****t
{
return m_iValue;
}
int* GetPtr()
{
return m_pPtr;
}
private:
int* m_pPtr;
int m_iValue;
};
c*****t CMyClass o;
o.GetValue(); //允許使用的方法
o.GetPtr(); //編譯出錯
5、注意:
使用了c*****t后,編譯器會作一些優化,例如:
c*****t int x = 4;
TRACE("%d\n", x);
int* pX = ( int* ) (&x); //打印出來是4。
*pX = 3;
TRACE("%d\n", x); //打印出來的是什么?
打印出來還是4。因為經過編譯器優化,對TRACE函數調用時,直接傳了固定值(push 4)。在VC2005上以Debug和Release編譯均是如此。
c*****t能讓編譯器更好的理解你的程序,自然她產出的代碼也就會更強壯。同時,c*****t也可以理解為程序員之間的小默契,例如你遇到了某個c*****t修飾的函數,你就應該知道應該乖乖地不要去動這個函數的返回值。
C++是一門不安全的語言,所以C++程序員必須要特別小心,利用C++語法提供的每一項特性來加強程序的強壯性。c*****t能加強您程序的強壯性,所以擅以運用類似於c*****t這樣的特性(對於Java是final)是程序員的基本功,而更多的對c*****t的體會還需要我們在實踐中去領悟。