C語言
一、C程序運行機制
1.1 C程序運行機制簡述
-
編輯:比如編寫我們的hello.c 文件, 就是 源代碼
-
編譯:將 hello.c 程序 翻譯成 目標文件(hello.obj) // 在計算機底層執行
-
鏈接:將目標文件 hello.obj + 庫文件 生成可執行文件 (MyProject01.exe) //在計算機底層執行
-
運行:執行 .exe文件, 得到運行結果
-
圖解:

1.2 編譯、鏈接和運行詳解
1.2.1 什么是編譯
- 有了C源文件,通過編譯器將其編譯成obj 文件( 目標文件)
- 如果程序沒有錯誤,沒有任何提示,但在Debug目錄下會出現一個Hello.obj文件,該文件稱為目標文件
1.2.2 什么是鏈接
- 有了目標文件(.obj文件),通過鏈接程序將其和運行需要的c 庫文件鏈接 成exe文件( 可執行文件)
- 如果程序沒有錯誤,沒有任何提示,但在Debug目錄下會出現一個項目名.exe文件,該文件稱為可執行文件
- 為什么需要鏈接庫文件呢? 因為我們的C程序中會使用 C程序庫的內容,比如<stdio.h> <stdlib.h> 中的函數printf() system()等等, 這些函數不是程序員自己寫的,而是C程序庫中提供的,因此需要鏈接
- 鏈接后,生成的.exe 文件,比obj 文件大了很多
1.2.3 什么是運行
- 有了可執行的exe文件, 也稱為可執行程序 (二進制文件),就可以在控制臺下可以直接運行 exe文件
注意:對修改后的hello.c源文件需要重新編譯鏈接,生成新的exe文件后,再執行,才能生效
- C程序源文件以“c”為擴展名
- C程序的執行入口是main()函數
- C語言嚴格區分大小寫
- C程序由一條條語句構成,每個語句以“;”結束
- 大括號都是成對出現的,缺一不可
二、數據類型
2.1 常量
2.1.1 關鍵字

2.1.2 常量
- 常量是固定值,在程序執行期間不能改變。這些固定的值,又叫做字面量
- 常量可以是任何的基本數據類型,比如整數常量、浮點常量、字符常量,或字符串字面值,也有枚舉常量
- 常量的值在定義后不能進行修改
2.1.3 整型常量
-
整數常量可以是十進制、八進制或十六進制的常量。前綴指定基數:0x 或 0X 表示十六進制,0 表示八進制,不帶前綴則默認表示十進制。整數常量也可以帶一個后綴,后綴是 U 和 L 的組合,U 表示無符號整數(unsigned),L 表示長整數(long)。后綴可以是大寫,也可以是小寫,U 和 L 的順序任意
-
示例:
85 /* 十進制 */ 0213 /* 八進制 */ 0x4b /* 十六進制 */ 30 /* 整數 */ 30u /* 無符號整數 */ 30l /* 長整數 */ 30ul /* 無符號長整數 */ -
C語言如何表示相應進制數
| 十進制 | 以正常數字1-9開頭,如123 |
|---|---|
| 八進制 | 以數字0開頭,如0123 |
| 十六進制 | 以0x開頭,如0x123 |
| 二進制 | C語言不能直接書寫二進制數 |
2.1.4 浮點型常量
-
浮點常量由整數部分、小數點、小數部分和指數部分組成。您可以使用小數形式或者指數形式來表示浮點常量
-
示例:
3.14159; //double 常量 314159E-5; // 科學計數法 3.1f; //float常量
2.1.5 字符常量
- 字符常量是括在單引號中,例如,'x' 可以存儲在 char 類型的變量中。字符常量可以是一個普通的字符(例如 'x')、一個轉義序列(例如 '\t')
- 字符常量舉例說明:'X','Y','A','b','1','\t
2.1.5 常量的定義
-
使用 #define 預處理器
#define 常量名 常量值 #define PI 3.14 //定義常量 PI 常量值3.14 int main() { //PI = 3.1415 可以嗎? double area; double r = 1.2;//半徑 area = PI * r * r; printf("面積 : %.2f", area); getchar(); return 0; } -
使用 const 關鍵字
//可以使用 const 聲明指定類型的常量 const 數據類型 常量名 = 常量值; const double PI = 3.14; int main() { //PI = 3.1415 可以嗎? double area; double r = 1.2; area = PI * r * r; printf("面積 : %.2f", area); getchar(); return 0; } -
const 和 和 #define 的區別
- const定義的常量時,帶類型,define不帶類型
- const是在 編譯、運行的時候起作用,而define是在編譯的預處理階段起作用
- define只是簡單的替換,沒有類型檢查。簡單的字符串替換會導致 邊界效應
- const常量可以進行調試的,define是不能進行調試的,主要是預編譯階段就已經替換掉了,調試的時候就沒它了
- const不能重定義,不可以定義兩個一樣的,而define通過undef取消某個符號的定義,再重新定義
- define可以配合#ifdef、 #ifndef、 #endif 來使用, 可以讓代碼更加靈活,比如我們可以通過#define 來 啟動或者關閉 調試信息
2.2 變量
2.2.1 變量的概念
-
變量相當于內存中一個數據存儲空間的表示,你可以把變量看做是一個房間的門牌號,通過門牌號我們可以找到房間,而通過變量名可以訪問到變量(值)
-
標識符命名規則:
- 標識符不能是關鍵字
- 標識符只能由字母、數字、下劃線組成
- 第一個字符必須為字母或下劃線
- 標識符中字母區分大小寫
-
變量特點:
- 變量在編譯時為其分配相應的內存空間
- 可以通過其名字和地址訪問相應內存
-
使用示例:
#include <stdio.h> void main() { int num = 1 ; //整型 double score = 2.3; //小數 char gender = 'A'; //字符 char name[] = "尚硅谷"; //字符串 //說明 //1. 如果輸出的整數 %d //2. 如果輸出的是小數 %f , 如果希望保留小數點 %.2f //3. 如果輸出的是字符 %c //4. 如果輸出的是字符串 %s //5. 在輸出不同數據時,對應的格式化的 形式要對應起來 printf("num=%d score=%.2f gender=%c name=%s", num, score, gender, name); getchar(); } -
變量使用注意事項
- 變量表示內存中的一個存儲區域(不同的數據類型,占用的空間大小不一樣)
- 該區域有自己的名稱 和類型
- 變量必須先聲明,后使用
- 聲明和定義區別
- 聲明變量不需要建立存儲空間,如:extern int a;
- 定義變量需要建立存儲空間,如:int b;
- 變量在同一個作用域內不能重名
- 變量三要素 (變量名+值+數據類型)
2.2.2 變量的數據類型

注意:
- 在c中,沒有字符串類型,使用字符數組表示字符串
- 在不同的系統上,部分數據類型的字節長度不一樣
2.3 整型
- 整型的類型
| 類型 | 存儲大小 | 值范圍 |
|---|---|---|
| char | 1字節 | -128 即-(2^7) 到 127 (2^7-1) |
| unsigned char | 1字節 | 0 到 255 (2^8 - 1) |
| signed char | 1字節 | -128 即-(2^7) 到 127 (2^7-1) |
| int/signed int | 2或者4字節 | -32,768 (- 2^15 ) 到 32,767 (2^15-1)或 -2,147,483,648 (- 2^31) 到 2,147,483,647 (2^31 -1) |
| unsigned int | 2或者4字節 | 0 到65,535 (2^16-1) 或0 到4,294,967,295 (2^32 -1) |
| short/signed short | 2字節 | -32,768 (- 2^15)到 32,767 (2^15 -1) |
| unsigned short | 2字節 | 0 到65,535 (2^16 - 1) |
| long/signed long | 4字節 | -2,147,483,648 (- 2^31) 到 2,147,483,647 (2^31 - 1) |
| unsigned long | 4字節 | 0 到4,294,967,295 (2^32 - 1) |
- 整型變量的輸出格式
| 打印格式 | 含義 |
|---|---|
| %d | 輸出一個有符號的10進制int類型 |
| %o(字母o) | 輸出8進制的int類型 |
| %x | 輸出16進制的int類型,字母以小寫輸出 |
| %X | 輸出16進制的int類型,字母以大寫輸出 |
| %u | 輸出一個10進制的無符號數 |
-
示例:
#include <stdio.h> int main() { int a = 123; //定義變量a,以10進制方式賦值為123 int b = 0567; //定義變量b,以8進制方式賦值為0567 int c = 0xabc; //定義變量c,以16進制方式賦值為0xabc printf("a = %d\n", a);//a = 123 printf("8進制:b = %o\n", b);//8進制:b = 567 printf("10進制:b = %d\n", b);//10進制:b = 375 printf("16進制:c = %x\n", c);//16進制:c = abc printf("16進制:c = %X\n", c);//16進制:c = ABC printf("10進制:c = %d\n", c);//有符號方式打印:d = -1 unsigned int d = 0xffffffff; //定義無符號int變量d,以16進制方式賦值 printf("有符號方式打?。篸 = %d\n", d);//有符號方式打?。篸 = -1 printf("無符號方式打印:d = %u\n", d);//無符號方式打?。篸 = 4294967295 return 0; }
注:C語言的整型類型,分為有符號 signed 和無符號 unsigned 兩種,默認是 signed
2.4 浮點型
- 浮點型的分類
| 類型 | 存儲大小 | 值范圍 | 精度 |
|---|---|---|---|
| float 單精度 | 4字節 | 1.2E-38 到 3.4E+38 | 6 位小數 |
| double 雙精度 | 8字節 | 2.3E-308 到 1.7E+308 | 15 位小數 |
注: 關于浮點數在機器中存放形式的簡單說明,浮點數=符號位+指數位+尾數位 , 浮點數是近似值。尾數部分可能丟失,造成精度損失
- 注意事項
- 浮點型常量默認為double型 ,聲明float型常量時,須后加‘f’或‘F’
- 浮點型常量有兩種表示形式
- 十進制數形式:如:5.12 512.0f .512 (必須有小數點)
- 科學計數法形式:如:5.12e2 、 5.12E-2
- 通常情況下,應該使用double型,因為它比float型更精確。
2.5 布爾類型
- C語言標準(C89)沒有定義布爾類型,所以C語言判斷真假時以0為假,非0為真
- C語言標準(C99)提供了_Bool 型,_Bool仍是整數類型,但與一般整型不同的是,_Bool變量只能賦值為0或1,非0的值都會被存儲為1,C99還提供了一個頭文件<stdbool.h> 定義了bool代表_Bool,true代表1,false代表0。只要導入 stdbool.h ,就能方便的操作布爾類型了 , 比如 bool flag = false
2.6 字符類型(char)
-
基本介紹
字符類型可以表示單個字符 ,字符類型是char,char是1個字節(可以存字母或者數字),多個字符稱為字符串,在C語言中 使用 char數組 表示,數組不是基本數據類型,而是構造類型
-
注意事項
- 在C中,char的本質是一個整數,在輸出時,是 ASCII碼對應
的字符 - 可以直接給char賦一個整數,然后輸出時,會按照對應的
ASCII 字符輸出
- 在C中,char的本質是一個整數,在輸出時,是 ASCII碼對應
-
字符類型本質
- 字符型存儲到計算機中,需要將字符對應的碼值(整數)找出來
- 存 儲:字符'a'——>碼值 (97)——>二進制 (1100001)——>存儲()
- 讀 ?。憾M制(1100001)——>碼值(97)——> 字符'a'——>讀取(顯示)
- 字符型存儲到計算機中,需要將字符對應的碼值(整數)找出來
-
轉義字符
轉義字符 含義 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 數字0 000 \ddd 8進制轉義字符,d范圍0~7 3位8進制 \xhh 16進制轉義字符,h范圍09,af,A~F 3位16進制 注意:加粗字體標注的為不可打印字符。
2.7 sizeof關鍵字
- sizeof不是函數,所以不需要包含任何頭文件,它的功能是計算一個數據類型的大小,單位為字節
- sizeof的返回值為size_t
- size_t類型在32位操作系統下是unsigned int,是一個無符號的整數
2.8 類型限定符
| 限定符 | 含義 |
|---|---|
| extern | 聲明一個變量,extern聲明的變量沒有建立存儲空間。 extern int a;//變量在定義的時候創建存儲空間 |
| const | 定義一個常量,常量的值不能修改。 const int a = 10; |
| volatile | 防止編譯器優化代碼 |
| register | 定義寄存器變量,提高效率。register是建議型的指令,而不是命令型的指令,如果CPU有空閑寄存器,那么register就生效,如果沒有空閑寄存器,那么register無效。 |
2.9字符串格式化輸入輸出
2.9.1 字符串常量
-
字符串是內存中一段連續的char空間,以'\0'(數字0)結尾
-
字符串常量是由雙引號括起來的字符序列,如“china”、“C program”,“$12.5”等都是合法的字符串常量
-
字符串常量與字符常量的不同:

每個字符串的結尾,編譯器會自動的添加一個結束標志位'\0',即 "a" 包含兩個字符'a'和’\0’
2.9.2 printf函數和putchar函數
-
printf()是C語言標準庫函數,用于將格式化后的字符串輸出到標準輸出。標準輸出,即標準輸出文件,對應終端的屏幕。printf()申明于頭文件stdio.h。
-
函數原型:
int printf ( const char * format, ... ); -
返回值:
正確返回輸出的字符總數,錯誤返回負值,與此同時,輸入輸出流錯誤標志將被置值,可由指示器ferror來檢查輸入輸出流的錯誤標志。
-
調用格式:printf()函數的調用格式為:
printf("格式化字符串",輸出表列)格式化字符串包含三種對象,分別為:
- 字符串常量
- 格式控制字符串
- 轉義字符
字符串常量原樣輸出,在顯示中起提示作用。輸出表列中給出了各個輸出項,要求格式控制字符串和各輸出項在數量和類型上應該一一對應。其中格式控制字符串是以%開頭的字符串,在%后面跟有各種格式控制符,以說明輸出數據的類型、寬度、精度等
-
-
putchar函數是字符輸出函數,其功能是在終端(顯示器)輸出單個字符。其函數原型為
int putchar(int ch);ch表示要輸出的字符內容,返回值作用為:如果輸出成功返回一個字符的ASCII碼,失敗則返回EOF即-1
-
printf格式字符:
打印格式 對應數據類型 含義 %d int 接受整數值并將它表示為有符號的十進制整數 %hd short int 短整數 %hu unsigned short 無符號短整數 %o unsigned int 無符號8進制整數 %u unsigned int 無符號10進制整數 %x,%X unsigned int 無符號16進制整數,x對應的是abcdef,X對應的是ABCDEF %f float 單精度浮點數 %lf double 雙精度浮點數 %e,%E double 科學計數法表示的數,此處"e"的大小寫代表在輸出時用的"e"的大小寫 %c char 字符型??梢园演斎氲臄底职凑誂SCII碼相應轉換為對應的字符 %s char * 字符串。輸出字符串中的字符直至字符串中的空字符(字符串以'\0‘結尾,這個'\0'即空字符)即碰到‘\0’就會停止輸出 %p void * 以16進制形式輸出指針 %% % 輸出一個百分號 printf附加格式:
字符 含義 l(字母l) 附加在d,u,x,o前面,表示長整數 - 左對齊 如%-5d m(代表一個整數) 數據最小寬度 如%5d 0(數字0) 將輸出的前面補上0直到占滿指定列寬為止不可以搭配使用 - m.n(代表一個整數) m指域寬,即對應的輸出項在輸出設備上所占的字符數。n指精度,用于說明輸出的實型數的小數位數。對數值型的來說,未指定n時,隱含的精度為n=6位。 -
示例:
#include <stdio.h> int main() { int a = 100; printf("a = %d\n", a);//格式化輸出一個字符串 printf("%p\n", &a);//輸出變量a在內存中的地址編號 printf("%%d\n"); char c = 'a'; putchar(c);//putchar只有一個參數,就是要輸出的char long a2 = 100; printf("%ld, %lx, %lo\n", a2, a2, a2); long long a3 = 1000; printf("%lld, %llx, %llo\n", a3, a3, a3); int abc = 10; printf("abc = '%6d'\n", abc); printf("abc = '%-6d'\n", abc); printf("abc = '%06d'\n", abc); printf("abc = '%-06d'\n", abc); double d = 12.3; printf("d = \' %-10.3lf \'\n", d); return 0; }輸出結果:

2.9.3 scanf函數
scanf函數介紹
-
scanf()是C語言中的一個輸入函數。與printf函數一樣,都被聲明在頭文件stdio.h里,因此在使用scanf函數時要加上#include <stdio.h>。它是格式輸入函數,即按用戶指定的格式從鍵盤上把數據輸入到指定的變量之中
-
函數原型
int scanf(const char * format,...);-
參數:函數的第一個參數是格式字符串,它指定了輸入的格式,并按照格式說明符解析輸入對應位置的信息并存儲于可變參數列表中對應的指針所指位置。每一個指針要求非空,并且與字符串中的格式符一一順次對應
-
返回值:scanf函數返回成功讀入的數據項數,讀入數據時遇到了“文件結束”則返回EOF
-
實例說明:scanf
("%d %d",&a,&b);- 函數返回值為int型。如果a和b都被成功讀入,那么scanf的返回值就是2
- 如果只有a被成功讀入,返回值為1
- 如果a和b都未被成功讀入,返回值為0
- 如果遇到錯誤或遇到end of file,返回值為EOF。end of file相當于Ctrl+z 或者Ctrl+d
-
-
格式說明符
格式說明符 作用 c 讀入單個字符(后面不會加上空字節 s 讀入一個的字符序列,后面會加上空字節,遇到空白字符(\t \r \n 空格等)完成讀取。 d 讀入可選有符號十進制整數 p 讀入一個指針值 -
第一個參數格式字符串中的空白字符和非空白字符
- 空白字符會使scanf函數在讀操作中略去輸入中的一個或多個空白字符。空白符可以是空格、制表符也即\t和換行符;本質上,控制串中的空白符使 scanf() 在輸入流中讀,但不保存結果,直到發現非空白字符為止。
- 一個非空白字符會使scanf()函數在讀入時剔除掉與這個非空白字符相同的字符;非空白符使 scanf() 在流中讀一個匹配的字符并忽略之。例如,"%d,%d" 使 scanf() 先讀入一個整數,讀入中放棄逗號,然后讀另一個整數 如 12,13
注意:
-
scanf() 中用于保存讀入值的變元必須都是變量指針,即相應變量的地址
-
在輸入流中,數據項必須由空格、制表符和換行符分割。逗號和分號等不是分隔符,比如以下代碼
scanf("%d%d",&r,&c);//將接受輸入 10 20,但遇到 10,20 則失敗 -
格式命令可以說明最大域寬。 在百分號(%)與格式碼之間的整數用于限制從對應域讀入的最大字符數
scanf("%20s",address);//可以向 address 讀入不多于 20 個字符 //如果輸入流的內容多于 20 個字符,則下次 scanf() 從此次停止處開始讀入。 若達到最大域寬前已遇到空白符,則對該域的讀立即停止;此時,scanf() 跳到下一個域。 -
雖然空格、制表符和新行符都用做域分割符號,但讀單字符操作中卻按一般字符處理
scanf("%c%c%c",&a,&b,&c);//對輸入流 "x y" 調用,返回后,x 在變量 a 中,空格在變量 b 中,y 在變量 c 中 -
在高版本的 Visual Studio 編譯器中,scanf 被認為是不安全的,被棄用,應當使用scanf_s代替 scanf
scanf函數使用常見問題
-
如何讓scanf()函數正確接受有空格的字符串?
#include <stdio.h> int main() { char a[20]; scanf("%s", a); printf("%s\n", a); return 0; }-
運行結果:

-
結果分析:
上述程序并沒有hello world。因為scanf掃描到"o"后面的空格就認為對str的掃描結束(空格沒有被掃描),并忽略后面的"world"。殘留的信息 "world"是存在于stdin流中,而不是在鍵盤中。
-
解決方案:
#include <stdio.h> int main() { char a[20]; scanf("%[^\n]", a); printf("%s\n", a); return 0; }
-
-
鍵盤緩沖區殘余信息問題
#include <stdio.h> int main() { int a; char c; while (1) { scanf("%d", &a); scanf("%c", &c); printf("a=%d c=%c\n", a, c); } return 0; }-
運行結果:

-
結果分析:
輸入1后輸入"Enter"鍵,即向鍵盤緩沖區發去一個“換行"(\n),在這里\n被scanf()函數處理掉了,“錯誤”地賦給了c.
-
解決方案:
可以在兩個scanf()函數之后加getchar()
-
2.9.4 getchar函數
getchar函數介紹
-
函數原型
int getchar(void);// 返回類型為int,參數為void -
頭文件:#include<stdio.h>
-
返回值:
- getchar返回的是字符的ASCII碼值(整型)
- getchar在讀取結束或者失敗的時候,會返回EOF;(EOF意思是end of file,本質上是-1)
-
讀取方式:只能輸入字符型,輸入時遇到回車鍵才從緩沖區依次提取字符。
-
結束輸入的方式:以Enter結束輸入(空格不結束),接受空格符
-
舍棄回車符的方法:以Enter結束輸入時,接受空格,會舍棄最后的回車符
getchar函數執行過程詳解
-
程序執行到getchar()函數時,自動從輸入緩沖區中去找字符,如果輸入緩沖區中沒有字符的話,那么就等待用戶輸入字符,此時用戶使用鍵盤輸入的字符,被輸入到輸入緩沖區中,鍵盤輸入字符的時候首先進入輸入緩沖區,然后getchar()函數獲得的字符是從輸入緩沖區中提取的且每次只能提取一個字符。
-
用法示例
#include<stdio.h> int main() { int ch = getchar();//輸入字符,最好用整型接收 putchar(ch); return 0; } -
結果解析:
它的簡單意思就是從鍵盤讀入一個字符,然后輸出到屏幕。理所當然,我們輸入A,輸出就是A,輸入B,輸出就是B。
那么我們如果輸出的是ABC呢?答案是A。
解釋如下:當我們從鍵盤輸入字符‘A’,‘B’, 'C',并按下回車后,我們的輸入被放入了輸入緩沖區,這個時候getchar()會從緩沖區中讀取我們剛才的輸入,一次只讀一個字符,所以字符A就被拿出來了,賦值給了ch,然后putchar()又將ch放在了標準輸出,也就是這里的屏幕,所以我們看見了最終的顯示結果A。同時字符‘A’也被緩沖區釋放了,而字符‘B’,'C'仍然被留在了緩沖區。而這樣是很不安全的,有可能下次使用的時候,我們的緩沖區會讀到一些垃圾,但是當程序結束的時候,它會自動刷新。
三、運算符
3.1 三元運算符
-
基本語法
條件表達式 ? 表達式1: 表達式2
- 如果條件表達式為非0 (真),運算后的結果是表達式1;
- 如果條件表達式為0 (假),運算后的結果是表達式2;
-
使用細節
- 表 達式1和表達式2要為可以賦給接收變量的類型( 或可以自動轉換), 否則會有精度損失 如:int n=a>b?1.1:1.2 //警告 double->int
- 三元運算符可以轉成if--else 語句
3.2 運算符優先級
| 優先級 | 運算符 | 名稱或含義 | 使用形式 | 結合方向 | 說明 |
|---|---|---|---|---|---|
| 1 | [] | 數組下標 | 數組名[常量表達式] | 左到右 | 無 |
| () | 圓括號 | (表達式)/函數名(形參表) | |||
| . | 成員選擇(對象) | 對象.成員名 | |||
| -> | 成員選擇(指針) | 對象指針->成員名 | |||
| 2 | - | 負號運算符 | -表達式 | 右到左 | 單目運算符 |
| ~ | 按位取反運算符 | ~表達式 | |||
| ++ | 自增運算符 | ++變量名/變量名++ | |||
| -- | 自減運算符 | --變量名/變量名-- | |||
| * | 取值運算符 | *指針變量 | |||
| & | 取地址運算符 | &變量名 | |||
| ! | 邏輯非運算符 | !表達式 | |||
| (類型) | 強制類型轉換 | (數據類型)表達式 | 無 | ||
| sizeof | 長度運算符 | sizeof(表達式) | |||
| 3 | / | 除 | 表達式/表達式 | 左到右 | 雙目運算符 |
| * | 乘 | 表達式*表達式 | |||
| % | 余數(取模) | 整型表達式%整型表達式 | |||
| 4 | + | 加 | 表達式+表達式 | 左到右 | 雙目運算符 |
| - | 減 | 表達式-表達式 | |||
| 5 | << | 左移 | 變量<<表達式 | 左到右 | 雙目運算符 |
| >> | 右移 | 變量>>表達式 | |||
| 6 | > | 大于 | 表達式>表達式 | 左到右 | 雙目運算符 |
| >= | 大于等于 | 表達式>=表達式 | |||
| < | 小于 | 表達式<表達式 | |||
| <= | 小于等于 | 表達式<=表達式 | |||
| 7 | == | 等于 | 表達式==表達式 | 左到右 | 雙目運算符 |
| != | 不等于 | 表達式!= 表達式 | |||
| 8 | & | 按位與 | 表達式&表達式 | 左到右 | 雙目運算符 |
| 9 | ^ | 按位異或 | 表達式^表達式 | 左到右 | 雙目運算符 |
| 10 | | | 按位或 | 表達式|表達式 | 左到右 | 雙目運算符 |
| 11 | && | 邏輯與 | 表達式&&表達式 | 左到右 | 雙目運算符 |
| 12 | || | 邏輯或 | 表達式||表達式 | 左到右 | 雙目運算符 |
| 13 | ?: | 條件運算符 | 表達式1? 表達式2: 表達式3 | 右到左 | 三目運算符 |
| 14 | = | 賦值運算符 | 變量=表達式 | 右到左 | 無 |
| /= | 除后賦值 | 變量/=表達式 | |||
| *= | 乘后賦值 | 變量*=表達式 | |||
| %= | 取模后賦值 | 變量%=表達式 | |||
| += | 加后賦值 | 變量+=表達式 | |||
| -= | 減后賦值 | 變量-=表達式 | |||
| <<= | 左移后賦值 | 變量<<=表達式 | |||
| >>= | 右移后賦值 | 變量>>=表達式 | |||
| &= | 按位與后賦值 | 變量&=表達式 | |||
| ^= | 按位異或后賦值 | 變量^=表達式 | |||
| |= | 按位或后賦值 | 變量|=表達式 | |||
| 15 | , | 逗號運算符 | 表達式,表達式,… | 左到右 | 無 |
四、程序流程控制
4.1 switch分支結構
4.1.1 基本語法
switch(表達式){
case 常量 1 : // 當表達式值等于 常量 1
語句塊 1 ;
break; // 退出 switch
case 常量 2 ; // 含義一樣
語句塊 2 ;
break ;
...
case 常量 n;
語句塊 n ;
break ;
default :
default 語句塊 ;
break ;
}
示例:
#include <stdio.h>
int main()
{
char c;
c = getchar();
switch (c) //參數只能是整型變量
{
case '1':
printf("OK\n");
break;//switch遇到break就中斷了
case '2':
printf("not OK\n");
break;
default://如果上面的條件都不滿足,那么執行default
printf("are u ok?\n");
}
return 0;
}
4.1.2 switch 細節討論
- switch 語句中的 expression 是一個常量表達式,必須是一個整型(char、short、int、long等) 或枚舉類型
- case子句中的值必須是常量,而不能是變量
- default子句是可選的,當沒有匹配的case時,執行default
- break語句用來在執行完一個case分支后使程序跳出switch語句塊;
- 如果沒有寫break,會執行下一個case 語句塊,直到遇到break 或者執行到switch結尾, 這個現象稱為穿透.
4.2 do...while循環控制
4.2.1基本語法
①循環變量初始化;
do{
②循環體(多條語句);
③循環變量迭代;
}while(④循環條件);
注意:do – while 后面有一個 分號,不能省略
- 執行流程圖:

-
用法示例:
#include <stdio.h> int main() { int a = 1; do { a++; printf("a = %d\n", a); } while (a < 10); return 0; } -
注意事項
do..while循環是先執行,再判斷
4.3 跳轉控制語句
4.3.1 break語句
- 在switch條件語句和循環語句中都可以使用break語句:
- 當它出現在switch條件語句中時,作用是終止某個case并跳出switch結構
- 當它出現在循環語句中,作用是跳出當前內循環語句,執行后面的代碼
- 當它出現在嵌套循環語句中,跳出最近的內循環語句,執行后面的代碼
4.3.2 continue語句
-
在循環語句中,如果希望立即終止本次循環,并執行下一次循環,此時就需要使用continue語句
-
以do-while使用continue為例,畫出示意圖

-
注意事項
continue語句,只能配合循環語言使用,不能單獨和switch/if使用
4.3.3 goto語句
-
C 語言的 goto 語句可以無條件地轉移到程序中指定的行
-
goto語句通常與條件語句配合使用。可用來實現條件轉移,跳出循環體等功能
-
在C程序設計中 一般不主張使用goto 語句, 以免造成程序流程的混亂,使理解和調試程序都產生困難
-
用法示例
int main() { printf("start\n"); goto lable1; //lable1 稱為標簽 printf("ok1\n"); printf("ok2\n"); lable1: printf("ok3\n"); printf("ok4\n"); } //輸出 ok3 和 ok4
五、數組和字符串
5.1 數組概述
-
在程序設計中,為了方便處理數據把具有相同類型的若干變量按有序形式組織起來——稱為數組
-
數組就是在內存中連續的相同類型的變量空間。同一個數組所有的成員都是相同的數據類型,同時所有的成員在內存中的地址是連續的。

注意:數組名就代表該數組的首地址 ,即 a[0]地址
-
數組屬于構造數據類型
-
一個數組可以分解為多個數組元素:這些數組元素可以是基本數據類型或構造類型
int a[10]; struct Stu boy[10]; -
按數組元素類型的不同,數組可分為:數值數組、字符數組、指針數組、結構數組等類別
int a[10]; char s[10]; char *p[10];
-
-
通常情況下,數組元素下標的個數也稱為維數,根據維數的不同,可將數組分為一維數組、二維數組、三維數組、四維數組等。通常情況下,我們將二維及以上的數組稱為多維數組
5.2 一維數組
5.2.1 一維數組的定義
-
數組名字符合標識符的書寫規定(數字、英文字母、下劃線)
-
數組名不能與其它變量名相同,同一作用域內是唯一的
-
方括號[]中常量表達式表示數組元素的個數;int a[3]表示數組a有3個元素。其下標從0開始計算,因此3個元素分別為a[0],a[1],a[2]
-
定義數組時[]內最好是常量,使用數組時[]內即可是常量,也可以是變量
-
數組的定義
數據類型 數組名 [數組大小]; int a [5]; // a 數組名,類型 int , [5] 大小, 即 a 數組最多存放 5個int 數據 //賦初值 a[0] = 1; a[1] = 30; ....
5.2.2 數組的使用
-
訪問數組元素
數組名[下標] 比如:你要使用 a 數組的第三個元素 a[2 ], 下標是從 0 開始計算
-
一維數組的初始化
在定義數組的同時進行賦值,稱為初始化。全局數組若不初始化,編譯器將其初始化為零。局部數組若不初始化,內容為隨機值
int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定義一個數組,同時初始化所有成員變量 int a[10] = { 1, 2, 3 };//初始化前三個成員,后面所有元素都設置為0 int a[10] = { 0 };//所有的成員都設置為0 //[]中不定義元素個數,定義時必須初始化 int a[] = { 1, 2, 3, 4, 5 };//定義了一個數組,有5個成員 -
數組名
數組名是一個地址的常量,代表數組中首元素的地址
#include <stdio.h> int main() { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//定義一個數組,同時初始化所有成員變量 printf("a = %p\n", a);//a = 000000000062FDE0 printf("&a[0] = %p\n", &a[0]);//&a[0] = 000000000062FDE0 int n = sizeof(a); //數組占用內存的大小,10個int類型,10 * 4 = 40 int n0 = sizeof(a[0]);//數組第0個元素占用內存大小,第0個元素為int,4 int i = 0; for (i = 0; i < sizeof(a) / sizeof(a[0]); i++) { printf("%d ", a[i]); } printf("\n"); return 0; }注意:C的數組屬構造類型, 是引用傳遞(傳遞的是地址),因此當把一個數組傳遞給一個函數時/或者變量,函數/變量操作數組會影響到原數組
5.3 二維數組
5.3.1 二維數組的定義
-
二維數組定義的一般形式是:
類型說明符 數組名[常量表達式1][常量表達式2] //其中常量表達式1 表示第一維下標的長度,常量表達式2 表示第二維下標的長度。如:int a[3][4]; -
命名規則同一維數組
-
定義了一個三行四列的數組,數組名為a其元素類型為整型,該數組的元素個數為3×4個,即 int a [3][4];

注:二維數組a是按行進行存放的,先存放a[0]行,再存放a[1]行、a[2]行,并且每行有四個元素,也是依次存放的
- 二維數組在概念上是二維的:其下標在兩個方向上變化,對其訪問一般需要兩個下標
- 在內存中并不存在二維數組,二維數組實際的硬件存儲器是連續編址的,也就是說內存中只有一維數組,即放完一行之后順次放入第二行,和一維數組存放方式是一樣的
-
用法示例:
#include <stdio.h> int main() { //定義了一個二維數組,名字叫a //由3個一維數組組成,這個一維數組是int [4] //這3個一維數組的數組名分別為a[0],a[1],a[2] int a[3][4]; a[0][0] = 0; //…… a[2][3] = 12; //給數組每個元素賦值 int i = 0; int j = 0; int num = 0; for (i = 0; i < 3; i++) { for (j = 0; j < 4; j++) { a[i][j] = num++; } } //遍歷數組,并輸出每個成員的值 for (i = 0; i < 3; i++) { for (j = 0; j < 4; j++) { printf("%d, ", a[i][j]); } printf("\n"); } return 0; }
5.3.2 二維數組的初始化
//分段賦值 int a[3][4] = {{ 1, 2, 3, 4 },{ 5, 6, 7, 8, },{ 9, 10, 11, 12 }};
int a[3][4] =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8, },
{ 9, 10, 11, 12 }
};
//連續賦值
int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12 };
//可以只給部分元素賦初值,未初始化則為0
int a[3][4] = { 1, 2, 3, 4 };
//所有的成員都設置為0
int a[3][4] = {0};
//[]中不定義元素個數,定義時必須初始化
int a[][4] = { 1, 2, 3, 4, 5, 6, 7, 8};
5.3.3 數組名
數組名是一個地址的常量,代表數組中首元素的地址
#include <stdio.h>
int main()
{
//定義了一個二維數組,名字叫a
//二維數組是本質上還是一維數組,此一維數組有3個元素
//每個元素又是一個一維數組int[4]
int a[3][4] = { 1, 2, 3, 4 , 5, 6, 7, 8, 9, 10, 11, 12 };
//數組名為數組首元素地址,二維數組的第0個元素為一維數組
//第0個一維數組的數組名為a[0]
printf("a = %p\n", a);//a = 000000000062FDF0
printf("a[0] = %p\n", a[0]);//a[0] = 000000000062FDF0
//測二維數組所占內存空間,有3個一維數組,每個一維數組的空間為4*4
//sizeof(a) = 3 * 4 * 4 = 48
printf("sizeof(a) = %d\n", sizeof(a));
//測第0個元素所占內存空間,a[0]為第0個一維數組int[4]的數組名,4*4=16
printf("sizeof(a[0]) = %d\n", sizeof(a[0]) );
//測第0行0列元素所占內存空間,第0行0列元素為一個int類型,4字節
printf("sizeof(a[0][0]) = %d\n", sizeof(a[0][0]));
//求二維數組行數
printf("i = %d\n", sizeof(a) / sizeof(a[0]));
// 求二維數組列數
printf("j = %d\n", sizeof(a[0]) / sizeof(a[0][0]));
//求二維數組行*列總數
printf("n = %d\n", sizeof(a) / sizeof(a[0][0]));
return 0;
}
5.4 字符數組和字符串
5.4.1 字符數組與字符串區別
-
C語言中沒有字符串這種數據類型,可以通過char的數組來替代
-
字符串一定是一個char的數組,但char的數組未必是字符串
-
數字0(和字符‘\0’等價)結尾的char數組就是一個字符串,但如果char數組沒有以數字0結尾,那么就不是一個字符串,只是普通字符數組,所以字符串是一種特殊的char的數組
-
示例:
#include <stdio.h> int main() { char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //普通字符數組 printf("c1 = %s\n", c1); //亂碼,因為沒有’\0’結束符 char c1[7] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //字符串 printf("c1 = %s\n", c1); //正常輸出,因為字符數組的大小為7,最后一個元素沒有初始化,置為0 //以‘\0’(‘\0’就是數字0)結尾的字符數組是字符串 char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'}; printf("c2 = %s\n", c2); //字符串處理以‘\0’(數字0)作為結束符,后面的'h', 'l', 'l', 'e', 'o'不會輸出 char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'}; printf("c3 = %s\n", c3); return 0; }
5.4.2 字符串的初始化
#include <stdio.h>
// C語言沒有字符串類型,通過字符數組模擬
// C語言字符串,以字符‘\0’, 數字0
int main()
{
//不指定長度, 沒有0結束符,有多少個元素就有多長
char buf[] = { 'a', 'b', 'c' };
printf("buf = %s\n", buf); //亂碼
//指定長度,后面沒有賦值的元素,自動補0
char buf2[100] = { 'a', 'b', 'c' };
char buf1[1000] = { "hello"};
printf("buf2 = %s\n", buf2);
//所有元素賦值為0
char buf3[100] = { 0 };
//char buf4[2] = { '1', '2', '3' };//數組越界
char buf5[50] = { '1', 'a', 'b', '0', '7' };
printf("buf5 = %s\n", buf5);
char buf6[50] = { '1', 'a', 'b', 0, '7' };
printf("buf6 = %s\n", buf6);
char buf7[50] = { '1', 'a', 'b', '\0', '7' };
printf("buf7 = %s\n", buf7);
//使用字符串初始化,編譯器自動在后面補0,常用
char buf8[] = "hello";//實際上該字符數組的大小為6,等價于buf8[] = {'h','e','l','l','o','\0'}
//'\0'后面最好不要連著數字,有可能幾個數字連起來剛好是一個轉義字符
//'\ddd'八進制字義字符,'\xdd'十六進制轉移字符
// \012相當于\n
char str[] = "\012abc";
printf("str == %s\n", str);
return 0;
}
5.4.3 字符串的輸入輸出
由于字符串采用了'\0'標志,字符串的輸入輸出將變得簡單方便。
#include <stdio.h>
int main()
{
char str[100];
printf("input string1 : \n");
scanf("%s", str);//scanf(“%s”,str)默認以空格分隔
printf("output:%s\n", str);
return 0;
}
5.4.3.1 gets()
-
函數原型:char *gets(char *str)
-
功能:從標準輸入讀入字符,并保存到str指定的內存空間,直到出現換行符或讀到文件結尾為止
-
參數:str:字符串首地址
-
返回值:
- 成功:讀入的字符串
- 失?。篘ULL
-
gets(str)與scanf(“%s”,str)的區別:
- gets(str)允許輸入的字符串含有空格
- scanf(“%s”,str)不允許含有空格
-
關于使用 gets() 函數需要注意:使用 gets() 時,系統會將最后“敲”的換行符從緩沖區中取出來,然后丟棄,所以緩沖區中不會遺留換行符。這就意味著,如果前面使用過 gets(),而后面又要從鍵盤給字符變量賦值的話就不需要吸收回車清空緩沖區了,因為緩沖區的回車已經被 gets() 取出來扔掉了
驗證:
# include <stdio.h> int main(void) { char str[30]; char ch; printf("請輸入字符串:"); gets(str); printf("%s\n", str); scanf("%c", &ch); printf("ch = %c\n", ch); return 0; }
注意:由于scanf()和gets()無法知道字符串s大小,必須遇到換行符或讀到文件結尾為止才接收輸入,因此容易導致字符數組越界(緩沖區溢出)的情況
5.4.3.2 fgets()
-
函數原型:char *fgets(char *s,int size,FILE *stream);
-
功能:從stream指定的文件內讀入字符,保存到s所指定的內存空間,直到出現換行字符、讀到文件結尾或是已讀了size - 1個字符為止,最后會自動加上字符 '\0' 作為字符串結束。
-
參數:
- s:字符串
- size:指定最大讀取字符串的長度(size - 1)
- stream:文件指針,如果讀鍵盤輸入的字符串,固定寫為stdin
-
返回值:
- 成功:成功讀取的字符串
- 讀到文件尾或出錯: NULL
-
示例:
int main(void) { char ch[10]; //"hello\n\0" //fgets可以接收空格 //fgets獲取字符串少于元素個數會有\n 大于等于 沒有\n fgets(ch, sizeof(ch), stdin); printf("%s", ch); return 0; }
注意:fgets()在讀取一個用戶通過鍵盤輸入的字符串的時候,同時把用戶輸入的回車也做為字符串的一部分。通過scanf和gets輸入一個字符串的時候,不包含結尾的“\n”,但通過fgets結尾多了“\n”。fgets()函數是安全的,不存在緩沖區溢出的問題
5.4.3.3 puts()
-
函數原型:int puts(const char *s);
-
功能:標準設備輸出s字符串,遇到'\0'停止,在輸出完成后自動輸出一個'\n'
-
參數:s:字符串首地址
-
返回值:
- 成功:非負數
- 失?。?1
-
示例:
int main(void) { char ch[] = "hello world"; //puts自帶換行 //puts(ch); //puts("hello\0 world"); //puts("");換行 return 0; }
5.4.3.4 fputs()
-
函數原型:int fputs(const char * str, FILE * stream);
-
功能:將str所指定的字符串寫入到stream指定的文件中, 字符串結束符 '\0' 不寫入文件
-
參數:
- str:字符串
- stream:文件指針,如果把字符串輸出到屏幕,固定寫為stdout
-
返回值:
- 成功:0
- 失敗:-1
-
示例:
int main(void) { char ch[] = "hello world"; //fputs(ch, stdout); //printf("%s", ch); return 0; }
注意:fputs()是puts()的文件操作版本,但fputs()不會自動輸出一個'\n'
六、函數
6.1 概述
6.1.1 函數分類
C 程序是由函數組成的,我們寫的代碼都是由主函數 main()開始執行的。函數是 C 程序的基本模塊,是用于完成特定任務的程序代碼單元。
從函數定義的角度看,函數可分為系統函數和用戶定義函數兩種:
- 系統函數,即庫函數:這是由編譯系統提供的,用戶不必自己定義這些函數,可以直接使用它們,如我們常用的打印函數printf()。
- 用戶定義函數:用以解決用戶的專門需要。
6.1.2 函數的作用
- 函數的使用可以省去重復代碼的編寫,降低代碼重復率
- 函數可以讓程序更加模塊化,從而有利于程序的閱讀,修改和完善
6.1.3 函數的調用
當調用函數時,需要關心5要素:
-
頭文件:包含指定的頭文件
-
函數名字:函數名字必須和頭文件聲明的名字一樣
-
功能:需要知道此函數能干嘛后才調用
-
參數:參數類型要匹配
-
返回值:根據需要接收返回值
-
示例:
#include <time.h> time_t time(time_t *t); 功能:獲取當前系統時間 參數:常設置為NULL 返回值:當前系統時間, time_t 相當于long類型,單位為毫秒 #include <stdlib.h> void srand(unsigned int seed); 功能:用來設置rand()產生隨機數時的隨機種子 參數:如果每次seed相等,rand()產生隨機數相等 返回值:無 #include <stdlib.h> int rand(void); 功能:返回一個隨機數值 參數:無 返回值:隨機數 #include <stdio.h> #include <time.h> #include <stdlib.h> int main() { time_t tm = time(NULL);//得到系統時間 srand((unsigned int)tm);//隨機種子只需要設置一次即可 int r = rand(); printf("r = %d\n", r); return 0; }
6.2 函數的定義
6.2.1 函數定義的格式
函數定義的一般形式:
返回類型 函數名(形式參數列表)
{
數據定義部分;
執行語句部分;
}

6.2.2 函數名字、形參、函數體、返回值
-
函數名
理論上是可以隨意起名字,最好起的名字見名知意,應該讓用戶看到這個函數名字就知道這個函數的功能。注意,函數名的后面有個圓換號(),代表這個為函數,不是普通的變量名
-
形參列表
在定義函數時指定的形參,在未出現函數調用時,它們并不占內存中的存儲單元,因此稱它們是形式參數或虛擬參數,簡稱形參,表示它們并不是實際存在的數據,所以,形參里的變量不能賦值
//在定義函數時指定的形參,必須是,類型+變量的形式: //1: right, 類型+變量 void max(int a, int b) { } //2: error, 只有類型,沒有變量 void max(int, int) { } //3: error, 只有變量,沒有類型 int a, int b; void max(a, b) { } //4: error, 形參不能賦值 void max(int a = 10, int b = 20) //在定義函數時指定的形參,可有可無,根據函數的需要來設計,如果沒有形參,圓括號內容為空,或寫一個void關鍵字: // 沒形參, 圓括號內容為空 void max() { } // 沒形參, 圓括號內容為void關鍵字 void max(void) { } -
函數體
花括號{ }里的內容即為函數體的內容,這里為函數功能實現的過程,這和以前的寫代碼沒太大區別,以前我們把代碼寫在main()函數里,現在只是把這些寫到別的函數里。
-
返回值
函數的返回值是通過函數中的return語句獲得的,return后面的值也可以是一個表達式。
//a)盡量保證return語句中表達式的值和函數返回類型是同一類型。 int max() // 函數的返回值為int類型 { int a = 10; return a;// 返回值a為int類型,函數返回類型也是int,匹配 } //b)如果函數返回的類型和return語句中表達式的值不一致,則以函數返回類型為準,即函數返回類型決定返回值的類型。對數值型數據,可以自動進行類型轉換。 double max() // 函數的返回值為double類型 { int a = 10; return a;// 返回值a為int類型,它會轉為double類型再返回 } //c)return語句的另一個作用為中斷return所在的執行函數,類似于break中斷循環、switch語句一樣。 int max() { return 1;// 執行到,函數已經被中斷,所以下面的return 2無法被執行到 return 2;// 沒有執行 } //d)如果函數帶返回值,return后面必須跟著一個值,如果函數沒有返回值,函數名字的前面必須寫一個void關鍵字,這時候,我們寫代碼時也可以通過return中斷函數(也可以不用),只是這時,return后面不帶內容( 分號“;”除外)。 void max()// 最好要有void關鍵字 { return; // 中斷函數,這個可有可無 }注意:如果函數返回的類型和return語句中表達式的值不一致,而它又無法自動進行類型轉換,程序則會報錯。
6.3 函數的調用
定義函數后,我們需要調用此函數才能執行到這個函數里的代碼段。這和main()函數不一樣,main()為編譯器設定好自動調用的主函數,無需人為調用,我們都是在main()函數里調用別的函數,一個 C 程序里有且只有一個main()函數
6.3.1 函數執行流程
#include <stdio.h>
void print_test()
{
printf("this is for test\n");
}
int main()
{
print_test(); // print_test函數的調用
return 0;
}
- 進入main()函數
- 調用print_test()函數:
- 它會在main()函數的前尋找有沒有一個名字叫“print_test”的函數定義;
- 如果找到,接著檢查函數的參數,這里調用函數時沒有傳參,函數定義也沒有形參,參數類型匹配;
- 開始執行print_test()函數,這時候,main()函數里面的執行會阻塞( 停 )在print_test()這一行代碼,等待print_test()函數的執行。
- print_test()函數執行完( 這里打印一句話 ),main()才會繼續往下執行,執行到return 0, 程序執行完畢。
6.3.2 函數的形參和實參
- 形參出現在函數定義中,在整個函數體內都可以使用,離開該函數則不能使用。
- 實參出現在主調函數中,進入被調函數后,實參也不能使用。
- 實參變量對形參變量的數據傳遞是“值傳遞”,即單向傳遞,只由實參傳給形參,而不能由形參傳回來給實參。
- 在調用函數時,編譯系統臨時給形參分配存儲單元。調用結束后,形參單元被釋放。
- 實參單元與形參單元是不同的單元。調用結束后,形參單元被釋放,函數調用結束返回主調函數后則不能再使用該形參變量。實參單元仍保留并維持原值。因此,在執行一個被調用函數時,形參的值如果發生改變,并不會改變主調函數中實參的值。
6.3.3 函數參數的傳遞方式
C語言傳遞參數可以是 值傳遞(pass by value),也可以是 傳遞指針(a pointer passed by value)也叫傳遞地址或者引用傳遞
其實,不管是值傳遞還是引用傳遞,傳遞給函數的都是變量的副本,不同的是,值傳遞的是值的拷貝,引用傳遞的是地址的拷貝,一般來說,地址拷貝效率高,因為數據量小,而值拷貝決定拷貝的數據大小,數據越大,效率越低
6.3.4 無參函數調用
如果是調用無參函數,則不能加上“實參”,但括號不能省略。
/ 函數的定義
void test()
{
}
int main()
{
// 函數的調用
test(); // right, 圓括號()不能省略
test(250); // error, 函數定義時沒有參數
return 0;
}
6.3.5 有參函數調用
//a)如果實參表列包含多個實參,則各參數間用逗號隔開
// 函數的定義
void test(int a, int b)
{
}
int main()
{
int p = 10, q = 20;
test(p, q); // 函數的調用
return 0;
}
//b)實參與形參的個數應相等,類型應匹配(相同或賦值兼容)。實參與形參按順序對應,一對一地傳遞數據。
//c)實參可以是常量、變量或表達式,無論實參是何種類型的量,在進行函數調用時,它們都必須具有確定的值,以便把這些值傳送給形參。所以,這里的變量是在圓括號( )外面定義好、賦好值的變量。
// 函數的定義
void test(int a, int b)
{
}
int main()
{
// 函數的調用
int p = 10, q = 20;
test(p, q); // right
test(11, 30 - 10); // right
test(int a, int b); // error, 不應該在圓括號里定義變量
return 0;
}
6.3.6 函數返回值
//a)如果函數定義沒有返回值,函數調用時不能寫void關鍵字,調用函數時也不能接收函數的返回值。
// 函數的定義
void test()
{
}
int main()
{
// 函數的調用
test(); // right
void test(); // error, void關鍵字只能出現在定義,不可能出現在調用的地方
int a = test(); // error, 函數定義根本就沒有返回值
return 0;
}
//b)如果函數定義有返回值,這個返回值我們根據用戶需要可用可不用,但是,假如我們需要使用這個函數返回值,我們需要定義一個匹配類型的變量來接收
// 函數的定義, 返回值為int類型
int test()
{
}
int main()
{
// 函數的調用
int a = test(); // right, a為int類型
int b;
b = test(); // right, 和上面等級
char *p = test(); // 雖然調用成功沒有意義, p為char *, 函數返回值為int, 類型不匹配
// error, 必須定義一個匹配類型的變量來接收返回值
// int只是類型,沒有定義變量
int = test();
// error, 必須定義一個匹配類型的變量來接收返回值
// int只是類型,沒有定義變量
int test();
return 0;
}
6.4 函數的聲明
如果使用用戶自己定義的函數,而該函數與調用它的函數(即主調函數)不在同一文件中,或者函數定義的位置在主調函數之后,則必須在調用此函數之前對被調用的函數作聲明。
所謂函數聲明,就是在函數尚在未定義的情況下,事先將該函數的有關信息通知編譯系統,相當于告訴編譯器,函數在后面定義,以便使編譯能正常進行。
注意:一個函數只能被定義一次,但可以聲明多次。
#include <stdio.h>
int max(int x, int y); // 函數的聲明,分號不能省略
// int max(int, int); // 另一種方式
int main()
{
int a = 10, b = 25, num_max = 0;
num_max = max(a, b); // 函數的調用
printf("num_max = %d\n", num_max);
return 0;
}
// 函數的定義
int max(int x, int y)
{
return x > y ? x : y;
}
函數定義和聲明的區別:
-
定義是指對函數功能的確立,包括指定函數名、函數類型、形參及其類型、函數體等,它是一個完整的、獨立的函數單位。
-
聲明的作用則是把函數的名字、函數類型以及形參的個數、類型和順序(注意,不包括函數體)通知編譯系統,以便在對包含函數調用的語句進行編譯時,據此對其進行對照檢查(例如函數名是否正確,實參與形參的類型和個數是否一致)。
七、預處理命令
7.1 預處理命令基本介紹
- 使用庫函數之前,應該用#include引入對應的頭文件。這種以# 號開頭的命令稱為預處理命令
- 這些在編譯之前對源文件進行簡單加工的過程,就稱為預處理(即預先處理、提前處理)
- 預處理主要是處理以#開頭的命令,例如#include <stdio.h>等。預處理命令要放在所有函數之外,而且一般都放在源文件的前面
- 預處理是C語言的一個重要功能,由預處理程序完成。當對一個源文件進行編譯時,系統將自動調用預處理程序對源程序中的預處理部分作處理,處理完畢自動進入對源程序的編譯
- C語言提供了多種預處理功能,如宏定義、文件包含、條件編譯等,合理地使用它們會使編寫的程序便于閱讀、修改、移植和調試,也有利于模塊化程序設計
7.2 頭文件
7.2.1 引入
在實際的開發中,我們往往需要在不同的文件中,去調用其它文件的定義的函數,比如hello.c中,去使用myfuns.c 文件中的函數,如何實現? ——頭文件
7.2.2 頭文件基本概念
- 頭文件是擴展名為 .h 的文件,包含了 C 函數聲明和宏定義,被多個源文件中引用共享。有兩種類型的頭文件:程序員編寫的頭文件和C標準庫自帶的頭文件
- 在程序中要使用頭文件,需要使用C預處理指令 #include 來引用它。前面我們已經看過 stdio.h 頭文件,它是C標準庫自帶的頭文件
- #include叫做文件包含命令,用來引入對應的頭文件(.h文件)。#include 也是C語言預處理命令的一種
- #include 的處理過程很簡單,就是將頭文件的內容插入到該命令所在的位置,從而把頭文件和當前源文件連接成一個源文件,這與復制粘貼的效果相同。但是我們不會直接在源文件中復制頭文件的內容,因為這么做很容易出錯,特別在程序是由多個源文件組成的時候
- 建議把所有的常量、宏、系統全局變量和函數原型寫在頭文件中,在需要的時候隨時引用這些頭文件
7.2.3 頭文件的示意圖

7.2.4 頭文件快速入門
-
說明:頭文件快速入門——C 程序相互調用函數,我們將 cal 聲明到文件 myfun.h , 在 myfun.c 中定義 cal 函數,當其它文件需要使用到 myfun.h 聲明 的函數時,可以#include 該頭文件,就可以使用了
-
示例:
myfun.h #include <stdio.h> //聲明函數 int myCal(int n1, int n2, char oper); void sayHello() ;myfun.c #include <stdio.h> //實現 int myCal(int n1, int n2, char oper) int myCal(int n1, int n2, char oper) { //定義一個變量 res ,保存運算的結果 double res = 0.0; switch(oper) { case '+' : res = n1 + n2; break; case '-': res = n1 - n2; break; case '*': res = n1 * n2; break; case '/': res = n1 / n2; break; default : printf("你的運算符有誤~"); } printf("\n%d %c %d = %.2f\n", n1, oper, n2, res); return res; } void sayHello() { //定義函數 printf("say Hello"); }hello.c #include <stdio.h> //引入我們需要的頭文件(用戶頭文件) #include "myfun.h" void main() { //使用 myCal 完成計算任務 int n1 = 10; int n2 = 50; char oper = '-'; double res = 0.0; //調用 myfun.c 中定義的函數 myCal res = myCal(n1, n2, oper); printf("\nres=%.2f", res); sayHello(); getchar(); }
7.2.5 頭文件的注意事項和細節說明
- 引用頭文件相當于復制頭文件的內容
- 源文件的名字 可以不和頭文件一樣,但是為了好管理,一般頭文件名和源文件名一樣
- C 語言中 include <> 與 include "" 的區別
- include <>:引用的是編譯器的類庫路徑里面的頭文件,用于引用系統頭文件
- include "":引用的是你程序目錄的相對路徑中的頭文件,如果在程序目錄沒有找到引用的頭文件則到編譯器的類庫路徑的目錄下找該頭文件,用于引用用戶頭文件。
- 所以:
- 引用 系統頭文件,兩種形式都會可以,include <> 效率高
- 引用 用戶頭文件,只能使用 include ""
- 一個 #include 命令只能包含一個頭文件,多個頭文件需要多個 #include 命令
- 同一個頭文件如果被多次引入,多次引入的效果和一次引入的效果相同,因為頭文件在代碼層面有防止重復引入的機制
- 在一個被包含的文件(.c)中又可以包含另一個文件頭文件
- 不管是標準頭文件,還是自定義頭文件,都只能包含變量和函數的聲明,不能包含定義,否則在多次引入時會引起重復定義錯誤(!!!!)
7.3 C語言宏定義
基本介紹:
-
#define 叫做宏定義命令,它也是 C 語言預處理命令的一種 。 所謂宏定義,就是用一個標識符來表示一個字符串,如果在后面的代碼中出現了該標識符,那么就全部替換成指定的字符串
-
示例:
#define N 100 int main(){ int sum = 20 + N;//int sum = 20 +100 printf("%d\n", sum); getchar(); return 0; } //說明 小結: int sum = 20 + N,N 被 100 代替了。 #define N 100 就是宏定義,N 為宏名,100 是宏的內容(宏所表示的字符串)。在預處理階段,對程序中所有出現的“宏名”,預處理器都會用宏定義中的字符串去代換,這稱為“宏替換”或“宏展開”。 宏定義是由源程序中的宏定義命令#define 完成的,宏替換是由預處理程序完成的
7.4 宏定義的形式
-
語法:#define 宏名 字符串
- #表示這是一條預處理命令,所有的預處理命令都以 # 開頭。宏名是標識符的一種,命名規則和變量相同。字符串可以是數字、表達式、if 語句、函數等
- 這里所說的字符串是一般意義上的字符序列,不要和 C 語言中的字符串等同,它不需要雙引號
- 程序中反復使用的表達式就可以使用宏定義
-
示例:
#include <stdio.h> //宏定義 , 宏名 M , 對應的字符串 (n*n+3*n) // 注意:如果宏對應的字符串 有 ( ) , 那么就不能省略 #define M (n*n+3*n) int main(){ int sum, n; printf("Input a number: "); scanf("%d", &n); //n = 3 sum = 3*M+4*M+5*M; // 宏展開 3*(n*n+3*n)+4*(n*n+3*n)+5*(n*n+3*n) printf("sum=%d\n", sum); getchar(); getchar(); return 0; } -
宏定義注意事項和細節
-
宏定義是用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名,這只是一種簡單的替換。字符串中可以含任何字符,它可以是常數、表達式、if 語句、函數等,預處理程序對它不作任何檢查,如有錯誤,只能在編譯已被宏展開后的源程序時發現。
-
宏定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起替換
-
宏定義必須寫在函數之外,其作用域為宏定義命令起到源程序結束。如要終止其作用域可使用#undef 命令
-
示例:
#define PI 3.14159 int main(){ printf("PI=%f", PI); return 0; } #undef PI //取消宏定義 void func(){ // Code printf("PI=%f", PI);//錯誤,這里不能使用到 PI 了 }
-
代碼中的宏名如果被引號包圍,那么預處理程序不對其作宏代替
#include <stdio.h> #define OK 100 int main(){ printf("OK\n"); return 0; } -
宏定義允許嵌套,在宏定義的字符串中可以使用已經定義的宏名,在宏展開時由預處理程序層層代換
#define PI 3.1415926 #define S PI*y*y /* PI 是已定義的宏名*/ printf("%f", S); //在宏替換后變為: printf("%f", 3.1415926*y*y) -
習慣上宏名用大寫字母表示,以便于與變量區別。但也允許用小寫字母
-
可用宏定義表示數據類型,使書寫方便
#define UINT unsigned int void main() { UINT a, b; // 宏替換 unsigned int a, b; } -
宏定義表示數據類型和用 typedef 定義數據說明符的區別: 宏定義只是簡單的字符串替換,而 typedef 是在編譯階段由編譯器處理的,它并不是簡單的字符串替換,而給原有的數據類型起一個新的名字,將它作為一種新的數據類型
-
7.5 帶參數的宏定義
-
基本介紹
-
C 語言允許宏帶有參數。在宏定義中的參數稱為“形式參數”,在宏調用中的參數稱為“實際參數”,這點和函數有些類似
-
對帶參數的宏,在展開過程中不僅要進行字符串替換,還要用實參去替換形參
-
帶參宏定義的一般形式為
#define 宏名(形參列表)字符串 ,在字符串中可以含有各個形參 -
帶參宏調用的一般形式為 : 宏名(實參列表)
-
示例:
#include <stdio.h> //說明 //1. MAX 就是帶參數的宏 //2. (a,b) 就是形參 //3. (a>b) ? a : b 是帶參數的宏對應字符串,該字符串中可以使用形參 #define MAX(a,b) (a>b) ? a : b int main(){ int x , y, max; printf("input two numbers: "); scanf("%d %d", &x, &y); //說明 //1. MAX(x, y); 調用帶參數宏定義 //2. 在宏替換時(預處理,由預處理器), 會進行字符串的替換,同時會使用實參, 去替換形參 //3. 即 MAX(x, y) 宏替換后 (x>y) ? x : y max = MAX(x, y); printf("max=%d\n", max); getchar(); getchar(); return 0; }
-
-
帶參宏定義的注意事項和細節
-
帶參宏定義中,形參之間可以出現空格,但是宏名和形參列表之間不能有空格出現
#define MAX(a,b) (a>b)?a:b 如果寫成了 #define MAX (a, b) (a>b)?a:b 將被認為是無參宏定義,宏名 MAX 代表字符串(a,b) (a>b)?a:b 而不是 : MAX(a,b) 代表 (a>b) ? a: b 了 -
在帶參宏定義中,不會為形式參數分配內存,因此不必指明數據類型。而在宏調用中,實參包含了具體的數據,要用它們去替換形參,因此實參必須要指明數據類型
-
在宏定義中,字符串內的形參通常要用括號括起來以避免出錯。
#include <stdlib.h> #define SQ(y) (y)*(y) // 帶參宏定義,字符串內的形參通常要用括號括起來以避免出錯 int main(){ int a, sq; printf("input a number: "); scanf("%d", &a); sq = SQ(a+1); // 宏替換 (a+1) * (a+1) //如果定義成SQ(y) y*y 宏替換 a+1 * a+1 printf("sq=%d\n", sq); system("pause"); return 0; }
-
7.6 帶參宏定義和函數的區別
-
宏展開僅僅是字符串的替換,不會對表達式進行計算;宏在編譯之前就被處理掉了,它沒有機會參與編譯,也不會占用內存。
-
函數是一段可以重復使用的代碼,會被編譯,會給它分配內存,每次調用函數,就是執行這塊內存中的代碼
-
示例:
//案例說明 :要求 使用函數計算平方值, 使用宏計算平方值, 并總結二者的區別 #include <stdlib.h> int SQ(int y){ return ((y)*(y)); } int main(){ int i=1; while(i<=5){ // 1, 4, 9, 16, 25 printf("%d^2 = %d\n", (i-1), SQ(i++)); } system("pause"); return 0; //------------------------------------------- #include <stdlib.h> #define SQ(y) ((y)*(y)) int main(){ int i=1; while(i<=5){ // 這里相當于計算了 1,3,5 的平方 ////進入循環 3 次,得到的是 1 * 1 = 1 3 * 3 = 9 , 5 * 5 = 25 // SQ(i++) 宏調用 展開 ((i++)*(i++)) printf("%d^2 = %d\n", i, SQ(i++)); } system("pause"); return 0; }
7.7 C 語言預處理命令總結
預處理指令是以#號開頭的代碼行,# 號必須是該行除了任何空白字符外的第一個字符。# 后是指令關鍵字,在關鍵字和 # 號之間允許存在任意個數的空白字符,整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對源代碼做某些轉換
| 指令 | 說明 |
|---|---|
| # | 空指令,無任何效果 |
| #include | 包含一個源代碼文件 |
| #define | 定義宏 |
| #undef | 取消已定義的宏 |
| #if | 如果給定條件為真,則編譯下面代碼 |
| #ifdef | 如果宏已經定義,則編譯下面代碼 |
| #ifndef | 如果宏沒有定義,則編譯下面代碼 |
| #elif | 如果前面的#if 給定條件不為真,當前條件為真,則編譯下面代碼 |
| #endif | 結束一個#if……#else 條件編譯塊 |
八、指針
8.1 概述
8.1.1 物理存儲器和存儲地址空間
有關內存的兩個概念:物理存儲器和存儲地址空間
- 物理存儲器:實際存在的具體存儲器芯片
- 主板上裝插的內存條
- 顯示卡上的顯示RAM芯片
- 各種適配卡上的RAM芯片和ROM芯片
- 存儲地址空間:對存儲器編碼的范圍。我們在軟件上常說的內存是指這一層含義。
- 編碼:對每個物理存儲單元(一個字節)分配一個號碼
- 尋址:可以根據分配的號碼找到相應的存儲單元,完成數據的讀寫
8.1.2 內存地址
- 將內存抽象成一個很大的一維字符數組
- 編碼就是對內存的每一個字節分配一個32位或64位的編號(與32位或者64位處理器相關)。這個內存編號我們稱之為內存地址
- 內存中的每一個數據都會分配相應的地址:
- char:占一個字節分配一個地址
- int: 占四個字節分配四個地址
- float、struct、函數、數組等
8.1.3 指針和指針變量
- 內存區的每一個字節都有一個編號,這就是“地址”
- 如果在程序中定義了一個變量,在對程序進行編譯或運行時,系統就會給這個變量分配內存單元,并確定它的內存地址(編號)
- 指針的實質就是內存“地址”。指針就是地址,地址就是指針
- 指針是內存單元的編號,指針變量是存放地址的變量
- 通常我們敘述時會把指針變量簡稱為指針,實際他們含義并不一樣
8.2 指針基礎知識
8.2.1 指針變量的定義和使用
-
指針也是一種數據類型,指針變量也是一種變量
-
指針變量指向誰,就把誰的地址賦值給指針變量
-
“*” 操作符操作的是指針變量指向的內存空間
-
示例:
int main() { int a = 0; char b = 100; printf("%p, %p\n", &a, &b); //打印a, b的地址 //int *代表是一種數據類型,int*指針類型,p才是變量名 //定義了一個指針類型的變量,可以指向一個int類型變量的地址 int* p; p = &a;//將a的地址賦值給變量p,p也是一個變量,值是一個內存地址編號 //&是取地址符號是升維度的 //*是取值符號是降維度的 printf("%d\n", *p);//p指向了a的地址,*p就是a的值 char* p1 = &b; printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值 return 0; }注意:&可以取得一個變量在內存中的地址。但是,不能取寄存器變量,因為寄存器變量不在內存里,而在CPU里面,所以是沒有地址的。
8.2.2 指針大小
-
使用sizeof()測量指針的大小,得到的總是:4或8
-
sizeof()測的是指針變量指向存儲地址的大小
-
在32位平臺,所有的指針(地址)都是32位(4字節)
-
在64位平臺,所有的指針(地址)都是64位(8字節)
int *p1; int **p2; char *p3; char **p4; printf("sizeof(p1) = %d\n", sizeof(p1)); printf("sizeof(p2) = %d\n", sizeof(p2)); printf("sizeof(p3) = %d\n", sizeof(p3)); printf("sizeof(p4) = %d\n", sizeof(p4)); printf("sizeof(double *) = %d\n", sizeof(double *));
8.2.3 野指針和空指針
指針變量也是變量,是變量就可以任意賦值,不要越界即可(32位為4字節,64位為8字節),但是,任意數值賦值給指針變量沒有意義,因為這樣的指針就成了野指針,此指針指向的區域是未知(操作系統不允許操作此指針指向的內存區域)。所以,野指針不會直接引發錯誤,操作野指針指向的內存區域才會出問題。
int main()
{
//不建議將一個變量的值直接賦值給指針
//野指針 -》指針變量指向一個未知的空間
int* p = 100;//程序中允許存在野指針
//操作系統將0-255作為系統占用不允許訪問操作
//操作野指針對應的內存空間可能報錯
printf("%d\n", *p);
return EXIT_SUCCESS;
}
野指針和有效指針變量保存的都是數值,為了標志此指針變量沒有指向任何變量(空閑可用),C語言中,可以把NULL賦值給此指針,這樣就標志此指針為空指針,沒有任何指針
int *p = NULL;
NULL是一個值為0的宏常量:#define NULL ((void *)0)
int main(void)
{
//空指針是指內存地址編號為0的空間
int* p = NULL;
//操作空指針對應的空間一定會報錯
*p = 100;//這里對空指針進行了賦值操作,出錯
printf("%d\n", *p);
//空指針可以用在條件判斷
return 0;
}
8.2.4 萬能指針void *
void *指針可以指向任意變量的內存空間
int main()
{
int a = 10;
//int* p = &a;
//萬能指針可以接收任意類型變量的內存地址
void* p = &a;
//在通過萬能指針修改變量的值時 需要找到變量對應的指針類型
*(int*)p = 100;
printf("%d\n", a);
printf("%d\n", *(int*)p);
//printf("%p\n", p);
printf("萬能指針在內存占的字節大?。?d\n", sizeof(void*));
//printf("void在內存占的字節大?。?d\n", sizeof(void));無法查看void的大小
return EXIT_SUCCESS;
}
8.2.5 const修飾的指針變量
-
const修飾常量
int main() { //常量 const int a = 10; //a = 100;//err,不可直接修改可通過指針修改 //指針間接修改常量的值 int* p = &a; *p = 100; printf("%d\n", a); return EXIT_SUCCESS; } -
const修飾指針類型
可以修改指針變量的值,不可以修改指針指向內存空間的值
int main(void) { int a = 10; int b = 20; const int* p = &a; //p = &b;//ok //*p = 100;//err printf("%d\n", *p); return 0; } -
const修飾指針變量
可以修改指針指向的內存空間的值,不可以修改指針變量的值
int main(void) { int a = 10; int b = 20; int* const p = &a; //p = &b;//err *p = 200;//ok printf("%d\n", a); return 0; } -
const既修飾指針類型又修飾指針變量
int main(void) { int a = 10; int b = 20; //const修飾指針類型 修飾指針變量 只讀指針 const int* const p = &a; //可通過二級指針修改 //二級指針操作 int** pp = &p;//指向p指針的地址 //*pp = &b;改變p指針的值 **pp = 100;//改變p指針指向內容的值為100,即把10改成100 printf("%d\n", *p); //p = &b;//err //*p = 100;//err return 0; } -
總結:const靠近哪個,哪個便不能修改。但可以通過高一級指針進行修改。即可通過升維來修改
8.3 指針與數組
8.3.1 數組名
- 數組名字是數組的首元素地址,但它是一個常量
int main()
{
int arr[] = {123456,2,3,4,5,6,7,8,9,10};
//數組名是一個常量 不允許賦值
//數組名是數組首元素地址
//arr = 100;//err,即不可給數組名賦值
int* p;
p = arr;
//printf("%p\n", p);
//printf("%p\n", arr);
*p = 123;
for (int i = 0; i < 10; i++)
{
//*==[]??
//printf("%d\n", *(arr+i));//arr[0]
//printf("%d\n", p[i]);
//printf("%d\n", *(p + i));
printf("%d\n", *p);
p++;//指針類型變量+1 等同于內存地址+sizeof(int)
}
//printf("%p\n", arr);
//printf("%p\n", p);
//兩個指針相減 等到的結果是兩個指針的偏移量 (步長)
//所有的指針類型 相減結果都是int類型
int step = p - arr;//10 +1相當于+sizeof(int) 40/sizeof(int)
printf("%d\n", step);
return EXIT_SUCCESS;
}
- 數組名與指針的區別
int main(void)
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//指向數組的指針
int* p = arr;
//p是變量 arr是常量
//p是一個指針 4個字節大小
//arr是一個數組 40字節大小
printf("指針類型大?。?d\n", sizeof(p));
printf("數組大?。?d\n", sizeof(arr));
//p[i];
//*(p+i);
return 0;
}
- 數組作為函數參數會退化為指針 丟失數組的精度
//比如下面的冒泡排序函數,數組長度參數必須傳入,不可以通過arr[]來得到數組的長度,因為數組作為函數參數會退化為指針 丟失數組的精度
void BubbleSort(int arr[],int len)//等價于 void BubbleSort(int* arr,int len)
{
//int len = sizeof(arr);//4或者8
//printf("%d\n", sizeof(arr));
for (int i = 0; i < len-1; i++)
{
for (int j = 0; j < len-1-i; j++)
{
//if (arr[j] > arr[j + 1])
//{
// int temp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = temp;
//}
if (*(arr + j) > *(arr + j + 1))
{
int temp = *(arr + j);
*(arr + j) = *(arr + j + 1);
*(arr + j + 1) = temp;
}
}
}
}
8.3.2 指針運算
-
加法運算
-
指針計算不是簡單的整數相加
-
如果是一個int *,+1的結果是增加一個int的大小
-
如果是一個char *,+1的結果是增加一個char大小
-
代碼示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> void my_strcpy01(char* dest,char* ch) { int i = 0; //while (ch[i] != '\0'); //while (ch[i] != 0); while (ch[i])//等價于上面兩個 { dest[i] = ch[i]; i++; } dest[i] = 0; } void my_strcpy02(char* dest, char* ch) { int i = 0; while (*(ch+i)) { *(dest + i) = *(ch + i); i++; } *(dest + i) = 0; } void my_strcpy03(char* dest, char* ch) { while (*ch) { *dest = *ch; dest++;//指針+1 相當于指向數組下一個元素 內存地址變化了sizeof(char) ch++; } *dest = 0; } void my_strcpy(char* dest, char* ch) { while (*dest++ = *ch++); //解讀:首先分析++ * = 三種運算的優先級++高于*(取值運算符)高于= //再分析三種運算的結合性:都是自右向左 //1.先算ch++,dest++但由于++在后面所以先賦值,后面再自增 //2.再進行取值運算*ch,*dest //3.再進行賦值運算將*ch賦值給*dest,注意此時還未自增 //4.賦完值后,進行while語句的判斷,判斷*dest的值是否非零 //5.判斷完后此時才對ch,dest,+1 } int main() { //字符串拷貝 char ch[] = "hello world"; char dest[100]; my_strcpy(dest, ch); printf("%s\n", dest); return EXIT_SUCCESS; }
-
8.3.3 指針數組
指針數組,它是數組,數組的每個元素都是指針類型
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
int main01()
{
//定義數組 數據類型 數據名[元素個數] ={值1,值2}
//定義指針數組
int a = 10;
int b = 20;
int c = 30;
int* arr[3] = { &a,&b,&c };
//arr[0] arr[1] arr[2]
//printf("%d\n", *arr[0]);
for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
{
printf("%d\n", *arr[i]);
}
printf("指針數組大小:%d\n", sizeof(arr));
printf("指針元素大小:%d\n", sizeof(arr[0]));
return EXIT_SUCCESS;
}
int main02(void)
{
//指針數組里面元素存儲的是指針
int a[] = { 1,2,3 };
int b[] = { 4,5,6 };
int c[] = { 7,8,9 };
//指針數組是一個特殊的二維數組模型
//指針數組對應于二級指針
int* arr[] = {a,b,c};
//指針數組和二級指針建立關系
int** p = arr;
//arr是指針數組的首地址
//printf("%p\n", arr);
//printf("%p\n", &arr[0]);
//printf("%p\n", a);
//printf("%d\n", arr[0][1]);
//printf("%p\n", arr[0]);
//printf("%p\n", a);//a[1]
//printf("%p\n", &a[0]);//a[1]
//for (int i = 0; i < 3; i++)
//{
// printf("%d\n", *arr[i]);
//}
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
//二維數組
//printf("%d ", arr[i][j]);
//printf("%d ", *(arr[i] + j));
printf("%d ", *(*(arr + i) + j));//三個式子等價
}
puts("");
}
return 0;
}
8.3.4 多級指針
-
C語言允許有多級指針存在,在實際的程序中一級指針最常用,其次是二級指針
-
二級指針就是指向一個一級指針變量地址的指針
-
代碼示例:
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> int main0401() { int a[] = { 1,2,3 }; int b[] = { 4,5,6 }; int c[] = { 7,8,9 }; //指針數組是一個特殊的二維數組模型 //指針數組對應于二級指針 int* arr[] = { a,b,c }; //指針數組和二級指針建立關系 int** p = arr; //arr[0][0] a[0] //printf("%d\n", **p); //二級指針加偏移量 相當于跳過了一個一維數組大小 //printf("%d\n", **(p + 1)); //一級指針加偏移量 相當于跳過了一個元素 //printf("%d\n", *(*p + 1));//arr[0][1] //printf("%d\n", *(*(p + 1) + 1)); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { //printf("%d ", p[i][j]); //printf("%d ", *(p[i] + j)); //printf("%d ", *(*(p + i) + j)); } puts(""); } return EXIT_SUCCESS; } int main0402(void) { int a = 10; int b = 20; int* p = &a; int** pp = &p; int*** ppp = &pp; //*ppp==pp==&p //**ppp==*pp==p==&a; //***ppp==**pp==*p==a //*pp = &b;//等價于p=&b; **pp = 100; //*pp = 100;//err printf("%d\n", *p); printf("%d\n", a); return 0; }
8.4 指針與函數
8.4.1 指針作為函數參數
//指針作為函數參數
void swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 10;
int b = 20;
//值傳遞 形參不影響實參的值
//swap(a, b);
//地址傳遞 形參可以改變實參的值
swap(&a, &b);
printf("%d\n", a);
printf("%d\n", b);
return EXIT_SUCCESS;
}
8.4.2 數組名(指針)作為函數參數
-
字符串追加問題
#include<stdio.h> //數組運算 void my_strcat01(char* ch1, char* ch2) { //strlen(ch1); int i = 0; while (ch1[i] != '\0') { i++; } int j = 0; while (ch2[j] != '\0') { ch1[i + j] = ch2[j]; j++; } } //指針加偏移量 void my_strcat02(char* ch1, char* ch2) { int i = 0; while (*(ch1 + i) != '\0') { i++; } int j = 0; while (*(ch2 + j) != '\0') { *(ch1 + i + j) = *(ch2 + j); j++; } } //指針運算 void my_strcat03(char* ch1, char* ch2) { while (*ch1)ch1++; while (*ch2) { *ch1 = *ch2; ch1++; ch2++; } } //指針運算的優化 void my_strcat(char* ch1, char* ch2) { while (*ch1)ch1++; while (*ch1++ = *ch2++); } int main() { char ch1[100] = "hello"; char ch2[] = "world"; my_strcat(ch1, ch2); printf("%s\n", ch1); return EXIT_SUCCESS; } -
去除字符串中的空格
//思路:創建一個空字符數組,將需要處理的字符串中的非空格字符復制到空字符數組中 void remove_space01(char* ch) { char str[100] = {0}; char* temp = str; int i = 0; int j = 0; while (ch[i] != '\0') { if (ch[i] != ' ') { str[j] = ch[i]; j++; } i++; } while (*ch++ = *temp++); } //去除字符串中的空格 //思路: 可以使用兩個指針開始時都指向字符串開始位置,一個指針遍歷整個字符串,找到不是空格的字符,將其賦值給另一個指針, //然后另一個指針往后移一個位置,直到遍歷到\0字符。 void remove_space(char* ch) { //用來遍歷字符串 char* ftemp = ch; //記錄非空格字符串 char* rtemp = ch; while (*ftemp) { if (*ftemp != ' ') { *rtemp = *ftemp; rtemp++; } ftemp++; } *rtemp = 0; } int main(void) { char ch[] = " h e ll o w o r lld "; remove_space(ch); printf("%s\n", ch); return 0; }
8.4.3 指針作為函數返回值
-
查詢某個字符出現在字符串中的位置
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> char* my_strchr01(char* str,char ch) { int i = 0; while (str[i]) { if (str[i] == ch) { return &str[i]; } i++; } return NULL; } char* my_strchr(char* str, char ch) { while (*str) { if (*str == ch) return str; str++; } return NULL; } int main() { char str[] = "hell worldll"; char* p = my_strchr(str, 'o'); if (p == NULL) { printf("未找到\n"); } else { printf("%s\n", p); } return EXIT_SUCCESS; } -
查詢某個子串出現在字符串中的位置
#include <stdio.h> char* mystr_str(char* src,char* dest){ //定義三個臨時指針 char* fsrc=src;//用于遍歷源字符串 char* tsrc=src;//用于記錄可能匹配到目標字符串的字符位置 char* tdest=dest;//用于遍歷目標字符串 while(*fsrc){ tsrc=fsrc; while(*fsrc==*tdest&&*fsrc!=0){ fsrc++; tdest++; } if(*tdest==0){ return tsrc; } fsrc=tsrc; tdest=dest; fsrc++; } return NULL; } int main(){ char ch[]="hellewordllo"; char dest[]="llo"; char* p =mystr_str(ch,dest); printf("%s\n",p); printf("%d",p-ch); return 0; }
8.5 指針與字符串
8.5.1 字符串的表現形式及區別
-
用字符數組存放一個字符串
//用字符數組存放一個字符串 char str[]="hello tom";//棧區字符串 char str2[] = {'h','e'}; -
用字符指針指向一個字符串
char* pStr="hello";//數據區常量區字符串說明:
-
C 語言對字符串常量" hello"是按字符數組處理的,在內存中開辟了一個字符數組用來存放字符串常量,程序在定義字符串指針變量 str 時只是把字符串首地址(即存放字符串的字符數組的首地址)賦給 pStr
-
對應的內存布局圖

-
-
區別
-
字符數組由若干個元素組成,每個元素放一個字符;而字符指針變量中存放的是地址(字符串/字符數組的首地 址),絕不是將整個字符串放到字符指針變量中(是字符串首地址)
-
對字符數組只能對各個元素賦值,不能用以下方法對字符數組賦值
char str[14]; str = "hello tom"; //錯誤 str[0] = 'i'; //ok -
對字符指針變量,采用下面方法賦值, 是可以的
char* a="yes"; a="hello tom"; *(a + 1) = 'o';//錯誤
-
8.5.2 字符串數組和指針數組
int main(void)
{
//指針數組
char ch1[] = "hello";
char ch2[] = "world";
char ch3[] = "dabaobei";
char* arr[] = { ch1, ch2, ch3 };
//字符串數組
char* arr[] = { "hello","world","dabaobei" };
char** p = arr;//ok
printf("%c\n", *(*(p+1)+1));//輸出o
//arr[0] arr[1] arr[2]
for (int i = 0; i < 3; i++)
{
printf("%s\n", arr[i]);
}
}
8.6 常用字符串應用模型
8.6.1 查找子字符串在字符串中出現的次數
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
char* my_strstr(char* src, char* dest)
{
char* fsrc = src;//用作于循環遍歷的指針
char* rsrc = src;//記錄每次相同的首地址
char* tdest = dest;
while (*fsrc)
{
rsrc = fsrc;
while (*fsrc == *tdest && *fsrc != '\0')
{
fsrc++;
tdest++;
}
if (*tdest == '\0')
{
return rsrc;
}
//回滾
fsrc = rsrc;
tdest = dest;
fsrc++;
}
return NULL;
}
int main()
{
char str[] = "11abcd111122abcd333abcd3322abcd3333322qqq";
char ch[] = "abcd";
char* p = my_strstr(str, ch);
printf("%d\n", p - str);
int count = 0;//記錄個數
while (p)
{
count++;
p += strlen(ch);
p = my_strstr(p, ch);
if(p)printf("%d\n", p-str);//輸出出現的位置
}
printf("abcd在字符串中出現:%d次\n", count);
return 0;
}
8.6.2 字符串逆序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
void inverse01(char* ch)
{
int i = 0;
int j = strlen(ch) - 1;
while (i < j)
{
char temp = ch[i];
ch[i] = ch[j];
ch[j] = temp;
i++;
j--;
}
return;
}
void inverse(char* ch)
{
char* ftemp = ch;
char* btemp = ch + strlen(ch) - 1;
//printf("%c\n", *btemp);
while (ftemp < btemp)
{
char temp = *ftemp;
*ftemp = *btemp;
*btemp = temp;
ftemp++;
btemp--;
}
return;
}
int main0701()
{
char ch[] = "hello world";
inverse(ch);
printf("%s\n", ch);
return EXIT_SUCCESS;
}
//回文字符串
//abcba abccba abcbdcba
int symm(char* ch)
{
char* ftemp = ch;
char* btemp = ch + strlen(ch) - 1;
while (ftemp < btemp)
{
if (*ftemp != *btemp)
return 1;
ftemp++;
btemp--;
}
return 0;
}
int main(void)
{
char ch[] = "abcbdcba";
int value = symm(ch);
if (!value)
{
printf("相同\n");
}
else
{
printf("不相同\n");
}
return 0;
}
8.7 字符串處理函數
8.7.1 strcpy()
-
函數原型 char *strcpy(char *dest, const char *src);
-
功能:把src所指向的字符串復制到dest所指向的空間中,'\0'也會拷貝過去
-
參數:
- dest:目的字符串首地址
- src:源字符首地址
-
返回值:
- 成功:返回dest字符串的首地址
- 失?。篘ULL
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> void my_strcpy(char* dest,const char* src) { while (*dest++ = *src++); } void my_strncpy(char* dest, const char* src,size_t n) { while ((*dest++ = *src++) && --n);//注意這里是--n } int main() { char ch[] = "hello world"; char str[100] = {0}; //my_strcpy(str, ch); //字符串拷貝 //strcpy(str, ch); //字符串有限拷貝 //strncpy(str, ch, 50); my_strncpy(str, ch, 5); printf("%s\n", str); return EXIT_SUCCESS; }注意:如果參數dest所指的內存空間不夠大,可能會造成緩沖溢出的錯誤情況。
8.7.2 strncpy()
- 原型:char *strncpy(char *dest, const char *src, size_t n);
- 功能:把src指向字符串的前n個字符復制到dest所指向的空間中,是否拷貝結束看指定的長度是否包含'\0'。
- 參數:
- dest:目的字符串首地址
- src:源字符首地址
- n:指定需要拷貝字符串個數
- 返回值:
- 成功:返回dest字符串的首地址
- 失敗:NULL
8.7.3 strcat()
-
原型:char *strcat(char *dest, const char *src);
-
功能:將src字符串連接到dest的尾部,‘\0’也會追加過去
-
參數:
- dest:目的字符串首地址
- src:源字符首地址
-
返回值:
- 成功:返回dest字符串的首地址
- 失敗:NULL
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> void my_strcat(char* dest, const char* src) { //找到dest字符串中\0位置 while (*dest)dest++; while (*dest++ = *src++); } void my_strncat(char* dest, char* src, size_t n) { while (*dest)dest++; while ((*dest++ = *src++)&& --n); } int main09() { char dest[100] = "hello"; char src[] = "world"; //字符串追加 //strcat(dest, src); //字符串有限追加 //strncat(dest, src, 30); //my_strcat(dest, src); my_strncat(dest, src, 3); printf("%s\n", dest); return EXIT_SUCCESS; }
8.7.4 strncat()
- 原型:char *strncat(char *dest, const char *src, size_t n);
- 功能:將src字符串前n個字符連接到dest的尾部,‘\0’也會追加過去
- 參數:
- dest:目的字符串首地址
- src:源字符首地址
- n:指定需要追加字符串個數
- 返回值:
- 成功:返回dest字符串的首地址
- 失敗:NULL
8.7.5 strcmp()
-
原型:int strcmp(const char *s1, const char *s2);
-
功能:比較 s1 和 s2 的大小,比較的是字符ASCII碼大小。
-
參數:
- s1:字符串1首地址
- s2:字符串2首地址
-
返回值:
- 相等:0
- 大于:>0 在不同操作系統strcmp結果會不同 返回ASCII差值
- 小于:<0
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> int my_strcmp(const char *s1, const char *s2) { while (*s1 == *s2) { if (!*s1) return 0; s1++; s2++; } return *s1 > *s2 ? 1 : -1; } int my_strncmp(const char *s1, const char *s2, size_t n) { for (int i = 0; i < n && s1[i] && s2[i]; i++) { if (s1[i] != s2[i]) { return s1[i] > s2[i] ? 1 : -1; } } return 0; } //int my_strcmp(const char *s1, const char *s2) //{ // while (*s1++ == *s2++ && !*s1); // if (!*--s1 && !*--s2)return 0; // return *s1 > *s2 ? 1 : -1; //} int main10() { char ch1[] = "hallo world"; char ch2[] = "hello world"; //int value = strcmp(ch1, ch2); //int value = strncmp(ch1, ch2, 15); //int value = my_strcmp(ch1, ch2); //int value = my_strncmp(ch1, ch2,3); //printf("%d\n", value); if (strcmp(ch1, ch2)) { printf("不相同\n"); } else { printf("相同\n"); } return EXIT_SUCCESS; }
8.7.6 strncmp()
- 原型:int strncmp(const char *s1, const char *s2, size_t n);
- 功能:比較 s1 和 s2 前n個字符的大小,比較的是字符ASCII碼大小。
- 參數:
- s1:字符串1首地址
- s2:字符串2首地址
- n:指定比較字符串的數量
- 返回值:
- 相等:0
- 大于: > 0
- 小于: < 0
8.7.7 strchr()
-
原型:char *strchr(const char *s, int c);
-
功能:在字符串s中查找字母c出現的位置
-
參數:
- s:字符串首地址
- c:匹配字母(字符)
-
返回值:
- 成功:返回第一次出現的c地址
- 失?。篘ULL
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> char* my_strchr(const char* s,int c) { while (*s) { if (*s == c) return s; s++; } return NULL; } int main() { char ch[] = "hello world"; char c = 'l'; //char* p = strchr(ch, c); char* p = my_strchr(ch, c); printf("%s\n", p); return EXIT_SUCCESS; } int main(void) { char ch[] = "hello world"; char str[] = "llo"; char* p = strstr(ch, str); printf("%s\n", p); return 0; }
8.7.8 strstr()
- 原型:char *strstr(const char *haystack, const char *needle);
- 功能:在字符串haystack中查找字符串needle出現的位置
- 參數:
- haystack:源字符串首地址
- needle:匹配字符串首地址
- 返回值:
- 成功:返回第一次出現的needle地址
- 失?。篘ULL
8.7.9 strtok()
-
原型:char *strtok(char *str, const char *delim);
-
功能:能將字符串分割成一個個片段。當strtok()在參數s的字符串中發現參數delim中包含的分割字符時, 則會將該字符改為\0 字符,當連續出現多個時只替換第一個為\0。
-
參數:
- str:指向欲分割的字符串
- delim:為分割字符串中包含的所有字符
-
返回值:
- 成功:分割后字符串首地址
- 失?。篘ULL
注意:在第一次調用時:strtok()必需給予參數s字符串;往后的調用則將參數s設置成NULL,每次調用成功則返回指向被分割出片段的指針
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> int main1() { //字符串截取 strtok 會破壞源字符串 用\0替換分割的標志位 char ch[] = "www.itcast.cn";//123456@qq.com //www\0itcast.cn //www\0itcast\0cn char* p = strtok(ch, "."); printf("%s\n", p);//www //剩下的字符串在緩沖區中,可以用NULL來再次使用 p = strtok(NULL, "."); printf("%s\n", p); p = strtok(NULL, "."); printf("%s\n", p); //printf("%s\n", ch); //printf("%p\n", p); //printf("%p\n", ch); return EXIT_SUCCESS; } int main2(void) { char ch[] = "123456@qq.com"; char str[100] = { 0 }; //字符串備份 strcpy(str, ch); char* p = strtok(str, "@"); printf("%s\n", p); p = strtok(NULL, "."); printf("%s\n", p); return 0; } int main3(void) { //char ch[] = "nichousha\nchounizadi\nzaichouyigeshishi\nduibuqidagewocuole\nguawazi"; char ch[] = "你瞅啥\n瞅你咋啦\n再瞅一個試試\n對不起大哥我錯嘍\n瓜娃子"; char* p = strtok(ch, "\n"); while (p) { printf("%s\n", p); p = strtok(NULL, "\n"); } return 0; }
8.7.10 atoi()
-
原型:int atoi(const char *nptr);
-
功能:atoi()會掃描nptr字符串,跳過前面的空格字符,直到遇到數字或正負號才開始做轉換,而遇到非數字或字符串結束符('\0')才結束轉換,并將結果返回返回值。
-
參數:nptr:待轉換的字符串
-
返回值:成功轉換后整數
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> int main1() { char ch[] = " -123-456abc123"; int i = atoi(ch); printf("%d\n", i);//-123 return EXIT_SUCCESS; } int main2() { char ch[] = " -123.456-456abc123"; double i = atof(ch); printf("%.2f\n", i);//-123.46 return EXIT_SUCCESS; } int main() { char ch[] = " -123.456-456abc123"; long i = atol(ch); printf("%ld\n", i); return EXIT_SUCCESS; } -
類似的函數有:
- atof():把一個小數形式的字符串轉化為一個浮點數。
- atol():將一個字符串轉化為long類型
九、內存管理
9.1 作用域
C語言變量的作用域分為:
-
代碼塊作用域(代碼塊是{}之間的一段代碼)
-
函數作用域
-
文件作用域
9.1.1 局部變量與全局變量
-
局部變量:局部變量也叫auto自動變量(auto可寫可不寫),一般情況下代碼塊{}內部定義的變量都是自動變量,它有如下特點
-
在一個函數內定義,只在函數范圍內有效
-
在復合語句中定義,只在復合語句中有效
-
隨著函數調用的結束或復合語句的結束局部變量的聲明聲明周期也結束
-
如果沒有賦初值,內容為隨機
-
示例:
#include <stdio.h> void test() { //auto寫不寫是一樣的 //auto只能出現在{}內部 auto int b = 10; } int main(void) { //b = 100; //err, 在main作用域中沒有b if (1) { //在復合語句中定義,只在復合語句中有效 int a = 10; printf("a = %d\n", a); } //a = 10; //err離開if()的復合語句,a已經不存在 return 0; }
-
-
全局變量
-
在函數外定義,可被本文件及其它文件中的函數所共用,若其它文件中的函數調用此變量,須用extern聲明
-
全局變量的生命周期和程序運行周期一樣
-
不同文件的全局變量不可重名
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> //數據區 全局變量可以和局部變量重名 //全局變量 在函數外部定義的變量 //作用域:整個項目中所有文件 如果在其他文件中使用 需要聲明 //生命周期:從程序創建到程序銷毀 int a = 10; void fun02() { a = 100; printf("%d\n", a); } int main02() { //數據在操作時會采用就進原則 printf("%d\n", a); int a = 123; printf("%p\n", &a); //匿名內部函數 { int a = 456;//這是是匿名內部函數新定義的局部變量不會影響外面的a //a = 456;如果這將456賦值給a改變的是主函數內定義的a,也即將123改成了456,后面輸出就是456 printf("%p\n", &a); printf("%d\n", a); } printf("%d\n", a); fun02(); return EXIT_SUCCESS; }
-
9.1.2 靜態變量(static)
- 靜態局部變量
- static局部變量的作用域也是在定義的函數內有效
- static局部變量的生命周期和程序運行周期一樣,同時staitc局部變量的值只初始化一次,但可以賦值多次
- static局部變量與全局變量若未賦以初值,則由系統自動賦值:數值型變量自動賦初值0,字符型變量賦空字符
- 靜態全局變量
- 在函數外定義,作用范圍被限制在所定義的文件中
- 不同文件靜態全局變量可以重名,但作用域不沖突
- static全局變量的生命周期和程序運行周期一樣,同時staitc全局變量的值只初始化一次
9.1.3 變量總結

注意:同一源文件中,允許全局變量和局部變量同名,在局部變量的作用域內,全局變量不起作用。
9.1.4 全局函數與靜態函數
-
在C語言中函數默認都是全局的,使用關鍵字static可以將函數聲明為靜態,函數定義為static就意味著這個函數只能在定義這個函數的文件中使用,在其他文件中不能調用,即使在其他文件中聲明這個函數都沒用。
-
對于不同文件中的staitc函數名字可以相同。
-
總結:

9.2 內存布局
9.2.1 內存分區
C代碼經過預處理、編譯、匯編、鏈接4步后生成一個可執行程序。
在 Windows 下,程序是一個普通的可執行文件,以下列出一個二進制可執行文件的基本情況:

通過上圖可以得知,在沒有運行程序前,也就是說程序沒有加載到內存前,可執行程序內部已經分好3段信息,分別為代碼區(text)、數據區(data)和未初始化數據區(bss)3 個部分(有些人直接把data和bss合起來叫做靜態區或全局區)。
-
代碼區
存放 CPU 執行的機器指令。通常代碼區是可共享的(即另外的執行程序可以調用它),使其可共享的目的是對于頻繁被執行的程序,只需要在內存中有一份代碼即可。代碼區通常是只讀的,使其只讀的原因是防止程序意外地修改了它的指令。另外,代碼區還規劃了局部變量的相關信息。
-
全局初始化數據區/靜態數據區(data段)
該區包含了在程序中明確被初始化的全局變量、已經初始化的靜態變量(包括全局靜態變量和局部靜態變量)和常量數據(如字符串常量)。
-
未初始化數據區(又叫 bss 區)
存入的是全局未初始化變量和未初始化靜態變量。未初始化數據區的數據在程序開始執行之前被內核初始化為 0 或者空(NULL)。
程序在加載到內存前,代碼區和全局區(data和bss)的大小就是固定的,程序運行期間不能改變。然后,運行可執行程序,系統把程序加載到內存,除了根據可執行程序的信息分出代碼區(text)、數據區(data)和未初始化數據區(bss)之外,還額外增加了棧區、堆區。

-
棧區(stack)
棧是一種先進后出的內存結構,由編譯器自動分配釋放,存放函數的參數值、返回值、局部變量等。在程序運行過程中實時加載和釋放,因此,局部變量的生存周期為申請到釋放該段??臻g。
-
堆區(heap)
堆是一個大容器,它的容量要遠遠大于棧,但沒有棧那樣先進后出的順序。用于動態內存分配。堆在內存中位于BSS區和棧區之間。一般由程序員分配和釋放,若程序員不釋放,程序結束時由操作系統回收。
9.2.2 內存操作函數
9.2.2.1 memset()
-
原型:void *memset(void *s, int c, size_t n);
-
功能:將s的內存區域的前n個字節以參數c填入
-
參數:
- s:需要操作內存s的首地址
- c:填充的字符,c雖然參數為int,但必須是unsigned char , 范圍為0~255
- n:指定需要設置的大小
-
返回值:s的首地址
-
示例:
int main() { int* p = (int*)malloc(sizeof(int) * 10); //memset()重置內存空間的值 memset(p, 0, sizeof(int)*10); //memset(p, 1, 40);//如果填入的是1,是按照字節填入int類型占4個字節每個字節是0x01,4個字節就是01010101H打印出來不會是1 for (int i = 0; i < 10; i++) { printf("%d\n", p[i]); } free(p); char ch[10]; //memset(ch, 'A', sizeof(char) * 10);//這樣打印出來會亂碼,因為不含'\0' memset(ch, 0, sizeof(char)*10); printf("%s\n", ch); return EXIT_SUCCESS; }
9.2.2.2 memcpy()
-
原型:void *memcpy(void *dest, const void *src, size_t n);
-
功能:拷貝src所指的內存內容的前n個字節到dest所值的內存地址上。
-
參數
- dest:目的內存首地址
- src:源內存首地址,注意:dest和src所指的內存空間不可重疊,可能會導致程序報錯
- n:需要拷貝的字節數
-
返回值:dest的首地址
-
示例:
int main(void) { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = (int*)malloc(sizeof(int) * 10); //字符串拷貝 strcpy() //內存拷貝 memcpy(p, arr, sizeof(int) * 10); for (int i = 0; i < 10; i++) { printf("%d\n", p[i]); } free(p); char ch[] = "hello\0 world"; char str[100]; //字符串拷貝遇到\0停止 //strcpy(str, ch); //內存拷貝 拷貝的內容和字節有關 memcpy(str, ch, 13); //printf("%s\n", str); for (int i = 0; i < 13; i++) { printf("%c", str[i]);//\0后面的字符也會被拷貝 } int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; //如果拷貝的目標和源發生重疊 可能報錯 memcpy(&arr[5], &arr[3], 20); for (int i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
9.2.2.3 memmove()
memmove()功能用法和memcpy()一樣,區別在于:dest和src所指的內存空間重疊時,memmove()仍然能處理,不過執行效率比memcpy()低些。
示例:
int main(void)
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//如果拷貝的目標和源發生重疊 可能報錯
//memcpy(&arr[5], &arr[3], 20);
memmove(&arr[5], &arr[3], 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
}
9.2.2.4 memcmp()
-
原型:int memcmp(const void *s1, const void *s2, size_t n);
-
功能:比較s1和s2所指向內存區域的前n個字節
-
參數:
- s1:內存首地址1
- s2:內存首地址2
- n:需比較的前n個字節
-
返回值:
- 相等:=0
- 大于:>0
- 小于:<0
-
示例:
int main(void) { //int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; //int arr2[] = { 1,2,3,4,5 }; char arr1[] = "hello\0 world"; char arr2[] = "hello\0 world"; //strcmp(); int value = memcmp(arr1, arr2, 13);//按字節進行比較,可以比較\0后面的字符 printf("%d\n", value); return 0; }
9.2.3 堆區內存分配和釋放
9.2.3.1 malloc()
-
原型:void *malloc(size_t size);
-
功能:在內存的動態存儲區(堆區)中分配一塊長度為size字節的連續區域,用來存放類型說明符指定的類型。分配的內存空間內容不確定,一般使用memset初始化。
-
參數:size:需要分配內存大小(單位:字節)
-
返回值:
- 成功:分配空間的起始地址
- 失敗:NULL
-
示例:
int main() { //棧區大小 1M //int arr[210000] = {0}; //開辟堆空間存儲數據 int* p = (int*)malloc(sizeof(int)); printf("%p\n", p); //使用堆空間 *p = 123; printf("%d\n", *p); //釋放堆空間 free(p); p = NULL; //p 野指針 //printf("%p\n", p); //*p = 456; //printf("%d\n", *p); return EXIT_SUCCESS; }
9.2.3.2 free()
- 原型:void free(void *ptr);
- 功能:釋放ptr所指向的一塊內存空間,ptr是一個任意類型的指針變量,指向被釋放區域的首地址。對同一內存空間多次釋放會出錯。
- 參數:ptr:需要釋放空間的首地址,被釋放區應是由malloc函數所分配的區域。
- 返回值:無
9.2.4 二級指針對應的堆空間
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
int main()
{
//int arr[5][3]
//開辟二級指針對應的堆空間
int** p = (int**)malloc(sizeof(int*) * 5);
for (int i = 0; i < 5; i++)
{
//開辟一級指針對應的堆空間
p[i] = (int*)malloc(sizeof(int) * 3);
}
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 3; j++)
{
scanf("%d", &p[i][j]);
}
}
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", p[i][j]);
//printf("%d ", *(p[i] + j));
//printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
for (int i = 0; i < 5; i++)
{
free(p[i]);
}
free(p);
return EXIT_SUCCESS;
}
十、復合類型(自定義類型)
10.1 結構體
10.1.1 概述
數組:描述一組具有相同類型數據的有序集合,用于處理大量相同類型的數據運算。
有時我們需要將不同類型的數據組合成一個有機的整體,如:一個學生有學號/姓名/性別/年齡/地址等屬性。顯然單獨定義以上變量比較繁瑣,數據不便于管理。
C語言中給出了另一種構造數據類型——結構體。
10.1.2 結構體變量的定義和初始化
-
定義結構體變量的方式:
- 先聲明結構體類型再定義變量名
- 在聲明類型的同時定義變量
- 直接定義結構體類型變量(無類型名)

-
結構體類型和結構體變量關系:
- 結構體類型:指定了一個結構體類型,它相當于一個模型,但其中并無具體數據,系統對之也不分配實際內存單元。
- 結構體變量:系統根據結構體類型(內部成員狀況)為之分配空間。
-
示例:
//結構體類型的定義 struct stu { char name[50]; int age; }; //先定義類型,再定義變量(常用) struct stu s1 = { "mike", 18 }; //定義類型同時定義變量 struct stu2 { char name[50]; int age; }s2 = { "lily", 22 }; //直接定義結構體類型變量(無類型名) struct { char name[50]; int age; }s3 = { "yuri", 25 };
10.1.3 結構體成員的使用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
//struct 結構體名
//{
// 結構體成員列表
// 姓名
// 年齡
// 成績
//}
struct student
{
char name[21];
int age;
int score;
char addr[51];
};//stu = { "張三",18,100,"北京市昌平區北清路22號" };
int main1()
{
//創建結構體變量
//結構體類型 結構體變量
//struct student stu;
////stu.name = "張三";//不能直接給字符串數組賦值
//strcpy(stu.name, "張三");//可以使用strcpy()函數賦值
//stu.age = 18;
//stu.score = 100;
//strcpy(stu.addr, "北京市昌平區北清路22號");
struct student stu = { "張三",18,100,"北京市昌平區北清路22號" };
printf("姓名:%s\n", stu.name);
printf("年齡:%d\n", stu.age);
printf("成績:%d\n", stu.score);
printf("地址:%s\n", stu.addr);
return EXIT_SUCCESS;
}
int main2(void)
{
struct student stu;
scanf("%s%d%d%s", stu.name, &stu.age, &stu.score, stu.addr);
printf("姓名:%s\n", stu.name);
printf("年齡:%d\n", stu.age);
printf("成績:%d\n", stu.score);
printf("地址:%s\n", stu.addr);
return 0;
}
10.1.4 結構體數組
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
struct student
{
//結構體成員需要偏移對齊
char name[21];
int age;
char sex;
int score[3];
char addr[51];
};
int main1()
{
struct student stu[3] =
{
{"黃某航",22,'M',88,99,0,"河北唐山"},
{"馬某羊",18,'F',59,59,59,"河北邯鄲"},
{"大法師",30,'M',100,100,100,"黑龍江大慶"}
};
//sizeof()計算數據類型在內存中占的字節大小
printf("結構體數組大?。?d\n", sizeof(stu));//不會等于結構體各成員所占內存之和,因為要考慮個成員偏移對齊的情況
printf("結構體元素大?。?d\n", sizeof(stu[0]));
printf("結構體元素個數:%d\n", sizeof(stu) / sizeof(stu[0]));
for (int i = 0; i < 3; i++)
{
printf("姓名:%s\n", stu[i].name);
printf("年齡:%d\n", stu[i].age);
printf("性別:%s\n", stu[i].sex == 'M' ? "男" : "女");
printf("成績1:%d\n", stu[i].score[0]);
printf("成績2:%d\n", stu[i].score[1]);
printf("成績3:%d\n", stu[i].score[2]);
printf("地址:%s\n", stu[i].addr);
}
return EXIT_SUCCESS;
}
int main2(void)
{
struct student stu[3] =
{
{ "黃某航",22,'M',88,99,0,"河北唐山" },
{ "馬某羊",18,'F',59,59,59,"河北邯鄲" },
{ "大法師",30,'M',100,100,100,"黑龍江大慶" }
};
for (int i = 0; i < 3 - 1; i++)
{
for (int j = 0; j< 3 - 1 - i; j++)
{
if (stu[j].age < stu[j + 1].age)
{
//結構體賦值
struct student temp = stu[j];
stu[j] = stu[j + 1];
stu[j + 1] = temp;
}
}
}
for (int i = 0; i < 3; i++)
{
printf("姓名:%s\n", stu[i].name);
printf("年齡:%d\n", stu[i].age);
printf("性別:%s\n", stu[i].sex == 'M' ? "男" : "女");
printf("成績1:%d\n", stu[i].score[0]);
printf("成績2:%d\n", stu[i].score[1]);
printf("成績3:%d\n", stu[i].score[2]);
printf("地址:%s\n", stu[i].addr);
}
return 0;
}
10.1.5 開辟堆空間存儲結構體
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
typedef struct student ss;
struct student
{
//結構體成員需要偏移對齊
char name[21];
int age;
char sex;
int score[3];
char addr[51];
};
int main0401()
{
//printf("%d\n", sizeof(struct student));
ss * p = (ss *)malloc(sizeof(ss) * 3);
printf("結構體指針大?。?d", sizeof(ss*));
for (int i = 0; i < 3; i++)
{
scanf("%s%d,%c%d%d%d%s", p[i].name, &p[i].age, &p[i].sex,
&p[i].score[0], &p[i].score[1], &p[i].score[2], p[i].addr);
}
for (int i = 0; i < 3; i++)
{
printf("姓名:%s\n", p[i].name);
printf("年齡:%d\n", p[i].age);
printf("性別:%s\n", p[i].sex == 'M' ? "男" : "女");
printf("成績1:%d\n", p[i].score[0]);
printf("成績2:%d\n", p[i].score[1]);
printf("成績3:%d\n", p[i].score[2]);
printf("地址:%s\n", p[i].addr);
}
free(p);
return EXIT_SUCCESS;
}
10.1.6 結構體嵌套結構體
#include <stdio.h>
struct person
{
char name[20];
char sex;
};
struct stu
{
int id;
struct person info;
};
int main()
{
struct stu s[2] = { 1, "lily", 'F', 2, "yuri", 'M' };
int i = 0;
for (i = 0; i < 2; i++)
{
printf("id = %d\tinfo.name=%s\tinfo.sex=%c\n", s[i].id, s[i].info.name, s[i].info.sex);
}
return 0;
}
10.1.7 結構體賦值
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
struct student
{
char name[21];
//char* name;//??
int age;
int score;
char addr[51];
};
int main()
{
struct student stu = { "孫尚香",26,60,"巴蜀" };
struct student s1 = stu;
//int a = 10;
//int b = a;
//b = 20;
//深拷貝和淺拷貝
strcpy(s1.name, "甘夫人");
s1.age = 28;
s1.score = 80;
//這里不會改變源結構體的值
//但結構體成員換成char* name就不一樣
printf("%s\n", stu.name);//孫尚香
printf("%d\n", stu.age);//26
printf("%d\n", stu.score);//60
return EXIT_SUCCESS;
}
10.1.8 結構體指針
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
//結構體成員為指針類型
struct student
{
char* name;
int age;
int *scores;
char* addr;
};
int main1()
{
struct student stu;
//stu.name = "張三";
stu.name = (char*)malloc(sizeof(char) * 21);
stu.scores = (int*)malloc(sizeof(int) * 3);
stu.addr = (char*)malloc(sizeof(char) * 51);
strcpy(stu.name, "張三");
stu.age = 18;
stu.scores[0] = 88;
stu.scores[1] = 99;
stu.scores[2] = 100;
strcpy(stu.addr, "北京市");
printf("%s\n", stu.name);
printf("%d\n", stu.age);
printf("%d\n", stu.scores[0]);
printf("%d\n", stu.scores[1]);
printf("%d\n", stu.scores[2]);
printf("%s\n", stu.addr);
free(stu.name);
free(stu.scores);
free(stu.addr);
return EXIT_SUCCESS;
}
struct stu
{
char name[21];
int age;
int scores[3];
char addr[51];
};
int main2(void)
{
//結構體指針
struct stu ss = { "林沖",30,100,100,100,"汴京" };
struct stu * p = &ss;
//printf("%s\n", (*p).name);
//printf("%d\n", (*p).age);
//結構體指針->成員
//結構體變量.成員
printf("%s\n", p->name);
printf("%d\n", p->age);
printf("%d\n", p->scores[0]);
printf("%d\n", p->scores[1]);
printf("%d\n", p->scores[2]);
printf("%s\n", p->addr);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
typedef struct student ss;
struct student
{
char* name;
int age;
int* scores;
char* addr;
};
int main08()
{
//通過結構體指針操作堆空間
ss* p = (ss*)malloc(sizeof(ss) * 3);
for (int i = 0; i < 3; i++)
{
//(p + i)->name;
p[i].name = (char*)malloc(sizeof(char) * 21);
p[i].scores = (int*)malloc(sizeof(int) * 3);
p[i].addr = (char*)malloc(sizeof(char) * 51);
}
for (int i = 0; i < 3; i++)
{
scanf("%s%d%d%d%d%s", p[i].name, &p[i].age, &p[i].scores[0],
&p[i].scores[1], &p[i].scores[2], p[i].addr);
}
for (int i = 0; i < 3; i++)
{
printf("%s ", p[i].name);
printf("%d ", p[i].age);
printf("%d ", p[i].scores[0]);
printf("%d ", (p + i)->scores[1]);
printf("%d ", (p + i)->scores[2]);
printf("%s\n", (p + i)->addr);
}
//釋放堆空間
//先釋放內層,再釋放外層
for (int i = 0; i < 3; i++)
{
free(p[i].name);
free(p[i].scores);
free(p[i].addr);
}
free(p);
system("pause");
return EXIT_SUCCESS;
}
10.1.9 結構體與函數
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
typedef struct student ss;
struct student
{
char name[21];
//char *name;
int age;
int score[3];
char addr[51];
};
void fun01(ss stu1)
{
//stu1.name = (char*)malloc(sizeof(char)*21);
strcpy(stu1.name, "盧俊義");
printf("%s\n", stu1.name);
}
int main01()
{
ss stu = { NULL,50,101,"水泊梁山" };
//stu.name = (char*)malloc(sizeof(char) * 21);
strcpy(stu.name, "宋江");
fun01(stu);
printf("%s\n", stu.name);
return EXIT_SUCCESS;
}
void fun02(ss * p)
{
strcpy(p->name, "公孫勝");
printf("%s\n", p->name);
}
int main02(void)
{
//結構體指針作為函數參數
ss stu = { "吳用",50,101,"水泊梁山" };
fun02(&stu);
printf("%s\n", stu.name);
return 0;
}
//數組作為函數參數退化為指針 丟失元素精度 需要傳遞個數
void BubbleSort(ss * stu, int len)
{
//printf("%d\n", sizeof(stu));
for (int i = 0; i < len - 1; i++)
for (int j = 0; j < len - i - 1; j++)
{
//if (stu[j].age>stu[j + 1].age)
if ((stu + j)->age > (stu + j + 1)->age)
{
ss temp = stu[j];
stu[j] = stu[j + 1];
stu[j + 1] = temp;
}
}
}
int main03(void)
{
ss stu[3] =
{
{ "魯智深",30,33,33,33,"五臺山" },
{"呼延灼",45,44,44,44,"汴京"},
{"顧大嫂",28,33,33,33,"汴京"},
};
BubbleSort(stu, 3);
for (int i = 0; i < 3; i++)
{
printf("姓名:%s\n", stu[i].name);
printf("年齡:%d\n", stu[i].age);
printf("成績1:%d\n", stu[i].score[0]);
printf("成績2:%d\n", stu[i].score[1]);
printf("成績3:%d\n", stu[i].score[2]);
printf("地址:%s\n", stu[i].addr);
}
}
10.1.10 const修飾的結構體指針
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<time.h>
typedef struct student ss;
struct student
{
char name[21];
//char *name;
int age;
int score[3];
char addr[51];
};
int main1001()
{
ss stu1 = { "孫悟空",700,101,101,101,"花果山" };
ss stu2 = { "豬八戒",1200,1000,1000,1000,"高老莊" };
//const修飾結構體指針類型
const ss* p = &stu1;
//p = &stu2;//OK
//p->age = 888;//err
//(*p).age = 888;//err
return EXIT_SUCCESS;
}
int main1003(void)
{
ss stu1 = { "孫悟空",700,101,101,101,"花果山" };
ss stu2 = { "豬八戒",1200,1000,1000,1000,"高老莊" };
//const 修飾結構體指針變量
ss* const p = &stu1;
//p = &stu2;//err
p->age = 888;
strcpy(p->name, "沙悟凈");
}
int main1004(void)
{
ss stu1 = { "孫悟空",700,101,101,101,"花果山" };
ss stu2 = { "豬八戒",1200,1000,1000,1000,"高老莊" };
//const 修飾結構體指針類型
//const 修飾結構體指針變量
const ss* const p = &stu1;
//p = &stu2;//err;
//p->age = 888;//err
ss** pp = &p;
//*pp = &stu2;
(*pp)->age = 888;
(**pp).age = 888;
}
10.2 共用體(聯合體)
-
聯合union是一個能在同一個存儲空間存儲不同類型數據的類型
-
聯合體所占的內存長度等于其最長成員的長度倍數,也有叫做共用體
-
同一內存段可以用來存放幾種不同類型的成員,但每一瞬時只有一種起作用
-
共用體變量中起作用的成員是最后一次存放的成員,在存入一個新的成員后原有的成員的值會被覆蓋
-
共用體變量的地址和它的各成員的地址都是同一地址
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> union Var { int a; float b; double c; char d; short f[6];//12 }; int main11() { union Var var; var.a = 100; var.b = 3.14; printf("%d\n", var.a); printf("%f\n", var.b); printf("大?。?d\n", sizeof(var));//16個字節 printf("%p\n", &var); printf("%p\n", &var.a); printf("%p\n", &var.b); printf("%p\n", &var.c); return EXIT_SUCCESS; }
10.3 枚舉
-
枚舉:將變量的值一一列舉出來,變量的值只限于列舉出來的值的范圍內。
-
枚舉類型定義:
enum 枚舉名 { 枚舉值表 }; -
在枚舉值表中應列出所有可用值,也稱為枚舉元素
-
枚舉值是常量,不能在程序中用賦值語句再對它賦值
-
枚舉元素本身由系統定義了一個表示序號的數值從0開始順序定義為0,1,2 …
-
示例:
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> #include<time.h> enum TYPE { run,attack,skill,dance=10,showUI,frozen=20,dizz,dath,moti=30 }; int main() { int value; while (1) { scanf("%d", &value); switch (value) { case run: printf("英雄正在移動中...\n"); //value = 30; break; case attack: printf("英雄正在攻擊中...\n"); break; case skill: printf("英雄正在釋放技能中...\n"); break; case dance: printf("英雄正在跳舞中...\n"); break; case showUI: printf("英雄正在顯示徽章...\n"); break; case frozen: printf("英雄被冰凍中...\n"); break; case dizz: printf("英雄被眩暈中...\n"); break; case dath: printf("英雄死亡...\n"); return 0; break; case moti: printf("英雄等待釋放命令...\n"); break; } } return EXIT_SUCCESS; }
10.4 typedef
typedef為C語言的關鍵字,作用是為一種數據類型(基本類型或自定義數據類型)定義一個新名字,不能創建新類型。
- 與#define不同,typedef僅限于數據類型,而不是能是表達式或具體的值
-
define發生在預處理,typedef發生在編譯階段
示例:
#include <stdio.h>
typedef int INT;
typedef char BYTE;
typedef BYTE T_BYTE;
typedef unsigned char UBYTE;
typedef struct type
{
UBYTE a;
INT b;
T_BYTE c;
}TYPE, *PTYPE;
//這里相當于這樣定義
//typedef struct type TYPE
//typedef struct type* PTYPE
int main()
{
TYPE t;
t.a = 254;
t.b = 10;
t.c = 'c';
PTYPE p = &t;
printf("%u, %d, %c\n", p->a, p->b, p->c);
return 0;
}
浙公網安備 33010602011771號