1、預處理


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語言特有的功能,它是在對源程序正式編譯前由預處理程序完成的,程序員在程序中用預處理命令來調用這些功能。

  宏定義可以帶有參數,宏調用時是以實參代換形參,而不是“值傳送”。

  為了避免宏代換時發生錯誤,宏定義中的字符串應加括號,字符串中出現的形式參數兩邊也應加括號。

  文件包含是預處理的一個重要功能,它可用來把多個源文件連接成一個源文件進行編譯,結果將生成一個目標文件。

  條件編譯允許只編譯源程序中滿足條件的程序段,使生成的目標程序較短,從而減少了內存的開銷並提高了程序的效率。

  使用預處理功能便於程序的修改、閱讀、移植和調試,也便於實現模塊化程序設計。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM