網站首頁 編程語言 正文
“江山如畫,一時多少豪杰——時二二年五月六日”
這里是目錄
- 前言
- 一、#define指令
- 1.#define定義宏
- 2.#define 替換 宏
- 3.帶副作用的宏參數
- 4.#undef撤銷宏定義
- 5.宏和函數對比(重點)
- 二、條件編譯指令
- 1.單分支
- 2.多分支條件編譯
- 3.判斷某個符號是否被定義
- 4.嵌套指令
- 三、#include指令
- 1.本地文件包含
- 2.庫文件包含
- 3.嵌套文件包含
前言
了解敲完hello world后,編譯器是怎么處理代碼的第一步的呢,這是學習C和C++的基礎。
Hello World代碼如下。
當你敲完Hello World這串代碼時。編譯器會對這些代碼進行編譯 和 鏈接的操作。
而 編譯: 又分為 預處理、編譯、匯編。
所以說 當你敲完C代碼后的第一步,編譯器會對C代碼進行預處理.
那么預處理主要做了那些事情呢?
預處理大致做了以下事情:
1.定義和替換由 #define指令定義的符號
2.刪除注釋
3.確定代碼部分內容是否應該根據一些 條件編譯指令 進行編譯
4.插入被 #include指令包含的內容
所以本章詳解預處理指令 #define、#include、條件編譯指令。
一、#define指令
1.#define定義宏
什么是宏?
宏的定義:#define 允許把參數替換到文本中,這種實現通常稱為宏或定義宏
宏的聲明格式:
#define NAME stuff
解釋:沒當有符號name出現在#define NAME stuff這條語句后面時,預處理器就會把它替換為 stuff。
NAME:
1.NAME是宏的名字。在這里 name 相當于變量,或者也可以相當于函數。但不等于函數!
2.一般NAME都是大寫,因為宏和函數語法很相似,語言本身我們無法區分,所以宏名要全部大寫
stuff:可以是常量。可以是表達式。也可以是一段程序。
例如:
以下代碼在預處理后是什么樣子呢?
//定義聲明宏
//定義中我們使用了括號,這是一個好習慣,避免優先級的錯誤
#define SQUARE(x) (x)*(x)
int main()
{
printf("%d ", SQUARE(5));
return 0;
}
預處理后的代碼,以下你看到的代碼是編譯器實實在在的處后的代碼。
#define SQUARE(x) (x)*(x)
int main()
{
//將SQUARE(5)替換為(5)*(5)
printf("%d ", (5)*(5));
return 0;
}
你是否還對#define 替換迷惑?請繼續往下看
2.#define 替換 宏
到底上面的代碼是怎么替換的 宏?
1.再調用宏時,首先對參數檢查,看是否包含了#define定義的符號,比如SQUARE(5),然后將它的x * x替換為5 * 5.
2.對于宏,參數名被他們的值所替代。
3.最后,再次對文本掃描,看是否包含了熱任何由#define定義的符號。如果是,就重復上述處理過程。
為什么會有第3步的重復呢?
因為有時候#define定義可以包含其他#define定義的符號。但是宏不可以遞歸!
3.帶副作用的宏參數
什么是帶副作用的宏參數?
副作用:就是表達式求值的時候出現的永久性效果。
例如:
x+1;//不帶副作用
x++;//帶有副作用
下面代碼輸出結果是什么?
#include
#define ADD(a, b) (a)+(b)
int main()
{
int x = 2;
int y = 3;
int z = ADD(x++, y++);
//輸出的結果是什么?
//x=3 y=4 z=5
printf("x=%d y=%d z=%d\n", x, y, z);
return 0;
}
因為被替換的代碼是int z = ADD(x++, y++);
替換后為:int z = (x++)+(y++);
這樣結果就一目了然。
4.#undef撤銷宏定義
#undef:這條指令用于移除一個宏定義
例如:移除MAX這個宏。
5.宏和函數對比(重點)
屬性 | #define定義宏 | 函數 |
---|---|---|
代碼長度 | 每次使用時,宏代碼都會被插入到程序中。除了非常小的宏之外,程序的長度會大幅度增長 | 函數代碼只出現于一個地方。每次使用函數時,都調用同一個地方的代碼 |
執行速度 | 更快 | 存在函數的調用和返回 的格外開銷,所以相對慢一些 |
操作符優先級 | 宏參數求值需要加上括號,否則容易造成不可以預料的后果 | 只在函數調用事求值一次,不會帶副作用 |
帶有副作用的參數 | 參數可能被替換到宏的多個位置,有的可能帶有副作用 | 函數參數只在傳參的時候求值一次,結果更容易控制 |
參數類型 | 宏的參數與類型無關,可以是任何類型的的參數 | 函數參數與類型有關,參數類型不同就需要不同的函數,因為C語言沒有C++的重載 |
調試 | 宏是不可以調試的,因為在程序運行前就已經替換的宏 | 可以逐語句調試 |
二、條件編譯指令
什么是條件編譯?
意思就是我們可以選擇性的編譯。
條件編譯:你可以選擇代碼的一部分是被正常編譯還是完全忽略。用于支持條件編譯的基本結構是#if指令和與其匹配的#endif指令。
1.單分支
常量表達式expression,由預處理器求值。
如果expression為真,那么statements將被執行,否則預處理器就安靜的刪除它們。
#if expression
statements;
#endif
//常量表達式expression,由預處理器求值。
2.多分支條件編譯
同if else語句,為真則執行。
#if expression
//...
#elif expression
//...
#else
//...
#endif
3.判斷某個符號是否被定義
為了測試一個符號是否已經被定義。在條件編譯中完成這個任務更方便。
以下兩條語句功能想通過。
1.#if defined(symbol)
2.#ifdef symbol
4.嵌套指令
某個程序既要在windows系統下能夠運行,也需要在Linux系統下運行,這就要條件編譯來解決跨平臺問題。這時候嵌套指令很容易解決。
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
三、#include指令
#include在預處理時會被展開。
這種展開的方式很簡單:
1.預處理器先刪除這條指令,并用**#include**所包含文件的內容替換。
2.這樣一個源文件被包含10次,那就實際被編譯10次。
1.本地文件包含
#include "Add.h"
查找方法:
1.先在源文件所在目錄下查找
2.如果該頭文件未找到,編譯器就像查找庫函數頭文件一樣在標準位置查找頭文件。
2.庫文件包含
#include
查找方法:查找頭文件直接去標準路徑下去查找,如果找不到就提示編譯錯誤。
這樣是不是可以說,對于庫文件也可以使用 “” 的形式包含?
答案是肯定的,可以。
但是這樣做查找的效率就低些,當然這樣也不容易區分是庫文件還是本地文件了。
3.嵌套文件包含
有時候會重復包含頭文件,以前為了解決這個方法,人們用了條件編譯。代碼如下
每個頭文件的開頭寫:
例如有個test.h的頭文件。用下劃線分開頭文件。全大寫。
#ifndef __TEST_H__
#define __TEST_H__
//這里面寫頭文件的內容
#endif
上面這種寫法比較古老。
現在一般用這個寫法
#pragma once
#pragma once也是是用來防止頭文件被包含的。
原文鏈接:https://blog.csdn.net/qq2466200050/article/details/124615042
相關推薦
- 2022-09-17 Python高效處理大文件的方法詳解_python
- 2023-06-18 C#?System.TypeInitializationException?異常處理方案_C#教程
- 2022-06-21 C++簡明分析臨時對象是什么_C 語言
- 2022-10-22 Redis?布隆過濾器命令的使用詳解_Redis
- 2022-11-22 GraphQL在react中的應用示例詳解_React
- 2022-05-16 .Net?MVC將Controller數據傳遞到View_實用技巧
- 2022-12-09 C++中利用cout和fstream采用非科學計數法輸出_C 語言
- 2022-09-26 Transformer模型Encoder和Decoder的Pytorch逐行實現
- 最近更新
-
- 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同步修改后的遠程分支