新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 牛人業(yè)話 > C語言的那些小秘密之預(yù)處理

C語言的那些小秘密之預(yù)處理

作者: 時間:2015-06-14 來源:網(wǎng)絡(luò) 收藏

  的一個重要知識點(diǎn),它能改善程序設(shè)計(jì)的環(huán)境,有助于編寫易移植、易調(diào)試的程序。因此,我們有必要掌握好命令,在自己編程的時候靈活的使用它,使得編寫的程序結(jié)構(gòu)優(yōu)良,更加易于調(diào)試和閱讀。接下來我盡可能的把中重要知識點(diǎn)向讀者講解清楚,使讀者能夠在自己以后編程的過程中熟練的使用預(yù)處理命令。

本文引用地址:http://www.butianyuan.cn/article/275697.htm

  的預(yù)處理主要有三個方面:

  1、文件的包含

  2、宏定義

  3、條件編譯

  一、文件包含的形式有下面兩種

  1、#include "文件名"

  2、#include <文件名>

  它們之間的區(qū)別在于:<文件名>系統(tǒng)到頭文件目錄查找文件, "文件名"則先在當(dāng)前目錄查找,如果沒有才到頭文件目錄查找;當(dāng)然我們也可以使用在命令行來指定頭文件路徑方法。還要注意就是如果在源文件包含的頭文件之間出現(xiàn)調(diào)用的情況,那么被調(diào)用的頭文件要出現(xiàn)在調(diào)用頭文件的前面。

  二、宏定義

  宏定義的使用有兩種形式,一種不帶參數(shù),而另外一種帶參數(shù)。

  1、不帶參數(shù)

  格式: #define 標(biāo)識符 字符串

  相信上面這個格式大家并不陌生,下面還是來看看如何使用吧。當(dāng)然在講解之前我們的看看使用過程中的如下幾個注意要點(diǎn):

  (1)預(yù)處理不做語法檢查,所以我們選用的時候要尤其小心

  (2)宏定義寫在函數(shù)的花括號外邊,作用域?yàn)槠浜蟮某绦?,通常在文件開頭部分,直到用#undef命令終止宏定義的作用域

  (3)不要在字符串中使用宏,如果宏名出現(xiàn)在字符串中那么將按照字符串進(jìn)行處理

  下面來看段代碼的使用。

  [html] view plaincopy#include

  #define N 9

  int main ()

  {

  int i,a[N];

  for(i=0;i

  {

  a[i]=i;

  printf("%dt",a[i]);

  if((i+1)%3==0)

  printf("n");

  }

  //#undef N

  printf("%dn",N);

  }

  運(yùn)行結(jié)果為:

  [html] view plaincopy0 1 2

  3 4 5

  6 7 8

  9

  Press any key to continue

  我們在此主要是介紹下宏的作用域問題,當(dāng)在以上代碼中注釋掉#undef N時,接下來的打印語句能夠正常的打印出;但是當(dāng)我們沒有注釋掉#undef N的時候就會出現(xiàn)error C2065: 'N' : undeclared identifier錯誤,提示N沒有定義。接下來看看帶參數(shù)的宏的使用。

  2、帶參數(shù)

  #define 宏名(參數(shù)表) 字符串

  注意要點(diǎn):

  (1)宏名和參數(shù)的括號間不能有空格

  (2)宏替換只作替換,不做計(jì)算,不做表達(dá)式求解,這點(diǎn)要尤其注意

  (3)函數(shù)調(diào)用在編譯后程序運(yùn)行時進(jìn)行,并且分配內(nèi)存。宏替換在編譯前進(jìn)行,不分配內(nèi)存

  (4)宏的啞實(shí)結(jié)合(所謂的啞實(shí)結(jié)合類似于函數(shù)調(diào)用過程中實(shí)參替代形參的過程)不存在類型,也沒有類型轉(zhuǎn)換。

  (5)宏展開使源程序變長,函數(shù)調(diào)用不會

  下面來看看linux下一個典型的應(yīng)用:

  #define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })

  #define max(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x > _y ? _x : _y; })

  在上面的兩個宏中我們發(fā)現(xiàn)有這么一句代碼(void) (&_x == &_y);可能不少讀者有點(diǎn)發(fā)懵的感覺,這啥意思呢?!其實(shí)我們細(xì)細(xì)分析就知道,首先看看“==”,這是一個邏輯表達(dá)式,它要求兩邊的比較類型必須一致,如果我們的&x和&y類型不一致,如一個為char*,另一個為int*,不是同一個類型,當(dāng)我們使用gcc編譯的時候就會出現(xiàn)警告信息,vc6則會報(bào)錯error C2446: '==' : no conversion from 'char *' to 'int *'。這句代碼(void) (&_x == &_y); 在此的功能就相當(dāng)于執(zhí)行一個簡單的判斷操作,我們用來判斷x和y的類型是否一致。別小看了這句代碼,如果學(xué)會了使用它會給你的代碼帶來不少的便捷。下面給出一個小小的事例:

  [cpp] view plaincopy#include

  void print()

  {

  printf("hello world!!!n");

  return ;

  }

  void main(int argc,char*argv)

  {

  print();

  return ;

  }

  運(yùn)行結(jié)果為:

  

 

  [cpp] view plaincopyhello world!!!

  Press any key to continue

  現(xiàn)在我們來修改下代碼后看看運(yùn)行結(jié)果:

  [cpp] view plaincopy#include

  void print()

  {

  printf("hello world!!!n");

  return ;

  }

  void main(int argc,char*argv)

  {

  #define print() ((void)(3))

  print();

  return ;

  }

  運(yùn)行結(jié)果為:

  [cpp] view plaincopyPress any key to continue

  這兒的結(jié)果沒有了我們之前的那句hello world!!!,可以看出這個時候函數(shù)并沒有被調(diào)用,這是因?yàn)槲覀兪褂昧?define print() ((void)(3)),使得之后調(diào)用函數(shù)print()轉(zhuǎn)換為了一個空操作,所以這個函數(shù)在接下來的代碼中都不會被調(diào)用了,就像被“沖刷掉”了一樣??吹竭@兒你是不是想起我們之前的那篇《的那些小秘密之?dāng)嘌浴妨四?,我們同樣可以使用這種方法來實(shí)現(xiàn)斷言的關(guān)閉,方法與之類似,在此就不再講解了,有興趣的讀者可以自己試試。講到這兒似乎應(yīng)該結(jié)束了,但是細(xì)心的讀者會有另外一個疑惑?在#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })中,我們?yōu)槭裁匆褂孟駎ypeof(y) _y = (y)這樣的轉(zhuǎn)換呢?而不直接使用typeof(x)==typeof(y)或者(void) (&x == &y); x < y ? x : y; 呢?如果我們使用typeof(x)==typeof(y)就好比使用了char==int一樣,這是不允許的。我們使用一個typeof(y) _y = (y)這樣的轉(zhuǎn)換,這是為了防止x和y為一個表達(dá)式的情況,如x=i++之類的,如果不轉(zhuǎn)換的話i++就多執(zhí)行了幾次操作,得到的不是我們想要的結(jié)果,但是如果我們使用了typeof(y) _y = (y)這樣的轉(zhuǎn)換,那就不會出現(xiàn)這樣的問題了。下面我們來看看如何使用宏定義實(shí)現(xiàn)變參,先看看實(shí)現(xiàn)方法。

  #define print(...) printf(__VA_ARGS__)

  看看上面的宏,其中“...”指可變參數(shù)。實(shí)現(xiàn)的可變參數(shù)的實(shí)現(xiàn)方式就是使用“...”所代表的內(nèi)容替代__VA_ARGS__,看看下面的代碼就知道了。

  [cpp] view plaincopy#include

  #define print(...) printf(__VA_ARGS__)

  int main(int argc,char*argv)

  {

  print("hello world----%dn",1111);

  return 0;

  }

  運(yùn)行結(jié)果為:

  [cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg

  hello world----1111

  接著往下看。

  #define printf (tem, ...) fprintf (stdout, tem, ## __VA_ARGS__)

  如有對fprintf不熟悉的讀者可以自己查查函數(shù)手冊,在此不再講解。

  [cpp] view plaincopy#include

  #define print(temp, ...) fprintf(stdout, temp, ##__VA_ARGS__)

  int main(int argc,char*argv)

  {

  print("hello world----%dn",1111);

  return 0;

  }

  運(yùn)行結(jié)果為:

  [cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg

  hello world----1111

  temp在此的作用為設(shè)定輸出字符串的格式,后邊“...”為可變參數(shù)?,F(xiàn)在問題來了,我們在宏定義中為什么要使用“##”呢?如果我們沒有使用##會怎么樣呢?看看下面的代碼:

  [cpp] view plaincopy#include

  #define print(temp, ...) fprintf(stdout, temp, __VA_ARGS__)

  int main(int argc,char*argv)

  {

  print("hello worldn");

  return 0;

  }

  編譯時發(fā)生了如下錯誤:

  [cpp] view plaincopyroot@ubuntu:/home/shiyan# gcc arg.c -o arg

  arg.c: In function ‘main’:

  arg.c:7:2: error: expected expression before ‘)’ token

  為什么會出現(xiàn)上面的錯誤呢,現(xiàn)在我們來分析下,我們進(jìn)行下宏替換,print("hello worldn")就變?yōu)榱薴printf(stdout, "hello worldn",)這樣我們就發(fā)現(xiàn)了后面出現(xiàn)了一個逗號,所以導(dǎo)致了錯誤,如果有“##”就不會出現(xiàn)這樣的錯誤了,這是因?yàn)槿绻勺儏?shù)被忽略或?yàn)榭盏臅r候,“##”操作將使預(yù)處理器去除掉它前面的那個逗號。如果存在可變參數(shù)時候,它也能正常工作。講了“##”,我們當(dāng)然也要講講“#”。先來看看下面一段代碼:

  [cpp] view plaincopy#include

  #define return_exam(p) if(!(p))

  {printf("error: "#p" file_name:%stfunction_name:%stline:%d .n",

  __FILE__, __func__, __LINE__); return 0;}

  int print()

  {

  return_exam(0);

  }

  int main(int argc,char*argv)

  {

  print();

  printf("hello world!!!n");

  return 0;

  }

  運(yùn)行結(jié)果為:

  [cpp] view plaincopyroot@ubuntu:/home/shiyan# ./arg

  error: 0 file_name:arg.c function_name:print line:9 .

  hello world!!!

  我們發(fā)現(xiàn)在運(yùn)行結(jié)果中打印出了出錯的文件名、函數(shù)名、以及行號。采用宏定義來檢測函數(shù)的返回值是否正確,僅僅是為了體現(xiàn)出我們要講解的宏,所以代碼做了最大的簡化工作,讀者在自己編寫代碼時候要學(xué)會這樣的檢測方式。“#”的作用就是將其后面的宏參數(shù)進(jìn)行字符串化操作,就是在宏變量進(jìn)行替換之后在其左右各加上一個上雙引號,這就使得"#p"變味了""p""我們發(fā)現(xiàn)這樣的話剛好兩邊的“""”就消失了。下面來看看最后一個知識點(diǎn)條件編譯。

  三、條件編譯

  條件編譯命令#if、#else、#elif、#endif、#ifdef、#ifndef,條件編譯指令的意思很簡單,跟我們學(xué)習(xí)的if語句類似。

  一般格式

  #if 常量表達(dá)式

  程序段1;

  [#else

  程序段2;]

  #endif

  功能:當(dāng)表達(dá)式為非0(“邏輯真”)時,編譯程序段1,否則編譯程序段2。

  一般格式

  #ifdef 標(biāo)識符

  程序段1;

  [#else

  程序段2;]

  #endif

  功能:當(dāng)“標(biāo)識符”已經(jīng)被#define命令定義過,則編譯程序段1,否則編譯程序段2。

  #ifndef 標(biāo)識符

  程序段1;

  [#else

  程序段2;]

  #endif

  功能:當(dāng)“標(biāo)識符”未被#define命令定義過,則編譯程序段1,否則編譯程序段2。

  學(xué)習(xí)了條件編譯指令之后,我們在調(diào)試代碼的時候,就不要再隨心所欲的刪減代碼了,如果我們不想某段代碼被編譯就可以使用條件編譯指令來將其注釋掉。如:

  #if (0)

  注釋代碼段;

  #endif

  就可以實(shí)現(xiàn)代碼的注釋了,需要的時候也可以將其啟用,而不會為需要重新編輯代碼時,發(fā)現(xiàn)已被刪除而頭疼了。

  其中值得注意的地方為,常量表達(dá)式在編譯時求值,所以表達(dá)式只能是常量或者已經(jīng)定義過的標(biāo)識符,不能為變量,也不可以為那些在編譯時候求值的操作符,如sizeof。

  下面來看段代碼:

  [cpp] view plaincopy#include

  #define N 1

  int main(int argc,char*argv)

  {

  int a=3;

  #if(a)

  printf("#if后面的表達(dá)式為變量n");

  #endif

  #if(N)

  printf("#if后面的表達(dá)式已定義,且不為0---successn");

  #else

  printf("#if后面的表達(dá)式已定義,且不為0---failn");

  #endif

  return 0;

  }

  運(yùn)行結(jié)果為:

  

 

  [cpp] view plaincopy#if后面的表達(dá)式已定義,且不為0---success

  Press any key to continue

  看看上面的代碼我們的表達(dá)式為變量a時并沒有打印出來,所以我們不能在其后的表示中使用變量。如果我們使用sizeof操作符會怎么樣呢?為了加深印象看看下面的代碼后結(jié)果吧。

  [cpp] view plaincopy#include

  int main(int argc,char*argv)

  {

  int a=9;

  #if(sizeof(a))

  printf("#if后面的表達(dá)式含有sizeof操作符n");

  #endif

  return 0;

  }

  編譯出現(xiàn)了如下錯誤:

  [cpp] view plaincopyfatal error C1017: invalid integer constant expression

  所以我們在使用條件編譯的時候要牢記這兩點(diǎn),常量表達(dá)式不能為變量和含有sizeof等在編譯時求值的操作符。

  接下來看看這里要講的最后一個#pragma指令。

  一般格式為:

  #pragma 參數(shù)

  下面給出幾種經(jīng)常使用的形式

  1、#pragma message("消息")

  看看下面一段代碼。

  [cpp] view plaincopy#include

  #define FDSA

  int main(int argc,char*argv)

  {

  #ifdef FDSA

  #pragma message("FDSA 已經(jīng)定義過了")

  #endif

  return 0;

  }

  編譯的時候我們可以在編譯輸出窗口中看到了輸出“FDSA 已經(jīng)定義過了”,通過這種方式我們可以在一些我們想要的地方輸出很多我們需要的信息。

  2、#pragma once

  如果我們在頭文件的開頭部分加入這條指令,那么就能保證我們的頭文件僅僅被編譯一次。

  3、#pragma hdrstop

  該指令表示編譯頭文件到此為止,后面的無需在進(jìn)行編譯了。

  4、#pragma pack()

  設(shè)定字節(jié)的對齊長度,這個指令我們在《C語言的那些小秘密之字節(jié)對齊》中已經(jīng)講解了,在此不再復(fù)述。

  5、#pragma warning(disable:M N;once:H;error:K)

  表示不顯示M和N號的警告信息,H號警告信息只報(bào)告一次,把K號警告信息作為一個錯誤來處理。

  到此關(guān)于預(yù)處理的講解就結(jié)束了。由于本人水平有限,博客中的不妥或錯誤之處在所難免,殷切希望讀者批評指正。同時也歡迎讀者共同探討相關(guān)的內(nèi)容,如果樂意交流的話請留下你寶貴的意見。

c語言相關(guān)文章:c語言教程




關(guān)鍵詞: C語言 預(yù)處理

評論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉