1. 程式人生 > >(一)預定義宏、__func__、_Pragma、變長參數宏定義以及__VA_ARGS__

(一)預定義宏、__func__、_Pragma、變長參數宏定義以及__VA_ARGS__

-s 只需要 需要 成對 位置 以及 fin 編譯 一次

作為第一篇,首先要說一下C++11與C99的兼容性。

C++11將 對以下這些C99特性的支持 都納入新標準中:

1) C99中的預定義宏

2) __func__預定義標識符

3) _Pragma操作符

4) 不定參數宏定義以及__VA_ARGS__

5) 寬窄字符串連接

這些特性並不像語法規則一樣常用,並且有的C++編譯器實現也都先於標準地將這些特性實現,因此可能大多數程序員沒有發現這些不兼容。但將這些C99的特性在C++11中標準化無疑可以更廣泛地保證兩者的兼容性。我們來分別看一下。

這次,我們只討論前四個,第五個後面會有具體討論。

一、預定義宏

下面這些是C++11中與C99兼容的宏,這些宏,我個人感覺並不常用,只是做一個記錄,下面那個部分可能會實用些。

宏名稱 功能描述
__STDC_HOSTED__ 如果編譯器的目標系統環境中包含完整的標準C庫,那麽這個宏就定義為1,否則宏的值為0
__STDC__ C編譯器通常用這個宏的值來表示編譯器的實現是否和C標準一致。C++11標準中這個宏是否定義以及定成什麽值由編譯器決定
__STDC_VERSION__  C編譯器通常用這個宏來表示所支持的C標準的版本,比如 1999mmL。C++11標準中這個宏是否定義以及定成什麽值將由編譯器來決定
__STDC_ISO_10646__ 這個宏通常定義為一個yyyymmL格式的整數常量,例如 199712L,用來表示C++編譯環境符合某個版本的ISO/IEC 10646標準

使用這些宏,我們可以查驗機器環境對C標準和C庫的支持狀況,在我的VS2015上沒有找到相關的宏定義,下面是本書作者的一些測試情況:

#include<iostream>
using namespace std;

int main()
{
    cout << "Standard Clib:" << __STDC_HOSTED__ << endl;   //Standard Clib:1
    cout << "Standard C:" << __STDC__ << endl;             //
Standard C:1 cout << "ISO/IEC " << __STDC_ISO_10646__ << endl; //ISO/IEC 200009 }

作者的試驗機上也沒有第三個宏定義(也符合標準規定,見上表),其余可以打印出一些常量。

預定義宏對於多目標平臺代碼的編寫通常具有重大意義。通過以上的宏,程序員通過使用#ifdef / #endif等預處理指令,就可使得平臺相關代碼只在適合於當前平臺的代碼上編譯,從而在同一套代碼中完成對多平臺的支持。從這個意義上講,平臺信息相關的宏越豐富,代碼的多平臺支持卻準確。

二、__func__ 預定義標識符

很多現實的編譯器都支持__func__預定義標識符功能

其功能:返回所在函數的名字。

詳見代碼:

#include<iostream>
using namespace std;

const char* hello() { return __func__; }
const char* world() { return __func__; }
int main()
{
    cout << hello() << , << world() << endl;
}

技術分享圖片

事實上,按照標準定義,編譯器會隱式地在函數的定義之後定義__func__標識符。

例如上述例子中的hello函數等同於如下代碼:

const char* hello()
{
    static const char* __func__ = "hello";      //當然,你測試的時候是存在__func__的,會報錯,你可以改成__func2__
    return __func__;
}

__func__預定義標識符對於輕量級的調試代碼具有十分重要的作用。

而在C++11中,標準甚至允許其使用在類或者結構體中。

請看如下代碼:

struct Test
{
    const char*name;
    Test():name(__func__){}
};

int main()
{
    Test ts;
    cout << ts.name << endl;
}

技術分享圖片

可以看到,在結構體的構造函數中,初始化成員列表使用__func__預定義標識符是可行的,其效果跟在函數中使用一樣。

上述代碼測試的是結構體,類也是一樣。

我們可以用類的數據成員和成員函數分別測試一下:

class Test
{
    int t;
    const char* m_s;
public:
    Test() :t(0),m_s(__func__) { }
    const char* get_s()const { return m_s; }
    void testfunc()const { cout << "成員函數測試結果為:" << __func__ << endl; }
};

int main()
{
    Test ts;
    cout << "數據成員 m_s 的值為:" << ts.get_s() << endl;
    ts.testfunc();
}

技術分享圖片

結果如我們預期的那樣。

不過將__func__標識符作為函數參數的默認值是不允許的,例如

void FuncFail(string func_name = __func__){};

由於在參數聲明的時候,__func__還未被定義,前面提到過__func__在函數中的隱式定義。

__func__ usually in function、struct or class body .

三、_Pragma 操作符

在C/C++標準中,#pragma 是一條預處理的指令(preprocessor directive)。簡單地說,#pragma 是用來向編譯器傳達語言標準以外的一些信息。

舉個簡單的例子,如果我們在代碼的頭文件中定義了一下語句:

#pragma  once

那麽該指令會指示編譯器(如果編譯器支持),該頭文件應該只被編譯一次。這與使用如下代碼來定義頭文件所達到的效果是一樣的。

#ifndef THIS_HEADER
#define THIS_HEADER
//一些頭文件的定義
#endif

在C++11中,標準定義了與預處理指令#pragma 功能相同的操作符_Pragma。 _Pragma操作符的格式如下所示:

_Pragma(字符串字面量)

其使用方法跟sizeof等操作符一樣,將字符串字面量作為參數寫在括號內即可。那麽要達到與上例#pragma類似的效果,則只需要如下代碼即可。

_Pragma("once");

由於_Pragma是一個操作符,因此,可以用在一些宏中,形成可以在宏中展開的效果 ,而#pragma則不行,所以,C++11的_Pragma具有更大的靈活性。

由於宏展開很少用到,所以,這裏不做測試。

四、變長參數的宏定義以及__VA_ARGS__

在 C99 標準,程序員可以使用變長參數的宏定義。變長參數的宏定義是指在宏定義中參數列表的最後一個參數為省略號,而預定義宏__VA_ARGS__則可以在宏定義的實現部分替換省略號所代表的字符串。比如:

#define PR(...)  printf(__VA_ARGS__)

就可以定義一個printf的別名PR。事實上,變長參數宏與printf是一對好搭檔。

簡單的一個代碼測試如下:

#include<iostream>
using namespace std;
#define PR(...) printf(__VA_ARGS__)
int main()
{
    PR("%s = %d\n", "真值",1);
}

技術分享圖片

下面的代碼是一個應用:

#include<iostream>
#define LOG(...){\
   fprintf(stderr,"%s:Line %d:\t",__FILE__,__LINE__);   fprintf(stderr,__VA_ARGS__);   fprintf(stderr,"\n");}

int main()
{
    int x = 3;
    LOG("x=%d", x);
}

技術分享圖片

其中__FILE__是一個文件所處的相對路徑,__LINE__是當前代碼所在的行數

__LINE__還可以規定行數,代碼如下所示:

 1 #include<iostream>
 2 #line 30                  //規定下一行的__LINE__值為30
 3 #define LOG(...){ 4    fprintf(stderr,"%s:Line %d:\t",__FILE__,__LINE__); 5    fprintf(stderr,__VA_ARGS__); 6    fprintf(stderr,"\n"); 7 }
 8 
 9 int main()
10 {
11     int x = 3;
12     LOG("x=%d", x);
13 }

技術分享圖片

從輸出結果可以看出來行數發生了變化。

定義LOG宏用於記錄代碼位置中的一些信息。

程序員可以根據stderr產生的日誌追溯到代碼中產生這些記錄的位置。引入這樣的特性,對於輕量級調試 ,簡單的錯誤輸出都是具有積極意義的。

我能給大家測試的都測試了,但願大家能夠學到些東西。

感謝您的閱讀,生活愉快~

(一)預定義宏、__func__、_Pragma、變長參數宏定義以及__VA_ARGS__