首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

C/C++ 巨集技巧:# 字串化運算子將符號轉為字串字面值,簡化日誌與除錯輸出

     編輯:VC++

核心概念:# 字串化運算子

在 C/C++ 前置處理器中,# 運算子 (Stringizing Operator) 可將巨集參數不經展開直接轉換為字串字面值,並自動加上雙引號與必要的逸出處理。


#define SymbolToStr(s) #s

// 展開範例
SymbolToStr(HelloWorld)   → "HelloWorld"
SymbolToStr(x + y)        → "x + y"
SymbolToStr("AlreadyStr") → "\"AlreadyStr\""

實戰應用 1:列舉值自動轉字串 (替代手寫 switch)


enum LogLevel { DEBUG, INFO, WARN, ERROR, FATAL };

#define LogLevelToStr(level) #level

void Log(LogLevel level, const char* msg)
{
    printf("[%s] %s\n", LogLevelToStr(level), msg);
    // 輸出: [DEBUG] 訊息內容
}

// 更進階:支援任意符號 (變數名、函數名、巨集名)
#define LOG_VAR(var) printf(#var " = %d\n", var)

int count = 42;
LOG_VAR(count); // 輸出: count = 42

實戰應用 2:除錯追蹤巨集 — 自動包含檔案、行號、函數名


#define DEBUG_TRACE(fmt, ...) \
    do { \
        fprintf(stderr, "[TRACE] %s:%d %s() " fmt "\n", \
                __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
    } while(0)

#define DEBUG_VAR(var) \
    do { \
        fprintf(stderr, "[VAR] %s:%d %s = %d\n", \
                __FILE__, __LINE__, #var, var); \
    } while(0)

// 使用範例
void ProcessData(int id, int retry)
{
    DEBUG_TRACE("開始處理 ID=%d", id);
    int timeout = 30;
    DEBUG_VAR(timeout);
    if (retry > 3) DEBUG_TRACE("重試次數過多: %d", retry);
}

// 輸出:
// [TRACE] process.c:15 ProcessData() 開始處理 ID=1001
// [VAR] process.c:17 timeout = 30
// [TRACE] process.c:19 ProcessData() 重試次數過多: 5

實戰應用 3:單元測試斷言巨集 — 失敗時顯示運算式原文


#define ASSERT_EXPR(expr) \
    do { \
        if (!(expr)) { \
            fprintf(stderr, "[ASSERT FAILED] %s:%d: %s\n", \
                    __FILE__, __LINE__, #expr); \
            abort(); \
        } \
    } while(0)

void TestStack()
{
    Stack s; s.Push(1); s.Push(2);
    ASSERT_EXPR(s.Size() == 2);       // 通過
    ASSERT_EXPR(s.Top() == 2);        // 通過
    s.Pop();
    ASSERT_EXPR(s.Size() == 1);       // 通過
    ASSERT_EXPR(s.Top() == 1);        // 通過
    // ASSERT_EXPR(s.Top() == 2);     // 失敗輸出: [ASSERT FAILED] test.c:45: s.Top() == 2
}

進階技巧:## 連接運算子 配合 # 實現動態符號生成


// 產生 Get/Set 函數名稱
#define DECLARE_PROPERTY(type, name) \
    type Get##name() const { return m_##name; } \
    void Set##name(type val) { m_##name = val; } \
    static const char* NameOf##name() { return #name; }

class Person
{
    DECLARE_PROPERTY(std::string, Name)
    DECLARE_PROPERTY(int, Age)
private:
    std::string m_Name;
    int m_Age;
};

// 展開後:
// std::string GetName() const { return m_Name; }
// void SetName(std::string val) { m_Name = val; }
// static const char* NameOfName() { return "Name"; }
// ... Age 同理

實戰應用 4:編譯期字串化檢查 — 確保字串與程式碼同步


// 狀態碼定義
#define STATUS_CODES \
    X(SUCCESS, 0) \
    X(INVALID_PARAM, -1) \
    X(OUT_OF_MEMORY, -2) \
    X(IO_ERROR, -3)

// 自動產生枚舉
#define X(name, val) name = val,
enum StatusCode { STATUS_CODES };
#undef X

// 自動產生字串化對照表
#define X(name, val) { name, #name },
const std::pair StatusCodeStr[] = { STATUS_CODES };
#undef X

// 查找函數
const char* StatusToStr(StatusCode code)
{
    for (auto& p : StatusCodeStr)
        if (p.first == code) return p.second;
    return "UNKNOWN";
}

// 使用
StatusToStr(SUCCESS);        // "SUCCESS"
StatusToStr(INVALID_PARAM);  // "INVALID_PARAM"

注意事項與限制

限制說明解決方案
不支援參數預先展開#define A 100; #A → "A" 而非 "100"雙層巨集: #define STR(s) #s; #define XSTR(s) STR(s) → XSTR(A) → "100"
字串字面值會被雙重引號包裹#"hello" → "\"hello\""預期行為,若需原始字串請勿對字面值使用 #
巨集參數空白字元保留# a + b → " a + b "前置處理器會壓縮前導/尾隨空白,但保留中間空白
無法處理多行參數參數跨行需用 \ 續行設計時避免跨行巨集參數

現代 C++ 替代方案 (C++20/23)


// C++20: source_location 捕獲檔案/行號/函數
#include 
#include 

void ModernLog(std::string_view msg, std::source_location loc = std::source_location::current())
{
    std::cerr << std::format("[{}:{} {}] {}\n", loc.file_name(), loc.line(), loc.function_name(), msg);
}

// C++23: std::to_underlying, std::format 增強
// 反射提案 (P2996) 未來可直接取得列舉名稱字串,無需巨集

結語

# 字串化運算子是 C/C++ 前置處理器的「瑞士刀」之一,配合 ## 連接運算子、__FILE__/__LINE__/__func__ 預定義巨集,能以極低成本構建強大的日誌、追蹤、斷言、序列化、程式碼生成基礎設施。雖然現代 C++ 提供 source_location、format、未來反射等型別安全替代方案,但在相容 C、嵌入式、跨平台、零依賴、編譯期計算等場景,巨集字串化技術仍具不可替代的實用價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved