1、預處理命令的定義
使用庫函數之前,應該用#include引入對應的頭文件。這種以#號開頭的命令稱為預處理命令。
所謂預處理是指在進行編譯時的第一遍掃描(詞法掃描和語法分析)之前所做的工作。預處理是C語言的一個重要功能,它由於處理程序負責完成。當編譯一個程序時,系統將自動調用預處理程序對程序中“#”開頭的預處理部分進行處理,處理完畢之后可以進入源程序的編譯階段。
C語言源文件要經過編譯、鏈接才能生成可執行程序:
(1)編譯(Compile)會將源文件(.c
文件)轉換為目標文件。對於 VC/VS,目標文件后綴為.obj
;對於GCC,目標文件后綴為.o
。編譯是針對單個源文件的,一次編譯操作只能編譯一個源文件,如果程序中有多個源文件,就需要多次編譯操作。
(2)鏈接(Link)是針對多個文件的,它會將編譯生成的多個目標文件以及系統中的庫、組件等合並成一個可執行程序。
在實際開發中,有時候在編譯之前還需要對源文件進行簡單的處理。例如,我們希望自己的程序在 Windows 和 Linux 下都能夠運行,那么就要在 Windows 下使用 VS 編譯一遍,然后在 Linux 下使用 GCC 編譯一遍。但是現在有個問題,程序中要實現的某個功能在 VS 和 GCC 下使用的函數不同(假設 VS 下使用 a(),GCC 下使用 b()),VS 下的函數在 GCC 下不能編譯通過,GCC 下的函數在 VS 下也不能編譯通過,怎么辦呢?
這就需要在編譯之前先對源文件進行處理:如果檢測到是 VS,就保留 a() 刪除 b();如果檢測到是 GCC,就保留 b() 刪除 a()。
這些在編譯之前對源文件進行簡單加工的過程,就稱為預處理(即預先處理、提前處理)。
預處理主要是處理以#
開頭的命令,例如#include <stdio.h>
等。預處理命令要放在所有函數之外,而且一般都放在源文件的前面。
預處理是C語言的一個重要功能,由預處理程序完成。當對一個源文件進行編譯時,系統將自動調用預處理程序對源程序中的預處理部分作處理,處理完畢自動進入對源程序的編譯。
編譯器會將預處理的結果保存到和源文件同名的.i
文件中,例如 main.c 的預處理結果在 main.i 中。和.c
一樣,.i
也是文本文件,可以用編輯器打開直接查看內容。
C語言提供了多種預處理功能,如宏定義、文件包含、條件編譯等,合理地使用它們會使編寫的程序便於閱讀、修改、移植和調試,也有利於模塊化程序設計。
C語言中提供了多種預處理功能,如宏定義、文件包含、條件編譯等,合理的使用預處理便於閱讀、修改、移植和調試,也有利於模塊化程序設計。
2、include的用法
#include叫做文件包含命令,用來引入對應的頭文件(.h文件)。#include 也是C語言預處理命令的一種。
#include 的處理過程很簡單,就是將頭文件的內容插入到該命令所在的位置,從而把頭文件和當前源文件連接成一個源文件,這與復制粘貼的效果相同。
#include 的用法有兩種,如下所示:
#include <stdio.h> //使用尖括號表示在系統頭文件目錄中去找 #include “math.h” //使用雙引號表示首先在當前的源文件目錄中去查找,若未找到再到系統頭文件目錄中去找
使用尖括號< >和雙引號" "的區別在於頭文件的搜索路徑不同:
使用尖括號< >,編譯器會到系統路徑下查找頭文件;
而使用雙引號" ",編譯器首先在當前目錄下查找頭文件,如果沒有找到,再到系統路徑下查找。
也就是說,使用雙引號比使用尖括號多了一個查找路徑,它的功能更為強大。
stdio.h 和 stdlib.h 都是標准頭文件,它們存放於系統路徑下,所以使用尖括號和雙引號都能夠成功引入;而我們自己編寫的頭文件,一般存放於當前項目的路徑下,所以不能使用尖括號,只能使用雙引號。
當然,你也可以把當前項目所在的目錄添加到系統路徑,這樣就可以使用尖括號了,但是一般沒人這么做,純粹多此一舉,費力不討好。
在以后的編程中,大家既可以使用尖括號來引入標准頭文件,也可以使用雙引號來引入標准頭文件;不過,我個人的習慣是使用尖括號來引入標准頭文件,使用雙引號來引入自定義頭文件(自己編寫的頭文件),這樣一眼就能看出頭文件的區別。
關於 #include 用法的注意事項:
- 一個 #include 命令只能包含一個頭文件,多個頭文件需要多個 #include 命令。
- 同一個頭文件可以被多次引入,多次引入的效果和一次引入的效果相同,因為頭文件在代碼層面有防止重復引入的機制。
- 文件包含允許嵌套,也就是說在一個被包含的文件中又可以包含另一個文件。
3、宏定義define
#define 叫做宏定義命令,它也是C語言預處理命令的一種。所謂宏定義,就是用一個標識符來表示一個字符串,如果在后面的代碼中出現了該標識符,那么就全部替換成指定的字符串。
(1)、無參宏定義
格式:#define 宏名 字符串
#
表示這是一條預處理命令,所有的預處理命令都以 # 開頭。宏名
是標識符的一種,命名規則和變量相同。字符串
可以是數字、表達式、if 語句、函數等。
字符串可以是常量、表達式、格式串等
注意:字符串為表達式時該加括號時記得加括號,如果字符串后面有分號會連分號一同替換。
例如:
#define N 100
N
為宏名,100
是宏的內容(宏所表示的字符串)。在預處理階段,對程序中所有出現的“宏名”,預處理器都會用宏定義中的字符串去代換,這稱為“宏替換”或“宏展開”。
宏定義是由源程序中的宏定義命令#define
完成的,宏替換是由預處理程序完成的。
宏定義只是簡單的字符串代換,是在預處理完成的,而typedef是在編譯時處理的,它不是簡單地代換,而是對類型說明符重新命名。
宏定義的作用域包括從宏定義命名起到源程序結束,如果要終止其作用域可以使用#undef命令來取消宏定義:
格式:#undef 標識符
(2)、帶參宏定義
格式:#define 宏名(形參表) 字符串
注意:宏名域形參表之間不能有空格出現,否則會把形參當做字符串處理
C語言允許宏帶有參數。在宏定義中的參數稱為“形式參數”,在宏調用中的參數稱為“實際參數”,這點和函數有些類似。對帶參數的宏,在展開過程中不僅要進行字符串替換,還要用實參去替換形參。
帶參宏定義和函數的區別:
帶參數的宏和函數很相似,但有本質上的區別:宏展開僅僅是字符串的替換,不會對表達式進行計算;宏在編譯之前就被處理掉了,它沒有機會參與編譯,也不會占用內存。而函數是一段可以重復使用的代碼,會被編譯,會給它分配內存,每次調用函數,就是執行這塊內存中的代碼。
(3)C語言宏參數的字符串化和宏參數的連接
在宏定義中,有時還會用到#
和##
兩個符號,它們能夠對宏參數進行操作:
#
用來將宏參數轉換為字符串,也就是在宏參數的開頭和末尾添加引號。例如有如下宏定義:
#define STR(s) #s 那么: printf("%s", STR(c.biancheng.net)); printf("%s", STR("c.biancheng.net")); 分別被展開為: printf("%s", "c.biancheng.net"); printf("%s", "\"c.biancheng.net\"");
可以發現,即使給宏參數“傳遞”的數據中包含引號,使用#
仍然會在兩頭添加新的引號,而原來的引號會被轉義。
##
稱為連接符,用來將宏參數或其他的串連接起來。例如有如下的宏定義:
#define CON1(a, b) a##e##b #define CON2(a, b) a##b##00 那么: printf("%f\n", CON1(8.5, 2)); printf("%d\n", CON2(12, 34)); 將被展開為: printf("%f\n", 8.5e2); printf("%d\n", 123400);
對 #define 用法的幾點說明
(1)宏定義是用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名,這只是一種簡單粗暴的替換。字符串中可以含任何字符,它可以是常數、表達式、if 語句、函數等,預處理程序對它不作任何檢查,如有錯誤,只能在編譯已被宏展開后的源程序時發現。
(2)宏定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起替換。
(3)宏定義必須寫在函數之外,其作用域為宏定義命令起到源程序結束。如要終止其作用域可使用#undef
命令。
(4)代碼中的宏名如果被引號包圍,那么預處理程序不對其作宏代替。
(5)宏定義允許嵌套,在宏定義的字符串中可以使用已經定義的宏名,在宏展開時由預處理程序層層代換。
(6)習慣上宏名用大寫字母表示,以便於與變量區別。但也允許用小寫字母。
(7)可用宏定義表示數據類型,使書寫方便。
應注意用宏定義表示數據類型和用 typedef 定義數據說明符的區別。宏定義只是簡單的字符串替換,由預處理器來處理;而 typedef 是在編譯階段由編譯器處理的,它並不是簡單的字符串替換,而給原有的數據類型起一個新的名字,將它作為一種新的數據類型。
4、預定義宏
在C語言中,有一些預處理定義的符號串,他們的值是字符串常量,或者是十進制數字常量,通常在調試程序時用於輸出源程序的各項信息。
符號 |
|
含義 |
_FILE_ |
字符串常量 |
正在預編譯的源文件名 |
_FUNCTION_ |
當前所在的函數名 |
|
_DATE_ |
預編譯文件的日期 |
|
_TIME_ |
預編譯文件的時間 |
|
_LINE_ |
整數常量 |
文件當前行的行號 |
_STDC_ |
如果編譯器遵循ANSI C,則值為1
|
5、條件編譯
預處理程序提供了條件編譯的功能,可以按不同的條件去編譯不同的程序代碼,從而產生不同的目標代碼文件,這對於程序的移植和調試是很有用的。避免了重復引用相同的頭文件。
(1)#if的一般格式
#if 標識符 程序段1 #else 程序段2 #endif #if 標識符 程序段1 #elif 標識符 程序段2 #endif #if 標識符 程序段1 #elif 標識符 程序段2 #elif 標識符 程序段3 #else 程序段4 #endif 標識符值為真,就執行程序段1,否則執行程序段2.
(2)#ifdef的一般格式
#ifdef 標識符 程序段 #endif #ifdef 標識符 程序段1 #else 程序段2 #endif 若標識符已經被define宏定義過,就執行程序段1,否則執行1程序段2。
(3)#ifndef的一般格式
#ifndef 標識符 程序段 #endif #ifndef 標識符 程序段1 #else 程序段2 #endif 若標識符沒有被define宏定義過,就執行程序段1,否則執行1程序段2。
三者之間的區別
最后需要注意的是,#if 后面跟的是“整型常量表達式”,而 #ifdef 和 #ifndef 后面跟的只能是一個宏名,不能是其他的。
6、#error命令
#error 指令用於在編譯期間產生錯誤信息,並阻止程序的編譯,其形式如下:
#error error_message
例如,我們的程序針對 Linux 編寫,不保證兼容 Windows,那么可以這樣做:
#ifdef WIN32 #error This programme cannot compile at Windows Platform #endif
WIN32 是 Windows 下的預定義宏。當用戶在 Windows 下編譯該程序時,由於定義了 WIN32 這個宏,所以會執行 #error 命令,提示用戶發生了編譯錯誤,錯誤信息是:
This programme cannot compile at Windows Platform
7、預處理總結
預處理指令是以#
號開頭的代碼行,# 號必須是該行除了任何空白字符外的第一個字符。# 后是指令關鍵字,在關鍵字和 # 號之間允許存在任意個數的空白字符,整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對源代碼做某些轉換。
下面是本章涉及到的部分預處理指令:
指令 | 說明 |
---|---|
# | 空指令,無任何效果 |
#include | 包含一個源代碼文件 |
#define | 定義宏 |
#undef | 取消已定義的宏 |
#if | 如果給定條件為真,則編譯下面代碼 |
#ifdef | 如果宏已經定義,則編譯下面代碼 |
#ifndef | 如果宏沒有定義,則編譯下面代碼 |
#elif | 如果前面的#if給定條件不為真,當前條件為真,則編譯下面代碼 |
#endif | 結束一個#if……#else條件編譯塊 |
預處理功能是C語言特有的功能,它是在對源程序正式編譯前由預處理程序完成的,程序員在程序中用預處理命令來調用這些功能。
宏定義可以帶有參數,宏調用時是以實參代換形參,而不是“值傳送”。
為了避免宏代換時發生錯誤,宏定義中的字符串應加括號,字符串中出現的形式參數兩邊也應加括號。
文件包含是預處理的一個重要功能,它可用來把多個源文件連接成一個源文件進行編譯,結果將生成一個目標文件。
條件編譯允許只編譯源程序中滿足條件的程序段,使生成的目標程序較短,從而減少了內存的開銷並提高了程序的效率。
使用預處理功能便於程序的修改、閱讀、移植和調試,也便於實現模塊化程序設計。