網站首頁 編程語言 正文
1、#define的深度認識
1.1 數值宏常量
宏定義數值常量相信大家都不陌生,相信很多小伙伴用過,這里我們就簡單的提一下,我們前面也講過,#define 本質上是替換,它可以出現在代碼的任何地方,也可以把任何東西都定義成宏,編譯器會在預編譯的時候進行替換掉,舉例:
#dfeine PI 3.1415926
這樣在以后的代碼中你就可以用 PI 來代替 3.1415926 那么這樣做的好處是什么呢?假設在未來的某一天,你要提升這個精度,如果你代碼中出現 3.1415926 過多的話,你提升精度還得一個個修改, 如果使用宏定義的話,你只需要改一次即可。
1.2 字符串宏常量
除了宏定義常量之外,還經常用來定義字符串,特別是路徑:
① #define PATH_1 D:\code\lesson1\test
②#define PATH_1 "D:\code\lesson1\test"
以上哪個是正確的呢?如果覺得太長還可以用續行符:
③ #define PATH_1 "D:\code\lesson1\\test"
很顯然第一個肯定是不對的,字符串需要用 "" 引起來,第三個也不對,第二個呢?我們去實踐證明下(以上寫法都不推薦!):
在Linux平臺環境下:
在Windows環境下:
很顯然他們都有同樣的警告,都是未知轉義序列,也無法正確打印出我們的路徑,在前面我們講到,' \ ' 是轉義字符,當我們要打印路徑的時候需要用轉義字符 ' \ ' 去還原 ' \ ' 的字面意思,所以這里打印路徑要用 \\ !
注意:Windows路徑分隔是用 ' \ ',而Linux路徑分隔是用 ' / ',所以如上測試用例改成 ' / ' 的話是不會報警告的。
所以要正確的打印如上用例應該這樣寫:
//不使用續行符
#define PATH_1 "D:\\code\\lesson1\\test"
//使用續行符
#define PATH_1 "D:\\code\\lesson1\\\test"
1.3 用宏充當注釋符號
因為 Linux 環境能直接查看預處理過程,便于我們驗證,所以我們下邊會在 Linux 環境下測試。
我們先簡單了解下程序的翻譯過程:
- 預處理-E:頭文件展開,去注釋,宏替換,條件編譯...
- 編譯-S:將預處理后的C語言翻譯成匯編語言
- 匯編-c:將匯編語言轉化為可目標二進制文件( 可被鏈接 )
- 鏈接:將目標二進制文件與相關庫鏈接,形成可執行程序
這里我們來看一段用宏充當注釋符號的代碼:
#include <stdio.h>
#define BSC //
int main()
{
BSC printf("hello world\n");
printf("you can see me!\n");
return 0;
}
這里我們要探討一個什么問題呢?如果替換成功,則不會執行第一個函數,如果替換失敗,則我們會看到兩行打印:
這究竟是為什么呢?我們可以執行:
[lwp@localhost code]$ gcc -E test.c -o test.i
把預處理后的結果保留下來為 test.i 文件,接著我們可以去用 vim 編輯器查看一下它與源文件的區別在哪,究竟是如何替換的:
通過上圖我們可以發現,在預處理之后的文件中,并沒有去成功通過宏替換注釋掉第一個 printf 函數,由此可見,在預處理階段,是先執行去掉注釋,然后在進行宏替換,如上代碼,本質是直接定義了一個空宏,我們特別不推薦這樣寫代碼!(C語言注釋風格也一樣不行,感興趣可以下去嘗試下)
1.4 用宏替換多條語句
先看一段代碼:
#include <stdio.h>
#define INIT_VALUE(a, b) a = 0; b = 0;
int main()
{
int flag = 0;
scanf("%d", &flag);
int a = 100;
int b = 200;
if (flag)
INIT_VALUE(a, b);
else
printf("%d, %d\n", a, b);
return 0;
}
我想請問,這段代碼有問題嗎?應該如何改進呢?這段代碼明顯是編譯不會通過的,但是可以通過執行預處理指令,發現預處理并沒有出問題,那么,我們可以看一下預處理之后的結果與源文件的區別在哪:
通過預處理之后的結果我們可以看到,宏替換多了一個分號。于是有小伙伴就討論出來如下三種解決方法:
- 去掉宏定義的最后一個分號
- 規范代碼風格,給 if 和 else 加上大括號
- 給宏定義要替換的部分用大括號括起來
第一種解決方法肯定是不行的,去掉最后一個分號并不能解決問題,if else 在沒有大括號的情況下后面只能跟一條語句,所以第一條行不通。
第二種解決方案看似不錯,但是我們有沒有想過,并不是所有人都會有良好的代碼風格,我們作為程序員,寫出的宏應該具有健壯性,所以第二條不可取。
第三種解決方案我們看著好像靠譜,但是我們通常寫完一條語句中后面都會帶上分號,那可想而知會出現這種情況:{a = 0, b = 0;}; 大括號外是不能跟分號的,所以這個方法也不可取!
最好的解決方法是什么呢?使用 do while 結構:
#include <stdio.h>#define INIT_VALUE(a, b) do{a = 0; b = 0;}while(0)int main(){ int flag = 0; scanf("%d", &flag); int a = 100; int b = 200; if (flag) INIT_VALUE(a, b); else printf("%d, %d\n", a, b); return 0;}#include <stdio.h>
#define INIT_VALUE(a, b) do{a = 0; b = 0;}while(0)
int main()
{
int flag = 0;
scanf("%d", &flag);
int a = 100;
int b = 200;
if (flag)
INIT_VALUE(a, b);
else
printf("%d, %d\n", a, b);
return 0;
}
循環會被看成一條復合語句,所以 if 不帶大括號也沒事(建議帶上),這樣我們的宏就會更健壯,也不會出錯,同時你也可以在中間添加續行符,讓他們的格式更清晰!同時我也有個小建議,宏定義的結尾最好都不要帶分號。
結論: 當我們需要宏進行多條語句替換的時候,推薦使用 do-while-zero結構。
1.5 宏定義的使用建議
【建議1】在宏定義體的結尾省略分號。
【建議2】函數宏的調用不能省略參數。
【建議3】函數宏的定義中,每個參數都應該以小括號括起來,避免替換之后出現優先級的問題。
2、#undef 撤銷宏
2.1 宏的定義位置和有效范圍
第一個問題,宏定義的位置有限制要求嗎?
答案:源文件的任何地方,宏都可以定義,與是否在函數內外無關。
第二個問題,宏的有效范圍有多大呢?
#include <stdio.h>
void test()
{
printf("test: %d\n", M);
}
int main()
{
test();
#define M 10
printf("main: %d\n", M);
return 0;
}
這段代碼我們就發現編譯不通過了,那么我們來進入預處理文件來對比下源文件:
答案:宏的作用范圍,從定義處開始,往后都是有效的!
2.2 宏的取消
這里我們用一個例子就能很好的證明了:
#include <stdio.h>
#define M 10
int main()
{
printf("%d\n", M);
#undef M
printf("undef: %d\n", M);
return 0;
}
我們來查看如上代碼的預處理之后的結果:
結論:undef 是取消宏的意思,可以用來限定宏的有效范圍!
2.3 一道筆試題
#include <stdio.h>
int main()
{
#define X 3
#define Y X*2
#undef X
#define X 2
int z = Y;
printf("%d\n", z);
return 0;
}
請問小伙伴們,這段代碼打印什么?
這里我就不截圖給大家看了,感興趣的可以自行下去敲一敲,經過Linux平臺和windows平臺的測試,最終打印的都是 4,因為編譯器都是從上到下掃描代碼的,以最近的宏定義為準。
原文鏈接:https://blog.csdn.net/m0_61784621/article/details/125634631
相關推薦
- 2022-04-23 npm publish 組件流程以及報錯總結
- 2022-09-27 Android?JetPack組件的支持庫Databinding詳解_Android
- 2023-03-29 Python利用pynimate實現制作動態排序圖_python
- 2022-12-30 React錯誤邊界Error?Boundaries詳解_React
- 2022-05-28 C++?超詳細講解stack與queue的使用_C 語言
- 2022-06-19 Rainbond云原生快捷部署生產可用的Gitlab步驟詳解_云其它
- 2024-02-27 idea中xml文件用瀏覽器打開
- 2022-09-19 React?Hook?四種組件優化總結_React
- 最近更新
-
- 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同步修改后的遠程分支