【C语言重点难点精讲】关键字精讲
必讀:
- C語言關鍵字是一個非常重要的話題,因為它能在相當的程度上將C語言的核心內容串聯起來,起到一種提綱挈領的效果
- 下面的內容重點提及的是相應關鍵字特別值得注意的地方,這些地方是我們經常忽略的,而且考試也會經常涉及到
- 講解這些關鍵字時默認大家都有C語言的基礎,因此不會從0開始談起
文章目錄
- 一:auto關鍵字
- 二:register關鍵字
- (1)存儲器分級
- (2)register修飾變量
- 三:static關鍵字
- (1)修飾全局變量和函數
- (2)修飾局部變量
- 四:sizeof關鍵字
- 五:signed、unsigned關鍵字
- 六:if、else
- (1)關于C語言中bool類型
- (2)float與“零值”的比較
- (3)if和else的匹配問題
- 七:switch-case組合
- 八:do 、while 、for關鍵字
- 九:goto關鍵字
- 十:void關鍵字
- 十一:return關鍵字
- 十二:const關鍵字
- 十三:volatile關鍵字
- 十四:extern關鍵字
- 十五:struct關鍵字
- 十六:Union關鍵字
- 十七:enum關鍵字
- 十八:typedef關鍵字
- 總結
- (1)關鍵字分類
一般來講,C語言一共有32個關鍵字(C90標準),當然C99后又新增了5個關鍵字,不過我們還是重點討論這32個關鍵字
| auto | 聲明自動變量 |
| short | 聲明短整型變量或函數 |
| int | 聲明整形變量或函數 |
| long | 聲明長整形變量或函數 |
| float | 聲明浮點型變量或函數 |
| double | 聲明雙精度變量或函數 |
| char | 聲明字符型變量或函數 |
| struct | 聲明結構體變量或函數 |
| union | 聲明共用數據類型 |
| enum | 聲明枚舉類型 |
| typedef | 用以給數據類型取別名 |
| const | 聲明只讀變量 |
| unsigned | 聲明無符號類型變量或函數 |
| signed | 聲明有符號類型變量或函數 |
| extern | 聲明變量是在其它文件中正聲明 |
| register | 聲明寄存器變量 |
| static | 聲明靜態變量 |
| volatile | 說明變量在程序執行過程中可以被隱含地改變 |
| void | 聲明函數無返回值或無參數,聲明無類型指針 |
| if | 條件語句 |
| else | 條件語句否定分支(與if連用) |
| switch | 用于開關語句 |
| case | 開關語句分支 |
| for | 一種循環語句 |
| do | 循環語句的循環體 |
| while | 循環語句的循環條件 |
| goto | 無條件跳轉語句 |
| continue | 結束當前循環,開始下一輪循環 |
| break | 跳出當前循環 |
| default | 開關語句中的“其它”分支 |
| sizeof | 計算數據類型長度 |
| return | 子程序返回語句,循環條件 |
一:auto關鍵字
一般來說,在代碼塊中定義的變量(也即局部變量),默認都是auto修飾的,不過會省略。但是一定要注意:不是說默認的所有變量都是auto的,它只是一般用來修飾局部變量
當然在C語言中,我們已經不再使用auto了,或者稱其為過時了,但是在C++中卻賦予了auto新的功能,它變得更加強大了。有興趣請點擊2-6:C++快速入門之內聯函數,auto關鍵字,C++11基于范圍的for循環和nullptr
二:register關鍵字
register意味寄存器
(1)存儲器分級
這個概念我們在計算機組成原理中講得已經非常詳細了,請點擊:(計算機組成原理)第三章存儲系統-第一節:存儲器分類、多級存儲系統和存儲器性能指標
(2)register修飾變量
可以看出,如果將變量放到寄存器中,那么效率就會提高。可以用register修飾的變量有以下幾種
- 局部的(全局變量會導致CPU寄存器長時間被占用)
- 不會被寫入的(寫入的話就需要被寫回內存,要是這樣的話register就沒有意義的)
- 高頻需要被讀取的
如果要使用,不要大量使用,因為寄存器的數量有限。
另外還需要注意的一點是:被register修飾的變量,是不能取地址的,因為它已經放在了寄存器中,地址會涉及到內存,但是可以被寫入
當然這個register關鍵字現在也基本不會用了,因為如今的編譯器優化已經很智能了,不需要你自己手動優化
三:static關鍵字
(1)修飾全局變量和函數
我們知道全局變量(加入關鍵字extern聲明)和函數都可以跨文件使用的
但是有一些應用場景中,我們不想讓全局變量或函數跨文件訪問應該怎么辦呢?那么就可以使用static關鍵字
static int g_value=100;//修飾staic后全局變量將不能跨文件使用
可以看出被static修飾的全局變量是不能被外部其他文件直接訪問的,而只能在本文件內使用
- 需要注意這里說的是直接訪問,那意味著可以間接訪問,比如通過函數的方式實現
同樣,被static修飾的函數只能在本文件內訪問,而不能在外部其它文件中直接訪問
- 還是需要注意,這里是不能直接訪問,并不是不能訪問,比如可以通過函數嵌套的方式
static這種功能本質為了封裝,因為我們可以把一些不需要或者不想要暴露的細節保護起來了,只提供一個功能函數個,該函數在內部調用它們即可,這樣的話代碼安全性也比較高
(2)修飾局部變量
我們知道全局變量僅在當前代碼塊內有效,代碼塊結束之后局部變量會自動釋放空間,因此下面代碼的結果就會是這樣
如果使用static修飾局部變量,會更改其生命周期,但其作用域不變,如下當用static修飾后,變量i地址不變,且結果累加
static為什么可以更改局部變量的生命周期呢?因為被static修飾的變量會將其從棧區移動到數據段,當然這就涉及到了C/C++地址空間的問題了
查看實際地址
#include <stdio.h> #include <stdlib.h>int gobal_val=100;//全局變量已經初始化 int gobal_unval;//全局變量未初始化 int main(int argc,char* argv[],char* env[]) {printf("main函數處于代碼段,地址為:%p,十進制為:%d\n",main,main);printf("\n");printf("全局變量gobal_val,地址為:%p,十進制為:%d\n",&gobal_val,&gobal_val);printf("\n");printf("全局變量未初始化gobal_unval,地址為:%p,十進制為:%d\n",&gobal_unval,&gobal_unval);printf("\n");char* mem=(char*)malloc(10);printf("mem開辟的堆空間,mem是堆的起始地址,是%p,十進制為:%d\n",mem,mem);printf("\n"); printf("mem是指針變量,指針變量在棧上開采,其地址為%p,十進制為:%d\n",&mem,&mem);printf("\n"); printf("命令行參數起始地址:%p,十進制為:%d\n",argv[0],argv[0]);printf("\n");printf("命令行參數結束地址:%p,十進制為:%d\n",argv[argc-1],argv[argc-1]);printf("\n");printf("第一個環境變量的地址:%p,十進制為:%d\n",env[0],env[0]);printf("\n"); }四:sizeof關鍵字
sizeof用于確定一種類型對應在開辟空間的時候的大小,注意它是關鍵字而不是函數
它的基本用法就是下面這樣,這我就不再多說了(注意Windows32位平臺)
int main() {cout <<"char:" <<sizeof(char) << endl;cout << "short:" << sizeof(short) << endl;cout << "int:" << sizeof(int) << endl;cout << "long:" << sizeof(long) << endl;cout << "long long:" << sizeof(long long) << endl;cout << "float:" << sizeof(float) << endl;cout << "double:" << sizeof(double) << endl; }
特別注意,sizeof求一種類型大小的寫法共有三種,特別第三種很多人認為是錯誤的,而考試就愛給你整這些犄角旮旯的東西
五:signed、unsigned關鍵字
這一部分需要涉及數據存儲及原碼反碼等基礎概念,請參照以下章節
- (計算機組成原理)第二章數據的表示和運算-第二節1:定點數的表示(原碼、反碼、補碼和移碼)
- (計算機組成原理)第二章數據的表示和運算-第二節2:原碼、反碼、補碼和移碼的作用
第一點: 需要深刻理解signed和unsigned只是對數據的一種解讀方式,其中signed會把首位數據解讀為符號位,符號位用于標識其正負,unsigned的首位也算作數據位,也就是說類型決定了其讀寫的時候的解釋方式
因此像下面的這樣一句代碼,看似不合適,但是它是沒有問題的,因為存儲時對于變量a它只關心我所開辟的空間上的二進制數據放進了沒有,并不關心你之前是怎么樣的
也就是說b里面的存儲的內容會按照不同的解釋方式而變化
第二點: signed和unsigned也是相關C語言考試的重點,下面代碼可以幫助你很好的理解
#include <windows.h> #include <stdio.h> #include <stdlib.h>int main() {unsigned int i;for (i = 9; i >= 0; i--){printf("%u\n", i);Sleep(100);}}由于變量i是無符號整形,因此在與0比較的時候,不會小于0,所以會死循環,并且打印時從9開始減小到0,然后接著是42億多,然后依次減小,最后再到0
第三點:使用unsigned時初始化變量時,建議帶上u,也即
unsigned int b=10u;六:if、else
if和else如果簡單點學其實也很簡單,主要就是以下內容
(1)關于C語言中bool類型
在C99之前C語言是沒有bool類型的,在C00之后引入了_Bool類型,它處于頭文件stdbool.h中
#include <windows.h> #include <stdio.h> #include <stdbool.h>int main() {bool ret = false;ret = true;printf("%d\n", sizeof(ret));//在vs中為1return 0; }源碼中顯示就是一個宏定義
// // stdbool.h // // Copyright (c) Microsoft Corporation. All rights reserved. // // The C Standard Library <stdbool.h> header. // #ifndef _STDBOOL #define _STDBOOL#define __bool_true_false_are_defined 1#ifndef __cplusplus#define bool _Bool #define false 0 #define true 1#endif /* __cplusplus */#endif /* _STDBOOL *//** Copyright (c) 1992-2010 by P.J. Plauger. ALL RIGHTS RESERVED.* Consult your license regarding permissions and restrictions. V5.30:0009 */(2)float與“零值”的比較
使用if進行浮點數比較時,下面的代碼正確嗎?按照道理1.0-0.9=0.1,應該是正確的
int main() {double x = 1.0;double y = 0.1;if ((x - 0.9) == y){printf("correct\n");}else{printf("wrong\n");}return 0; }但實際結果卻是:
為什么會這樣呢,其實這涉及到的的浮點數如何在計算機中存儲的問題,詳細細節請移步:
- (計算機組成原理)第二章數據的表示和運算-第三節1:浮點數的表示
其實如果你打印出來后,你會發現兩者根本不相等,精度丟失
既然浮點數比較時不能直接使用“==”,那么應該怎么辦呢?
比較時,有點像高等數學中的取極限,δ\deltaδ可以被視為一個誤差范圍,這個δ\deltaδ需要你自己定義,當兩者的絕對值之差小于該范圍時,C語言就認定他們相等,否則不相等
int main() {double x = 1.0;double y = 0.1;if (fabs((1.0 - 0.9)-0.1) < CMP){printf("correct\n");}else{printf("wrong\n");}return 0; }這里的δ\deltaδ其實C語言已經幫我們定義好了,處在float.h頭文件之下
#define DBL_EPSILON 2.2204460492503131e-016 /* smallest such that 1.0+DBL_EPSILON !=1.0 */ #define FLT_EPSILON 1.192092896e-07F /* smallest such that 1.0+FLT_EPSILON !=1.0 */回歸到主題,如果0被定義為了浮點數,我們要判斷某個數是否是0的話可以這樣寫
int main() {double x = 0;if (fabs(x) < DBL_EPSILON)//注意不要寫成<={printf("x是0\n");}else{printf("x不是0\n");}return 0; }(3)if和else的匹配問題
這是一個老生常談的話題。下面代碼看似會輸出“2”,但實際什么都不會輸出
int main() {int x = 0;int y = 1;if (10 == x)if (11 == y)printf("1\n");elseprintf("2\n");return 0;}這屬于代碼風格問題,else匹配采用的是就近原則
七:switch-case組合
第一: switch case的基本語法結構
switch(整型變量/常量/整型表達式)//注意只能這三種 {case var1://判斷在這里break;case var2:break;case var3:break;default:break; }其中case完成的判斷功能,break完成的是分支功能,所以如果忘記寫break,就會導致擊穿現象
第二: 注意一個語法細節,就是case里面如果要定義變量的話,必須加花括號
int main() {int num = 0;scanf("%d", &num);switch (num){case 1:{int a = 1;//注意花括號printf("first\n");break;}case 2:printf("second\n");break;case 3:printf("third\n");break;default:printf("other\n");break;}}第三: 多條件匹配時可以這樣寫
int main() {int num = 0;scanf("%d", &num);switch (num){case 1:case 2:case 3:printf("first\n");break;case 4:case 5:printf("second\n");break;default:printf("other\n");break;} }第四: 注意default可以放在任意位置
第五: switch中可以使用return語句,但不建議使用
八:do 、while 、for關鍵字
第一: 這三種循環基本語法如下
//while 條件初始化 while(條件判定){//業務更新條件更新 }//for for(條件初始化; 條件判定; 條件更新){//業務代碼 }//do while 條件初始化 do{條件更新}while(條件判定)第二: 三種循環對應的死循環寫法如下
while(1){ }for(;;){ }do{ }while(1);第三: break是跳出該循環,continue是結束一次循環
int main() {while (1){int c = getchar();if (c == '#'){break;//表示接受到“#”就結束}putchar(c);} } int main() {while (1){int c = getchar();if (c == '#'){continue;//表示接受到“#”略過}putchar(c);} }
這里需要注意for循環的continue,經常愛考察。for循環在continue時是跳到循環更新處
第四: for循環區間建議是前閉后開
九:goto關鍵字
第一: goto基本控制邏輯或者基本語法如下
int main() {int i = 0; START:printf("[%d]goto running ... \n", i);Sleep(1000);++i;if (i < 10){goto START;}printf("goto end ... \n");return 0; }十:void關鍵字
第一: void是不能用來定義變量的。因為定義變量的本質就是開辟內存空間,而void作為空類型是不應該開辟空間的,即使開辟了空間,也僅僅作為一個占位符來看待,所以這種行為直接就會被編譯器禁止
第二: 首先說明一點,在C語言中函數是可以不帶返回值的,返回類型為整型
的確在有些場景中我們是不需要函數的返回值的,但如果采用上面的那種方式書寫,很容易產生閱讀上的歧義,因此如果函數不想讓其返回,可以用void,這里一定要將其理解為一種占位符,它是告知用戶和編譯器的
第二: 在如下情形中,編譯器是不會報錯的,因此會有很大的安全隱患
而如果限制void后,編譯器將會報警。因此void可以充當函數的形參列表,用于告知編譯器和用戶該函數不需要傳入參數
第四: void的確不可以定義變量,但是void*可以,因為指針變量的大小是明確的(Windows32位下為4個字節大小)
void* p=nullptr;第五: void*可以被任何類型的指針接受,void*也可以接受任意指針類型
int main() {void* p = NULL;int* x = NULL;double* y = NULL;p = x;//void*接受int*p = y;//void*接受double* }- 尤其注意 void*也可以接受任意指針類型,這一點通常用作一些通用接口的設計
第六: 我們知道,普通類型的指針可以進行位運算
int* p=NULL; p++; p--;而對于void*呢?要視平臺而定,一般VS下不可以,Linux下可以(Linux認為void是1)
十一:return關鍵字
第一:return不可以返回指向“棧內存”的指針,因為在函數體結束時會被自動銷毀
因此下面的語句會出現亂碼
char* show() {char str[] = "hello world";return str; } int main() {char* s = show();printf("%s\n", s);return 0; }第二: 函數的返回值,通過寄存器的方式,返回給函數的調用方(注意區別上面,上面不能那樣做,因為那是指向棧的指針)
int GetData() {int x = 0x11223344;printf("running\n");return x; }int main() {int y = GetData();printf("return value:%x\n", y);return 0; }return x對應的匯編代碼為:
十二:const關鍵字
第一: const修飾的變量不可以直接被修改
const int a=10; a=20;//錯誤但間接可以修改
int main() {const int a = 10;int* p = &a;printf("change before:%d\n", a);*p = 20;printf("change after:%d\n", a);return 0; }那么既然這樣其意義何在呢?其實const修飾變量主要有下面兩個目的
真正意義上的不可修改如C語言中的常量字符串
int main() {char* str = "hello world\n";//常量字符串*str ='E';return 0; }第二: const int i 和int const i是等價的
第三: const修飾的變量同樣不能作為數組定義的一部分(標準C不可以,但是在Linux可以)
int main() {const int n = 100;int arr[n];//錯誤return 0; }第四: const在定義時必須初始化
第五: 建立只讀數組可以這樣寫
int const a[5]={1,2,3,4,5}; 或 const int a[5]={1,2,3,4,5};第六 :const放在誰后面就修飾誰,因此它與指針的關系如下
①:const int* i 與int const* i等價
其中i是指針,const修飾了int,表示指針可以變化,但是指針指向內容不能被修改
②:int* const i
const修飾的是指針,所以指針不可變,但是指向的內容可變
③:const int* const i=&a
表示指針不可以變,指向的內容也不可以變
第七: const 也可以用來修飾函數參數,表明不可更改
void show(const int* _p)//防止指針指向內容被修改 {printf("value:%d\n", *_p);*_p = 20;//非法操作 }int main() {int a = 10;int* p = &a;show(p); }十三:volatile關鍵字
有關volatile關鍵字的作用在下面這篇文章中有詳細介紹,請移步
- Linux系統編程34:進程信號之可重入函數,volatile關鍵字的作用和SIGHLD
volatile關鍵字的作用:volatile將保持內存的可見性,一個變量一旦被volatile修飾,那么系統總是會從內存中讀取數據,而不是從寄存器
需要注意const和volatile的區別,兩者并不矛盾
- const要求你不要進行寫入
- volatile意思是你讀的時候每次要從內存讀
十四:extern關鍵字
extern關鍵字這里就多說了,非常簡單
十五:struct關鍵字
第一: struct基本介紹
定義
初始化(不能初始化后整體賦值)
成員訪問
結構體傳參
第二: 在Linux中空結構體的大小為0
第三: 柔性數組
我們知道C語言中是不能有這樣的操作的,就是用變量對數組進行初始化
int main() {int i=0;scanf("%d",&i);int arr[i]; }在C語言中如果要完成動態數組,可以借助柔性數組。使用柔性數組時,我們采用結構體的方式,將一個數組作為結構體成員放置于其中,但注意該數組不初始化,什么都不寫
在上述結構體中,有兩個結構體變量,數組似乎不占空間,但其實不然。實則,該結構體將其所占空間劃分為兩部分,一部分就是那個整形,一部分用于動態開辟,以此滿足數組的動態變化
既然是柔性,那就可以修改,使用realloc修改
十六:Union關鍵字
第一: Union是什么
聯合也是一種特殊的自定義類型 這種類型定義的變量也包含一系列的成員,特征是這些成員公用同一塊空間,聯合體內所有成員的起始地址都是一樣的,每個成員都認為它是聯合體的第一個成員
第二: 根據內存地址分布,如下,b永遠定義在相對于a的低地址處
union Un {int a;int b; };根據這一性質我們可以利用聯合體來判斷機器是大端機還是小端機,如下
union Un {int a;char b; }; int main() {union Un u;u.a = 1;if (u.b == 1){printf("小端機\n");}else {printf("大端機\n");}return 0; }這是因為
十七:enum關鍵字
enum用于枚舉一堆常量,就像Excel中的數據有效性,它規定了數據的取值類型,比如說男性它只有男或女
定義
enum color是枚舉類型,括號中的內容是枚舉類型的可能取值,也叫做枚舉常量。這些可能取值實際上是有值的,默認是從0開始的
當然是可以修改的
枚舉的這樣的寫法其實和宏的寫法在代碼的邏輯上是相似的
十八:typedef關鍵字
第一: typedef的作用就是為類型重新命名
typedef unsigned int u_int;int main() {u_int a = 10;return 0; }
typedef 經常會在結構體重命名里
第二: 大家一定要對typedef理解到位,如下
int main() {int* a, b;//a是指針類型//b是整形 }typedef從某種方面可以理解一種全新的類型,因此下面的*就不存在和誰結合的問題了
typedef int* int_p;int main() {int_p a, b;//a是指針類型//b也是指針類型 }而對于#define而言它就是一種文本替換了,因此
#define int_p int*int main() {int_p a, b;//a是指針類型//b是整形 }第三:使用typedef定義后的新類型,不能配合其他關鍵字使用
#define INT_DE int typedef int INT_TY;int main() {unsigned INT_DE a;//正確unsigned INT_TY b;//錯誤 }總結
(1)關鍵字分類
數據類型關鍵字 :
- char
- short
- int
- long
- signed
- unsigned
- float
- double
- struct
- union
- enum
- void
控制語句關鍵字 :
1:循環控制
- for
- do
- while
- break
- continue
2:條件語句
- if
- else
- goto
3:開關語句
- switch
- case
- default
4:返回語句
- return
存儲類型關鍵字 :
- auto
- extern
- register
- static
- typedef
這里需要補充一點:使用typedef時不能同時出現多個存儲關鍵字
typedef static int//錯誤 typedef register int//錯誤其他關鍵字 :
- const
- sizeof
- volatile
總結
以上是生活随笔為你收集整理的【C语言重点难点精讲】关键字精讲的全部內容,希望文章能夠幫你解決所遇到的問題。