以下內容為本人的學習筆記,如需要轉載,請宣告原文鏈接 微信公眾號「englyf」https://mp.weixin.qq.com/s/Xr2pFCJ4j0DZYo2PO6-KQg
當年學習C語言的第一門課就提到過標記(Token)的概念,不過,相信在多年之后你再次聽到這個術語時會一臉懵逼,比如我,
因此特地翻了翻資料,整理下來這些筆記,
在C語言中什么是標記?
標記是編程語言處理的基本單元,也叫最小劃分元素,比如關鍵字、運算子、變數名、函式名、字串、數值等等,
下面舉例說明一下:
printf("hello world!");
對上面的陳述句進行標記劃分,可分為5個標記,如下:
printf // 函式名
( // 左小括號運算子
"hello world!" // 字串
) // 右小括號運算子
; // 分號
預處理字串運算子
在C語言中,預處理字串運算子有兩個,#和##,
# 字串化運算子
用途是,將標記(Token)轉成字串,
Syntax:
#define TOKEN_NAME(param) #param
Basic Usage:
#include <stdio.h>
#define MACRO_NAME(param) #param
int main()
{
printf(MACRO_NAME(hello world));
return 0;
}
Output:
hello world
在專案實踐中,用宏定義的值的同時也需要將宏名轉成字串使用,對日志的輸出尤其管用,
Best Practice:
#include <stdio.h>
#define NAME(param) #param
#define LEN_MAX 10
int main()
{
int array[LEN_MAX] = {0};
int index = 10;
if (index >= LEN_MAX) {
printf("error: %s:%d is over %s:%d\n", NAME(index), index, NAME(LEN_MAX), LEN_MAX);
} else {
printf("read %s[%d]=%d\n", NAME(array), index, array[index]);
}
return 0;
}
Output:
error: index:15 is over LEN_MAX:10
如果修改如下:
int index = 9;
Output:
read array[9]=0
## 標記(Token)連接運算子
用途是,將##前后的標記(Token)串接成新的單一標記,
syntax:
#define TOKEN_CONCATENATE(param1, param2) param1##param
Basic Usage:
#include <stdio.h>
#define TOKEN_CONCATENATE(param1, param2) param1##param2
int main()
{
printf("%d\n", TOKEN_CONCATENATE(12, 34));
return 0;
}
Output:
1234
通常,編碼實踐中,代碼中會出現一些書寫看上去雷同的片段,極其啰嗦冗余,為了壓縮原始碼篇幅,可以參考代碼生成器的思想,在預編譯階段用宏定義代碼片段展開替換,同時根據輸入的引數用##組合各種標記,
假設有個需求是宣告定義一組同一型別的結構體的變數,并初始化其內部成員,既然宣告定義的這些變數屬于同一型別的結構體,那么按照直接編碼的方式,就會有多次重復的代碼片段出現,里邊包括了宣告定義陳述句,以及初始化各個成員的陳述句,不同的只是變數名或者引數而已,
舉個栗子,下面基于同一型別的結構體,宣告定義兩個變數,并初始化,看代碼
#include <stdio.h>
#include <string.h>
#define NAME(param) #param
typedef struct {
char *data;
int data_size; /* number of byte real */
int max_size; /* maximnm data size.*/
} my_type;
#define my_type_create(name, size) \
char name ## _ ## data[size] = {0}; \
my_type name; \
memset(&name, 0x00, sizeof(name)); \
name.data = https://www.cnblogs.com/englyf/archive/2022/11/14/name ## _ ## data; /
name.max_size = size; /
printf("variable name=%s\nmember data=https://www.cnblogs.com/englyf/archive/2022/11/14/%s, data_size=%d, max_size=%d/n", \
NAME(name), NAME(name ## _ ## data), name.data_size, name.max_size); \
int main() {
my_type_create(var1, 10)
my_type_create(var2, 20)
}
上面的代碼中,定義了宏my_type_create,內部實作了結構體變數的宣告定義,以及內部成員的初始化,如果按照直接編碼的方式,代碼量相對于上面的代碼量會虛增n-1倍,n=變數的個數,
在main函式中,呼叫宏的時候輸入引數var和10,那么在編譯預處理階段,根據輸入的引數,宏my_type_create會展開為以下的代碼段,
char var_data[10] = {0}; \
my_type var; \
memset(&var, 0x00, sizeof(var)); \
var.data = https://www.cnblogs.com/englyf/archive/2022/11/14/var_data; /
var.max_size = 10; /
printf("variable name=%s\nmember data=https://www.cnblogs.com/englyf/archive/2022/11/14/%s, data_size=%d, max_size=%d", \
“var”, var_data, var.data_size, var.max_size); \
Output:
variable name=var1
member data=https://www.cnblogs.com/englyf/archive/2022/11/14/var1_data, data_size=0, max_size=10
variable name=var2
member data=var2_data, data_size=0, max_size=20
## 還有個特殊的用途
在宏定義中,也支持用...代表可變引數,
#define MY_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)
由于可變引數數目不確定,所以沒有具體的標記,于是為了參考可變引數,語言層面提供了可變宏(Variadic macros)__VA_ARGS__來參考它,
但是,在宏定義時,如果直接使用__VA_ARGS__來參考可變引數,一旦可變引數為空就會引起編譯器報錯,看看下面的例子
#include <stdio.h>
#define LOG_INFO(fmt, ...) printf("[I]" fmt "\n", __VA_ARGS__)
int main() {
LOG_INFO("info...");
LOG_INFO("%s, %s", "Hello", "world");
}
Output:
main.c: In function ‘main’:
main.c:3:62: error: expected expression before ‘)’ token
3 | #define LOG_INFO(fmt, ...) printf("[I]" fmt "\n", __VA_ARGS__)
| ^
main.c:6:3: note: in expansion of macro ‘LOG_INFO’
6 | LOG_INFO("info...");
| ^~~~~~~~
為了解決上面的問題,在__VA_ARGS__前面添加上##,這樣的目的是告訴前處理器,如果可變引數為空,那么前面緊跟者的逗號,在宏定義展開時會被清理掉,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/532542.html
標籤:其他
上一篇:<六>指向類成員的指標
