c語言的常用的預處理指令和條件編譯

c語言的常用的預處理指令和條件編譯

  • 預處理詳解
  • 預定義符號
    • #define
      • #define 定義標識符
      • #define 定義宏
      • 帶副作用的宏參數
      • 宏和函數的對比
      • #define命名約定和#undef移除宏
    • # 和 ## 參數插入字符串
      • 字符串的自動連接
      • #宏參數
  • 命令行定義
  • 條件編譯
    • #if和#endif
    • 多分支條件編譯#if、#elif、#else和#endif
    • #ifdef和#ifndef 判斷某符號是否定義

預處理詳解

最早接觸預處理,還是在一篇文章看懂c語言_如何看懂c語言代碼-CSDN博客中介紹了c語言中#define定義的符號在編譯階段會被替換的行為。

這里將我所了解的預處理符號簡單做個收集。預處理指令肯定不止這些,曾經遇到過的:

#pragma pack()

也是預處理指令。

詳細參考《c語言深度剖析》。

預定義符號

__FILE__	//進行編譯的源文件
__LINE__	//文件當前的行號
__DATE__	//文件被編譯的日期
__TIME__	//文件被編譯的時間
__STDC__	//如果編譯器遵循ANSI C,其值為1,否則未定義

輸出這些符號可以知道代碼的信息。

#include <stdio.h>
#include <windows.h>int main()
{printf("%s\n%d\n%s\n%s\n", __FILE__, __LINE__, __DATE__, __TIME__);printf("%s", __STDC__);//vs下未定義,說明vs不是嚴格遵循ANSI C標準return 0;
}

在某一個的Devc++5.11的輸出:

D:\aDarkwanderor\_01ComputerLearn\_01C_Language_and_C++\_4CppProjectDebug\testC.c
6
Apr 25 2025
21:39:22

#define

#define 定義標識符

#define的用法:

#define name stuff

定義階段會將name替換成stuffstuff可以是數字,可以是關鍵字,還可以是語句。

如果定義的 stuff過長,可以分成幾行寫,除了最后一行外,每行的后面都加一個反斜杠(續行符)。

#define MAX 1000//為 register這個關鍵字,創建一個簡短的名字
#define reg register//用更形象的符號來替換一種實現
#define do_forever for(;;)//在寫case語句的時候自動把 break寫上
#define CASE break;case  // 如果定義的 stuff過長,可以分成幾行寫,除了最后一行外,每行的后面都加一個反斜杠(續行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \date:%s\ttime:%s\n" ,\__FILE__,__LINE__ ,       \__DATE__,__TIME__ )  

注意staff;需要謹慎,因為符號用到的地方可能是某個語句的一部分。

#define 定義宏

#define 機制包括了一個規定,允許把參數替換到文本中,這種實現通常稱為宏(macro)或定義宏(define macro)。

下面是宏的申明方式:

#define name( parament-list ) stuff

其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現在stuff中。

注意:

  • 參數列表的左括號必須與name緊鄰。

  • 如果兩者之間有任何空白存在,參數列表就會被解釋為stuff的一部分。

  • 對每個出現的參數和最終的宏,盡量用括號()括起來。

簡單使用宏:

#include <stdio.h>#define MUL(x) x*x
#define MUL2(x) (x)*(x)
#define ADD(x) (x)+(x)int main() {int a = 5;printf("%d\n", MUL(a));printf("%d\n", MUL(a + 1));printf("%d\n", MUL2(a + 1));printf("%d\n", ADD(a) * 6);return 0;
}

輸出:

25
11
36
35

但我們想要的預期輸出很明顯是{25,36,36,60},在MULADD兩個宏上出現了問題。

將宏的符號替換(預編譯之后),并省略stdio.h展開后的所有代碼,main函數變成這個樣子:

int main() {int a = 5;printf("%d\n", a * a);printf("%d\n", a + 1 * a + 1);printf("%d\n", (a + 1) * (a + 1));printf("%d\n", a + a * 6);return 0;
}

第4行的宏替換后,因為*的優先級高,故先計算1*a,使得結果錯誤,第6行的宏也是如此。因此對每個出現的參數和最終的宏,盡量用括號()括起來。

#define替換規則進行總結:

在程序中擴展#define定義符號和宏時,需要涉及幾個步驟。

  1. 在調用宏時,首先對參數(或源碼)進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先被替換。

  2. 替換文本隨后被插入到程序中原來文本的位置。對于宏,參數名被他們的值所替換。

  3. 最后,再次對結果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復1、2。

注意:

  1. 宏參數和#define 定義中可以出現其他#define定義的符號。但是對于宏,不能出現遞歸。

  2. 當預處理器搜索#define定義的符號的時候,字符串常量的內容并不被搜索。

帶副作用的宏參數

當宏參數在宏的定義中出現超過一次的時候,如果參數帶有副作用,那么你在使用這個宏的時候就可能

出現危險,導致不可預測的后果。副作用就是表達式求值的時候出現的永久性效果。

例如:

x+1;//不帶副作用
x++;//帶有副作用

這里的MAX宏可以證明具有副作用的參數所引起的問題。

#include <stdio.h>#define MAX(a, b) ( (a) > (b) ? (a) : (b) )int main() {int x = 5,y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n", x, y, z);return 0;
}

輸出:

x=6 y=10 z=9

宏和函數的對比

宏通常被應用于執行簡單的運算,比如在兩個數中找出較大的一個:

#define MAX(a, b) ((a)>(b)?(a):(b))

不用函數來完成這個任務的原因有二:

  1. 用于調用函數和從函數返回的代碼可能比實際執行這個小型計算工作所需要的時間更多(就是函數費時,宏省時)。所以宏比函數在程序的規模和速度方面更勝一籌

  2. 更為重要的是函數的參數必須聲明為特定的類型

所以函數只能在類型合適的表達式上使用。反之這個宏可以適用于整形、長整型、浮點型等可以用于>來比較的類型。即宏是類型無關的

此外,宏有時候可以做函數做不到的事情。比如:宏的參數可以出現類型,但是函數做不到。

宏的缺點:和函數相比宏也有劣勢的地方:

  1. 每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長度

  2. 宏是沒法調試的。

  3. 宏由于類型無關,也就不夠嚴謹。

  4. 宏可能會帶來運算符優先級的問題,導致程容易出現錯。

  5. 宏無法實現遞歸

  6. 參數可能被替換到宏體中的多個位置,所以帶有副作用的參數求值可能會產生不可預料的結果。

用一張表列出二者的差別:

對比項#define 定義宏函數
代碼長度每次使用時,宏代碼都會被插入到程序中。除了非常小的宏之外,程序的長度會大幅度增長函數代碼只出現于一個地方;每次使用這個函數時,都調用那個地方的同一份代碼
執行速度更快存在函數的調用和返回的額外開銷,所以相對慢一些
操作符優先級宏參數的求值是在所有周圍表達式的上下文環境里,除非加上括號,否則鄰近操作符的優先級可能會產生不可預料的后果,所以建議宏在書寫的時候多些括號。函數參數只在函數調用的時候求值一次,它的結果值傳遞給函數。表達式的求值結果更容易預測。
帶有副作用的參數參數可能被替換到宏體中的多個位置,所以帶有副作用的參數求值可能會產生不可預料的結果。函數參數只在傳參的時候求值一次,結果更容易控制。
參數類型宏的參數與類型無關,只要對參數的操作是合法的,它就可以使用于任何參數類型。函數的參數是與類型有關的,如果參數的類型不同,就需要不同的函數,即使他們執行的任務是相同的。
調試宏是不方便調試的函數是可以逐語句調試的
遞歸宏是不能遞歸的函數是可以遞歸的

#define命名約定和#undef移除宏

函數的宏的使用語法很相似。所以語言本身沒法幫我們區分二者。那我們平時的一個習慣是:

  • 把宏名全部大寫。

  • 函數名不要全部大寫。

此外如果現存的一個名字需要被重新定義,那么它的舊名字首先要被移除。可以用#undef移除宏。

#define NAME stuff
//...
#undef NAME

# 和 ## 參數插入字符串

字符串的自動連接

案例:

#include <stdio.h>int main() {char* p = "hello ""world\n";char* p2 = "hello"\" wor"\"ld\n";printf("hello"" world\n");printf("%s", p);printf("%s", p2);return 0;
}

輸出:

hello world
hello world
hello world

這說明,字符串是有自動連接的特點的。

因此可以利用這個特點繼續實現表現更豐富的宏。

#宏參數

在這之前先介紹#宏參數,這里的#宏參數是指把宏參數轉換成字符串

例如:

#include <stdio.h>#define STR(x) #xint main() {printf(STR(aasfsdgsg));return 0;
}

輸出:

aasfsdgsg

因此利用#宏參數和字符串的自動連接特性,完善的宏如下:

#include <stdio.h>#define PRINT(FORMAT, VALUE) \printf("the value is "FORMAT"\n", VALUE)#define PRINT2(FORMAT, VALUE) \printf("the value of " #VALUE " is "FORMAT "\n", VALUE);int main() {int i = 10;PRINT("%d", 10);//利用字符串的自動連接特性PRINT2("%d", i + 3);//利用#宏參數和字符串的自動連接特性return 0;
}

輸出:

the value is 10
the value of i + 3 is 13

##可以把位于它兩邊的符號合成一個符號。它允許宏定義從分離的文本片段創建標識符。

例如:

#include <stdio.h>#define CAT(x,y) x##yint main() {int a = 2025;int b = CAT(a, -3);//將a和-3連接在一起變成a-3printf("%d", b);return 0;
}

這樣的連接必須產生一個合法的標識符。否則其結果就是未定義的。

命令行定義

許多C 的編譯器提供了一種能力,允許在命令行中定義符號。用于啟動編譯過程。

命令行是一種通過輸入文本命令來與計算機系統(特別是操作系統)進行交互的界面,它與圖形用戶界面(GUI)相對應,具有高效、靈活和強大的特點,在系統管理、軟件開發等領域應用廣泛。

c語言代碼通過編譯最終變成的可執行程序,只要滿足條件是可以直接運行在操作系統上的。從代碼到可執行程序,在一些編譯器可以通過輸入命令干涉編譯過程。

當我們根據同一個源文件要編譯出一個程序的不同版本的時候,這個特性有點用處。

舉個例子,假定某個程序中聲明了一個某個長度的數組,如果機器內存有限,我們需要一個很小的數組,但是另外一個機器內存大些,我們需要一個數組能夠大些。

例如這個代碼:

#include <stdio.h>
int main()
{int array[ARRAY_SIZE];int i = 0;for (i = 0; i < ARRAY_SIZE; i++){array[i] = i;}for (i = 0; i < ARRAY_SIZE; i++){printf("%d ", array[i]);}printf("\n");return 0;
}

在封裝嚴密的 IDE 比如Devc++、vs2019等,這個代碼可能直接報錯。

但如果是在vscode,則可以通過命令行在編譯階段指定ARRAY_SIZE變成我們想要的數值,從而生成不同功能的程序。

例如通過命令gcc -D ARRAY_SIZE=10 testc.c可以指定ARRAY_SIZE變成指定數值10,然后生成可執行程序 a.exe 。再輸入.\a.exe即可執行它。

請添加圖片描述

也可在原命令的基礎上加-o ProgramName.exe來指定生成的可執行程序的程序名。這里讓生成的可執行程序名和c語言的代碼名保持一致。

請添加圖片描述

條件編譯

在編譯一個程序的時候若要將一條語句(一組語句)編譯或者放棄參與編譯,可通過條件

編譯指令實現。

這些調試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性的編譯。

常見的條件編譯指令如下。

#if和#endif

#if 常量表達式//...
#endif

常量表達式由預處理器求值。

例如:

#include <stdio.h>
int main()
{
#if 0printf("asdfghjkl\n");
#endifprintf("qwertyyiop");return 0;
}

輸出:

qwertyyiop

在模塊化編程中的某個頭文件用#define定義某個符號,這個符號也能通過#if進行條件編譯。

例如,這里將#define和c語言代碼放在一起:

#include <stdio.h>#define ABC 1int main()
{
#if ABCprintf("asdfghjkl\n");
#endifprintf("qwertyyiop");return 0;
}

輸出:

asdfghjkl
qwertyyiop

多分支條件編譯#if、#elif、#else和#endif

#if實現多分支編譯的用法:

#if 常量表達式1//...
#elif 常量表達式2//...
#else//...
#endif

其中#elif不可放在#else之后,一般#else是作為所有常量表達式都不滿足的情況下,作為最后的選擇。

例如:

#include <stdio.h>#define A 1
#define B 2
#define C 3void f1() {
#if A==1&&B!=2printf("1\n");
#elif A==1&&B==2printf("2\n");
#endif
}void f2() {
#if A==1&&B!=2printf("1\n");
#elseprintf("2\n");
#endif
}void f3() {
#if A==1&&B!=2printf("1\n");
#elif A==1&&B==2&&C!=3printf("2\n");
#elseprintf("3\n");
#endif
}int main()
{//f1();//f2();f3();printf("six");return 0;
}

#ifdef和#ifndef 判斷某符號是否定義

若定義了某個符號就執行相關語句:

#if defined(symbol)
//...
#endif#ifdef symbol
//...
#endif

若沒定義某個符號就執行相關與:

#if !defined(symbol)
//...
#endif#ifndef symbol
//...
#endif

其中symbol表示某一標識符,這個標識符通常由#define預定義。

一般更喜歡用#ifdef#ifndef,因為可以少敲一個單詞。

例如,#ifdef的用法:

#include<stdio.h>#define A 1int main()
{
#ifdef Aprintf("A\n");
#endif
#ifdef Bprintf("B\n");
#endifprintf("six");return 0;
}

#ifndef

#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS 1
#endif#include <stdio.h>int main()
{int a;scanf("%d", &a);//在vs定義了_CRT_SECURE_NO_WARNINGS,才能正常使用scanf和其他別的函數printf("%d", a);return 0;
}

一般#ifndef#ifdef通常用于避免頭文件重復包含。

避免頭文件包含還可以在頭文件開頭加這樣一句:

#pragma once

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903510.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903510.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903510.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

TTL、RS-232 和 RS-485 串行通信電平標準區別解析

TTL、RS-232 和 RS-485 是三種常見的串行通信電平標準&#xff0c;它們各自有不同的協議特點&#xff0c;適用于不同的應用場景。以下是它們的主要特點對比&#xff1a; ??1. TTL&#xff08;Transistor-Transistor Logic&#xff09;?? ??主要特點?? ??單端信號?…

SwinTransformer改進(6):與Dual Cross-Attention結合的視覺模型

在計算機視覺領域,Transformer架構正逐漸取代傳統的CNN成為主流。 本文將深入解析一個結合了Swin Transformer和Dual Cross-Attention(DCA)的創新模型實現。 模型概述 這個實現的核心是將Swin Transformer(一種高效的視覺Transformer)與創新的Dual Cross-Attention模塊相結…

Dify框架面試內容整理-Dify框架

什么是Dify框架? Dify框架是一個開源的AI應用開發平臺,專注于幫助開發者和非技術人員快速構建、部署和管理基于大語言模型(如GPT系列、國產開源模型)的應用。 Dify框架的特點:

道可云人工智能每日資訊|“人工智能科技體驗展”在中國科學技術館舉行

道可云元宇宙每日簡報&#xff08;2025年4月28日&#xff09;訊&#xff0c;今日元宇宙新鮮事有&#xff1a; 《2025年提升全民數字素養與技能工作要點》發布 近日&#xff0c;中央網信辦、教育部、工業和信息化部、人力資源社會保障部聯合印發《2025年提升全民數字素養與技能…

基于javaweb的SpringBoot新聞發布系統設計與實現(源碼+文檔+部署講解)

技術范圍&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬蟲、數據可視化、小程序、安卓app、大數據、物聯網、機器學習等設計與開發。 主要內容&#xff1a;免費功能設計、開題報告、任務書、中期檢查PPT、系統功能實現、代碼編寫、論文編寫和輔導、論文…

蒼穹外賣心得體會

1 登錄認證 技術點&#xff1a;JWT令牌技術&#xff08;JSON Web Token&#xff09; JWT&#xff08;JSON Web Token&#xff09;是一種令牌技術&#xff0c;主要由三部分組成&#xff1a;Header頭部、Payload載荷和Signature簽名。Header頭部存儲令牌的類型&#xff08;如JW…

車載功能測試-車載域控/BCM控制器測試用例開發流程【用例導出方法+優先級劃分原則】

目錄 1 摘要2 位置燈手動控制簡述2.1 位置燈手動控制需求簡述2.2 位置燈手動控制邏輯交互圖 3 用例導出方法以及優先級原則3.1 用例導出方法3.1.1 用例導出方法介紹3.1.2 用例導出方法關鍵差異分析 3.2 優先級規則3.2.1 優先級劃分的核心原則3.2.2 具體等級定義與判定標準 3.3 …

Linux系統基礎:基礎指令簡介(網絡概念部分)

簡介&#xff1a;Linux 是一種開源的類 Unix 操作系統內核&#xff0c;由 Linus Torvalds 于 1991 年首次發布。經過多年發展&#xff0c;它已成為服務器、嵌入式設備和個人計算機領域的重要操作系統。 網絡基礎概念 初始協議 簡單來說&#xff0c;協議是一種約定&#xff0…

多模態(3):實戰 GPT-4o 視頻理解

最近&#xff0c;OpenAI 團隊的 GPT-4o 模型&#xff0c;在多模態方面的能力有了大幅提升&#xff0c;這次我們就使用 GPT-4o 完成一個視頻理解的實戰。 1. 環境搭建 1.1 安裝 FFmpeg 做視頻處理&#xff0c;我們需要用到 FFmpeg 這款功能強大的開源多媒體處理工具。FFmpeg…

(27)VTK C++開發示例 ---將點坐標寫入 STL文件

文章目錄 1. 概述2. CMake鏈接VTK3. main.cpp文件4. 演示效果 更多精彩內容&#x1f449;內容導航 &#x1f448;&#x1f449;VTK開發 &#x1f448; 1. 概述 此示例使用 vtkSTLWriter 將存儲在 vtkPolyData 對象中的 3D 幾何數據保存到 STL 文件&#xff0c;并讀取stl文件顯示…

2. python協程/異步編程詳解

目錄 1. 簡單的異步程序 2. 協程函數和協程對象 3. 事件循環 4. 任務對象Task及Future對象 4.1 Task與Future的關系 4.2 Future對象 4.3 全局對象和循環事件對象 5. await關鍵字 6. 異步上下文管理 7.異步迭代器 8. asyncio的常用函數 8.1 asyncio.run 8.2 asyncio.get…

智慧園區IOT項目與AI時代下的機遇 - Java架構師面試實戰

在互聯網大廠的Java求職者面試中&#xff0c;面試官通常會針對實際業務場景提出一系列問題。以下是關于智慧園區IOT項目及AI時代下的機遇的面試模擬對話。 第一輪提問 面試官&#xff1a;馬架構&#xff0c;請簡要介紹下智慧園區IOT項目的整體架構設計。 馬架構&#xff1a;…

論文導讀 - 基于特征融合的電子鼻多任務深度學習模型研究

基于特征融合的電子鼻多任務深度學習模型研究 原論文地址&#xff1a;https://www.sciencedirect.com/science/article/pii/S0925400524009365 引用此論文&#xff08;GB/T 7714-2015&#xff09;&#xff1a; NI W, WANG T, WU Y, et al. Multi-task deep learning model f…

AI超級智能體項目教程(二)---后端項目初始化(設計knif4j接口文檔的使用)

文章目錄 1.選擇JDK的版本和相關配置2.添加依賴信息2.1指定lombok版本信息2.2引入hutool工具類2.3了解knif4j依賴2.4引入knif4j依賴 3.contrller測試3.1完成yml文件配置3.2修改默認掃描路徑3.3controller具體的內容3.4配置接口和訪問路徑3.5如何訪問3.6調試接口3.6調試接口 1.選…

linux blueZ 第四篇:BLE GATT 編程與自動化——Python 與 C/C++ 實戰

本篇聚焦 BLE(Bluetooth Low Energy)GATT 協議層的編程與自動化實踐,涵蓋 GATT 基礎、DBus API 原理、Python(dbus-next/bleak)示例、C/C++ (BlueZ GATT API)示例,以及自動發現、讀寫特征、訂閱通知、安全配對與腳本化測試。 目錄 BLE GATT 基礎概念 BlueZ DBus GATT 模…

kafka與flume的整合、spark-streaming

kafka與flume的整合 前期配置完畢&#xff0c;開啟集群 需求1&#xff1a; 利用flume監控某目錄中新生成的文件&#xff0c;將監控到的變更數據發送給kafka&#xff0c;kafka將收到的數據打印到控制臺&#xff08;三個node01中運行&#xff09; 1.在kafka中建立topic kafka…

redis高級進階

1.redis主從復制 redis主從復制1 2.redis哨兵模式 嗶哩嗶哩視頻 redis哨兵模式1 redis哨兵模式2 redis哨兵模式3 3.redis分片集群 redis分片集群1 redis分片集群2 redis分片集群3

uniapp: 低功耗藍牙(BLE)的使用

在微信小程序中實現藍牙對接藍牙秤的重量功能&#xff0c;主要依賴微信小程序提供的低功耗藍牙&#xff08;BLE&#xff09;API。以下是一個清晰的步驟指南&#xff0c;幫助你完成從連接藍牙秤到獲取重量數據的開發流程。需要注意的是&#xff0c;具體實現可能因藍牙秤的協議和…

3D架構圖軟件 iCraft Editor 正式發布 @icraft/player-react 前端組件, 輕松嵌入3D架構圖到您的項目

安裝 pnpm install icraft/player-react --saveimport { ICraftPlayer } from "icraft/player-react";export default function MyScene() {return <ICraftPlayer srcyour-scene.iplayer />; }icraft/player-react 為開發者提供了一站式的3D數字孿生可視化解決…

云數據中心整體規劃方案PPT(113頁)

1. 引言 概述&#xff1a;云數據中心整體規劃方案旨在構建彈性、高效的云計算基礎設施&#xff0c;通過軟件定義數據中心&#xff08;SDDC&#xff09;實現資源虛擬化與管理自動化。 2. 技術趨勢與背景 技術革新&#xff1a;隨著云計算、虛擬化及自動化技術的發展&#xff0c…