頭文件是擴展名爲 .h 的文件,包含了 C 函數聲明和宏定義,被多個源文件中引用共享。有兩種類型的頭文件:程序員編寫的頭文件和編譯器自帶的頭文件。html
在程序中要使用頭文件,須要使用 C 預處理指令 #include 來引用它。前面咱們已經看過 stdio.h 頭文件,它是編譯器自帶的頭文件。程序員
引用頭文件至關於複製頭文件的內容,可是咱們不會直接在源文件中複製頭文件的內容,由於這麼作很容易出錯,特別在程序是由多個源文件組成的時候。express
A simple practice in C 或 C++ 程序中,建議把全部的常量、宏、系統全局變量和函數原型寫在頭文件中,在須要的時候隨時引用這些頭文件。編程
使用預處理指令 #include 能夠引用用戶和系統頭文件。它的形式有如下兩種:編程語言
#include <file>
這種形式用於引用系統頭文件。它在系統目錄的標準列表中搜索名爲 file 的文件。在編譯源代碼時,您能夠經過 -I 選項把目錄前置在該列表前。ide
#include "file"
這種形式用於引用用戶頭文件。它在包含當前文件的目錄中搜索名爲 file 的文件。在編譯源代碼時,您能夠經過 -I 選項把目錄前置在該列表前。函數
#include 指令會指示 C 預處理器瀏覽指定的文件做爲輸入。預處理器的輸出包含了已經生成的輸出,被引用文件生成的輸出以及 #include 指令以後的文本輸出。例如,若是您有一個頭文件 header.h,以下:操作系統
char *test (void);
和一個使用了頭文件的主程序 program.c,以下:指針
int x; #include "header.h" int main (void) { puts (test ()); }
編譯器會看到以下的代碼信息:code
int x; char *test (void); int main (void) { puts (test ()); }
若是一個頭文件被引用兩次,編譯器會處理兩次頭文件的內容,這將產生錯誤。爲了防止這種狀況,標準的作法是把文件的整個內容放在條件編譯語句中,以下:
// 若是HEADER_FILE不存在 #ifndef HEADER_FILE #define HEADER_FILE the entire header file file #endif
這種結構就是一般所說的包裝器 #ifndef。當再次引用頭文件時,條件爲假,由於 HEADER_FILE 已定義。此時,預處理器會跳過文件的整個內容,編譯器會忽略它。
有時須要從多個不一樣的頭文件中選擇一個引用到程序中。例如,須要指定在不一樣的操做系統上使用的配置參數。您能夠經過一系列條件來實現這點,以下:
#if SYSTEM_1 # include "system_1.h" #elif SYSTEM_2 # include "system_2.h" #elif SYSTEM_3 ... #endif
可是若是頭文件比較多的時候,這麼作是很不穩當的,預處理器使用宏來定義頭文件的名稱。這就是所謂的有條件引用。它不是用頭文件的名稱做爲 #include 的直接參數,您只須要使用宏名稱代替便可:
#define SYSTEM_H "system_1.h" ... #include SYSTEM_H
SYSTEM_H 會擴展,預處理器會查找 system_1.h,就像 #include 最初編寫的那樣。SYSTEM_H 可經過 -D 選項被您的 Makefile 定義。
強制類型轉換是把變量從一種類型轉換爲另外一種數據類型。例如,若是您想存儲一個 long 類型的值到一個簡單的整型中,您須要把 long 類型強制轉換爲 int 類型。您可使用強制類型轉換運算符來把值顯式地從一種類型轉換爲另外一種類型,以下所示:
(type_name) expression
請看下面的實例,使用強制類型轉換運算符把一個整數變量除以另外一個整數變量,獲得一個浮點數:
#include <stdio.h> int main() { int sum = 17, count = 5; double mean; mean = (double) sum / count; printf("Value of mean : %f\n", mean ); }
當上面的代碼被編譯和執行時,它會產生下列結果:
Value of mean : 3.400000
這裏要注意的是強制類型轉換運算符的優先級大於除法,所以 sum 的值首先被轉換爲 double 型,而後除以 count,獲得一個類型爲 double 的值。
類型轉換能夠是隱式的,由編譯器自動執行,也能夠是顯式的,經過使用強制類型轉換運算符來指定。在編程時,有須要類型轉換的時候都用上強制類型轉換運算符,是一種良好的編程習慣。
整數提高是指把小於 int 或 unsigned int 的整數類型轉換爲 int 或 unsigned int 的過程。請看下面的實例,在 int 中添加一個字符:
#include <stdio.h> int main() { int i = 17; char c = 'c'; /* ascii 值是 99 */ int sum; sum = i + c; printf("Value of sum : %d\n", sum ); }
當上面的代碼被編譯和執行時,它會產生下列結果:
Value of sum : 116
在這裏,sum 的值爲 116,由於編譯器進行了整數提高,在執行實際加法運算時,把 'c' 的值轉換爲對應的 ascii 值。
經常使用的算術轉換是隱式地把值強制轉換爲相同的類型。編譯器首先執行整數提高,若是操做數類型不一樣,則它們會被轉換爲下列層次中出現的最高層次的類型:
經常使用的算術轉換不適用於賦值運算符、邏輯運算符 && 和 ||。讓咱們看看下面的實例來理解這個概念:
#include <stdio.h> int main() { int i = 17; char c = 'c'; /* ascii 值是 99 */ float sum; sum = i + c; printf("Value of sum : %f\n", sum ); }
當上面的代碼被編譯和執行時,它會產生下列結果:
Value of sum : 116.000000
在這裏,c 首先被轉換爲整數,可是因爲最後的值是 float 型的,因此會應用經常使用的算術轉換,編譯器會把 i 和 c 轉換爲浮點型,並把它們相加獲得一個浮點數。
C 語言不提供對錯誤處理的直接支持,可是做爲一種系統編程語言,它以返回值的形式容許您訪問底層數據。在發生錯誤時,大多數的 C 或 UNIX 函數調用返回 1 或 NULL,同時會設置一個錯誤代碼 errno,該錯誤代碼是全局變量,表示在函數調用期間發生了錯誤。您能夠在 errno.h 頭文件中找到各類各樣的錯誤代碼。
因此,C 程序員能夠經過檢查返回值,而後根據返回值決定採起哪一種適當的動做。開發人員應該在程序初始化時,把 errno 設置爲 0,這是一種良好的編程習慣。0 值表示程序中沒有錯誤。
C 語言提供了 perror() 和 strerror() 函數來顯示與 errno 相關的文本消息。
讓咱們來模擬一種錯誤狀況,嘗試打開一個不存在的文件。您可使用多種方式來輸出錯誤消息,在這裏咱們使用函數來演示用法。另外有一點須要注意,您應該使用 stderr 文件流來輸出全部的錯誤。
#include <stdio.h> #include <errno.h> #include <string.h> extern int errno ; int main () { FILE * pf; int errnum; pf = fopen ("unexist.txt", "rb"); if (pf == NULL) { errnum = errno; fprintf(stderr, "錯誤號: %d\n", errno); perror("經過 perror 輸出錯誤"); fprintf(stderr, "打開文件錯誤: %s\n", strerror( errnum )); } else { fclose (pf); } return 0; }
當上面的代碼被編譯和執行時,它會產生下列結果:
錯誤號: 2
經過 perror 輸出錯誤: No such file or directory
打開文件錯誤: No such file or directory
在進行除法運算時,若是不檢查除數是否爲零,則會致使一個運行時錯誤。
爲了不這種狀況發生,下面的代碼在進行除法運算前會先檢查除數是否爲零:
#include <stdio.h> #include <stdlib.h> int main() { int dividend = 20; int divisor = 0; int quotient; if( divisor == 0){ fprintf(stderr, "除數爲 0 退出運行...\n"); exit(-1); } quotient = dividend / divisor; fprintf(stderr, "quotient 變量的值爲 : %d\n", quotient ); exit(0); }
當上面的代碼被編譯和執行時,它會產生下列結果:
除數爲 0 退出運行...
一般狀況下,程序成功執行完一個操做正常退出的時候會帶有值 EXIT_SUCCESS。在這裏,EXIT_SUCCESS 是宏,它被定義爲 0。
若是程序中存在一種錯誤狀況,當您退出程序時,會帶有狀態值 EXIT_FAILURE,被定義爲 -1。因此,上面的程序能夠寫成:
#include <stdio.h> #include <stdlib.h> int main() { int dividend = 20; int divisor = 5; int quotient; if( divisor == 0){ fprintf(stderr, "除數爲 0 退出運行...\n"); exit(EXIT_FAILURE); } quotient = dividend / divisor; fprintf(stderr, "quotient 變量的值爲: %d\n", quotient ); exit(EXIT_SUCCESS); }
當上面的代碼被編譯和執行時,它會產生下列結果:
quotient 變量的值爲 : 4
遞歸指的是在函數的定義中使用函數自身的方法。
語法格式以下:
void recursion() { statements; ... ... ... recursion(); /* 函數調用自身 */ ... ... ... } int main() { recursion(); }
流程圖:
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; }
當上面的代碼被編譯和執行時,它會產生下列結果:
15 的階乘爲 1307674368000.000000
下面的實例使用遞歸函數生成一個給定的數的斐波那契數列:
#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; }
當上面的代碼被編譯和執行時,它會產生下列結果:
0
1
1
2
3
5
8
13
21
34