日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

C语言再学习 -- 关键字const

發布時間:2025/3/15 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C语言再学习 -- 关键字const 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

const 關鍵字其實我們并不陌生,之前有講過const修飾數組和指針。現在來詳細介紹這個關鍵字。

參看:【C/C++和指針】著名的《const的思考》


一、const 介紹

1、const 定義

const 修飾的數據類型是指常類型,常類型的變量或對象的值是不能被更新的

2、const 目的

const 推出的初始目的,正是為了取代預編譯指令,消除它的缺點,同時繼承它的優點(后面會講到 const 與 宏的區別)。

3、const 作用

1)可以定義 const 常量,具有不可變性。例如:

const int Max = 100; ?Max++會產生錯誤。

2)便于進行類型檢查,使編譯器對處理內容有更多了解,消除一些隱患。例如:

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

3)可以避免意義模糊的數字出現,同樣可以很方便進行參數的調整和修改。同宏定義一樣,可以做到不變則已,一變都變。

(這句話,沒搞懂什么意思,希望有知道的大神告知!!)

4)可以保護被修改的東西,防止意外的修改,增強程序的健壯性。例如:

[cpp]?view plain?copy ? ?
  • #include?<stdio.h>??
  • void?f?(const?int?i)??
  • {??
  • ????i?=?10;??//在函數體內修改了?i?,編譯器就會報錯。??
  • }?????
  • int?main?(void)??
  • {??
  • ????f?(1);??
  • ????return?0;??
  • }??
  • ??
  • 輸出結果:??
  • 錯誤:?向只讀形參‘i’賦值??
  • 5)可以節省空間,避免不必要的內存分配。例如:

    #define PI 3.14159 //常量宏?
    const double Pi=3.14159; //此時并未將Pi放入RAM中
    double i=Pi; //此時為Pi分配內存,以后不再分配!?
    double I=PI; //編譯期間進行宏替換,分配內存?
    double j=Pi; //沒有內存分配?

    double J=PI; //再進行宏替換,又一次分配內存!?

    //test.c #include <stdio.h> int main (void) { const double Pi; double i = Pi; double j = Pi; return 0; } objdump -d test 080483b4 <main>: 80483b4: 55 push %ebp 80483b5: 89 e5 mov %esp,%ebp 80483b7: 83 e4 f8 and $0xfffffff8,%esp 80483ba: 83 ec 20 sub $0x20,%esp 80483bd: dd 44 24 08 fldl 0x8(%esp) 80483c1: dd 5c 24 10 fstpl 0x10(%esp) 80483c5: dd 44 24 08 fldl 0x8(%esp) 80483c9: dd 5c 24 18 fstpl 0x18(%esp) 80483cd: b8 00 00 00 00 mov $0x0,%eax 80483d2: c9 leave 80483d3: c3 ret //test1.c #include <stdio.h> #define PI 3.14159 int main (void) { double i = PI; double j = PI; } objdump -d test1 080483b4 <main>: 80483b4: 55 push %ebp 80483b5: 89 e5 mov %esp,%ebp 80483b7: 83 e4 f8 and $0xfffffff8,%esp 80483ba: 83 ec 10 sub $0x10,%esp 80483bd: dd 05 b0 84 04 08 fldl 0x80484b0 80483c3: dd 1c 24 fstpl (%esp) 80483c6: dd 05 b0 84 04 08 fldl 0x80484b0 80483cc: dd 5c 24 08 fstpl 0x8(%esp) 80483d0: c9 leave 80483d1: c3 ret 80483d2: 90 nop 80483d3: 90 nop const定義常量從匯編的角度來看,只是給出了對應的內存地址,而不是像#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若干份拷貝。?

    6)為函數重載提供了一個參考

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

    7)提高效率

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


    二、const 使用

    1、const 修飾一般常量

    一般常量是指簡單類型的只讀變量。這種常量在定義時,修飾符const可以用在類型說明符前,也可以用在類型說明符后。例如:?
    int const x=2; ?或 ?const int x=2;

    const int a = 10; a = 20; // 錯誤,變量a為常量,只讀,不能被修改;int const b = 10; b = 20; // 錯誤,變量b為常量,只讀,不能被修改;

    注意:

    1)在定義該const變量時,通常需要對它進行初始化,因為以后就沒有機會再改變它了

    //C 下 #include <stdio.h>int main (void) {const int i; //自動初始值為 隨機數//i = 10; //如果此時再向它賦值,會出現錯誤: 向只讀變量‘i’賦值printf ("%d\n", i); } 輸出結果: -1217368076 //隨機數 //C++下 #include <iostream> int main (void) {int const i;//i = 10; //如果此時再向它賦值,會出現錯誤: 向只讀變量‘i’賦值 } /* gcc編譯器不夠嚴格,g++編譯器下會報錯 */錯誤: 未初始化的常量‘i’ 2)const int const i = 10; 是否可行

    //在C 下是可行的,但是還是不推薦使用 #include <stdio.h> int main (void) {const int const i = 10;printf ("%d\n", i);return 0; } 輸出結果: 10 //在C++ 下是錯誤的 #include <iostream> int main (void) {const int const i = 10;std::cout << i << std::endl; } 輸出結果: 錯誤: 重復的‘const’


    擴展:常量與變量

    參看:如何理解C語言常量與變量

    說著說著,其實搞混了const到底修飾的是什么了。什么是常量,什么是變量?

    常量,例如5, "abc",等,肯定是只讀的,因為常量是被編譯器放在內存中的只讀區域,當然也就不能夠去修改它。

    enum類型和#define宏,這兩個都可以用來定義常量。

    采用宏定義#define指令創建一個指定數組大小的明顯常量(SIZE),可以在定義數組和設置循環限制時使用這個常量,以后更改數組大小的時候方便處理,例如:

    #define SIZE 5

    int arr[SIZE];


    變量?其值是可以改變的。一個變量應該有一個名字,在內存中占據一定的存儲單元。變量定義必須放在變量使用之前。一般放在函數體的開頭部分。要區分變量名和變量值是兩個不同的概念。例如:int?x = 3;


    而“只讀變量”則是在內存中開辟一個地方來存放它的值,只不過這個值由編譯器限定不允許被修改。C語言關鍵字const就是用來?限定一個變量?不允許被改變的修飾符(Qualifier)。 ? ? ? ?

    例如,const int a;

    const只是一個修飾符,不管怎么樣 a 仍然是一個int型的變量。

    指定數組大小

    直到C99標準出現之前,聲明數組時在方括號內只能使用整數常量表達式。整數常量表達式是由整數常量組成的表達式。sizeof表達式被認為是一個整數常量,而(和C++不同)一個const值卻不是整數常量。并且該表達式的值必須大于0。

    #define SIZE 5 int n = 5; float a1[5]; //可以 float a2[5*2 + 1]; //可以 float a3[sizeof (int) +1]; //可以 float a4[-4]; //不可以,數組大小必須大于0 float a5[0]; //不可以,數組大小必須大于0 float a6[2.5]; //不可以,數組大小必須大于0 float a7[(int)2.5]; //可以,把float類型指派為int類型 float a8[n]; //C99之前不允許 float a9[SIZE]; //可以

    //C99支持 這種形式,并不會報錯 #include <stdio.h> int main (void) {const int n = 5;int a[n];return 0; }


    但是?const修飾的只讀變量?不能放在 case ?關鍵字后面、不能放在enum枚舉名稱后面,因為 case 關鍵字后面和枚舉類型聲明必須要 整數常量

    #include <stdio.h> #define n 2 //常量 int main (void) {//int n = 2; //變量,會出現錯誤: case 標號不能還原為一個整常量//const int n = 2; //只讀變量,會出現錯誤: case 標號不能還原為一個整常量switch (3){case 1:printf ("11111\n");break;case n:printf ("222222\n");break;case 3:printf ("333333\n");break;default:printf ("4444444\n");break;}return 0; } 輸出結果: 333333

    #include <stdio.h> #define n 3 //常量 //int n = 3; //變量,會出現錯誤: ‘QIU’的枚舉值不是一個整數常量 //const int n = 3; //只讀變量,會出現錯誤: ‘QIU’的枚舉值不是一個整數常量 typedef enum {CHUN = 1,XIA = 2,QIU = n,DONG = 4 }Season;int main (void) {printf ("%d\n", QIU);return 0; } 輸出結果: 3

    2、const修飾指針、數組

    const定義的變量具有只讀性const修飾的只讀變量必須在定義的時候初始化

    1)修飾數組

    定義或說明一個只讀數組可采用如下格式:
    int const a[5]={1, 2, 3, 4, 5};或
    const int a[5]={1, 2, 3, 4, 5};

    const int numbers[] = {1, 2, 3, 4, 5}; numbers[1] = 10; // 錯誤,數組被const修飾,因此,數組內容不可修改

    2)修飾指針

    這里給出一個記憶和理解的方法:
    先忽略類型名(編譯器解析的時候也是忽略類型名),我們看 const 離哪個近。“近水樓先得月”,離誰近就修飾誰。

    int arr[5];
    const?int?*p = arr;?//const 修飾*p,p 是指針,可變; *p 是指針指向的對象,不可變。
    int?const *p = arr;?//const 修飾*p,p 是指針, 可變;*p 是指針指向的對象,不可變。
    int?*const p = arr;?//const 修飾 p, p 是指針,不可變; p 指向的對象可變。
    const?int?*const p= arr;?//前一個 const 修飾*p,后一個 const 修飾 p,指針 p 和 p 指向的對象都不可變。

    //示例一 int a = 10; int b = 20; const int *p = &a; //等同 int const *p = &a; p = &b; // 正確 *p = 20; // 錯誤,指針變量p所指向的地址中的內容不能通過指針變量修改 a = 20; // 正確,變量a并沒有被const關鍵字修飾; //示例二 int a = 10; int b = 20; int * const p = &a; p = &b; // 錯誤,指針p只能指向同一個地址; *p = 20; // 正確 //示例三 int a = 10; int b = 20; const int * const p = &a; p = &b; // 錯誤 *p = 20; // 錯誤

    擴展:

    指針數組和數組指針

    指針數組:首先它是一個數組,數組的元素都是指針,例如:int *ptr1[10];

    數組指針:首先它是一個指針,它指向一個數組,例如:int (*ptr2)[10];

    這里需要明白一個符號之間優先級的問題,"[ ]"的優先級比"*"要高。p1 先與“ []”結合,構成一個數組的定義,數組名為 p1, int *修飾的是數組的內容,即數組的每個元素。那現在我們清楚,這是一個數組,其包含 10 個指向 int 類型數據的指針,即指針數組。

    至于 p2 就更好理解了,在這里"( )"的優先級比"[ ]"高,"*"號和 p2 構成一個指針的定義,指針變量名為 p2, int 修飾的是數組的內容,即數組的每個元素。數組在這里并沒有名字,是個匿名數組。那現在我們清楚 p2 是一個指
    針,它指向一個包含 10 個 int 類型數據的數組,即數組指針。


    為什么要講指針數組和數組指針呢?是因為看到Dan Saks總結的const 用法很受啟發。從另一個角度,來分析了const 的真實意義。

    參看:const 的真實的意義(包含了Dan Saks以及一些網絡人的理解)

    文章從下面例子開始:

    typedef void *VP;
    const VP vectorTable[]
    ={..<data>..}; ? ? ? ? ? (1)
    應該等同于:
    const void* vectorTable[]
    ={..<data>..}; ? ? ? ? ? (2)
    然而,在(1)中連接器把vectorTable放在了CONSTANT(只讀)區,但是在(2)中卻放在了DATA(數據)區。這是編譯器的正常行為還是BUG?”


    typedef關鍵字我們比較熟悉,參看:C語言再學習 -- 關鍵字typedef

    如果 const 只是單純的修飾指針,如,const void *P, void * const P,這也不過是簡單考慮 指針常量,和常量指針問題。但是本例中修飾的?const void* vectorTable[]?是指針數組。這也是為什么要先區分指針數組和數組指針了。

    再有就是需要清楚,存儲類說明符和數據類型及類型修飾符。參看:C語言再學習--關鍵字

    對應的就是文章里所說的聲明說明符聲明符

    每一條C/C++聲明語句都是有兩個基本部分組成:零個或多個聲明說明符序列;以及一個或多個 聲明符序列,中間用逗號隔開。比如:

    static unsigned int n = 3, m = 2;

    extern int n;?等等

    可以看出,存儲類說明符,對于數據類型沒有直接影響。而const 和 volatile 不是數據類型,它是限定符(specifier)。不會影響數據類型。

    然后就明白了, 在(1)中,可看做?constVP vectorTable[] 修飾的是數組,所以vectorTable為只讀;在(2)中,可以看做?constvoid?*vectorTable[] 修飾的是指針數組,*vectorTable[]不可變,vectorTable[]是可變的,所以放在了DATA(數據)區。


    3、const 修飾函數的形參和返回值

    1)const 修飾符也可以修飾函數的傳遞參數,格式如下:

    void Fun (const int Var);

    告訴編譯器Var在函數中是無法改變的,從而防止了使用者的一些無意或錯誤的修改。之前講字符串,可以看到許多字符串函數就是如此定義的。

    參看:C語言再學習 -- 字符串和字符串函數

    2)const 修飾符也可以修飾函數的返回值,返回值不可被改變,格式如下:

    const int Fun1 ( );

    const MyClass Fun2 ( );

    上述寫法限定函數的返回值不可被更新,當函數返回內部的類型時,已經是一個數值,當然不可被賦值更新,所以,此時const無意義,最好去掉,以免困惑。當函數返回自定義的類型時,這個類型仍然包含可以被賦值的變量成員,所以,此時有意義。


    在C++里,對 const 進行了進一步擴展 :

    4、const 修飾常對象

    常對象是指 對象常量,定義格式,如下:

    class A;const A a; A const a;

    定義常對象時,同樣要進行初始化,并且該對象不能再被更新,修飾符const可以放在類名后面,也可以放在類名前面。


    5、const 修飾常引用

    使用const修飾符也可以說明引用,被說明的引用為常引用,該引用所引用的對象不能被更新。其定義格式,如下:
    const double & v;


    6、const 修飾類的成員變量

    const修飾類的成員函數,表示成員常量,不能被修改,同時它只能在初始化列表中賦值。

    class A { …const int nValue; //成員常量不能被修改…A(int x): nValue(x) { } ; //只能在初始化列表中賦值 }

    規則:
    1)const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數.
    2)const對象的成員是不可修改的,然而const對象通過指針維護的對象卻是可以修改的.
    3)const成員函數不可以修改對象的數據,不管對象是否具有const性質.它在編譯時,以是否修改成員數據為依據,進行檢查.
    4) 然而加上mutable修飾符的數據成員,對于任何情況下通過任何手段都可修改,自然此時的const成員函數是可以修改它的。


    7、const 修飾類的成員函數

    const修飾符也可以修飾類的成員函數,格式如下:

    class ClassName { public:int Fun() const;..... };

    這樣,在調用函數Fun時就不能修改類里面的數據 。

    對于const類對象/指針/引用,只能調用類的const成員函數,因此,const修飾成員函數的最重要作用就是限制對于const對象的使用。


    總結:

    關鍵字const的作用是為給讀你代碼的人傳達非常有用的信息,實際上,聲明一個參數為常量是為了告訴了用戶這個參數的應用目的
      如果你曾花很多時間清理其它人留下的垃圾,你就會很快學會感謝這點多余的信息。(當然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)
    通過給優化器一些附加的信息,使用關鍵字const也許能產生更緊湊的代碼。合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。
    欲阻止一個變量被改變,可以使用 const 關鍵字。?
    1)在定義該const 變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了;
    2)對指針來說,可以指定指針本身為const,也可以指定指針所指的數據為 const,或二者同時指定為const;
    3)在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
    4)對于類的成員函數,若指定其為const 類型,則表明其是一個常函數,不能修改類的成員變量;
    5)對于類的成員函數,有時候必須指定其返回值為const 類型,以使得其返回值不為“左值”。


    三、const 與 extern 和 define的區別和聯系

    1、const 與 extern關系

    參看:c與c++中的extern const的區別和聯系

    extern const int n; ?//通過

    extern const int i = 10; ?//錯誤

    示例一:

    //file1.c const int n = 10; //file2.c #include <stdio.h> extern const int n;int main (void) {printf ("%d\n", n);return 0; } 編譯:gcc file1.c file2.c -o file 輸出結果: 10 示例二:

    #include <stdio.h> extern const int i = 10; //如果聲明、定義 int main (void) {printf ("%d\n", i);return 0; } 輸出結果: 警告: ‘i’已初始化,卻又被聲明為‘extern’


    示例一中,gcc -c file1.c 生成 file1.o。然后使用?readelf -s file1.o 查看符號表:?

    root@# readelf -s file1.o Symbol table '.symtab' contains 9 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS file1.c2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 2 4: 00000000 0 SECTION LOCAL DEFAULT 3 5: 00000000 0 SECTION LOCAL DEFAULT 4 6: 00000000 0 SECTION LOCAL DEFAULT 6 7: 00000000 0 SECTION LOCAL DEFAULT 5 8: 00000000 4 OBJECT GLOBAL DEFAULT 4 n 可以看到最后一行,n 在符號表中是 GLOBAL(全局)的。

    接下來,我們不用改動代碼,只是使用 g++ file1.c file2.c -o file 編譯程序,可以看出錯誤:

    g++ file1.c file2.c -o file /tmp/cc3vh9lu.o: In function `main': file2.c:(.text+0xa): undefined reference to `n' collect2: ld 返回 1 鏈接錯誤原因是找不到 n 的定義。

    使用 g++ -c file1.c 生成 file1.o,再使用 readelf -s file1.o 查看符號表:

    readelf -s file1.o Symbol table '.symtab' contains 9 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS file1.c2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 2 4: 00000000 0 SECTION LOCAL DEFAULT 3 5: 00000000 0 SECTION LOCAL DEFAULT 4 6: 00000000 4 OBJECT LOCAL DEFAULT 4 _ZL1n7: 00000000 0 SECTION LOCAL DEFAULT 6 8: 00000000 0 SECTION LOCAL DEFAULT 5 ?6: 00000000 ? ? 4 OBJECT ?LOCAL ?DEFAULT ? ?4 _ZL1n

    表明,n 變成了一個 LOCAL(本地)對象,只能在 file1.c 中可見,對file2.c 不可見。


    解決方法:

    將 file1.c中的

    //file.c const int n = 10; 改為:

    //file.c extern const int n = 10; 這樣g++編譯器在第一次看到 n 的定義的時候,因為存在extern關鍵字,就把它當成GLOBAL對象寫入符號表:

    g++ -c file1.c 生成 file1.O readelf -s file1.o Symbol table '.symtab' contains 9 entries:Num: Value Size Type Bind Vis Ndx Name0: 00000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000 0 FILE LOCAL DEFAULT ABS file1.c2: 00000000 0 SECTION LOCAL DEFAULT 1 3: 00000000 0 SECTION LOCAL DEFAULT 2 4: 00000000 0 SECTION LOCAL DEFAULT 3 5: 00000000 0 SECTION LOCAL DEFAULT 4 6: 00000000 0 SECTION LOCAL DEFAULT 6 7: 00000000 0 SECTION LOCAL DEFAULT 5 8: 00000000 4 OBJECT GLOBAL DEFAULT 4 n


    2、const 與 define 關系

    參看:const的用法詳解

    上面有提到,?由于const定義常量從匯編的角度來看,只是給出了對應的內存地址, 而不是象#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。

    const 與define宏定義:
    1)編譯器處理方式不同: ?
    define宏是在預處理階段展開;const常量是編譯運行階段使用。
    2)類型和安全檢查不同:define宏沒有類型,不做任何類型檢查,僅僅是展開;const常量有具體的類型,在編譯階段會執行類型檢查;
    3)存儲方式不同:define宏僅僅是展開不會分配內存;const常量會在內存中分配;(只是說一般情況)
    4)const 可以節省空間,避免不必要的內存分配。 例如:

    #define PI 3.14159 //常量宏 const doulbe Pi=3.14159; //此時并未將Pi放入ROM中 ...... double i=Pi; //此時為Pi分配內存,以后不再分配! double I=PI; //編譯期間進行宏替換,分配內存 double j=Pi; //沒有內存分配 double J=PI; //再進行宏替換,又一次分配內存!

    總結

    以上是生活随笔為你收集整理的C语言再学习 -- 关键字const的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。