qq郵箱登錄入口網(wǎng)頁版廣州seo網(wǎng)站推廣公司
歡迎關(guān)注博主 Mindtechnist 或加入【智能科技社區(qū)】一起學習和分享Linux、C、C++、Python、Matlab,機器人運動控制、多機器人協(xié)作,智能優(yōu)化算法,濾波估計、多傳感器信息融合,機器學習,人工智能等相關(guān)領(lǐng)域的知識和技術(shù)。
C語言標準定義的32個關(guān)鍵字
- 1. 數(shù)據(jù)類型關(guān)鍵字(12個)
- (1) 聲明和定義的區(qū)別
- (2) 數(shù)據(jù)類型關(guān)鍵字
- 2. 控制語句關(guān)鍵字(12個)
- 3. 存儲類關(guān)鍵字(5個)
- 4. 其他關(guān)鍵字(3個)
專欄:《精通C語言》
1. 數(shù)據(jù)類型關(guān)鍵字(12個)
C語言中的數(shù)據(jù)類型主要有下面幾種。實際上,數(shù)據(jù)類型可以理解為固定大小內(nèi)存塊的別名,給變量指定類型就是告訴編譯器給該變量分配多大的內(nèi)存空間,而變量相當于是內(nèi)存塊的門牌號。
(1) 聲明和定義的區(qū)別
定義可以看作是聲明的一個特例,并非所有的聲明都是定義??梢酝ㄟ^是否分配內(nèi)存來區(qū)分定義和聲明,定義會建立存儲空間,而聲名不會建立存儲空間。
int function()
{//定義int val; //定義一個變量val,此時會給val分配內(nèi)存,由數(shù)據(jù)類型int決定分配多大內(nèi)存,int為4字節(jié)。val = 10; //可以為val賦值。//聲明extern int val_2; //聲明變量val_2,不會建立內(nèi)存。//val_2 = 10; //error: 聲明不會建立內(nèi)存,沒有內(nèi)存空間所以無法賦值。return 0;
}
- 定義:定義是指創(chuàng)建一個對象并為這個對象分配一塊內(nèi)存,同時將變量名和這個內(nèi)存塊進行綁定。但是,同一個變量在同一作用域只能定義一次,如果多次定義的話,編譯器會提示重定義錯誤。
- 聲明:
- 告訴編譯器,某個名稱已經(jīng)被預定了,其他對象/內(nèi)存塊不能再使用這個名稱。
- 告訴編譯器,某個名稱已經(jīng)綁定好內(nèi)存塊了,該對象是在其他位置定義的,這里用到本名稱時不要報錯。
(2) 數(shù)據(jù)類型關(guān)鍵字
char:聲明字符型變量。
char類型用于存儲一個單一字符,即1字節(jié)存儲單元。在給char類型變量賦值時需要把值用英文半角單引號’'引起來,存儲時并非真正把該字符放到存儲空間,而是把該字符對應的ASCII碼存放到存儲單元中。(也可以把char類型看作是1字節(jié)整形)。
ASCII對照表如下
ASCII值 | 控制字符 | ASCII值 | 字符 | ASCII值 | 字符 | ASCII值 | 字符 |
---|---|---|---|---|---|---|---|
0 | NUT | 32 | (space) | 64 | @ | 96 | 、 |
1 | SOH | 33 | ! | 65 | A | 97 | a |
2 | STX | 34 | " | 66 | B | 98 | b |
3 | ETX | 35 | # | 67 | C | 99 | c |
4 | EOT | 36 | $ | 68 | D | 100 | d |
5 | ENQ | 37 | % | 69 | E | 101 | e |
6 | ACK | 38 | & | 70 | F | 102 | f |
7 | BEL | 39 | , | 71 | G | 103 | g |
8 | BS | 40 | ( | 72 | H | 104 | h |
9 | HT | 41 | ) | 73 | I | 105 | i |
10 | LF | 42 | * | 74 | J | 106 | j |
11 | VT | 43 | + | 75 | K | 107 | k |
12 | FF | 44 | , | 76 | L | 108 | l |
13 | CR | 45 | - | 77 | M | 109 | m |
14 | SO | 46 | . | 78 | N | 110 | n |
15 | SI | 47 | / | 79 | O | 111 | o |
16 | DLE | 48 | 0 | 80 | P | 112 | p |
17 | DCI | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 | 50 | 2 | 82 | R | 114 | r |
19 | DC3 | 51 | 3 | 83 | S | 115 | s |
20 | DC4 | 52 | 4 | 84 | T | 116 | t |
21 | NAK | 53 | 5 | 85 | U | 117 | u |
22 | SYN | 54 | 6 | 86 | V | 118 | v |
23 | TB | 55 | 7 | 87 | W | 119 | w |
24 | CAN | 56 | 8 | 88 | X | 120 | x |
25 | EM | 57 | 9 | 89 | Y | 121 | y |
26 | SUB | 58 | : | 90 | Z | 122 | z |
27 | ESC | 59 | ; | 91 | [ | 123 | { |
28 | FS | 60 | < | 92 | / | 124 | | |
29 | GS | 61 | = | 93 | ] | 125 | } |
30 | RS | 62 | > | 94 | ^ | 126 | ` |
31 | US | 63 | ? | 95 | _ | 127 | DEL |
在上面的ASCII碼表中,ASCII值0-31表示非打印控制字符,用于控制打印機等外圍設(shè)備;32-126為打印字符,這些字符在鍵盤上都可以找到;127表示del命令。
轉(zhuǎn)義字符
轉(zhuǎn)義字符 | 含義 | ASCII碼值(十進制) |
---|---|---|
\a | 警報 | 007 |
\b | 退格(BS) ,將當前位置移到前一列 | 008 |
\f | 換頁(FF),將當前位置移到下頁開頭 | 012 |
\n | 換行(LF) ,將當前位置移到下一行開頭 | 010 |
\r | 回車(CR) ,將當前位置移到本行開頭 | 013 |
\t | 水平制表(HT) (跳到下一個TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\ | 代表一個反斜線字符"" | 092 |
’ | 代表一個單引號(撇號)字符 | 039 |
" | 代表一個雙引號字符 | 034 |
? | 代表一個問號 | 063 |
\0 | 數(shù)字0 | 000 |
\ddd | 8進制轉(zhuǎn)義字符,d范圍0~7 | 3位8進制 |
\xhh | 16進制轉(zhuǎn)義字符,h范圍09,af,A~F | 3位16進制 |
int:聲明整型變量。
在C語言標準中并沒有明確規(guī)定整型數(shù)據(jù)的長度,整型數(shù)據(jù)在內(nèi)存中所占的字節(jié)數(shù)與操作系統(tǒng)有關(guān)系。(一般為4字節(jié))
打印格式 | 含義 |
---|---|
%d | 輸出一個有符號的10進制int類型 |
%o | 輸出8進制的int類型 |
%x | 輸出16進制的int類型,字母以小寫輸出 |
%X | 輸出16進制的int類型,字母以大寫寫輸出 |
%u | 輸出一個10進制的無符號數(shù) |
short:聲明短整型變量。
長度一般不長于int型數(shù)據(jù)。(一般為2字節(jié))
long:聲明長整型變量。
長度一般不短于int型數(shù)據(jù)。(Windows為4字節(jié);Linux為4字節(jié)(32位),8字節(jié)(64位)。)
打印格式 | 含義 |
---|---|
%hd | 輸出short類型 |
%d | 輸出int類型 |
%l | 輸出long類型 |
%ll | 輸出long long類型 |
%hu | 輸出unsigned short類型 |
%u | 輸出unsigned int類型 |
%lu | 輸出unsigned long類型 |
%llu | 輸出unsigned long long類型 |
float:聲明單精度浮點型變量。
浮點型變量也叫做實型變量,用于存儲小數(shù)數(shù)值。float單精度浮點型一般占用4字節(jié)存儲空間,7位有效數(shù)字。
double:聲明雙精度浮點型變量。
double雙精度浮點型精度高于float單精度浮點型,占用8字節(jié)存儲空間,15-16位有效數(shù)字。
浮點型變量存儲的是小數(shù),并且浮點型變量的存儲單元是有限的,這就導致一個小數(shù)有效位以外的數(shù)字將被舍去,這樣便會出現(xiàn)一些誤差。尤其是float單精度浮點型,有時候?qū)⒁粋€小數(shù)賦值給一個float型變量,然后打印該浮點型變量都會出現(xiàn)和原小數(shù)不一致這樣的情況。一般使用double雙精度可以提升精度,并且在C語言中,一個小數(shù)后面不加f則被認為是雙精度double類型,只有小數(shù)后面加f才表示float類型,比如3.14f。
signed:聲明有符號類型變量。
缺省時,編譯器默認為signed有符號類型。在計算機中,所有的數(shù)據(jù)都是以01的二進制形式來存儲的,對于有符號數(shù)來說如何表示一個數(shù)值的正負是一個問題,因此便有了原碼、反碼和補碼。
- 原碼:二進制數(shù)據(jù)的最高位用來作為符號位,1表示負數(shù),0表示正數(shù),剩余位來表示這個數(shù)據(jù)的數(shù)值大小(絕對值),也就是說,負數(shù)的原碼是在其絕對值原碼的基礎(chǔ)上將最高位變成0。原碼的表示簡單易懂,正負數(shù)區(qū)分方便且易于轉(zhuǎn)換,但是在實際用于計算時卻不太方便,當兩個正數(shù)做減法運算,或者兩個異號的數(shù)相加時,必須先比較兩個數(shù)的絕對值大小才能進行減法運算,以便于決定最終結(jié)果是正號還是負號。所以,原碼表示數(shù)據(jù)時不便于做加減運算。
- 反碼:正數(shù)的反碼與原碼相同;負數(shù)的反碼是在負數(shù)的原碼基礎(chǔ)上,符號位不變,其它位全部取反。反碼的存在一般是為了方便計算補碼。
- 補碼:對正數(shù)來說,原碼、反碼、補碼是完全一致的;對負數(shù)來說,補碼是在其反碼的基礎(chǔ)上將整個數(shù)加1。在計算機系統(tǒng)中,所有的數(shù)值一律用補碼的形式來存儲。補碼的存在主要有這幾種意義:
- 統(tǒng)一0的編碼。不管是用原碼還是反碼來表示0,都會有兩種表示方式,即正0和負0,但是我們知道,0不區(qū)分正負。這就導致同一個數(shù)值0出現(xiàn)兩種表示方式,而使用補碼表示時,對于正0,原碼為00000000,反碼為00000000,補碼為00000000;對于負0,原碼為10000000,反碼為11111111,補碼為11111111+1=00000000,其中最高位(第九位)數(shù)字1被舍棄。這樣,正0和負0的補碼就一樣了。
- 便于運算。使用補碼進行運算時可以將減法轉(zhuǎn)化為加法,對于任何數(shù)的加減運算,都直接使用補碼進行加法運算即可,并且可以將符號位和其他位統(tǒng)一處理,當兩個用補碼表示的數(shù)相加時,如果最高位(符號位)有進位,則進位直接舍棄。
unsigned:聲明無符號類型變量。
數(shù)據(jù)類型 | 占用空間 | 取值范圍 |
---|---|---|
short | 2字節(jié) | -32768 到 32767 (-2^15 ~ 2^15-1) |
int | 4字節(jié) | -2147483648 到 2147483647 (-2^31 ~ 2^31-1) |
long | 4字節(jié) | -2147483648 到 2147483647 (-2^31 ~ 2^31-1) |
unsigned short | 2字節(jié) | 0 到 65535 (0 ~ 2^16-1) |
unsigned int | 4字節(jié) | 0 到 4294967295 (0 ~ 2^32-1) |
unsigned long | 4字節(jié) | 0 到 4294967295 (0 ~ 2^32-1) |
struct:聲明結(jié)構(gòu)體變量。
數(shù)組是相同類型數(shù)據(jù)的集合,而結(jié)構(gòu)體可以把不同的數(shù)據(jù)組合成一個整體。通過結(jié)構(gòu)體,我們可以把大量的不同類型數(shù)據(jù),甚至是函數(shù)和其他復合類型數(shù)據(jù)打包為一個整體。在使用struct關(guān)鍵字時,應區(qū)分開結(jié)構(gòu)體類型和結(jié)構(gòu)體變量的區(qū)別,聲明結(jié)構(gòu)體類型并不會分配內(nèi)存,只有在定義結(jié)構(gòu)體類型的時候才會分配內(nèi)存。通常struct關(guān)鍵字會和typedef關(guān)鍵字一塊使用,通過別名的方式可以在定義結(jié)構(gòu)體變量時不需要再寫struct關(guān)鍵字。
struct st
{int a;char b;
}; //聲明結(jié)構(gòu)體類型
struct st s_val = {1, 'a'}; //定義結(jié)構(gòu)體變量,分配內(nèi)存//定義結(jié)構(gòu)體變量時不能省略struct關(guān)鍵字typedef struct st
{int a;char b;
}_st; //給結(jié)構(gòu)體類型取別名為_st
_st val = {1, 'a'}; //可以不寫struct
結(jié)構(gòu)體變量所占的存儲空間大小是所有結(jié)構(gòu)體成員所占存儲空間大小的總和,并且需要考慮內(nèi)存對齊方式。而且,空結(jié)構(gòu)體(沒有任何成員)也是占存儲空間的,空結(jié)構(gòu)體占1字節(jié)存儲空間。
在結(jié)構(gòu)體中可以包含一種稱為柔性數(shù)組的成員,柔性數(shù)組是一個未知大小的數(shù)組,它必須是結(jié)構(gòu)體的最后一個成員,并且柔性數(shù)組成員的前面必須有一個其他成員。
struct st
{int val;int arr[0]; //int arr[];
};
這個0長度的數(shù)組成員arr是不占存儲空間的,這個結(jié)構(gòu)體的大小為4字節(jié)。有了這個0長度數(shù)組我們便可以方便的擴展這個結(jié)構(gòu)體的大小了
struct st *p_st = (struct st *)malloc(sizeof(struct st) + 10 * sizeof(int));
如上,我們使用包含0長度數(shù)組的結(jié)構(gòu)體類型定義一個結(jié)構(gòu)體指針,并通過malloc在堆上為其分配一塊內(nèi)存,這塊內(nèi)存的大小為44字節(jié),而結(jié)構(gòu)體類型大小只有4字節(jié),但是我們卻可以像訪問普通數(shù)組一樣通過p_st[i]來訪問這塊內(nèi)存。也就是說,柔性數(shù)組并不是結(jié)構(gòu)體類型的成員,但是通過結(jié)構(gòu)體成員卻可以訪問我們自定義的柔性數(shù)組存儲空間。
最后,在C++中,struct結(jié)構(gòu)體和class類的區(qū)別,struct成員默認是public屬性,而class的成員默認是private屬性。
同樣,在C語言中也可以實現(xiàn)C++面向?qū)ο蟮男Ч?#xff0c;使用struct結(jié)構(gòu)可以實現(xiàn)封裝,而結(jié)構(gòu)體做結(jié)構(gòu)體成員又可以實現(xiàn)C++中的繼承,并且,函數(shù)指針做結(jié)構(gòu)體成員可是模仿C++類中的方法。
union:聲明聯(lián)合數(shù)據(jù)類型。
聯(lián)合union是一個能在同一個存儲空間存儲不同類型數(shù)據(jù)的類型,也就是說,union的所有成員共享同一塊存儲空間,同一存儲空間段可以用來存放幾種不同類型的成員,但每一時刻只有一種起作用。聯(lián)合體所占的存儲空間長度為占用存儲空間最大的成員的長度,所以也叫做共用體。共用體變量中起作用的成員是最后一次存放的成員,在存入一個新的成員后原有的成員的值會被覆蓋。并且,共用體變量的地址和它的各成員的地址都是同一地址。
一個union變量只分配一個足夠大的存儲空間能夠存儲最大長度的成員,而不會給每一個成員都分配內(nèi)存,這是union與struct最大的區(qū)別。union主要用來達到節(jié)省空間的目的,和struct一樣,在C++中,union的成員默認屬性為public。
看下面的例子
typedef union
{int data;char buf[2];
}u_t;int main()
{u_t* p, u;memset(&u, 0, sizeof(u));p = &u;p->buf[0] = 0x12;p->buf[1] = 0x34;printf("%x\n", p->data);return 0;
}
對union成員的訪問也需要考慮大端存儲模式和小端存儲模式。
enum:聲明枚舉類型。
通過enum枚舉類型可以定義枚舉變量,該枚舉變量的值只能是枚舉類型中列舉出來的那些值。
enum 枚舉名
{枚舉值表
};
枚舉值表中的所有可用值是枚舉變量可以使用的值,也成為枚舉元素。枚舉值是常量,在程序中枚舉值不能作為左值(不能給枚舉值使用賦值語句賦值)。另外,枚舉元素本身由系統(tǒng)定義了一個表示序號的數(shù)值從0開始順序定義為0,1,2 …依次遞增,我們也可以顯示的給枚舉元素賦值。
enum day
{mom = 1,tue, //2wed, //3fri = 5,sat, //6sun //7
};
我們知道使用宏定義#define也可以定義常量,但是宏定義常量和枚舉常量是有區(qū)別的,#define 宏常量是在預編譯階段進行簡單替換,而枚舉常量則是在編譯的時候確定其值。
void:聲明空類型指針(void類型指針可以接受任何類型指針的賦值,無需類型轉(zhuǎn)換),聲明函數(shù)無返回值或無參數(shù)等。
void主要的用途是限制函數(shù)的返回值或者函數(shù)參數(shù)。在C語言中,如果一個函數(shù)不加返回值類型限定,那么編譯器會默認該函數(shù)返回整型值,所以,當一個函數(shù)沒有返回值的時候,一定要聲明為void類型。當函數(shù)沒有參數(shù)時,也應該聲明為void。實際上,在C++中函數(shù)參數(shù)為void表示該函數(shù)不接受任何參數(shù),如果調(diào)用該函數(shù)時添加了參數(shù)那么會報錯;而C語言中,參數(shù)為void的函數(shù)可以接受任何類型的參數(shù)。為了統(tǒng)一,無論C還是C++,只要函數(shù)沒有參數(shù),都要顯式指明參數(shù)為void。
void類型指針可以指向任何類型的內(nèi)存塊,但是使用void類型指針的時候要格外注意。在ANSI標準中,不允許對void類型指針進行加減操作,這是因為指針的步長是由指針的類型決定的。比如
int *p = 0xaa;
p++; //指針類型為int,每次加一移動4字節(jié)
這里int類型的指針每次自加一會移動4字節(jié),因為int類型的對象占據(jù)的存儲空間就是4字節(jié)。而void類型的指針在移動時你并不知道它指向的存儲空間的大小。但是在GNU標準中是允許對void類型指針進行加減操作的。為了統(tǒng)一,我們可以在對void類型指針進行加減操作時強制類型轉(zhuǎn)換,以此來說明指針移動步長。
void *p;
(int *)p++;
對于函數(shù)來說,如果函數(shù)的參數(shù)可以是任意類型指針,那么可以將函數(shù)參數(shù)聲明為void*類型,比如典型的C語言內(nèi)存操作函數(shù)memset和memcpy函數(shù),內(nèi)存操作函數(shù)所操作的對象是一塊內(nèi)存本身,本就不應該關(guān)心這塊內(nèi)存是什么類型,只要我們通過函數(shù)參數(shù)告訴編譯器我們要操作的這塊內(nèi)存的大小就行了,這也是C語言內(nèi)存操作函數(shù)的精髓所在,并且也體現(xiàn)了作為一個內(nèi)存操作API的統(tǒng)一性。比如
int buf[20];
memset(&buf, 0, 20 * (sizeof(int)));
這句代碼的意思是把buf這個數(shù)組清0,我們只要把buf這塊內(nèi)存的首地址傳給memset函數(shù),并將要清0的這塊內(nèi)存的大小通過參數(shù)傳入就可以了。
最后,void是一種抽象,可以參考C++中的抽象類來理解。抽象類不能實例化,同樣我們也不能去定義一個void類型的變量,因為在定義變量時,編譯器要為變量分配內(nèi)存,而void類型本身就是一種抽象,編譯器不知道分配多大內(nèi)存給這個變量。通常,void類型用于定義一個可以指向任何類型內(nèi)存塊的指針。
2. 控制語句關(guān)鍵字(12個)
if:條件語句。
else:條件語句中的否定分支,在if后使用或作為else if分支。
switch:開關(guān)語句。
case:開關(guān)語句分支。
case后面的值只能是整型或字符型的常量或者常量表達式。當有較多的case選項時,應該盡量把出現(xiàn)概率更大的case選項放在前面,以提升程序的執(zhí)行效率。
default:開關(guān)語句中的其他分支。
for:循環(huán)語句。
do:循環(huán)語句中的循環(huán)體。
while:循環(huán)語句中的條件。
break:跳出循環(huán)。
continue:跳出本次循環(huán),進入下一次循環(huán)。
goto:無條件跳轉(zhuǎn)。
return:返回語句,可帶參數(shù)。
return用來終止一個函數(shù),并將return后面的值返回給函數(shù)的返回值。在函數(shù)內(nèi)部,當執(zhí)行到return語句的時候就會終止這個函數(shù),并返回值,return語句后面的程序?qū)⒉粫俦粓?zhí)行。
return返回的值不能是存儲在棧上的值(局部變量),因為局部變量在這個函數(shù)結(jié)束的時候被自動銷毀,它的生命周期僅限于這個函數(shù)內(nèi)部,所以不能作為return語句的返回值。
3. 存儲類關(guān)鍵字(5個)
auto:聲明自動變量,缺省時編譯器默認為auto。
默認情況下,缺省時所有變量都是auto的。
extern:聲明外部變量。
extern表示外部的,通過extern聲明的變量或函數(shù)表示該變量或者函數(shù)是在外部文件定義的,告訴編譯器在本文件中遇到該變量或者函數(shù)時,去其他文件中尋找變量或函數(shù)的定義。
register:聲明寄存器變量。
定義寄存器變量,提高效率。register是建議型的指令,而不是命令型的指令,如果CPU有空閑寄存器,那么register就生效,如果沒有空閑寄存器,那么register無效。該關(guān)鍵字請求編譯器盡量的將變量存放在CPU內(nèi)部寄存器中,這樣在訪問變量時不需要再通過內(nèi)存尋址的方式訪問,而是直接在寄存器中訪問,大大提升了訪問速度。但是CPU內(nèi)部寄存器是有限的,所以register關(guān)鍵字只能是盡可能的請求編譯器把變量存放在寄存器,而不是一定存放在寄存器。因為register關(guān)鍵字用于請求將數(shù)據(jù)存放在寄存器,所以使用register修飾符來修飾的變量必須是能被CPU寄存器所接受的類型,即register修飾的變量必須是長度小于或等于整形長度的值。同時,因為register修飾的變量可能會存放在寄存器中(也可能存放在內(nèi)存中),所以不能對register修飾的變量進行取址操作,即不能通過取址操作符&來獲取register修飾變量的地址。
static:聲明靜態(tài)變量。
-
修飾變量
static關(guān)鍵字可以修飾全局變量和局部變量,并且他們都會被存放在內(nèi)存的靜態(tài)區(qū)。
- 靜態(tài)全局變量:限定變量的作用域為當前文件,即從變量定義之處開始一直到當前文件末尾,當前文件中該變量定義之前也無法使用(除非加extern聲明),其他文件中即便是使用extern聲明也無法使用。
- 靜態(tài)局部變量:定義在函數(shù)體內(nèi)部,并且作用域僅限于當前函數(shù),當前文件該函數(shù)體外部無法使用。因為static修飾的靜態(tài)變量存放在內(nèi)存的靜態(tài)區(qū),所以函數(shù)運行結(jié)束這個靜態(tài)變量也不會被銷毀,函數(shù)下次被調(diào)用時這個變量的值依然存在,也就是我們說的靜態(tài)局部變量只能被初始化一次,并且有記憶功能,下次調(diào)用函數(shù)時可以使用上次函數(shù)調(diào)用結(jié)束時靜態(tài)局部變量的值。需要注意的是,普通的局部變量存放在棧區(qū),函數(shù)調(diào)用結(jié)束變量就會被析構(gòu),也就是說普通局部變量的聲明周期為定義該變量的函數(shù)體內(nèi)。而靜態(tài)局部變量存放在靜態(tài)區(qū),它的生命周期是整個程序執(zhí)行期間,也就是說定義該靜態(tài)局部變量的函數(shù)執(zhí)行完畢,并不會析構(gòu)靜態(tài)局部變量,而是在當前程序執(zhí)行完畢才會析構(gòu)。
-
修飾函數(shù)
使用static關(guān)鍵字修飾函數(shù)可以將函數(shù)變?yōu)殪o態(tài)函數(shù),也成為內(nèi)部函數(shù),靜態(tài)函數(shù)的作用域為當前文件,在該文件之外無法訪問。使用靜態(tài)函數(shù)的好處是可以避免不同文件中函數(shù)同名引起的錯誤,但是會導致該文件之外無法調(diào)用的問題。
const:聲明只讀變量(C和C++區(qū)別)。
在C語言中,const定義的并不是真正的常量,而是具有只讀屬性的變量,其本質(zhì)還是變量,只不過不可修改(實際上在C語言中是可以通過指針等其他方式間接修改的);而在C++中,const定義的是真正的常量,C++中是通過符號表一一對應的方式實現(xiàn)的。通過下面的例子也可以證明
const int NUM = 10;
char buf[NUM];
上面代碼在C語言中編譯不通過,但是在C++中編譯通過。我們知道,定義數(shù)組時要指定數(shù)組大小,以便于編譯器分配內(nèi)存。在C語言中編譯不通過也就證明了const定義的依然是變量,而不是常量。
編譯器通常不會為const只讀變量分配存儲空間,而是將它們保存在符號表中,這使得它們成為一個編譯期間的值,沒有讀寫內(nèi)存的操作,大大提高了效率。另外需要注意const與宏#define的區(qū)別
#define NUM 1 //宏定義一個常量
const int VAL = 2; //還沒有將VAL放入內(nèi)存中
int a = VAL; //此時為VAL分配內(nèi)存,后面不再分配內(nèi)存
int b = NUM; //預編譯期間進行宏替換,分配內(nèi)存
int c = VAL; //不會分配內(nèi)存
int d = NUM; //宏替換,還會分配內(nèi)存
從匯編的角度來看,const定義的只讀變量只是給出了內(nèi)存地址,而#define給出的是立即數(shù)。所以,在程序運行過程中,const定義的只讀變量只有一份拷貝(全局只讀變量存放在靜態(tài)區(qū),而不是堆棧),而#define定義的常量在內(nèi)存中有多份拷貝。#define在預編譯的時候進行宏替換,而const只讀變量是在編譯時確定它的值。另外,#define定義的常量沒有類型,而const修飾的只讀變量是有類型的。const 修飾的只讀變量不能用來作為定義數(shù)組的維數(shù),
也不能放在case 關(guān)鍵字后面。
最后,當const修飾指針時,放在不同位置所代表的含義也不同。
const int *p; //const修飾指針指向的內(nèi)存,//指針本身可變,指針指向的內(nèi)存不可修改
int const *p; //const修飾指針指向的內(nèi)存,//指針本身可變,指針指向的內(nèi)存不可修改int * const p; //const修飾指針本身,//指針指向不可修改,指針指向的內(nèi)存可以修改const int const *p; //指針本身和指針指向的內(nèi)存都不可修改
4. 其他關(guān)鍵字(3個)
sizeof:計算一個對象所占的字節(jié)數(shù)。
sizeof在使用時雖然會加括號,但是他并不是函數(shù),而是一個關(guān)鍵字。實際上,通過sizeof計算一個變量所占的內(nèi)存大小時可以省略括號,sizeof(val)和sizeof val都可以,但是在計算數(shù)據(jù)類型的大小時必須加括號sizeof(int),否則的話會和類型擴展混淆,比如unsigned int就是擴展為無符號整型變量。因為sizeof不是函數(shù),所以在使用時不需要包含任何頭文件,但是sizeof是有返回值的,范圍值類型為size_t,在32位操作系統(tǒng)下是unsigned int類型。
在計算一個字符串變量的大小時要區(qū)分sizeof與strlen的區(qū)別,strlen是一個函數(shù),用于計算字符串的長度,所以不包含字符串最后的’\n’,而sizeof是計算變量所占內(nèi)存大小,包括字符串結(jié)束符’\n’。
typedef:取別名。
typedef可以為一個數(shù)據(jù)類型定義一個新的名字,但是不能創(chuàng)建一個新的類型。與#define不同,typedef僅限于為數(shù)據(jù)類型取別名,而不能為表達式或具體的值取別名。#define發(fā)生在預處理階段,typedef發(fā)生在編譯階段。
volatile:防止編譯器優(yōu)化,說明變量在程序執(zhí)行中可被隱含地改變。
volatile是易變的意思,它修飾的變量表示該變量的值可能被某些因素所修改,比如操作系統(tǒng)、硬件外設(shè)或其他線程等等。volatile關(guān)鍵字修飾的變量,編譯器不會對改變量進行優(yōu)化訪問。
當我們讀取一個普通變量的值時,編譯器為了加快訪問速度,一般會在緩存中讀取該變量的值,而不是直接去寄存器取值。但是,有時候寄存器的值并不是通過程序去修改的,比如嵌入式開發(fā)中常用開發(fā)板進行開發(fā),很多時候寄存器的值會被芯片的外設(shè)所修改。這時候,雖然我們程序中并沒有去修改寄存器的值,但是寄存器值卻因為外界因素而發(fā)生了改變。當我們?nèi)ピL問這種變量的時候,如果不加volatile關(guān)鍵字,編譯器默認會在緩存中取值,而此時緩存中的值是一個舊值,變量的真實值已經(jīng)發(fā)生了改變。所以,加volatile關(guān)鍵字就是為了告訴編譯器,不要對訪問進行優(yōu)化,每次都應該去變量的地址處去訪問變量值,以此來確保每次取到的都是變量的最新值。
下面通過例子說明
int val = 1;
int a = val;
int b = val;
在上面的代碼中,變量val沒有被用作左值(也就是說在程序中變量val的值沒有被顯式改變),這時候編譯器就會認為變量val的值沒有發(fā)生過改變,并會對val的訪問做優(yōu)化處理。當給變量a賦值時,編譯器取到val的值之后賦給a,并且這個值會被放到緩存中。當給b賦值時,因為編譯器認為val的值沒有發(fā)生改變,所以會直接在緩存中取val的值,而不會去val變量的地址處取值,這樣大大提高了訪問速度。這么做的前提是,兩次訪問val的語句之間沒有將val當左值的語句(即修改val值的語句)。
如果說將val變量修飾為volatile變量,那就不同了。
volatile int val = 1;
int a = val;
int b = val;
此時,編譯器認為val的值是隨時可能發(fā)生改變的,不管程序中有沒有將val當作左值的語句,每次訪問val變量都會區(qū)val變量的地址處去訪問。也就是說,在給a賦值時,編譯器將會在val地址處取值,當給b賦值時,編譯器依然會去val變量的地址處取值。
一般來說,對寄存器變量、端口數(shù)據(jù)變量、多線程共享數(shù)據(jù)變量使用volatile修飾可以保證對變量真實值的穩(wěn)定訪問。