頭文件
頭文件是擴展名為 .h 的文件,包含了 C 函數聲明和宏定義,被多個源文件中引用共享。有兩種類型的頭文件:程序員編寫的頭文件和編譯器自帶的頭文件。
在程序中要使用頭文件,需要使用 C 預處理指令 #include 來引用它。前面我們已經看過 stdio.h 頭文件,它是編譯器自帶的頭文件。
引用頭文件相當於復制頭文件的內容,但是我們不會直接在源文件中復制頭文件的內容,因為這么做很容易出錯,特別在程序是由多個源文件組成的時候。
A simple practice in C 或 C++ 程序中,建議把所有的常量、宏、系統全局變量和函數原型寫在頭文件中,在需要的時候隨時引用這些頭文件。
引用頭文件的語法
使用預處理指令 #include 可以引用用戶和系統頭文件。它的形式有以下兩種:
#include <file>
這種形式用於引用系統頭文件。它在系統目錄的標准列表中搜索名為 file 的文件。在編譯源代碼時,您可以通過 -I 選項把目錄前置在該列表前。
#include "file"
這種形式用於引用用戶頭文件。它在包含當前文件的目錄中搜索名為 file 的文件。在編譯源代碼時,您可以通過 -I 選項把目錄前置在該列表前。
引用頭文件的操作
#include 指令會指示 C 預處理器瀏覽指定的文件作為輸入。預處理器的輸出包含了已經生成的輸出,被引用文件生成的輸出以及 #include 指令之后的文本輸出。例如,如果您有一個頭文件 header.h,如下:
char *test (void);
和一個使用了頭文件的主程序 program.c,如下:
int x;
#include "header.h"
int main (void)
{
puts (test ());
}
編譯器會看到如下的代碼信息:
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 值表示程序中沒有錯誤。
errno、perror() 和 strerror()
C 語言提供了 perror() 和 strerror() 函數來顯示與 errno 相關的文本消息。
- perror() 函數顯示您傳給它的字符串,后跟一個冒號、一個空格和當前 errno 值的文本表示形式。
- 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