來個翻天覆地的大改造吧!《加油站大亨》DLC《全能大改造》現已上市
C 程式結構
在我們學習 C 語言的基本構建塊之前,讓我們先來看看一個最小的 C 程式結構.
C Hello World 例項
C 程式主要包括以下部分:
- 前處理器指令
- 函式
- 變數
- 語句 & 表示式
- 註釋
讓我們看一段簡單的程式碼,可以輸出單詞 "Hello World":
例項
#include <stdio.h>
int main()
{
/* 我的第一個 C 程式 */
printf("Hello, World! \n");
return 0;
}
接下來我們講解一下上面這段程式:
- 程式的第一行 #include <stdio.h>
- 下一行 int main() 是主函式,程式從這裡開始執行。
- 下一行 /.../ 將會被編譯器忽略,這裡放置程式的註釋內容。它們被稱為程式的註釋。
- 下一行 printf(...) 是 C 中另一個可用的函式,會在螢幕上顯示訊息 "Hello, World!"。
- 下一行 return 0; 終止 main() 函式,並返回值 0。
編譯 & 執行 C 程式
接下來讓我們看看如何把原始碼儲存在一個檔案中,以及如何編譯並執行它。下面是簡單的步驟:
- 開啟一個文字編輯器,新增上述程式碼。
- 儲存檔案為 hello.c。
- 開啟命令提示符,進入到儲存檔案所在的目錄。
- 鍵入 gcc hello.c,輸入回車,編譯程式碼。
- 如果程式碼中沒有錯誤,命令提示符會跳到下一行,並生成 a.out 可執行檔案。
- 現在,鍵入 a.out 來執行程式。
- 您可以看到螢幕上顯示 "Hello World"。
$ gcc hello.c
$ ./a.out
Hello, World!
請確保您的路徑中已包含 gcc 編譯器,並確保在包含原始檔 hello.c 的目錄中執行它。
如果是多個 c 程式碼的原始碼檔案,編譯方法如下:
$ gcc test1.c test2.c -o main.out
$ ./main.out
test1.c 與 test2.c 是兩個原始碼檔案。
C 的令牌(Token)
C 程式由各種令牌組成,令牌可以是關鍵字、識別符號、常量、字串值,或者是一個符號。例如,下面的 C 語句包括五個令牌:
printf("Hello, World! \n");
這五個令牌分別是:
printf
(
"Hello, World! \n"
)
;
關鍵字
下表列出了 C 中的保留字。這些保留字不能作為常量名、變數名或其他識別符號名稱。
關鍵字 | 說明 |
---|---|
auto | 宣告自動變數 |
break | 跳出當前迴圈 |
case | 開關語句分支 |
char | 宣告字元型變數或函式返回值型別 |
const | 定義常量,如果一個變數被 const 修飾,那麼它的值就不能再被改變 |
continue | 結束當前迴圈,開始下一輪迴圈 |
default | 開關語句中的"其它"分支 |
do | 迴圈語句的迴圈體 |
double | 宣告雙精度浮點型變數或函式返回值型別 |
else | 條件語句否定分支(與 if 連用) |
enum | 宣告列舉型別 |
extern | 宣告變數或函式是在其它檔案或本檔案的其他位置定義 |
float | 宣告浮點型變數或函式返回值型別 |
for | 一種迴圈語句 |
goto | 無條件跳轉語句 |
if | 條件語句 |
int | 宣告整型變數或函式 |
long | 宣告長整型變數或函式返回值型別 |
register | 宣告暫存器變數 |
return | 子程式返回語句(可以帶引數,也可不帶引數) |
short | 宣告短整型變數或函式 |
signed | 宣告有符號型別變數或函式 |
sizeof | 計算資料型別或變數長度(即所佔位元組數) |
static | 宣告靜態變數 |
struct | 宣告結構體型別 |
switch | 用於開關語句 |
typedef | 用以給資料型別取別名 |
unsigned | 宣告無符號型別變數或函式 |
union | 宣告共用體型別 |
void | 宣告函式無返回值或無引數,宣告無型別指標 |
volatile | 說明變數在程式執行中可被隱含地改變 |
while | 迴圈語句的迴圈條件 |
C 資料型別
在 C 語言中,資料型別指的是用於宣告不同型別的變數或函式的一個廣泛的系統。變數的型別決定了變數儲存佔用的空間,以及如何解釋儲存的位模式。
C 中的型別可分為以下幾種:
序號 | 型別與描述 |
---|---|
1 | 基本型別:它們是算術型別,包括兩種型別:整數型別和浮點型別。 |
2 | 列舉型別:它們也是算術型別,被用來定義在程式中只能賦予其一定的離散整數值的變數。 |
3 | void 型別:型別說明符 void 表明沒有可用的值。 |
4 | 派生型別:它們包括:指標型別、陣列型別、結構型別、共用體型別和函式型別。 |
void 型別
void 型別指定沒有可用的值。它通常用於以下三種情況下:
序號 | 型別與描述 |
---|---|
1 | 函式返回為空C 中有各種函式都不返回值,或者您可以說它們返回空。不返回值的函式的返回型別為空。例如 void exit (int status); |
2 | 函式引數為空C 中有各種函式不接受任何引數。不帶引數的函式可以接受一個 void。例如 int rand(void); |
3 | 指標指向 void型別為 void * 的指標代表物件的地址,而不是型別。例如,記憶體分配函式 void *malloc( size_t size ); 返回指向 void 的指標,可以轉換為任何資料型別。 |
位運算子
位運算子作用於位,並逐位執行操作。&、 | 和 ^ 的真值表如下所示:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假設如果 A = 60,且 B = 13,現在以二進位制格式表示,它們如下所示:
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
下表顯示了 C 語言支援的位運算子。假設變數 A 的值為 60,變數 B 的值為 13,則:
運算子 | 描述 | 例項 |
---|---|---|
& | 按位與操作,按二進位制位進行"與"運算。運算規則:0&0=0; 0&1=0; 1&0=0; 1&1=1;
|
(A & B) 將得到 12,即為 0000 1100 |
| | 按位或運算子,按二進位制位進行"或"運算。運算規則:`0 | 0=0; 0 |
^ | 異或運算子,按二進位制位進行"異或"運算。運算規則:0^0=0; 0^1=1; 1^0=1; 1^1=0;
|
(A ^ B) 將得到 49,即為 0011 0001 |
~ | 取反運算子,按二進位制位進行"取反"運算。運算規則:~1=-2; ~0=-1;
|
(~A ) 將得到 -61,即為 1100 0011,一個有符號二進位制數的補碼形式。 |
<< | 二進位制左移運算子。將一個運算物件的各二進位制位全部左移若干位(左邊的二進位制位丟棄,右邊補0)。 | A << 2 將得到 240,即為 1111 0000 |
>> | 二進位制右移運算子。將一個數的各二進位制位全部右移若干位,正數左補0,負數左補1,右邊丟棄。 | A >> 2 將得到 15,即為 0000 1111 |
例項
請看下面的例項,瞭解 C 語言中所有可用的位運算子:
例項
#include <stdio.h>
int main()
{
unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c 的值是 %d\n", c );
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c 的值是 %d\n", c );
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c 的值是 %d\n", c );
c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c 的值是 %d\n", c );
c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c 的值是 %d\n", c );
c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c 的值是 %d\n", c );
}
當上面的程式碼被編譯和執行時,它會產生下列結果:
Line 1 - c 的值是 12
Line 2 - c 的值是 61
Line 3 - c 的值是 49
Line 4 - c 的值是 -61
Line 5 - c 的值是 240
Line 6 - c 的值是 15
C 函式
函式是一組一起執行一個任務的語句。每個 C 程式都至少有一個函式,即主函式 main() ,所有簡單的程式都可以定義其他額外的函式。
您可以把程式碼劃分到不同的函式中。如何劃分程式碼到不同的函式中是由您來決定的,但在邏輯上,劃分通常是根據每個函式執行一個特定的任務來進行的。
函式宣告告訴編譯器函式的名稱、返回型別和引數。函式定義提供了函式的實際主體。
C 標準庫提供了大量的程式可以呼叫的內建函式。例如,函式 strcat() 用來連線兩個字串,函式 memcpy() 用來複制記憶體到另一個位置。
函式還有很多叫法,比如方法、子例程或程式,等等。
定義函式
C 語言中的函式定義的一般形式如下:
return_type function_name( parameter list )
{
body of the function
}
在 C 語言中,函式由一個函式頭和一個函式主體組成。下面列出一個函式的所有組成部分:
- 返回型別:一個函式可以返回一個值。return_type 是函式返回的值的資料型別。有些函式執行所需的操作而不返回值,在這種情況下,return_type 是關鍵字 void。
- 函式名稱:這是函式的實際名稱。函式名和引數列表一起構成了函式簽名。
- 引數:引數就像是佔位符。當函式被呼叫時,您向引數傳遞一個值,這個值被稱為實際引數。引數列表包括函式引數的型別、順序、數量。引數是可選的,也就是說,函式可能不包含引數。
- 函式主體:函式主體包含一組定義函式執行任務的語句。
例項
呼叫函式
建立 C 函式時,會定義函式做什麼,然後通過呼叫函式來完成已定義的任務。
當程式呼叫函式時,程式控制權會轉移給被呼叫的函式。被呼叫的函式執行已定義的任務,當函式的返回語句被執行時,或到達函式的結束括號時,會把程式控制權交還給主程式。
呼叫函式時,傳遞所需引數,如果函式返回一個值,則可以儲存返回值。例如:
#include <stdio.h>
/* 函式宣告 */
int max(int num1, int num2);
int main ()
{
/* 區域性變數定義 */
int a = 100;
int b = 200;
int ret;
/* 呼叫函式來獲取最大值 */
ret = max(a, b);
printf( "Max value is : %d\n", ret );
return 0;
}
/* 函式返回兩個數中較大的那個數 */
int max(int num1, int num2)
{
/* 區域性變數宣告 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
函式引數
如果函式要使用引數,則必須宣告接受引數值的變數。這些變數稱為函式的形式引數。
形式引數就像函式內的其他區域性變數,在進入函式時被建立,退出函式時被銷燬。
當呼叫函式時,有兩種向函式傳遞引數的方式:
呼叫型別 | 描述 |
---|---|
傳值呼叫 | 該方法把引數的實際值複製給函式的形式引數。在這種情況下,修改函式內的形式引數不會影響實際引數。 |
引用呼叫 | 通過指標傳遞方式,形參為指向實參地址的指標,當對形參的指向操作時,就相當於對實參本身進行的操作。 |
預設情況下,C 使用傳值呼叫來傳遞引數。一般來說,這意味著函式內的程式碼不能改變用於呼叫函式的實際引數。
全域性變數與區域性變數在記憶體中的區別:
- 全域性變數儲存在記憶體的全域性儲存區中,佔用靜態的儲存單元;
- 區域性變數儲存在棧中,只有在所在函式被呼叫時才動態地為變數分配儲存單元。
列舉變數的定義
前面我們只是聲明瞭列舉型別,接下來我們看看如何定義列舉變數。
我們可以通過以下三種方式來定義列舉變數
1、先定義列舉型別,再定義列舉變數
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2、定義列舉型別的同時定義列舉變數
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
3、省略列舉名稱,直接定義列舉變數
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
例項
#include <stdio.h>
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
int main()
{
enum DAY day;
day = WED;
printf("%d",day);
return 0;
}
以上例項輸出結果為:
3
回撥函式
函式指標作為某個函式的引數
函式指標變數可以作為某個函式的引數來使用的,回撥函式就是一個通過函式指標呼叫的函式。
簡單講:回撥函式是由別人的函式執行時呼叫你實現的函式。
以下是來自知乎作者常溪玲的解說:
你到一個商店買東西,剛好你要的東西沒有貨,於是你在店員那裡留下了你的電話,過了幾天店裡有貨了,店員就打了你的電話,然後你接到電話後就到店裡去取了貨。在這個例子裡,你的電話號碼就叫回調函式,你把電話留給店員就叫登記回撥函式,店裡後來有貨了叫做觸發了回撥關聯的事件,店員給你打電話叫做呼叫回撥函式,你到店裡去取貨叫做響應回撥事件。
C 字串
在 C 語言中,字串實際上是使用空字元 \0 結尾的一維字元陣列。因此,\0 是用於標記字串的結束。
空字元(Null character)又稱結束符,縮寫 NUL,是一個數值為 0 的控制字元,\0 是轉義字元,意思是告訴編譯器,這不是字元 0,而是空字元。
下面的宣告和初始化建立了一個 RUNOOB 字串。由於在陣列的末尾儲存了空字元 \0,所以字元陣列的大小比單詞 RUNOOB 的字元數多一個。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
依據陣列初始化規則,您可以把上面的語句寫成以下語句:
char site[] = "RUNOOB";
以下是 C/C++ 中定義的字串的記憶體表示:
其實,您不需要把 null 字元放在字串常量的末尾。C 編譯器會在初始化陣列時,自動把 \0 放在字串的末尾。
C 結構體
C 陣列允許定義可儲存相同型別資料項的變數,結構是 C 程式設計中另一種使用者自定義的可用的資料型別,它允許您儲存不同型別的資料項。
結構用於表示一條記錄,假設您想要跟蹤圖書館中書本的動態,您可能需要跟蹤每本書的下列屬性:
- Title
- Author
- Subject
- Book ID
定義結構
為了定義結構,您必須使用 struct 語句。struct 語句定義了一個包含多個成員的新的資料型別,struct 語句的格式如下:
//此宣告聲明瞭擁有3個成員的結構體,分別為整型的a,字元型的b和雙精度的c
//同時又聲明瞭結構體變數s1
//這個結構體並沒有標明其標籤
struct
{
int a;
char b;
double c;
} s1;
//此宣告聲明瞭擁有3個成員的結構體,分別為整型的a,字元型的b和雙精度的c
//結構體的標籤被命名為SIMPLE,沒有宣告變數
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE標籤的結構體,另外聲明瞭變數t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef建立新型別
typedef struct
{
int a;
char b;
double c;
} Simple2;
//現在可以用Simple2作為型別宣告新的結構體變數
Simple2 u1, u2[20], *u3;
C 錯誤處理
C 語言不提供對錯誤處理的直接支援,但是作為一種系統程式語言,它以返回值的形式允許您訪問底層資料。在發生錯誤時,大多數的 C 或 UNIX 函式呼叫返回 1 或 NULL,同時會設定一個錯誤程式碼 errno,該錯誤程式碼是全域性變數,表示在函式呼叫期間發生了錯誤。您可以在 errno.h 標頭檔案中找到各種各樣的錯誤程式碼。
所以,C 程式設計師可以通過檢查返回值,然後根據返回值決定採取哪種適當的動作。開發人員應該在程式初始化時,把 errno 設定為 0,這是一種良好的程式設計習慣。0 值表示程式中沒有錯誤。
errno、perror() 和 strerror()
C 語言提供了 perror() 和 strerror() 函式來顯示與 errno 相關的文字訊息。
- perror() 函式顯示您傳給它的字串,後跟一個冒號、一個空格和當前 errno 值的文字表示形式。
- strerror() 函式,返回一個指標,指標指向當前 errno 值的文字表示形式。
讓我們來模擬一種錯誤情況,嘗試開啟一個不存在的檔案。您可以使用多種方式來輸出錯誤訊息,在這裡我們使用函式來演示用法。另外有一點需要注意,您應該使用 stderr 檔案流來輸出所有的錯誤。
C 遞迴
遞迴指的是在函式的定義中使用函式自身的方法。
舉個例子:
從前有座山,山裡有座廟,廟裡有個老和尚,正在給小和尚講故事呢!故事是什麼呢?"從前有座山,山裡有座廟,廟裡有個老和尚,正在給小和尚講故事呢!故事是什麼呢?'從前有座山,山裡有座廟,廟裡有個老和尚,正在給小和尚講故事呢!故事是什麼呢?……'"
C 語言支援遞迴,即一個函式可以呼叫其自身。但在使用遞迴時,程式設計師需要注意定義一個從函式退出的條件,否則會進入死迴圈。
遞迴函式在解決許多數學問題上起了至關重要的作用,比如計算一個數的階乘、生成斐波那契數列,等等。
數的階乘
下面的例項使用遞迴函式計算一個給定的數的階乘:
#include <stdio.h>
double factorial(unsigned int i)
{
if(i <= 1)
{
return 1;
}
return i * factorial(i - 1);
}
int main()
{
int i = 15;
printf("%d 的階乘為 %f\n", i, factorial(i));
return 0;
}
斐波那契數列
下面的例項使用遞迴函式生成一個給定的數的斐波那契數列:
#include <stdio.h>
int fibonaci(int i)
{
if(i == 0)
{
return 0;
}
if(i == 1)
{
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}
int main()
{
int i;
for (i = 0; i < 10; i++)
{
printf("%d\t\n", fibonaci(i));
}
return 0;
}