const關(guān)鍵字到底該什么用?

文 | 守望先生
經(jīng)授權(quán)轉(zhuǎn)載自公眾號(hào)編程珠璣(id:shouwangxiansheng)
前言
我們都知道使用const關(guān)鍵字限定一個(gè)變量為只讀,但它是真正意義上的只讀嗎?實(shí)際中又該如何使用const關(guān)鍵字?在解答這些問(wèn)題之前,我們需要先理解const關(guān)鍵字的基本使用。本文說(shuō)明C中的const關(guān)鍵字,不包括C++。
基本介紹
const是constant的簡(jiǎn)寫,是不變的意思。但并不是說(shuō)它修飾常量,而是說(shuō)它限定一個(gè)變量為只讀。
修飾普通變量
例如:
const int NUM = 10; //與int const NUM等價(jià)
NUM = 9;? //編譯錯(cuò)誤,不可再次修改
由于使用了const修飾NUM,使得NUM為只讀,因此嘗試對(duì)NUM再次賦值的操作是非法的,編譯器將會(huì)報(bào)錯(cuò)。正因如此,如果需要使用const修飾一個(gè)變量,那么它只能在開始聲明時(shí)就賦值,否則后面就沒有機(jī)會(huì)了(后面會(huì)講到一種特殊情況)。
修飾數(shù)組
例如使用const關(guān)鍵字修飾數(shù)組,使其元素不允許被改變:
const int arr[] = {0,0,2,3,4}; //與int const arr[]等價(jià)
arr[2] = 1; //編譯錯(cuò)誤
試圖修改arr的內(nèi)容的操作是非法的,編譯器將會(huì)報(bào)錯(cuò):
error: assignment of read-only location ‘a(chǎn)rr[2]’
修飾指針
修飾指針的情況比較多,主要有以下幾種情況:
1.const 修飾 *p,指向的對(duì)象只讀,指針的指向可變:
int a = 9;
int b = 10;
const int *p = &a;//p是一個(gè)指向int類型的const值,與int const *p等價(jià)
*p = 11;? ? //編譯錯(cuò)誤,指向的對(duì)象是只讀的,不可通過(guò)p進(jìn)行改變
p = &b;? ? ?//合法,改變了p的指向
這里為了便于理解,可認(rèn)為const修飾的是*p,通常使用*對(duì)指針進(jìn)行解引用來(lái)訪問(wèn)對(duì)象,因而,該對(duì)象是只讀的。
2.const修飾p,指向的對(duì)象可變,指針的指向不可變:
int a = 9;
int b = 10;
int * const p = &a;//p是一個(gè)const指針
*p = 11;? ? //合法,
p = &b;? ? ?//編譯錯(cuò)誤,p是一個(gè)const指針,只讀,不可變
3.指針不可改變指向,指向的內(nèi)容也不可變
int a = 9;
int b = 10;
const int * const p = &a;//p既是一個(gè)const指針,同時(shí)也指向了int類型的const值
*p = 11;? ? //編譯錯(cuò)誤,指向的對(duì)象是只讀的,不可通過(guò)p進(jìn)行改變
p = &b;? ? ?//編譯錯(cuò)誤,p是一個(gè)const指針,只讀,不可變
看完上面幾種情況之后是否會(huì)覺得混亂,并且難以記憶呢?我們使用一句話總結(jié):
const放在*的左側(cè)任意位置,限定了該指針指向的對(duì)象是只讀的;const放在*的右側(cè),限定了指針本身是只讀的,即不可變的。
如果還不是很好理解,我們可以這樣來(lái)看,去掉類型說(shuō)明符,查看const修飾的內(nèi)容,上面三種情況去掉類型說(shuō)明符int之后,如下:
const *p; //修飾*p,指針指向的對(duì)象不可變
* const p; //修飾p,指針不可變
const * const p; //第一個(gè)修飾了*p,第二個(gè)修飾了p,兩者都不可變
const右邊修飾誰(shuí),就說(shuō)明誰(shuí)是不可變的。上面的說(shuō)法僅僅是幫助理解和記憶。借助上面這種理解,就會(huì)發(fā)現(xiàn)以下幾種等價(jià)情況:
const int NUM = 10; //與int const NUM等價(jià)
int a = 9;
const int *p? = &a;//與int const *p等價(jià)
const int arr[] = {0,0,2,3,4}; //與int const arr[]等價(jià)
const關(guān)鍵字該怎么用
前面介紹了這么多內(nèi)容,是不是都常用呢?const關(guān)鍵字到底該怎么用?
修飾函數(shù)形參
實(shí)際上,為我們可以經(jīng)常發(fā)現(xiàn)const關(guān)鍵字的身影,例如很多庫(kù)函數(shù)的聲明:
char *strncpy(char *dest,const char *src,size_t n);//字符串拷貝函數(shù)
int? *strncmp(const char *s1,const char *s2,size_t n);//字符串比較函數(shù)
通過(guò)看strncpy函數(shù)的原型可以知道,源字符串src是只讀的,不可變的,而dest并沒有該限制。我們通過(guò)一個(gè)小例子繼續(xù)觀察:
//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;
}
在這個(gè)例子中,我們不希望myPrint函數(shù)修改傳入的字符串內(nèi)容,因此入?yún)⑹褂昧薱onst限定符,表明傳入的字符串是只讀的,因此,如果myPrint函數(shù)內(nèi)部如果嘗試對(duì)str進(jìn)行修改,將會(huì)報(bào)錯(cuò):
$ gcc -o test test.c
test.c:6:12: error: assignment of read-only location ‘*str’
? ? ?str[0] = 'H';
因此,我們自己在編碼過(guò)程中,如果確定傳入的指針參數(shù)僅用于訪問(wèn)數(shù)據(jù),那么應(yīng)該將其聲明為一個(gè)指向const限定類型的指針,避免函數(shù)內(nèi)部對(duì)數(shù)據(jù)進(jìn)行意外地修改。
修飾全局變量
我們知道,使用全局變量是一種不安全的做法,因?yàn)槌绦虻娜魏尾糠侄寄軌驅(qū)θ謹(jǐn)?shù)據(jù)進(jìn)行修改。而如果對(duì)全局變量增加const限定符(假設(shè)該全局?jǐn)?shù)據(jù)不希望被修改),就可以避免被程序其他部分修改。這里有兩種使用方式。
第一種,在a文件中定義,其他文件中使用外部聲明,例如:
a.h
//a.h
const int ARR[] = {0,1,2,3,4,5,6,7,8,9};? //定義int數(shù)組
b.c
//b.c
extern const int ARR[];? ?//注意,這里不能再對(duì)ARR進(jìn)行賦值
//后面可以使用ARR
第二種,在a文件中定義,并使用static修飾,b文件包含a文件,例如:
a.h
//a.h
static const int ARR[] = {0,1,2,3,4,5,6,7,8,9};? //定義int數(shù)組
b.c
//b.c
#include<a.h>
//后面可以使用ARR
注意,這里必須使用static修飾,否則多個(gè)文件包含導(dǎo)致編譯會(huì)出現(xiàn)重復(fù)定義的錯(cuò)誤。有興趣的可以嘗試一下。
const修飾的變量是真正的只讀嗎?
使用const修飾之后的變量真的是完全的只讀嗎?看下面這個(gè)例子:
#include <stdio.h>
int main(void)
{
? ? const int a = 2018;
? ? int *p = &a;
? ? *p = 2019;
? ? printf("%d\n",a);
? ? return 0;
}
運(yùn)行結(jié)果:
2019
可以看到,我們通過(guò)另外定義一個(gè)指針變量,將被const修飾的a的值改變了。那么我們不禁要問(wèn),const到底做了什么呢?它修飾的變量是真正意義上的只讀嗎?為什么它修飾的變量的值仍然可以改變?
#include<stdio.h>
int main(void)
{
? ? int a = 2019;
? ? //const int a = 2019;
? ? printf("%d\n",a);
? ? return 0;
}
我們分別獲取有const修飾和無(wú)const修飾的匯編代碼。
無(wú)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
我們發(fā)現(xiàn),并沒有任何差異!當(dāng)然這一個(gè)例子并不能說(shuō)明所有的問(wèn)題。但是我們要知道的是,const關(guān)鍵字告訴了編譯器,它修飾的變量不能被改變,如果代碼中發(fā)現(xiàn)有類似改變?cè)撟兞康牟僮?,那么編譯器就會(huì)捕捉這個(gè)錯(cuò)誤。
那么它在實(shí)際中的意義之一是什么呢?幫助程序員提前發(fā)現(xiàn)問(wèn)題,避免不該修改的值被意外地修改,但是無(wú)法完全保證不被修改!例如我們可以通過(guò)對(duì)指針進(jìn)行強(qiáng)轉(zhuǎn):
#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;
}
運(yùn)行結(jié)果:
my print:Hello world
也就是說(shuō),const關(guān)鍵字是給編譯器用的,幫助程序員提早發(fā)現(xiàn)可能存在的問(wèn)題。
總結(jié)
介紹了這么多,關(guān)鍵點(diǎn)如下:
const關(guān)鍵字讓編譯器幫助我們發(fā)現(xiàn)變量不該被修改卻被意外修改的錯(cuò)誤。
const關(guān)鍵字修飾的變量并非真正意義完完全全的只讀。
對(duì)于不該被修改的入?yún)?,?yīng)該用const修飾,這是const使用的常見姿勢(shì)。
const修飾的變量只能正常賦值一次。
不要試圖將非const數(shù)據(jù)的地址賦給普通指針。
不要忽略編譯器的警告,除非你很清楚在做什么。
雖然可以通過(guò)某種不正規(guī)途徑修改const修飾的變量,但是永遠(yuǎn)不要這么做。
---END---
關(guān)注公眾號(hào)baiwenkeji第一時(shí)間獲得嵌入式干貨。
技術(shù)交流加個(gè)人威信13266630429,驗(yàn)證:B站