成人a级高清视频在线观看-成人a大片在线观看-成人a大片高清在线观看-成人av在线播放-一a一级片-一 级 黄 中国色 片

C語言預(yù)處理命令

預(yù)處理(或稱預(yù)編譯)是指在進行編譯的第一遍掃描(詞法掃描和語法分析)之前所作的工作。預(yù)處理指令指示在程序正式編譯前就由編譯器進行的操作,可放在程序中任何位置。


預(yù)處理是C語言的一個重要功能,它由預(yù)處理程序負責(zé)完成。當(dāng)對一個源文件進行編譯時,系統(tǒng)將自動引用預(yù)處理程序?qū)υ闯绦蛑械念A(yù)處理部分作處理,處理完畢自動進入對源程序的編譯。


C語言提供多種預(yù)處理功能,主要處理#開始的預(yù)編譯指令,如宏定義(#define)、文件包含(#include)、條件編譯(#ifdef)等。合理使用預(yù)處理功能編寫的程序便于閱讀、修改、移植和調(diào)試,也有利于模塊化程序設(shè)計。


二  宏定義


C語言源程序中允許用一個標識符來表示一個字符串,稱為“宏”。被定義為宏的標識符稱為“宏名”。在編譯預(yù)處理時,對程序中所有出現(xiàn)的宏名,都用宏定義中的字符串去代換,這稱為宏替換或宏展開。


宏定義是由源程序中的宏定義命令完成的。宏替換是由預(yù)處理程序自動完成的。


在C語言中,宏定義分為有參數(shù)和無參數(shù)兩種。下面分別討論這兩種宏的定義和調(diào)用。


2.1 無參宏定義


無參宏的宏名后不帶參數(shù)。其定義的一般形式為:


#define  標識符  字符串


其中,“#”表示這是一條預(yù)處理命令(以#開頭的均為預(yù)處理命令)。“define”為宏定義命令。“標識符”為符號常量,即宏名。“字符串”可以是常數(shù)、表達式、格式串等。


宏定義用宏名來表示一個字符串,在宏展開時又以該字符串取代宏名。這只是一種簡單的文本替換,預(yù)處理程序?qū)λ蛔魅魏螜z查。如有錯誤,只能在編譯已被宏展開后的源程序時發(fā)現(xiàn)。


注意理解宏替換中“換”的概念,即在對相關(guān)命令或語句的含義和功能作具體分析之前就要進行文本替換。


【例1】定義常量:


#define MAX_TIME 1000


若在程序里面寫if(time < MAX_TIME){.........},則編譯器在處理該代碼前會將MAX_TIME替換為1000。


注意,這種情況下使用const定義常量可能更好,如const int MAX_TIME = 1000;。因為const常量有數(shù)據(jù)類型,而宏常量沒有數(shù)據(jù)類型。編譯器可以對前者進行類型安全檢查,而對后者只進行簡單的字符文本替換,沒有類型安全檢查,并且在字符替換時可能會產(chǎn)生意料不到的錯誤。


【例2】反例:


#define pint (int*)

pint pa, pb;

     

本意是定義pa和pb均為int型指針,但實際上變成int* pa,pb;。pa是int型指針,而pb是int型變量。本例中可用typedef來代替define,這樣pa和pb就都是int型指針了。


因為宏定義只是簡單的字符串代換,在預(yù)處理階段完成,而typedef是在編譯時處理的,它不是作簡單的代換,而是對類型說明符重新命名,被命名的標識符具有類型定義說明的功能。


typedef的具體說明見附錄6.4。


無參宏注意事項:


宏名一般用大寫字母表示,以便于與變量區(qū)別。宏定義末尾不必加分號,否則連分號一并替換。宏定義可以嵌套。


可用#undef命令終止宏定義的作用域。


使用宏可提高程序通用性和易讀性,減少不一致性,減少輸入錯誤和便于修改。如數(shù)組大小常用宏定義。預(yù)處理是在編譯之前的處理,而編譯工作的任務(wù)之一就是語法檢查,預(yù)處理不做語法檢查。宏定義寫在函數(shù)的花括號外邊,作用域為其后的程序,通常在文件的最開頭。字符串" "中永遠不包含宏,否則該宏名當(dāng)字符串處理。

宏定義不分配內(nèi)存,變量定義分配內(nèi)存。


2.2 帶參宏定義


C語言允許宏帶有參數(shù)。在宏定義中的參數(shù)稱為形式參數(shù),在宏調(diào)用中的參數(shù)稱為實際參數(shù)。


對帶參數(shù)的宏,在調(diào)用中,不僅要宏展開,而且要用實參去代換形參。


帶參宏定義的一般形式為:

#define  宏名(形參表)  字符串


在字符串中含有各個形參。


帶參宏調(diào)用的一般形式為:

宏名(實參表);


在宏定義中的形參是標識符,而宏調(diào)用中的實參可以是表達式。


在帶參宏定義中,形參不分配內(nèi)存單元,因此不必作類型定義。而宏調(diào)用中的實參有具體的值,要用它們?nèi)ゴ鷵Q形參,因此必須作類型說明,這點與函數(shù)不同。函數(shù)中形參和實參是兩個不同的量,各有自己的作用域,調(diào)用時要把實參值賦予形參,進行“值傳遞”。而在帶參宏中只是符號代換,不存在值傳遞問題。


【例3】


 #define INC(x) x+1  //宏定義

 y = INC(5);         //宏調(diào)用


在宏調(diào)用時,用實參5去代替形參x,經(jīng)預(yù)處理宏展開后的語句為y=5+1。


【例4】反例:


#define SQ(r)    r*r

     

上述這種實參為表達式的宏定義,在一般使用時沒有問題;但遇到如area=SQ(a+b);時就會出現(xiàn)問題,宏展開后變?yōu)閍rea=a+b*a+b;,顯然違背本意。


相比之下,函數(shù)調(diào)用時會先把實參表達式的值(a+b)求出來再賦予形參r;而宏替換對實參表達式不作計算直接地照原樣代換。因此在宏定義中,字符串內(nèi)的形參通常要用括號括起來以避免出錯。


進一步地,考慮到運算符優(yōu)先級和結(jié)合性,遇到area=10/SQ(a+b);時即使形參加括號仍會出錯。因此,還應(yīng)在宏定義中的整個字符串外加括號,


綜上,正確的宏定義是#define SQ(r) ((r)*(r)),即宏定義時建議所有的層次都要加括號。


【例5】帶參函數(shù)和帶參宏的區(qū)別:


 #define SQUARE(x) ((x)*(x))


 int Square(int x){


   return (x * x); //未考慮溢出保護


}


int main(void){


    int i = 1;


   while(i <= 5)


       printf("i = %d, Square = %d\n", i, Square(i++));


   int j = 1;


   while(j <= 5)


      printf("j = %d, SQUARE = %d\n", j, SQUARE(j++));

  return 0;


 }


執(zhí)行后輸出如下:


i = 2, Square = 1


i = 3, Square = 4


i = 4, Square = 9


i = 5, Square = 16


i = 6, Square = 25


j = 3, SQUARE = 1


j = 5, SQUARE = 9


j = 7, SQUARE = 25


本例意在說明,把同一表達式用函數(shù)處理與用宏處理兩者的結(jié)果有可能是不同的。


調(diào)用Square函數(shù)時,把實參i值傳給形參x后自增1,再輸出函數(shù)值。因此循環(huán)5次,輸出1~5的平方值。調(diào)用SQUARE宏時,SQUARE(j++)被代換為((j++)*(j++))。在第一次循環(huán)時,表達式中j初值為1,兩者相乘的結(jié)果為1。相乘后j自增兩次變?yōu)?,因此表達式中第二次相乘時結(jié)果為3*3=9。同理,第三次相乘時結(jié)果為5*5=25,并在此次循環(huán)后j值變?yōu)?,不再滿足循環(huán)條件,停止循環(huán)。


從以上分析可以看出函數(shù)調(diào)用和宏調(diào)用二者在形式上相似,在本質(zhì)上是完全不同的。


帶參宏注意事項:


宏名和形參表的括號間不能有空格。

宏替換只作替換,不做計算,不做表達式求解。

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

函數(shù)只有一個返回值,利用宏則可以設(shè)法得到多個值。

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

宏展開不占用運行時間,只占編譯時間,函數(shù)調(diào)用占運行時間(分配內(nèi)存、保留現(xiàn)場、值傳遞、返回值)。

為防止無限制遞歸展開,當(dāng)宏調(diào)用自身時,不再繼續(xù)展開。

如:#define TEST(x)  (x + TEST(x))被展開為1 + TEST(1)。


2.3 實踐用例


包括基本用法(及技巧)和特殊用法(#和##等)。


#define可以定義多條語句,以替代多行的代碼,但應(yīng)注意替換后的形式,避免出錯。宏定義在換行時要加上一個反斜杠”\”,而且反斜杠后面直接回車,不能有空格。


2.3.1 基本用法

1. 定義常量:


#define PI   3.1415926

將程序中出現(xiàn)的PI全部換成3.1415926。


2. 定義表達式:

 #define M   (y*y+3*y)


編碼時所有的表達式(y*y+3*y)都可由M代替,而編譯時先由預(yù)處理程序進行宏替換,即用(y*y+3*y)表達式去置換所有的宏名M,然后再進行編譯。


注意,在宏定義中表達式(y*y+3*y)兩邊的括號不能少,否則可能會發(fā)生錯誤。如s=3*M+4*M在預(yù)處理時經(jīng)宏展開變?yōu)閟=3*(y*y+3*y)+4*(y*y+3*y),如果宏定義時不加括號就展開為s=3*y*y+3*y+4*y*y+3*y,顯然不符合原意。因此在作宏定義時必須十分注意。應(yīng)保證在宏替換之后不發(fā)生錯誤。


3. 得到指定地址上的一個字節(jié)或字:


#define MEM_B(x)     (*((char *)(x)))

 #define MEM_W(x)     (*((short *)(x)))


4. 求最大值和最小值:


#define MAX(x, y)     (((x) > (y)) ? (x) : (y))

#define MIN(x, y)     (((x) < (y)) ? (x) : (y))


以后使用MAX (x,y)或MIN (x,y),就可分別得到x和y中較大或較小的數(shù)。


但這種方法存在弊病,例如執(zhí)行MAX(x++, y)時,x++被執(zhí)行多少次取決于x和y的大小;當(dāng)宏參數(shù)為函數(shù)也會存在類似的風(fēng)險。所以建議用內(nèi)聯(lián)函數(shù)而不是這種方法提高速度。不過,雖然存在這樣的弊病,但宏定義非常靈活,因為x和y可以是各種數(shù)據(jù)類型。


以下給出MAX宏的兩個安全版本(源自linux/kernel.h):


 #define MAX_S(x, y) ({ \


    const typeof(x) _x = (x);  \


    const typeof(y) _y = (y);  \


    (void)(&_x == &_y);       \


     _x > _y ? _x : _y; })



 #define TMAX_S(type, x, y) ({ \


     type _x = (x);  \


     type _y = (y);  \


    _x > _y ? _x: _y; })


Gcc編譯器將包含在圓括號和大括號雙層括號內(nèi)的復(fù)合語句看作是一個表達式,它可出現(xiàn)在任何允許表達式的地方;復(fù)合語句中可聲明局部變量,判斷循環(huán)條件等復(fù)雜處理。而表達式的最后一條語句必須是一個表達式,它的計算結(jié)果作為返回值。MAX_S和TMAX_S宏內(nèi)就定義局部變量以消除參數(shù)副作用。


MAX_S宏內(nèi)(void)(&_x == &_y)語句用于檢查參數(shù)類型一致性。當(dāng)參數(shù)x和y類型不同時,會產(chǎn)生” comparison of distinct pointer types lacks a cast”的編譯警告。


注意,MAX_S和TMAX_S宏雖可避免參數(shù)副作用,但會增加內(nèi)存開銷并降低執(zhí)行效率。若使用者能保證宏參數(shù)不存在副作用,則可選用普通定義(即MAX宏)。 


5. 得到一個成員在結(jié)構(gòu)體中的偏移量(lint 545告警表示"&用法值得懷疑",此處抑制該警告):


 #define FPOS(type, field) \

 /*lint -e545 */ ((int)&((type *)0)-> field) /*lint +e545 */


6. 得到一個結(jié)構(gòu)體中某成員所占用的字節(jié)數(shù):


#define FSIZ(type, field)    sizeof(((type *)0)->field)


7. 按照LSB格式把兩個字節(jié)轉(zhuǎn)化為一個字(word):


#define FLIPW(arr)          ((((short)(arr)[0]) * 256) + (arr)[1])


8. 按照LSB格式把一個字(word)轉(zhuǎn)化為兩個字節(jié):


#define FLOPW(arr, val) \

    (arr)[0] = ((val) / 256); \

    (arr)[1] = ((val) & 0xFF)


9. 得到一個變量的地址:


#define B_PTR(var)       ((char *)(void *)&(var))

#define W_PTR(var)       ((short *)(void *)&(var))


10. 得到一個字(word)的高位和低位字節(jié):


#define WORD_LO(x)       ((char)((short)(x)&0xFF))

#define WORD_HI(x)       ((char)((short)(x)>>0x8))


11. 返回一個比X大的最接近的8的倍數(shù):


#define RND8(x)           ((((x) + 7) / 8) * 8)


12. 將一個字母轉(zhuǎn)換為大寫或小寫:


#define UPCASE(c)         (((c) >= 'a' && (c) <= 'z') ? ((c) + 'A' - 'a') : (c))

#define LOCASE(c)         (((c) >= 'A' && (c) <= 'Z') ? ((c) + 'a' - 'A') : (c))


注意,UPCASE和LOCASE宏僅適用于ASCII編碼(依賴于碼字順序和連續(xù)性),而不適用于EBCDIC編碼。


13. 判斷字符是不是10進值的數(shù)字:


 #define ISDEC(c)          ((c) >= '0' && (c) <= '9')


14. 判斷字符是不是16進值的數(shù)字:


  #define ISHEX(c)          (((c) >= '0' && (c) <= '9') ||\


     ((c) >= 'A' && (c) <= 'F') ||\


     ((c) >= 'a' && (c) <= 'f'))



15. 防止溢出的一個方法:


 #define INC_SAT(val)      (val = ((val)+1 > (val)) ? (val)+1 : (val))

16. 返回數(shù)組元素的個數(shù):


#define ARR_SIZE(arr)     (sizeof((arr)) / sizeof((arr[0])))


17. 對于IO空間映射在存儲空間的結(jié)構(gòu),輸入輸出處理:


 #define INP(port)           (*((volatile char *)(port)))


 #define INPW(port)          (*((volatile short *)(port)))


 #define INPDW(port)         (*((volatile int *)(port)))


 #define OUTP(port, val)     (*((volatile char *)(port)) = ((char)(val)))


 #define OUTPW(port, val)    (*((volatile short *)(port)) = ((short)(val)))


 #define OUTPDW(port, val)   (*((volatile int *)(port)) = ((int)(val)))


18. 使用一些宏跟蹤調(diào)試:


ANSI標準說明了五個預(yù)定義的宏名(注意雙下劃線),即:__LINE__、__FILE __、__DATE__、__TIME__、__STDC __。


若編譯器未遵循ANSI標準,則可能僅支持以上宏名中的幾個,或根本不支持。此外,編譯程序可能還提供其它預(yù)定義的宏名(如__FUCTION__)。


__DATE__宏指令含有形式為月/日/年的串,表示源文件被翻譯到代碼時的日期;源代碼翻譯到目標代碼的時間作為串包含在__TIME__中。串形式為時:分:秒。


如果實現(xiàn)是標準的,則宏__STDC__含有十進制常量1。如果它含有任何其它數(shù),則實現(xiàn)是非標準的。


可以借助上面的宏來定義調(diào)試宏,輸出數(shù)據(jù)信息和所在文件所在行。如下所示:


 #define MSG(msg, date)      printf(msg);printf(“[%d][%d][%s]”,date,__LINE__,__FILE__)

     

19. 用do{…}while(0)語句包含多語句防止錯誤:


#define DO(a, b) do{\

    a+b;\

    a++;\

 }while(0)


20. 實現(xiàn)類似“重載”功能


C語言中沒有swap函數(shù),而且不支持重載,也沒有模板概念,所以對于每種數(shù)據(jù)類型都要寫出相應(yīng)的swap函數(shù),如:


IntSwap(int *,  int *);  


LongSwap(long *,  long *);  


StringSwap(char *,  char *); 


可采用宏定義TSWAP (t,x,y)或SWAP(x, y)交換兩個整型或浮點參數(shù):


 

#define TSWAP(type, x, y) do{ \


      type _y = y; \


      y = x;       \


      x = _y;      \


  }while(0)


  #define SWAP(x, y) do{ \


      x = x + y;   \


      y = x - y;   \


      x = x - y;   \


 }while(0)



 int main(void){


     int a = 10, b = 5;


     TSWAP(int, a, b);


     printf(“a=%d, b=%d\n”, a, b);


     return 0;


}


21. 1年中有多少秒(忽略閏年問題) :


 #define SECONDS_PER_YEAR    (60UL * 60 * 24 * 365)


該表達式將使一個16位機的整型數(shù)溢出,因此用長整型符號L告訴編譯器該常數(shù)為長整型數(shù)。


注意,不可定義為#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL,否則將產(chǎn)生(31536000)UL而非31536000UL,這會導(dǎo)致編譯報錯。


以下幾種寫法也正確:


 #define SECONDS_PER_YEAR    60 * 60 * 24 * 365UL


 #define SECONDS_PER_YEAR    (60UL * 60UL * 24UL * 365UL)


 #define SECONDS_PER_YEAR    ((unsigned long)(60 * 60 * 24 * 365))

}


22. 取消宏定義:


#define [MacroName] [MacroValue]       //定義宏


#undef [MacroName]                     //取消宏



宏定義必須寫在函數(shù)外,其作用域為宏定義起到源程序結(jié)束。如要終止其作用域可使用#undef命令:


 #define PI   3.14159


 int main(void){

     //……

 }


 #undef PI

 int func(void){

     //……

 }


表示PI只在main函數(shù)中有效,在func1中無效。


2.3.2 特殊用法


主要涉及C語言宏里#和##的用法,以及可變參數(shù)宏。


2.3.2.1 字符串化操作符#


在C語言的宏中,#的功能是將其后面的宏參數(shù)進行字符串化操作(Stringfication),簡單說就是將宏定義中的傳入?yún)?shù)名轉(zhuǎn)換成用一對雙引號括起來參數(shù)名字符串。#只能用于有傳入?yún)?shù)的宏定義中,且必須置于宏定義體中的參數(shù)名前。例如:


 #define EXAMPLE(instr)      printf("The input string is:\t%s\n", #instr)

 #define EXAMPLE1(instr)     #instr


當(dāng)使用該宏定義時,example(abc)在編譯時將會展開成printf("the input string is:\t%s\n","abc");string str=example1(abc)將會展成string str="abc"。


 又如下面代碼中的宏:


  define WARN_IF(exp) do{ \


     if(exp) \


         fprintf(stderr, "Warning: " #exp"\n"); \


 } while(0)


則代碼WARN_IF (divider == 0)會被替換為:


do{

     if(divider == 0)

     

       fprintf(stderr, "Warning" "divider == 0" "\n");

 } while(0)


這樣,每次divider(除數(shù))為0時便會在標準錯誤流上輸出一個提示信息。


注意#宏對空格的處理:


忽略傳入?yún)?shù)名前面和后面的空格。如str= example1(   abc )會被擴展成 str="abc"。

當(dāng)傳入?yún)?shù)名間存在空格時,編譯器會自動連接各個子字符串,每個子字符串間只以一個空格連接。如str= example1( abc    def)會被擴展成 str="abc def"。


2.3.2.2 符號連接操作符##


 ##稱為連接符(concatenator或token-pasting),用來將兩個Token連接為一個Token。注意這里連接的對象是Token就行,而不一定是宏的變量。例如:

 #define PASTER(n)     printf( "token" #n " = %d", token##n)


 int token9 = 9;


則運行PASTER(9)后輸出結(jié)果為token9 = 9。


又如要做一個菜單項命令名和函數(shù)指針組成的結(jié)構(gòu)體數(shù)組,并希望在函數(shù)名和菜單項命令名之間有直觀的、名字上的關(guān)系。那么下面的代碼就非常實用:


struct command{


     char * name;


     void (*function)(void);


 };


#define COMMAND(NAME)   {NAME, NAME##_command}


然后,就可用一些預(yù)先定義好的命令來方便地初始化一個command結(jié)構(gòu)的數(shù)組:


 struct command commands[] = {


     COMMAND(quit),


     COMMAND(help),


     //...


 }


COMMAND宏在此充當(dāng)一個代碼生成器的作用,這樣可在一定程度上減少代碼密度,間接地也可減少不留心所造成的錯誤。


還可以用n個##符號連接n+1個Token,這個特性是#符號所不具備的。如:


 #define  LINK_MULTIPLE(a, b, c, d)      a##_##b##_##c##_##d


 typedef struct record_type LINK_MULTIPLE(name, company, position, salary);


這里這個語句將展開為typedef struct record_type name_company_position_salary。


注意:


當(dāng)用##連接形參時,##前后的空格可有可無。

連接后的實際參數(shù)名,必須為實際存在的參數(shù)名或是編譯器已知的宏定義。

凡是宏定義里有用'#'或'##'的地方,宏參數(shù)是不會再展開。如:


 #define STR(s)       #s


 #define CONS(a,b)    int(a##e##b)


則printf("int max: %s\n", STR(INT_MAX))會被展開為printf("int max: %s\n", "INT_MAX")。其中,變量INT_MAX為int型的最大值,其值定義在<climits.h>中。printf("%s\n", CONS(A, A))會被展開為printf("%s\n", int(AeA)),從而編譯報錯。


INT_MAX和A都不會再被展開,多加一層中間轉(zhuǎn)換宏即可解決這個問題。加這層宏是為了把所有宏的參數(shù)在這層里全部展開,那么在轉(zhuǎn)換宏里的那一個宏(如_STR)就能得到正確的宏參數(shù)。


#define _STR(s)         #s 


#define STR(s)          _STR(s)       // 轉(zhuǎn)換宏


#define _CONS(a,b)      int(a##e##b)


#define CONS(a,b)       _CONS(a,b)    // 轉(zhuǎn)換宏


則printf("int max: %s\n", STR(INT_MAX))輸出為int max: 0x7fffffff;而printf("%d\n", CONS(A, A))輸出為200。


這種分層展開的技術(shù)稱為宏的Argument Prescan,參見附錄6.1。




2.3.2.3 字符化操作符@#


@#稱為字符化操作符(charizing),只能用于有傳入?yún)?shù)的宏定義中,且必須置于宏定義體的參數(shù)名前。作用是將傳入的單字符參數(shù)名轉(zhuǎn)換成字符,以一對單引號括起來。


 #define makechar(x)    #@x

 a = makechar(b);


展開后變成a= 'b'。 


2.3.2.4 可變參數(shù)宏


在C語言宏中稱為Variadic Macro,即變參宏。C99編譯器標準允許定義可變參數(shù)宏(Macros with a Variable Number of Arguments),這樣就可以使用擁有可變參數(shù)表的宏。


可變參數(shù)宏的一般形式為:


#define  DBGMSG(format, ...)  fprintf (stderr, format, __VA_ARGS__)


省略號代表一個可以變化的參數(shù)表,變參必須作為參數(shù)表的最右一項出現(xiàn)。使用保留名__VA_ARGS__ 把參數(shù)傳遞給宏。在調(diào)用宏時,省略號被表示成零個或多個符號(包括里面的逗號),一直到到右括號結(jié)束為止。當(dāng)被調(diào)用時,在宏體(macro body)中,那些符號序列集合將代替里面的__VA_ARGS__標識符。當(dāng)宏的調(diào)用展開時,實際的參數(shù)就傳遞給fprintf ()。


注意:可變參數(shù)宏不被ANSI/ISO C++所正式支持。因此,應(yīng)當(dāng)檢查編譯器是否支持這項技術(shù)。 


在標準C里,不能省略可變參數(shù),但卻可以給它傳遞一個空的參數(shù),這會導(dǎo)致編譯出錯。因為宏展開后,里面的字符串后面會有個多余的逗號。為解決這個問題,GNU CPP中做了如下擴展定義:


#define  DBGMSG(format, ...)  fprintf (stderr, format, ##__VA_ARGS__)


若可變參數(shù)被忽略或為空,##操作將使編譯器刪除它前面多余的逗號(否則會編譯出錯)。若宏調(diào)用時提供了可變參數(shù),編譯器會把這些可變參數(shù)放到逗號的后面。


同時,GCC還支持顯式地命名變參為args,如同其它參數(shù)一樣。如下格式的宏擴展:


#define  DBGMSG(format, args...)  fprintf (stderr, format, ##args)


這樣寫可讀性更強,并且更容易進行描述。


用GCC和C99的可變參數(shù)宏, 可以更方便地打印調(diào)試信息,如:


 #ifdef DEBUG


     #define DBGPRINT(format, args...) \


         fprintf(stderr, format, ##args)


 #else


     #define DBGPRINT(format, args...)


 #endif


這樣定義之后,代碼中就可以用dbgprint了,例如dbgprint ("aaa [%s]", __FILE__)。


結(jié)合第4節(jié)的“條件編譯”功能,可以構(gòu)造出如下調(diào)試打印宏:

 

 #ifdef LOG_TEST_DEBUG


      /* OMCI調(diào)試日志宏 */


    //以10進制格式日志整型變量


      #define PRINT_DEC(x)          printf(#x" = %d\n", x)


      #define PRINT_DEC2(x,y)       printf(#x" = %d\n", y)


     //以16進制格式日志整型變量


      #define PRINT_HEX(x)          printf(#x" = 0x%-X\n", x)


      #define PRINT_HEX2(x,y)       printf(#x" = 0x%-X\n", y)


      //以字符串格式日志字符串變量


     #define PRINT_STR(x)          printf(#x" = %s\n", x)


     #define PRINT_STR2(x,y)       printf(#x" = %s\n", y)



     //日志提示信息


     #define PROMPT(info)          printf("%s\n", info)


     //調(diào)試定位信息打印宏


     #define  TP                   printf("%-4u - [%s<%s>]\n", __LINE__, __FILE__, __FUNCTION__);


     //調(diào)試跟蹤宏,在待日志信息前附加日志文件名、行數(shù)、函數(shù)名等信息


     #define TRACE(fmt, args...)\


     do{\


        printf("[%s(%d)<%s>]", __FILE__, __LINE__, __FUNCTION__);\


        printf((fmt), ##args);\


     }while(0)


 #else


     #define PRINT_DEC(x)


     #define PRINT_DEC2(x,y)


     #define PRINT_HEX(x)


     #define PRINT_HEX2(x,y)


     #define PRINT_STR(x)


     #define PRINT_STR2(x,y)


     #define PROMPT(info)


     #define  TP


     #define TRACE(fmt, args...)


 #endif

 


三  文件包含


文件包含命令行的一般形式為:


#include "文件名"


通常,該文件是后綴名為"h"或"hpp"的頭文件。文件包含命令把指定頭文件插入該命令行位置取代該命令行,從而把指定的文件和當(dāng)前的源程序文件連成一個源文件。


在程序設(shè)計中,文件包含是很有用的。一個大程序可以分為多個模塊,由多個程序員分別編程。有些公用的符號常量或宏定義等可單獨組成一個文件,在其它文件的開頭用包含命令包含該文件即可使用。這樣,可避免在每個文件開頭都去書寫那些公用量,從而節(jié)省時間,并減少出錯。


對文件包含命令要說明以下幾點:


包含命令中的文件名可用雙引號括起來,也可用尖括號括起來,如#include "common.h"和#include<math.h>。但這兩種形式是有區(qū)別的:使用尖括號表示在包含文件目錄中去查找(包含目錄是由用戶在設(shè)置環(huán)境時設(shè)置的include目錄),而不在當(dāng)前源文件目錄去查找;


使用雙引號則表示首先在當(dāng)前源文件目錄中查找,若未找到才到包含目錄中去查找。用戶編程時可根據(jù)自己文件所在的目錄來選擇某一種命令形式。


一個include命令只能指定一個被包含文件,若有多個文件要包含,則需用多個include命令。文件包含允許嵌套,即在一個被包含的文件中又可以包含另一個文件。

 


四  條件編譯


一般情況下,源程序中所有的行都參加編譯。但有時希望對其中一部分內(nèi)容只在滿足一定條件才進行編譯,也就是對一部分內(nèi)容指定編譯的條件,這就是“條件編譯”。有時,希望當(dāng)滿足某條件時對一組語句進行編譯,而當(dāng)條件不滿足時則編譯另一組語句。


條件編譯功能可按不同的條件去編譯不同的程序部分,從而產(chǎn)生不同的目標代碼文件。這對于程序的移植和調(diào)試是很有用的。


條件編譯有三種形式,下面分別介紹。


4.1 #ifdef形式


#ifdef  標識符  (或#if defined標識符)


    程序段1


#else


    程序段2


#endif



如果標識符已被#define命令定義過,則對程序段1進行編譯;否則對程序段2進行編譯。如果沒有程序段2(它為空),#else可以沒有,即可以寫為:


#ifdef  標識符  (或#if defined標識符)


    程序段


#endif


這里的“程序段”可以是語句組,也可以是命令行。這種條件編譯可以提高C源程序的通用性。


【例6】


#define NUM OK


 int main(void){


     struct stu{


          int num;


          char *name;


          char sex;


          float score;


     }*ps;


     ps=(struct stu*)malloc(sizeof(struct stu));


     ps->num = 102;


     ps->name = "Zhang ping";


     ps->sex = 'M';


     ps->score = 62.5;


 #ifdef NUM


     printf("Number=%d\nScore=%f\n", ps->num, ps->score); /*--Execute--*/


 #else


     printf("Name=%s\nSex=%c\n", ps->name, ps->sex);


 #endif


     free(ps);


     return 0;


 }

     

由于在程序中插入了條件編譯預(yù)處理命令,因此要根據(jù)NUM是否被定義過來決定編譯哪個printf語句。而程序首行已對NUM作過宏定義,因此應(yīng)對第一個printf語句作編譯,故運行結(jié)果是輸出了學(xué)號和成績。


程序首行定義NUM為字符串“OK”,其實可為任何字符串,甚至不給出任何字符串,即#define NUM也具有同樣的意義。只有取消程序首行宏定義才會去編譯第二個printf語句。


4.2 #ifndef 形式


#ifndef  標識符


    程序段1


#else


    程序段2


#endif



如果標識符未被#define命令定義過,則對程序段1進行編譯,否則對程序段2進行編譯。這與#ifdef形式的功能正相反。


“#ifndef  標識符”也可寫為“#if  !(defined 標識符)”。


4.3 #if形式


#if 常量表達式


    程序段1


#else


    程序段2


#endif


如果常量表達式的值為真(非0),則對程序段1 進行編譯,否則對程序段2進行編譯。因此可使程序在不同條件下,完成不同的功能。


【例7】輸入一行字母字符,根據(jù)需要設(shè)置條件編譯,使之能將字母全改為大寫或小寫字母輸出。


#define CAPITAL_LETTER   1


  int main(void){


      char szOrig[] = "C Language", cChar;


      int dwIdx = 0;


      while((cChar = szOrig[dwIdx++]) != '\0')


      {


  #if CAPITAL_LETTER


         if((cChar >= 'a') && (cChar <= 'z')) cChar = cChar - 0x20;


  #else


         if((cChar >= 'A') && (cChar <= 'Z')) cChar = cChar + 0x20;


 #endif


         printf("%c", cChar);


    }

     return 0;

 }


在程序第一行定義宏CAPITAL_LETTER為1,因此在條件編譯時常量表達式CAPITAL_LETTER的值為真(非零),故運行后使小寫字母變成大寫(C LANGUAGE)。


本例的條件編譯當(dāng)然也可以用if條件語句來實現(xiàn)。但是用條件語句將會對整個源程序進行編譯,生成的目標代碼程序很長;而采用條件編譯,則根據(jù)條件只編譯其中的程序段1或程序段2,生成的目標程序較短。如果條件編譯的程序段很長,采用條件編譯的方法是十分必要的。


4.4 實踐用例


1. 屏蔽跨平臺差異


在大規(guī)模開發(fā)過程中,特別是跨平臺和系統(tǒng)的軟件里,可以在編譯時通過條件編譯設(shè)置編譯環(huán)境。


例如,有一個數(shù)據(jù)類型,在Windows平臺中應(yīng)使用long類型表示,而在其他平臺應(yīng)使用float表示。這樣往往需要對源程序作必要的修改,這就降低了程序的通用性。可以用以下的條件編譯:


 #ifdef WINDOWS


     #define MYTYPE long


 #else


     #define MYTYPE float


 #endif


如果在Windows上編譯程序,則可以在程序的開始加上#define WINDOWS,這樣就編譯命令行    #define MYTYPE long;


如果在這組條件編譯命令前曾出現(xiàn)命令行#define WINDOWS 0,則預(yù)編譯后程序中的MYTYPE都用float代替。這樣,源程序可以不必作任何修改就可以用于不同類型的計算機系統(tǒng)。


2. 包含程序功能模塊


例如,在程序首部定義#ifdef FLV:


 #ifdef FLV


    include"fastleave.c"


 #endif


如果不許向別的用戶提供該功能,則在編譯之前將首部的FLV加一下劃線即可。


3. 開關(guān)調(diào)試信息


調(diào)試程序時,常常希望輸出一些所需的信息以便追蹤程序的運行。而在調(diào)試完成后不再輸出這些信息。可以在源程序中插入以下的條件編譯段:


 #ifdef DEBUG


     printf("device_open(%p)\n", file);


 #endif


 如果在它的前面有以下命令行#define DEBUG,則在程序運行時輸出file指針的值,以便調(diào)試分析。調(diào)試完成后只需將這個define命令行刪除即可,這時所有使用DEBUG作標識符的條件編譯段中的printf語句不起作用,即起到“開關(guān)”一樣統(tǒng)一控制的作用。 


4. 避開硬件的限制。


有時一些具體應(yīng)用環(huán)境的硬件不同,但限于條件本地缺乏這種設(shè)備,可繞過硬件直接寫出預(yù)期結(jié)果:


#ifndef TEST


     i = dial();  //程序調(diào)試運行時繞過此語句


 #else


     i = 0;


 #endif


調(diào)試通過后,再屏蔽TEST的定義并重新編譯即可。   


5. 防止頭文件重復(fù)包含


頭文件(.h)可以被頭文件或C文件包含。由于頭文件包含可以嵌套,C文件就有可能多次包含同一個頭文件;或者不同的C文件都包含同一個頭文件,編譯時就可能出現(xiàn)重復(fù)包含(重復(fù)定義)的問題。


在頭文件中為了避免重復(fù)調(diào)用(如兩個頭文件互相包含對方),常采用這樣的結(jié)構(gòu):


 #ifndef  <標識符>


     #define  <標識符>


     //真正的內(nèi)容,如函數(shù)聲明之類


 #endif


<標識符>可以自由命名,但一般形如__HEADER_H,且每個頭文件標識都應(yīng)該是唯一的。


事實上,不管頭文件會不會被多個文件引用,都要加上條件編譯開關(guān)來避免重復(fù)包含。 


6. 在#ifndef中定義變量出現(xiàn)的問題(一般不定義在#ifndef中)。


 

#ifndef PRECMPL


     #define PRECMPL


    int var;

    

 #endif


其中有個變量定義,在VC中鏈接時會出現(xiàn)變量var重復(fù)定義的錯誤,而在C中成功編譯。


(1) 當(dāng)?shù)谝粋€使用這個頭文件的.cpp文件生成.obj時,var在里面定義;當(dāng)另一個使用該頭文件的.cpp文件再次(單獨)生成.obj時,var又被定義;然后兩個obj被第三個包含該頭文件.cpp連接在一起,會出現(xiàn)重復(fù)定義。


(2) 把源程序文件擴展名改成.c后,VC按照C語言語法對源程序進行編譯。在C語言中,遇到多個int var則自動認為其中一個是定義,其他的是聲明。


(3) C語言和C++語言連接結(jié)果不同,可能是在進行編譯時,C++語言將全局變量默認為強符號,所以連接出錯。C語言則依照是否初始化進行強弱的判斷的(僅供參考)。


解決方法:


(1) 把源程序文件擴展名改成.c。


 (2) .h中只聲明 extern int var;,在.cpp中定義(推薦)


//<x.h>


 #ifndef  __X_H


     #define  __X_H


     extern int var;


 #endif


 //<x.c>


 int var = 0;


綜上,變量一般不要定義在.h文件中。



五  小結(jié)


預(yù)處理功能是C語言特有的功能,它是在對源程序正式編譯前由預(yù)處理程序完成的。程序員在程序中用預(yù)處理命令來調(diào)用這些功能。


宏定義是用一個標識符來表示一個字符串,這個字符串可以是常量、變量或表達式。在宏調(diào)用中將用該字符串代換宏名。


宏定義可以帶有參數(shù),宏調(diào)用時是以實參代換形參。而不是“值傳遞”。

為了避免宏替換時發(fā)生錯誤,宏定義中的字符串應(yīng)加括號,字符串中出現(xiàn)的形式參數(shù)兩邊也應(yīng)加括號。


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


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


使用預(yù)處理功能便于程序的修改、閱讀、移植和調(diào)試,也便于實現(xiàn)模塊化程序設(shè)計。

 


六 附錄

6.1 Argument Prescan

(摘自http://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html)


Macro arguments are completely macro-expanded before they are substituted into a macro body, unless they are stringified or pasted with other tokens. After substitution, the entire macro body, including the substituted arguments, is scanned again for macros to be expanded. The result is that the arguments are scanned twice to expand macro calls in them.


宏參數(shù)被完全展開后再替換入宏體,但當(dāng)宏參數(shù)被字符串化(#)或與其它子串連接(##)時不予展開。在替換之后,再次掃描整個宏體(包括已替換宏參數(shù))以進一步展開宏。結(jié)果是宏參數(shù)被掃描兩次以展開參數(shù)所(嵌套)調(diào)用的宏。


若帶參數(shù)宏定義中的參數(shù)稱為形參,調(diào)用宏時的實際參數(shù)稱為實參,則宏的展開可用以下三步來簡單描述(該步驟與gcc摘錄稍有不同,但更易操作):


1) 用實參替換形參,將實參代入宏文本中;


2) 若實參也是宏,則展開實參;


3) 繼續(xù)處理宏替換后的宏文本,若宏文本也包含宏則繼續(xù)展開,否則完成展開。


其中第一步將實參代入宏文本后,若實參前遇到字符“#”或“##”,即使實參是宏也不再展開實參,而當(dāng)作文本處理。


上述展開步驟示例如下:


#define TO_STRING(x)    _TO_STRING(x)


#define _TO_STRING(x)   #x


#define FOO             4


則_TO_STRING(FOO)展開為”FOO”;TO_STRING(FOO)展開為_TO_STRING(4),進而展開為”4”。相當(dāng)于借助_TO_STRING這樣的中間宏,先展開宏參數(shù),延遲其字符化。


6.2 宏的其他注意事項


1. 避免在無作用域限定(未用{}括起)的宏內(nèi)定義數(shù)組、結(jié)構(gòu)、字符串等變量,否則函數(shù)中對宏的多次引用會導(dǎo)致實際局部變量空間成倍放大。


 2. 按照宏的功能、模塊進行集中定義。即在一處將常量數(shù)值定義為宏,其他地方通過引用該宏,生成自己模塊的宏。嚴禁相同含義的常量數(shù)值,在不同地方定義為不同的宏,即使數(shù)值相同也不允許(維護修改后極易遺漏,造成代碼隱患)。


3. 用只讀變量適當(dāng)替代(類似功能的)宏,例如將#define PIE 3.14改為const float PIE = 3.14。


這樣做的好處如下:


1) 預(yù)編譯時用宏定義值替換宏名,編譯時報錯不易理解;


2) 跟蹤調(diào)試時顯示宏值,而不是宏名;


3) 宏沒有類型,不能做類型檢查,不安全;


4) 宏自身沒有作用域;


5) 只讀變量和宏的效率同樣高。


注意,C語言中只讀變量不可用于數(shù)組大小、變量(包括數(shù)組元素)初始化值以及case表達式。


4. 用inline函數(shù)代替(類似功能的)宏函數(shù)。好處如下:


1) 宏函數(shù)在預(yù)編譯時處理,編譯出錯信息不易理解;


2) 宏函數(shù)本身無法單步跟蹤調(diào)試,因此也不要在宏內(nèi)調(diào)用函數(shù)。但某些編譯器(為了調(diào)試需要)可將inline函數(shù)轉(zhuǎn)成普通函數(shù);


3) 宏函數(shù)的入?yún)]有類型,不安全;


5) inline函數(shù)會在目標代碼中展開,和宏的效率一樣高;


注意,某些宏函數(shù)用法獨特,不能用inline函數(shù)取代。當(dāng)不想或不能指明參數(shù)類型時,宏函數(shù)更合適。


5. 不帶參數(shù)的宏函數(shù)也要定義成函數(shù)形式,如#define HELLO( )  printf(“Hello.”)。


括號會暗示閱讀代碼者該宏是一個函數(shù)。


6. 帶參宏內(nèi)定義變量時,應(yīng)注意避免內(nèi)外部變量重名的問題:


 typedef struct{


     int d;


  }T_TEST;


  T_TEST gtTest = {0};


 #define ASSIGN1(_d) do{ \


     T_TEST t = {0}; \


      t.d = _d; \


      gtTest = t; \


  }while(0)


 #define ASSIGN2(_p) do{ \


     int _d; \


     _d = 5; \


     (_p) = _d; \


 }while(0)


若宏參數(shù)名或宏內(nèi)變量名不加前綴下劃線,則ASSIGN1(c)將會導(dǎo)致編譯報錯(t.d被替換為t.c),ASSIGN2(d)會因宏內(nèi)作用域而導(dǎo)致外部的變量d值保持不變(而非改為5)。


7. 不要用宏改寫語言。例如:


#define FOREVER   for ( ; ; )


 #define BEGIN     {


 #define END       }


C語言有完善且眾所周知的語法。試圖將其改變成類似于其他語言的形式,會使讀者混淆,難于理解。


6.3 do{…}while(0)妙用


1. 函數(shù)中使用do{…}while(0)可替代goto語句。例如:


goto寫法


替代寫法


bOk = func1();


if(!bOk) goto errorhandle; 


bOk = func2();


if(!bOk) goto errorhandle; 


bOk = func3();


if(!bOk) goto errorhandle;


 


//… …


//執(zhí)行成功,釋放資源并返回


delete p;   


p = NULL;


return true;


 


errorhandle:


delete p;   


p = NULL;


return false;


do{


      //執(zhí)行并進行錯誤處理


      bOk = func1();


      if(!bOk) break; 


      bOk = func2();


      if(!bOk) break; 


      bOk = func3();


      if(!bOk) break;


 


      // ..........


   }while(0);


 


    //釋放資源


    delete p;   


    p = NULL;


    return bOk;



2. 宏定義中使用do{…}while(0)的原因及好處:


1) 避免空的宏定義產(chǎn)生warning,如 #define DUMMY( ) do{}while(0)。


2) 存在一個獨立的代碼塊,可進行變量定義,實現(xiàn)比較復(fù)雜的邏輯處理。


注意,該代碼塊內(nèi)(即{…}內(nèi))定義的變量其作用域僅限于該塊。此外,為避免宏的實參與其內(nèi)部定義的變量同名而造成覆蓋,最好在變量名前加上_(基于如下編程慣例:除非是庫,否則不應(yīng)定義以_開始的變量)。


3) 若宏出現(xiàn)在判斷語句之后,可保證作為一個整體來實現(xiàn)。


如#define SAFE_DELETE(p)  delete p; p = NULL;,則以下代碼


 if(NULL != p)


     SAFE_DELETE(p)


 else


     DUMMY( );


就有兩個問題:


a) 因為if分支后有兩條語句,else分支沒有對應(yīng)的if,編譯失敗;


b) 假設(shè)沒有else,則SAFE_DELETE中第二條語句無論if判斷是否成立均會執(zhí)行,這顯然違背程序設(shè)計的原始目的。


那么,為了避免這兩個問題,將宏直接用{}括起來是否可以?如:


#define SAFE_DELETE(p)  {delete p; p = NULL;}


的確,上述問題不復(fù)存在。但C/C++編程中,在每條語句后加分號是約定俗成的習(xí)慣,此時以下代碼


 if(NULL != p)


     SAFE_DELETE(p);


 else


     DUMMY( );


其else分支就無法通過編譯(多出一個分號),而采用do{…}while(0)則毫無問題。


使用do{...} while(0)將宏包裹起來,成為一個獨立的語法單元,從而不會與上下文發(fā)生混淆。同時因為絕大多數(shù)編譯器都能夠識別do{...}while(0)這種無用的循環(huán)并優(yōu)化,所以該法不會導(dǎo)致程序的性能降低。


6.4 類型定義符typedef


C語言不僅提供了豐富的數(shù)據(jù)類型,而且還允許由用戶自己定義類型說明符,也就是說允許由用戶為數(shù)據(jù)類型取“別名”。類型定義符typedef即可用來完成此功能。


typedef定義的一般形式為:


           typedef 原類型名  新類型名


其中原類型名中含有定義部分,新類型名一般用大寫表示,以便于區(qū)別。 


例如,有整型量int a,b。其中int是整型變量的類型說明符。int的完整寫法為integer,為增加程序的可讀性,可把整型說明符用typedef定義為typedef  int  INTEGER。此后就可用INTEGER來代替int作整型變量的類型說明,如INTEGER a,b等效于int a,b。


用typedef定義數(shù)組、指針、結(jié)構(gòu)等類型將帶來很大的方便,不僅使程序書寫簡單而且意義更為明確,因而增強了可讀性。


例如,typedef char NAME[20]表示NAME是字符數(shù)組類型,數(shù)組長度為20。然后可用NAME 說明變量,如NAME a1,a2,s1,s2完全等效于:char a1[20],a2[20],s1[20],s2[20]。


又如:


 typedef struct{


     char name[20];


     int  age;


     char sex;


 }STU;


然后可用STU來定義結(jié)構(gòu)變量:STU body1,body2;


有時也可用宏定義來代替typedef的功能,但是宏定義是由預(yù)處理完成的,而typedef則是在編譯時完成的,后者更為靈活方便。


此外,采用typedef重新定義一些類型,可防止因平臺和編譯器不同而產(chǎn)生的類型字節(jié)數(shù)差異,方便移植。如:



  typedef unsigned char boolean;       /* Boolean value type. */


  typedef unsigned long int uint32;    /* Unsigned 32 bit value */


  typedef unsigned short uint16;       /* Unsigned 16 bit value */


  typedef unsigned char uint8;         /* Unsigned 8 bit value */


  typedef signed long int int32;       /* Signed 32 bit value */


  typedef signed short int16;          /* Signed 16 bit value */


  typedef signed char int8;            /* Signed 8 bit value */




  //下面的不建議使用


 typedef unsigned char byte;          /* Unsigned 8 bit value type. */


 typedef unsigned short word;         /* Unsinged 16 bit value type. */


 typedef unsigned long dword;         /* Unsigned 32 bit value type. */


 typedef unsigned char uint1;         /* Unsigned 8 bit value type. */


 typedef unsigned short uint2;        /* Unsigned 16 bit value type. */


 typedef unsigned long uint4;         /* Unsigned 32 bit value type. */


 typedef signed char int1;            /* Signed 8 bit value type. */


 typedef signed short int2;           /* Signed 16 bit value type. */


 typedef long int int4;               /* Signed 32 bit value type. */


 typedef signed long sint31;          /* Signed 32 bit value */


 typedef signed short sint15;         /* Signed 16 bit value */


 typedef signed char sint7;           /* Signed 8 bit value */


圖片加載中...

在線留言

◎歡迎您的留言,您也可以通過以下方式聯(lián)系我們:

◎客戶服務(wù)熱線:021-51095123

◎郵箱:xin021@126.com

展開
亚洲AV无一区二区三区| 坐公交车居然被弄了2个小时小说| 国产老妇伦国产熟女老妇久 | WWW.一本色道88久久爱| 欧美性猛烈XXXX极品少妇 | 亚洲大胸美女被操喷水| 狠狠五月激情六月丁香| 亚洲AV无码乱码在线观看性色扶 | 成人国产一区二区三区精品不卡 | 尤物蜜芽国产成人精品区| 久久综合九色综合欧美狠狠| 在线国内永久免费CRM| 免费污站18禁的刺激| 中文字幕人妻无码乱精品| 男人的天堂在线视频| MACBOOKPRO免费网站| 偷拍亚洲另类无码专区制服| 国产男男Gay做受ⅩXX高潮| 亚洲AV日韩AV无码污污网站| 精品国产一区二区AV片| 亚洲一区二区自偷自拍另类| 老奶奶BGMBGM人与自然| 波多野结衣Av无码久久一区二区| 日韩欧美人妻一区二区三区 | 人妻熟妇av又粗又爽| 东北往事之黑道风云20年第二部 | 秋霞国产午夜伦午夜福利片| 东京热无码人妻系列综合网站| 亚洲AV无码男人的天堂| 妺妺窝人体色77777791| 国产成 人 黄 色 网 站 小说| 午夜内射高潮视频| 精品国产AV一区二区三区| 999精产国品一二三产区区| 日本COSME大赏美白| 公车上玩弄白嫩少妇| 香蕉免费一区二区三区| 麻花传媒MV一二三区别在哪里| 免费人成在线观看视频无码| 一二三四免费观看在线视频| 黑人大性殖器大战欧美白妇| 亚洲情文字幕在线一区| 老首长曰杨钰莹好爽| 99精品又大又爽又粗少妇毛片 | 亚洲国产欧美在线人成大黄瓜| 久久人人爽人人爽人人AV东京热| 91夜色精品偷窥熟女精品网站 | 麻豆AV一区二区天美传媒| А√中文在线资源库| 试看A级看一毛片二十分钟| 国产拍揄自揄精品视频| 亚洲中文字幕一区精品自拍| 欧美精品一区二区| 高清一区二区三区日本久| 亚洲午夜性春猛交ⅩXXX| 日韩少妇人妻夜夜爽| 精品亚洲一区二区三区在线观看| 1—36集电视剧免费观看36集 | 老色鬼永久精品网站| 非洲妓女BBWBBWW| 野花社区大全免费观看3| 欧美性大战久久久久久| 国产成人一区二区三区免费| 亚洲精品无码久久久久久小说| 免费夜色污私人影院在线观看| 国产激情久久久久影院小草| 竹菊影视欧美日韩一区二区三区四| 无码国产69精品久久久孕妇| 久久99精品国产麻豆宅宅| 97无码免费人妻超级碰碰碰碰| 人成AAAAA毛天堂片| 麻豆视频传媒入口| 欧美老熟妇手机在线观看| 国产精彩乱子真实视频| 中文字幕亚洲综合久久蜜桃| 西西人体444WWW高清大但| 欧美性爱操逼大鸡吧| 精品国产一区二区三区性色AV| 成人一区二区三区| 亚洲AV成人男人的天堂手机| 邻居一晚让我高潮3次正常吗 | 漂亮人妻洗澡被公强BD| 国产无人区卡一卡二卡乱码| 中国熟妇人妻XXXXX| 少妇下蹲露大唇无遮挡| 蜜桃视频一区二区三区| 国产成人无码午夜视频在线观看| 亚洲精品国产AV成拍色拍婷婷 | 中文字幕人妻无码系列第三区| 无码视频一区二区三区| 两个黑人大战嫩白金发美女| 国产无套护士在线观看| 波多野结衣AV在线| 中国熟妇老熟女妓女9| 亚洲AV无码乱码在线观看代蜜桃 | 最新版天堂资源中文官网| 亚洲AV永久无码精品网站色欲 | 妺妺窝人体色WWW精品777| 黑人玩弄人妻一区二区三区| 当着老公的面被别人欺负该怎么办 | 中国女人内谢69XXXX免费视| 熟妇人妻午夜寂寞影院| 久久亚洲人成网站| 国产成人AV一区二区三区在线观| 97人妻成人免费视频| 亚洲欧洲成人A∨在线观看| 婷婷成人小说综合专区| 人畜禽CROPROATION| 麻豆传播媒体2023最新网站| 狠狠色丁香久久综合婷婷| 国产成人无码18禁午夜福利P| chineSe老女人老熟妇hd| 亚洲AV中文无码乱人伦在线视色 | 老奶奶能叫WOMAN吗| 国内综合精品午夜久久资源| MACBOOKPRO免费观看| 又湿又紧又大又爽A视频国产 | 少妇特殊按摩高潮爽翻天| 欧美日韩国产综合草草| 久久综合亚洲鲁鲁九月天| 狠狠人妻熟妇av又粗又大 | 欧美激情一区二区三区在线| 黑森林精选AV导航| 国产精品丝袜无码不卡一区| CHINESE性老妇老女人| 在线观看无码AV网址| 亚洲乱码日产精品M| 天干夜天干天天爽自慰| 内射后入在线观看一区| 狠狠久久亚洲欧美专区| 国产精品免费久久久久影院仙踪林 | 精品国产自在现线看久久| 国产人与ZOXXXX另类| 国产成人无码A区在线观看视频免| 被吊起来张开腿供人玩弄| 一本无码字幕在钱少妇人妻| 未满小14洗澡无码视频网站| 欧美午夜成人片在线观看| 麻豆果冻传媒新剧国产短视频| 久久大香国产成人AV| 黑人粗大无码A∨人妻一区| 国产精品亚洲片在线观看不卡| 国产AV妓女影视妓女影院| 99RE6热这里只精品首页| 张柏芝阿娇全套无删减1313| 亚洲制服丝袜AV一区二区三区| 亚洲成A人片在线观看无码下载| 性一交一无一伦一精一品| 日韩少妇内射免费播放| 人妻无码精品一区二区| 美女被强奸到高潮在线| 久久亚洲AV成人无码精品| 久久精品女人天堂AV免费观看| 国产精品国产免费无码专区蜜桃| 按摩师的巨大滑进我的身体| 在线观看免费A∨网站| 亚洲最大AV一区二区三区| 亚洲欧美乱日韩乱国产| 亚洲欧美韩国综合色| 亚洲国产一区二区三区亚瑟| 亚洲AV无码一区二区三区人区| 亚洲 熟 图片 小说 乱 妇| 性欧美暴力猛交69HD| 少妇高潮太爽了在线观看欧美| 欧美高清VIDEOS36OP| 女人被狂躁的高潮免费视频| 久久中文字幕无码专区| 久久婷婷综合色丁香五月| 快拨出天我是你母亲| 久久中文字幕AV一区二区不卡| 久久久久国产亚洲AⅤ麻豆| 记忆女神的女儿们| 国产女人被狂躁到高潮小说| 国产精品亚洲LV粉色| 国产午夜福利在线观看红一片 | 99国精产品W灬源码1688| 91人妻人人做人碰人人爽蜜闫| 77777欧美毛片777777| 99久久精品午夜一区二区| JAPANESE55丰满成熟妇| 中文字幕人成无码人妻综合社区| 中文字幕巨爆区乳爆系列| 7X7X7X任意槽2023进口| JAPANESE丰满少妇最高潮| 被公侵犯肉体中文字幕无码| 处破痛哭A√18成年片免| XOXOXO性ⅩYY欧美人与人| 7777色情XXXX欧美| となりの家のネツト在线| 成人网站在线进入爽爽爽| 国产av一区二区三区| 国产毛多水多高潮高清| 狠痕鲁狠狠爱2021在| 九色综合狠狠综合久久| 老熟女太熟了95AV| 嫩草院一区二区乱码| 全棵女性艺术写真素材| 色综合AV男人的天堂伊人| 三级特黄60分钟在线播放2| 日产精品卡2卡三卡乱码网址|