【C++】宏定義

嚴格來說,這個題目起名為C++是不合適的,因為宏定義是C語言的遺留特性。CleanCode并不推薦C++中使用宏定義。我當時還在公司做過宏定義為什么應該被取代的報告。但是適當使用宏定義對代碼是有好處的。壞處也有一些。

無參宏定義

最常見的一種宏定義,如下:

#define NUM_1 1

需要注意的是,宏定義執行的是替換操作,在預處理階段就完成了。因此編譯期間或者運行期間的代碼是感知不到宏定義的存在的,這也是宏定義不被推薦的原因——出了事很難找到問題。關于C++程序生成的各個階段可以參考我的這篇文章:【C++】template方法undefined reference to(二):C++代碼的編譯過程

#include <iostream>
#define NUM_1 1using namespace std;int main()
{cout << NUM_1 << endl;
}

數字的類型是可以通過后綴指定的,比如1U代表unsigned int類型的1。

可以使用const代替常量宏:

const int NUM_1 = 1;

如果你的編譯器支持C++11標準,那應該使用constexpr,這樣const就只有“只讀”的含義了。

constexpr int NUM_1 = 1;

有參宏定義

C++語言允許宏帶有參數。在宏定義中的參數稱為形式參數,在宏調用中的參數稱為實際參數。對帶參數的宏,在調用中,不僅要宏展開,而且要用實參去代換形參。你可以理解為一種“函數”。有參宏定義也是宏的另一個大量使用的用途。

一個最簡單的使用三元表達式返回更大值的宏定義:

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

代碼中使用:

#include <iostream>
#define MAX(a, b) a > b? a: busing namespace std;const int NUM_1 = 1;int main()
{int a = MAX(1,2);cout << a << endl;
}

這種宏定義的優點在于不會受參數類型的影響,現代C++提倡使用模板方法代替之:

template <typename T>
T max(T &a, T *b)
{return a > b ? a : b;
}

調用的地方基本一致:

    int b = max(1, 2);cout << b << endl;

由于模板方法本質是將函數實現挪到了編譯期,對所有模板類型的調用生成對應的函數,所以,它和正常的函數調用沒有任何區別。可以直接寫在里面:

	cout << max(1, 2) << endl;

模板方法也存在很多問題,比如多文件編譯時存在聲明實現不可分離的問題
:【C++】template方法undefined reference to

宏定義的副作用

仍然以剛才的MAX宏為例
感謝知乎閃耀大叔提供的例子,原文鏈接從Linux內核中學習高級C語言宏技巧

為了方便理解,我把所有數字都改成二進制標識:

int main(void)
{int i = 0b1110;int j = 0b0011;printf ("i&0b101 = %d\n",i&0b101);printf ("j&0b101 = %d\n",j&0b101);printf("max=%d\n",MAX(i&0b101,j&0b101));return 0;
}

輸出結果為:
輸出
顯然不符合預期,問題在哪呢?因為>運算符優先級大于&,所以會先進行比較再進行按位與。

Linux 內核中的寫法其實是這樣的:

#define MAX(x, y) ({        \typeof(x) _max1 = (x);      \typeof(y) _max2 = (y);      \(void) (&_max1 == &_max2);    \_max1 > _max2 ? _max1 : _max2; })

具體可以看上面的文章,這里就不展開了。

實現語法糖

C++有一些約定俗成的寫法,其實可以用宏定義進行簡化。

比如可以將if-continue簡化為一個宏:

#define CONTINUE_IF(exp) \if (exp)             \continue

還有其他比如return相關的:

#define RETURN_IF_VOID(exp) \if (exp)                \return#define RETURN_IF(exp, result) \if (exp)                   \return result

代碼里就可以替換,這里僅給出一個例子:

int main()
{for (int i = 0; i < 10; i++){CONTINUE_IF(i % 2 == 0);printf("%d, ", i);}
}

另外一種情況,比如C++的多態的一個重要實現就是虛函數和繼承,我們可以簡化虛函數的寫法。定義如下的宏:

#define OVERRIDE(exp) virtual exp override

就可以簡化虛函數繼承的寫法。如下:

struct Student
{virtual void printName(){printf("Student Name\n");}
};struct PrimaryStudent : Student
{OVERRIDE(void printName()){printf("PrimaryStudent Name\n");}
};void printName(Student& student)
{student.printName();
}

override關鍵字只有在C++11以后才能生效,我們可以使用__GNUC__宏來判斷。 __GNUC__ 的值表示gcc的版本。需要針對gcc特定版本編寫代碼時,可以使用該宏進行條件編譯。C++11標準從GCC4.8.1版本完全支持,__GNUC____GNUC_MINOR____GNUC_PATCHLEVEL__分別代表gcc的主版本號,次版本號,修正版本號。我們可以寫出如下判斷:

#ifdef __GNUC__printf("__GNUC__ = %d\n", __GNUC__);
#endif
#ifdef __GNUC_MINOR__printf("__GNUC_MINOR__ = %d\n", __GNUC_MINOR__);
#endif
#ifdef __GNUC_PATCHLEVEL__printf("__GNUC_PATCHLEVEL__ = %d\n", __GNUC_PATCHLEVEL__);
#endif

輸出結果為:
gcc版本
和控制臺的輸出是一致的:

PS D:\Codes\CPP\VSCodeProjects\2024\June\CPPMacros> g++ --version
g++.exe (GCC) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

接下來我們寫一個條件判斷宏,僅在4.8.1版本后在虛函數后加上overrride標識。由于gcc版本宏判斷過于復雜,我們用一個宏先將版本號轉為整數:

#define GCC_VERSION (__GNUC__ * 10000 \+ __GNUC_MINOR__ * 100 \+ __GNUC_PATCHLEVEL__)

然后判斷版本號是否大于40801,否則不使用override關鍵字:

#if GCC_VERSION > 40801
#define OVERRIDE(exp) virtual exp override
#else
#define OVERRIDE(exp) virtual exp
#endif

遺憾的是我本地沒有多個編譯器,沒法判斷這個代碼是否成功了。

簡化代碼

我們有時會碰到一個類有多個類似的方法的情況,比如:

struct Student
{
public:int getAge();int getNumber();int getPoint();
};

此時可以使用宏來簡化這種寫法。__VA_ARGS__ 是一個可變參數的宏。將宏定義中參數列表的最后一個參數為省略號(也就是三個點)。這樣預定義宏__VA_ARGS__就可以被用在替換部分中,替換省略號所代表的字符串。##運算符可以用于函數宏的替換部分,這個運算符把兩個語言符號組合成單個語言符號。

可以寫出如下代碼:

#define GET(...) int get##__VA_ARGS__()
struct Student
{
public:GET(Age);int getNumber();int getPoint();
};

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

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

相關文章

makefile總結

1,Makefile規則介紹 一個簡單的 Makefile 描述規則組成: TARGET... : PREREQUISITES... COMMAND 注意: 每一個命令行必須以[Tab]字符開始, [Tab]字符告訴 make 此行是一個命令行。 make 按照命令完成相應的動作。這也是書寫 Makefile 中容易產生,而且比較隱蔽的錯…

油煙凈化器:餐飲業健康環保的守護者

我最近分析了餐飲市場的油煙凈化器等產品報告&#xff0c;解決了餐飲業廚房油膩的難題&#xff0c;更加方便了在餐飲業和商業場所有需求的小伙伴們。 在現代餐飲業&#xff0c;油煙凈化器已經成為不可或缺的重要設備。它不僅是保障餐飲環境清潔的利器&#xff0c;更是守護健康…

新聲創新20年:無線技術給助聽器插上“娛樂”的翅膀

聽力損失并非現代人的專利&#xff0c;古代人也會有聽力損失。助聽器距今發展已經有二百多年了&#xff0c;從當初單純的聲音放大器到如今的全數字時代助聽器&#xff0c;助聽器發生了翻天覆地的變化&#xff0c;現代助聽器除了助聽功能&#xff0c;還具有看電視&#xff0c;聽…

【LeetCode】368. 最大整除子集

雖然這題挺難寫的&#xff0c;但是仍然提醒了我&#xff1a;解題要注意方法。在明確分析當一條道路走不通的時候&#xff0c;就不要再猶豫了&#xff0c;就要果斷的換方法&#xff0c;嘗試用其它方法解決。否則一味的消耗時間&#xff0c;得不償失。換方法的前提是明確的分析&a…

C++ 和C#的差別

首先把眼睛瞪大&#xff0c;然后憋住一口氣&#xff0c;讀下去&#xff1a; 1、CPP 就是C plus plus的縮寫&#xff0c;中國大陸的程序員圈子中通常被讀做"C加加"&#xff0c;而西方的程序員通常讀做"C plus plus"&#xff0c;它是一種使用非常廣泛的計算…

Maya崩潰閃退常見原因及解決方案

Autodesk Maya 是一款功能強大的 3D 計算機圖形程序&#xff0c;被電影、游戲和建筑等各個領域的設計師廣泛使用。然而&#xff0c;Maya 就像任何其他軟件一樣可能會發生崩潰問題。在前文中&#xff0c;小編給大家介紹了3ds Max使用V-Ray渲染時的崩潰閃退解決方案&#xff1a; …

Neo4j 圖數據庫 高級操作

Neo4j 圖數據庫 高級操作 文章目錄 Neo4j 圖數據庫 高級操作1 批量添加節點、關系1.1 直接使用 UNWIND 批量創建關系1.2 使用 CSV 文件批量創建關系1.3 選擇方法 2 索引2.1 創建單一屬性索引2.2 創建組合屬性索引2.3 創建全文索引2.4 列出所有索引2.5 刪除索引2.6 注意事項 3 清…

后端之路第三站(Mybatis)——JDBC跟Mybatis、lombok

一、什么是JDBC JDBC就是sun公司研發的一套通過java來操控數據庫的工具&#xff0c;對應不同的數據庫系統有不同的JDBC&#xff0c;而他們統稱【驅動】&#xff0c;這就是上一篇我們提到創建Mybatis項目時要引入的依賴、以及連接數據庫四要素里的第一要素。 JDBC有自己一套原始…

SerialportToTCP② 全

效果補全&#xff08;代碼&#xff09;&#xff1a; namespace SerialportToTCP {public partial class Form1 : Form{IniHelper Ini;string[] botelvs new string[] { "1200", "4800", "9600", "13200" };public Form1(){Initializ…

Elasticsearch:Painless scripting 語言(一)

Painless 是一種高性能、安全的腳本語言&#xff0c;專為 Elasticsearch 設計。你可以使用 Painless 在 Elasticsearch 支持腳本的任何地方安全地編寫內聯和存儲腳本。 Painless 提供眾多功能&#xff0c;這些功能圍繞以下核心原則&#xff1a; 安全性&#xff1a;確保集群的…

安卓gdb 建立鏈接

adbshell gdbserver :1234 testdcam --sensor 0 --workmode 0 --args preview-size1024x600,picture-size640x480, --time 10 adb forwardtcp:1234 tcp:1234 //設置adb的轉發 ./prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-gdb out/tar…

近紅外光譜腦功能成像(fNIRS):1.光學原理、變量選取與預處理

一、朗伯-比爾定律與修正的朗伯-比爾定律 朗伯-比爾定律 是一個描述光通過溶液時被吸收的規律。想象你有一杯有色液體&#xff0c;比如一杯紅茶。當你用一束光照射這杯液體時&#xff0c;光的一部分會被液體吸收&#xff0c;導致透過液體的光變弱。朗伯-比爾定律告訴我們&#…

mmdetection3D指定版本安裝指南

1. 下載指定版本號 選擇指定版本號下載mmdetection3d的源碼&#xff0c;如這里選擇的是0.17.2版本 git clone https://github.com/open-mmlab/mmdetection3d.git -b v0.17.22. 安裝 cd mmdetection3d安裝依賴庫 pip install -r requirment.txt編譯安裝 pip install -v e .…

redis主從復制哨兵模式集群管理

主從復制&#xff1a; 主從復制是高可用Redis的基礎&#xff0c;哨兵和集群都是在主從復制基礎上實現高可用的。主從復制主要實現了數據的多機備份&#xff0c;以及對于讀操作的負載均衡和簡單的故障恢復。缺陷&#xff1a;故障恢復無法自動化&#xff1b;寫操作無法負載均衡&…

軟件測試與質量保證 | 云班課簡答題庫

目錄 第14章 質量相關簡答題 第15章 測試實際相關簡答題 第16章 測試基本相關簡答題 第14章 質量相關簡答題 1. 簡述基本的測量原則。 測量應該基于該應用領域正確的理論之上&#xff0c;并在測量的定義中確定測度的目標&#xff1b;每一個技術測量的定義應該具有一致性和客…

HbuilderX:安卓打包證書.keystore生成與使用

前置條件 已安裝jdk或配置好jre環境。 .keystore生成 打開cmd,切換到目標路徑,輸入以下命令, keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore 輸入密鑰庫口令(要記住), 然后輸入一系列信息, …

ui.perfetto.dev sql 查詢某個事件范圍內,某個事件的耗時并降序排列

ui.perfetto.dev sql 查詢某個事件范圍內,某個事件的耗時并降序排列 1.打開https://ui.perfetto.dev 導入Chrome Trace Json文件2.ParallelMLP.forward下的RowParallelLinear.forward3.點擊Query(SQL),在輸入框中輸入以下內容,按CtrlEnter,顯示查詢結果4.點擊Show timeline,點擊…

2024年07年01日 Redis數據類型以及使用場景

String Hash List Set Sorted Set String&#xff0c;用的最多&#xff0c;對象序列化成json然后存儲 1.對象緩存&#xff0c;單值緩存 2.分布式鎖 Hash&#xff0c;不怎么用到 1.可緩存經常需要修改值的對象&#xff0c;可單獨對對象某個屬性進行修改 HMSET user {userI…

Windows快速打開某個路徑下的PowerShell

按住Shift右鍵打開&#xff1a; 在桌面或者文件夾頁面中&#xff0c;按住右鍵&#xff0c;在彈出的右鍵菜單中選擇“在終端中打開”或“在此處打開Powershell窗口“&#xff0c;就可打開windows PowerShell界面&#xff0c;且路徑為桌面或打開的文件夾所在路徑。

淺談貝葉斯定理

引言 貝葉斯定理用于確定事件的條件概率。它以一位英國統計學家的名字命名&#xff0c;托馬斯貝葉斯他在1763年發現了這個公式。貝葉斯定理是數學中一個非常重要的定理&#xff0c;它為一種獨特的統計推斷方法奠定了基礎。貝氏推論它用于根據可能與事件相關的條件的先驗知識&a…