1. 程式人生 > 遊戲 >來個翻天覆地的大改造吧!《加油站大亨》DLC《全能大改造》現已上市

來個翻天覆地的大改造吧!《加油站大亨》DLC《全能大改造》現已上市

C 程式結構

在我們學習 C 語言的基本構建塊之前,讓我們先來看看一個最小的 C 程式結構.

C Hello World 例項

C 程式主要包括以下部分:

  • 前處理器指令
  • 函式
  • 變數
  • 語句 & 表示式
  • 註釋

讓我們看一段簡單的程式碼,可以輸出單詞 "Hello World":

例項

#include <stdio.h> 

int main()
{   
	/* 我的第一個 C 程式 */
  printf("Hello, World! \n");
  
  return 0;
}

接下來我們講解一下上面這段程式:

  1. 程式的第一行 #include <stdio.h>
    是前處理器指令,告訴 C 編譯器在實際編譯之前要包含 stdio.h 檔案。
  2. 下一行 int main() 是主函式,程式從這裡開始執行。
  3. 下一行 /.../ 將會被編譯器忽略,這裡放置程式的註釋內容。它們被稱為程式的註釋。
  4. 下一行 printf(...) 是 C 中另一個可用的函式,會在螢幕上顯示訊息 "Hello, World!"。
  5. 下一行 return 0; 終止 main() 函式,並返回值 0。

編譯 & 執行 C 程式

接下來讓我們看看如何把原始碼儲存在一個檔案中,以及如何編譯並執行它。下面是簡單的步驟:

  1. 開啟一個文字編輯器,新增上述程式碼。
  2. 儲存檔案為 hello.c
  3. 開啟命令提示符,進入到儲存檔案所在的目錄。
  4. 鍵入 gcc hello.c,輸入回車,編譯程式碼。
  5. 如果程式碼中沒有錯誤,命令提示符會跳到下一行,並生成 a.out 可執行檔案。
  6. 現在,鍵入 a.out 來執行程式。
  7. 您可以看到螢幕上顯示 "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;
}