const關鍵字到底該怎么用


原文地址:https://www.yanbinghu.com/2019/01/28/7442.html

前言

我們都知道使用const關鍵字限定一個變量為只讀,但它是真正意義上的只讀嗎?實際中又該如何使用const關鍵字?在解答這些問題之前,我們需要先理解const關鍵字的基本使用。本文說明C中的const關鍵字,不包括C++。

基本介紹

const是constant的簡寫,是不變的意思。但並不是說它修飾常量,而是說它限定一個變量為只讀。

修飾普通變量

例如:

const int NUM = 10; //與int const NUM等價
NUM = 9;  //編譯錯誤,不可再次修改

由於使用了const修飾NUM,使得NUM為只讀,因此嘗試對NUM再次賦值的操作是非法的,編譯器將會報錯。正因如此,如果需要使用const修飾一個變量,那么它只能在開始聲明時就賦值,否則后面就沒有機會了(后面會講到一個特殊情況)。

修飾數組

例如使用const關鍵字修飾數組,使其元素不允許被改變:

const int arr[] = {0,0,2,3,4}; //與int const arr[]等價
arr[2] = 1; //編譯錯誤

試圖修改arr的內容的操作是非法的,編譯器將會報錯:

error: assignment of read-only location ‘arr[2]’

修飾指針

修飾指針的情況比較多,主要有以下幾種情況:
1.const 修飾 *p,指向的對象只讀,指針的指向可變:

int a = 9;
int b = 10;
const int *p = &a;//p是一個指向int類型的const值,與int const *p等價
*p = 11;    //編譯錯誤,指向的對象是只讀的,不可通過p進行改變
p = &b;     //合法,改變了p的指向

這里為了便於理解,可認為const修飾的是*p,通常使用*對指針進行解引用來訪問對象,因而,該對象是只讀的。

2.const修飾p,指向的對象可變,指針的指向不可變:

int a = 9;
int b = 10;
int * const p = &a;//p是一個const指針
*p = 11;    //合法,
p = &b;     //編譯錯誤,p是一個const指針,只讀,不可變

3.指針不可改變指向,指向的內容也不可變

int a = 9;
int b = 10;
const int * const p = &a;//p既是一個const指針,同時也指向了int類型的const值
*p = 11;    //編譯錯誤,指向的對象是只讀的,不可通過p進行改變
p = &b;     //編譯錯誤,p是一個const指針,只讀,不可變

看完上面幾種情況之后是否會覺得混亂,並且難以記憶呢?我們使用一句話總結:
const放在*的左側任意位置,限定了該指針指向的對象是只讀的;const放在*的右側,限定了指針本身是只讀的,即不可變的。

如果還不是很好理解,我們可以這樣來看,去掉類型說明符,查看const修飾的內容,上面三種情況去掉類型說明符int之后,如下:

const *p; //修飾*p,指針指向的對象不可變
const p; //修飾p,指針不可變
const * const p; //第一個修飾了*p,第二個修飾了p,兩者都不可變

const右邊修飾誰,就說明誰是不可變的。上面的說法僅僅是幫助理解和記憶。借助上面這種理解,就會發現以下幾種等價情況:

const int NUM = 10; //與int const NUM等價
int a = 9;
const int *p  = &a;//與int const *p等價
const int arr[] = {0,0,2,3,4}; //與int const arr[]等價

const關鍵字到底有什么用

前面介紹了這么多內容,是不是都常用呢?const關鍵字到底有什么用?

修飾函數形參

實際上,為我們可以經常發現const關鍵字的身影,例如很多庫函數的聲明:

char *strncpy(char *dest,const char *src,size_t n);//字符串拷貝函數
int  *strncmp(const char *s1,const char *s2,size_t n);//字符串比較函數

通過看strncpy函數的原型可以知道,源字符串src是只讀的,不可變的,而dest並沒有該限制。我們通過一個小例子繼續觀察:

//test.c
#include<stdio.h>
void myPrint(const char *str);
void myPrint(const char *str)
{
    str[0] = 'H';
    printf("my print:%s\n",str);
}
int main(void)
{
    char str[] = "hello world";
    myPrint(str);
    return 0;
}

在這個例子中,我們不希望myPrint函數修改傳入的字符串內容,因此入參使用了const限定符,表明傳入的字符串是只讀的,因此,如果myPrint函數內部如果嘗試對str進行修改,將會報錯:

$ gcc -o test test.c
test.c:6:12: error: assignment of read-only location ‘*str’
     str[0] = 'H';

因此,我們自己在編碼過程中,如果確定傳入的指針參數僅用於訪問數據,那么應該將其聲明為一個指向const限定類型的指針,避免函數內部對數據進行意外地修改

修飾全局變量

我們知道,使用全局變量是一種不安全的做法,因為程序的任何部分都能夠對全局數據進行修改。而如果對全局變量增加const限定符(假設該全局數據不希望被修改),就可以避免被程序其他部分修改。這里有兩種使用方式。
第一種,在a文件中定義,其他文件中使用外部聲明,例如:
a.h

//a.h
const int ARR[] = {0,1,2,3,4,5,6,7,8,9};  //定義int數組

b.c

//b.c
extern const int ARR[];   //注意,這里不能再對ARR進行賦值
//后面可以使用ARR

第二種,在a文件中定義,並使用static修飾,b文件包含a文件,例如:
a.h

//a.h
static const int ARR[] = {0,1,2,3,4,5,6,7,8,9};  //定義int數組

b.c

//b.c
#include<a.h>
//后面可以使用ARR

注意,這里必須使用static修飾,否則多個文件包含導致編譯會出現重復定義的錯誤。有興趣的可以嘗試一下。

const修飾的變量是真正的只讀嗎?

使用const修飾之后的變量真的是完全的只讀嗎?看下面這個例子:

#include <stdio.h>
int main(void)
{
    const int a = 2018;
    int *p = &a;
    *p = 2019;
    printf("%d\n",a);
    return 0;
}

運行結果:

2019

可以看到,我們通過另外定義一個指針變量,將被const修飾的a的值改變了。那么我們不禁要問,const到底做了什么呢?它修飾的變量是真正意義上的只讀嗎?為什么它修飾的變量的值仍然可以改變?

#include<stdio.h>
int main(void)
{
    int a = 2019;
    //const int a = 2019;
    printf("%d\n",a);
    return 0;
}

我們分別獲取有const修飾和無const修飾的匯編代碼。
無const修飾,匯編代碼:

.LC0:
        .string "%d\n"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 2019
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

有const修飾,匯編代碼:

.LC0:
        .string "%d\n"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 2019
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

我們發現,並沒有任何差異!當然這一個例子並不能說明所有的問題。但是我們要知道的是,const關鍵字告訴了編譯器,它修飾的變量不能被改變,如果代碼中發現有類似改變該變量的操作,那么編譯器就會捕捉這個錯誤。

那么它在實際中的意義之一是什么呢?幫助程序員提前發現問題,避免不該修改的值被意外地修改,但是無法完全保證不被修改!例如我們可以通過對指針進行強轉:

#include<stdio.h>
void myPrint(const char *str);
void myPrint(const char *str)
{
    char *b = (char *)str;
    b[0] = 'H';
    printf("my print:%s\n",b);

}
int main(void)
{
    char str[] = "hello world";
    myPrint(str);
    return 0;
}

運行結果:

my print:Hello world

也就是說,const關鍵字是給編譯器用的,幫助程序員提早發現可能存在的問題。
但是實際中永遠不要寫這樣的代碼!

總結

介紹了這么多,關鍵點如下:

  • const關鍵字讓編譯器幫助我們發現變量不該被修改卻被意外修改的錯誤。
  • const關鍵字修飾的變量並非真正意義完完全全的只讀。
  • 對於不該被修改的入參,應該用const修飾,這是const使用的常見姿勢。
  • const修飾的變量只能正常賦值一次。
  • 不要試圖將const數據的地址賦給普通指針。
  • 不要忽略編譯器的警告,除非你很清楚在做什么。
  • 雖然可以通過某種不正規途徑修改const修飾的變量,但是永遠不要這么做

思考

  • 與#define相比,const有什么優點?
  • const關鍵字到底該什么時候用?

微信公眾號【編程珠璣】:專注但不限於分享計算機編程基礎,Linux,C語言,C++,算法,數據庫等編程相關[原創]技術文章,號內包含大量經典電子書和視頻學習資源。歡迎一起交流學習,一起修煉計算機“內功”,知其然,更知其所以然。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM