?預處理詳解
1. 預定義符號
//C語?設置了?些預定義符號,可以直接使?,預定義符號也是在預處理期間處理的。
__FILE__ //進?編譯的源?件--預處理階段被替換成指向文件名字符串的指針--char* 類型的變量
__LINE__ //?件當前的?號 --預處理階段替換成使用該預處理符號所在的行號--unsigned int 類型
__DATE__ //?件被編譯的?期
__TIME__ //?件被編譯的時間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
2 #define 定義常量
宏替換注意點:
1 只能替換在同一行的內容,如果內容太長可以在每一行結尾用 \表示緒行
2 宏替換本質上就是文本替換在預處理階段就替換完成了,c語言代碼叫編譯型語言是因為在沒有編譯之前
代碼就是文本文件,是可以進行替換的。替換之后再做語法檢查
基本語法:
#define name stuff 1//這個只是宏替換的模板,后面使用到name字段按照這個模板替換成stuff 1
舉個例?:
#define MAX 1000
#define reg register //為 register這個關鍵字,創建?個簡短的名字
#define do_forever for(;;) //?更形象的符號來替換?種實現
#define CASE break;case //在寫case語句的時候?動把 break寫上。
// 如果定義的 stuff過?,可以分成??寫,除了最后??外,每?的后?都加?個反斜杠(續?符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__FILE__,__LINE__ , \
__DATE__,__TIME__ )
思考:在define定義標識符的時候,要不要在最后加上 ; ?
//?如:
#define MAX 1000;
#define MAX 1000//建議不要加上 ; ,這樣容易導致問題。
//?如下?的場景:
if(condition)
max = MAX;
else
max = 0;//如果是加了分號的情況,等替換后,if和else之間就是2條語句,?沒有?括號的時候,if后邊只能有?
//條語句。這?會出現語法錯誤。
3 #define定義宏
#define機制包括了?個規定,允許把參數替換到?本中,這種實現通常稱為宏(macro)或定義宏 下?是宏的申明?式:
#define name( parament-list ) stuff?
其中的 parament-list 是?個由逗號隔開的符號表,它們可能出現在stuff中。 注意: 參數列表的左括號必須與name緊鄰,如果兩者之間有任何空?存在,參數列表就會被解釋為stuff的 ?部分。
舉例:
#define SQUARE( x ) x * x
這個宏接收?個參數 x .如果在上述聲明之后,你把 SQUARE( 5 ); 置于程序中,預處理器就會?下?這個表達式替換上?的表達式:
5 * 5
警告: 這個宏存在?個問題: 觀察下?的代碼段:
int a = 5;
printf("%d\n" ,SQUARE( a + 1) );
乍?看,你可能覺得這段代碼將打印36,事實上它將打印11,為什么呢?? 替換?本時,參數x被替換成a+1,所以這條語句實際上變成了:
printf ("%d\n",a + 1 * a + 1 );?
這樣就?較清晰了,由替換產?的表達式并沒有按照預想的次序進?求值。 在宏定義上加上兩個括號,這個問題便輕松的解決了:
#define SQUARE(x) (x) * (x)?
這樣預處理之后就產?了預期的效果:
printf ("%d\n",(a + 1) * (a + 1) );
4 宏替換的規則
在程序中擴展#define定義符號和宏時,需要涉及?個步驟。
在調?宏時,?先對參數進?檢查,看看是否包含任何由#define定義的符號。如果是,它們?先 被替換。
替換?本隨后被插?到程序中原來?本的位置。對于宏,參數名被他們的值所替換。
最后,再次對結果?件進?掃描,看看它是否包含任何由#define定義的符號。如果是,就重復上 述處理過程。 注意:
宏參數和#define定義中可以出現其他#define定義的符號。但是對于宏,不能出現遞歸。
當預處理器搜索#define定義的符號的時候,字符串常量的內容并不被搜索。
5 宏函數和函數 的對?
注意點宏函數
宏函數的參數的替換是沒有類型檢查的,只是把參數替換進去最后編譯才進行類型檢查。是把宏參數做文本替換,替換掉后面的整個文本中的宏參數文本,比如:
#define MAX(a, b) ((a)>(b)?(a):(b)) int x=1,y=2;
//文本a和b替換成x,y,后面的文本中的a,b也被替換成x,y
MAX(x,y) ---替換成((x)>(y)?(x):(y))
//a,b-->1,2
MAX(1,2) ---替換成((1)>(2)?(1):(2))
//a,b -->1,2
MAX(1,x) --- ((1)>(x)?(1):(x))
宏通常被應?于執?簡單的運算。 ?如在兩個數中找出較?的?個時,寫成下?的宏,更有優勢?些。
#define MAX(a, b) ((a)>(b)?(a):(b))?
那為什么不?函數來完成這個任務? 原因有?:
?于調?函數和從函數返回的代碼可能?實際執?這個?型計算?作所需要的時間更多。所以宏? 函數在程序的規模和速度??更勝?籌。
更為重要的是函數的參數必須聲明為特定的類型。所以函數只能在類型合適的表達式上使?。反之 這個宏可以適?于整形、?整型、浮點型等可以?于 > 來?較的類型。宏的參數是類型?關的。 和函數相?宏的劣勢:
每次使?宏的時候,?份宏定義的代碼將插?到程序中。除?宏?較短,否則可能?幅度增加程序 的?度。
宏是沒法調試的。
宏由于類型?關,也就不夠嚴謹。
宏可能會帶來運算符優先級的問題,導致程容易出現錯。 宏有時候可以做函數做不到的事情。?如:宏的參數可以出現類型,但是函數做不到。
#define MALLOC(num, type)\
(type )malloc(num sizeof(type))
...
//使?
MALLOC(10, int); //類型作為參數
//預處理器替換之后:
(int * )malloc(10 sizeof(int));
6 #和##
1 #運算符
#運算符將宏的?個參數轉換為字符串字?量。它僅允許出現在帶參數的宏的替換列表中。 #運算符所執?的操作可以理解為”字符串化“。 當我們有?個變量 int a = 10; 的時候,我們想打印出: the value of a is 10 . 就可以寫:
//首先把a文本替換掉宏函數中的n文本,然后在把后面的文本中的n文本也替換成a文本
//最后整體替換文本,宏函數替換成后面一整個文本
#define PRINT(n) printf("the value of "#n " is %d", n);
int a=1;
PRINT(a);// the value of a is 10 .
當我們按照下?的?式調?的時候: PRINT(a);//當我們把a替換到宏的體內時,就出現了#a,?#a就是轉換為 字符串"a",同時兩個字符串是會被合并為一個字符串的,比如:
"abs""sad" ->"abssad" ? ?"add"fff"dsa" --> "addfffdsa"
2 ##運算符
可以把位于它兩邊的符號合成?個符號,它允許宏定義從分離的?本?段創建標識符。 ## 被稱為記號粘合 這樣的連接必須產??個合法的標識符。否則其結果就是未定義的。這?我們想想,寫?個函數求2個數的較?值的時候,不同的數據類型就得寫不同的函數。?如:
int int_max(int x, int y)
{
return x>y?x:y;
}
float float_max(float x, float y)
{
return x>yx:y;
}
但是這樣寫起來太繁瑣了,現在我們這樣寫代碼試試: //宏定義
#define GENERIC_MAX(type) \
type type##_max(type x, type y)\
{ \
return (x>y?x:y); \
}
使?宏,定義不同函數 GENERIC_MAX(int) //替換到宏體內后int##max ?成了新的符號 int_max做函數名 GENERIC_MAX(float) //替換到宏體內后float##max ?成了新的符號 float_max做函數名
GENERIC_MAX(int)
/*
預處理被文本替換成
int int_max(int x,int y)
{return ((x)>(y)?(x):(y));
}*/
GENERIC_MAX(float)
int main()
{
//調?函數
int m = int_max(2, 3);
printf("%d\n", m);
float fm = float_max(3.5f, 4.5f);
printf("%f\n", fm);
return 0;
}
輸出:
3
4.500000
不定參數的傳參使用##
#include<stdio.h>//__FILE__ :預處理符號,在預處理階段替換成當前符號所在的文件名類型為字符串
//__LINE__ :預處理符號, 在預處理階段替換成當前符號所在的文件的具體行數類型為int//不定宏參數的使用: ... 可變參數符號,不確定傳入的參數是多少,在宏定義中通常搭配##__VA_ARGS__使用
//Print(fmt,...) 預處理替換時 Print(fmt,...)中的fmt 替換printf中的fmt ,"jjjj"替換 ...
//##__VA_ARGS__:若可變參數為空,C99標準要求逗號必須保留(可能引發語法錯誤)。
//GCC/Clang通過##__VA_ARGS__優化,展開時自動去掉前面的逗號
//傳入Print(不定參數) 通過##__VA_ARGS__替換到printf函數中
//當多個字符串寫在一起時編譯預處理時會自動把多個字符串拼接在一起
//"ddd""dasd""asdsa" --> "ddddsdadsadsa"
#define Print(fmt,...) printf("[%s %d]"fmt"\n",__FILE__,__LINE__,##__VA_ARGS__)
int main()
{printf("[%s %d] %s\n",__FILE__,__LINE__,"jhhh");Print("%s","jjjj");Print("gggg");//傳入的可變參數為空Print("ddd""fff");return 0;
}