網站首頁 編程語言 正文
當年學習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 = name ## _ ## data; \ name.max_size = size; \ printf("variable name=%s\nmember data=%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 = var_data; \ var.max_size = 10; \ printf("variable name=%s\nmember data=%s, data_size=%d, max_size=%d", \ “var”, var_data, var.data_size, var.max_size); \
Output:
variable name=var1
member data=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://mp.weixin.qq.com/s/Xr2pFCJ4j0DZYo2PO6-KQg
相關推薦
- 2022-10-13 Python教程之類型轉換詳解_python
- 2022-06-01 C語言?超詳細介紹與實現線性表中的帶頭雙向循環鏈表_C 語言
- 2023-04-09 利用Matplotlib實現單畫布繪制多個子圖_python
- 2022-05-18 Python?pandas?計算每行的增長率與累計增長率_python
- 2022-10-13 Python?flask?sqlalchemy的簡單使用及常用操作_python
- 2022-10-16 python?math模塊使用方法介紹_python
- 2022-06-14 Github?Copilot結合python的使用方法詳解_python
- 2022-03-09 C++泛型編程Generic?Programming的使用_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支