這個列表收集了 C++ 語言的一些晦澀Obscure)特性,是我經年累月研究這門語言的各個方面收集起來的。C++非常龐大,我總是能學到一些新知識。即使你對C++已了如指掌,也希望你能從列表中學到一些東西。下面列舉的特性,根據晦澀程度由淺入深進行排序。
方括號的真正含義
用來訪問數組元素的ptr[3]其實只是*(ptr + 3)的縮寫,與用*(3 + ptr)是等價的,因此反過來與3[ptr]也是等價的,使用3[ptr]是完全有效的代碼
最煩人的解析
“most vexing parse”這個詞是由Scott Meyers提出來的,因為C++語法聲明的二義性會導致有悖常理的行為:
- // 這個解釋正確?
- // 1) 類型std::string的變量會通過std::string()實例化嗎?
- // 2) 一個函數聲明,返回一個std::string值並有一個函數指針參數,
- // 該函數也返回一個std::string但沒有參數?
- std::string foo(std::string());
- // 還是這個正確?
- // 1)類型int變量會通過int(x)實例化嗎?
- // 2)一個函數聲明,返回一個int值並有一個參數,
- // 該參數是一個名為x的int型變量嗎?
- int bar(int(x));
兩種情形下C++標准要求的是第二種解釋,即使第一種解釋看起來更直觀。程序員可以通過包圍括號中變量的初始值來消除歧義:
- //加括號消除歧義
- std::string foo((std::string()));
- int bar((int(x)));
第二種情形讓人產生二義性的原因是int y = 3;等價於int(y) = 3;
譯者注:這一點我覺得有點迷惑,下面是我在g++下的測試用例:
- #include <iostream>
- #include <string>
- using namespace std;
- int bar(int(x)); // 等價於int bar(int x)
- string foo(string()); // 等價於string foo(string (*)())
- string test() {
- return "test";
- }
- int main()
- {
- cout << bar(2) << endl; // 輸出2
- cout << foo(test); // 輸出test
- return 0;
- }
- int bar(int(x)) {
- return x;
- }
- string foo(string (*fun)()) {
- return (*fun)();
- }
能正確輸出,但如果按作者意思添加上括號後再編譯就會報一堆錯誤:“在此作用域尚未聲明”、“重定義”等,還不清楚作者的意圖。
替代運算標記符
標記符and, and_eq, bitand, bitor, compl, not, not_eq, or, or_eq, xor, xor_eq, <%, %>, <: 和 :>都可以用來代替我們常用的&&, &=, &, |, ~, !, !=, ||, |=, ^, ^=, {, }, [ 和 ]。在鍵盤上缺乏必要的符號時你可以使用這些運算標記符來代替。
重定義關鍵字
通過預處理器重定義關鍵字從技術上講會引起錯誤,但實際上是允許這樣做的。因此你可以使用類似#define true false 或 #define else來搞點惡作劇。但是,也有它合法有用的時候,例如,如果你正在使用一個很大的庫而且需要繞過C++訪問保護機制,除了給庫打補丁的方法外,你也可 以在包含該庫頭文件之前關閉訪問保護來解決,但要記得在包含庫頭文件之後一定要打開保護機制!
- #define class struct
- #define private public
- #define protected public
- #include "library.h"
- #undef class
- #undef private
- #undef protected
注意這種方式不是每一次都有效,跟你的編譯器有關。當實例變量沒有被訪問控制符修飾時,C++只需要將這些實例變量順序布局即可,所以編譯器可以對 訪問控制符組重新排序來自由更改內存布局。例如,允許編譯器移動所有的私有成員放到公有成員的後面。另一個潛在的問題是名稱重整name mangling),Microsoft的C++編譯器將訪問控制符合並到它們的name mangling表裡,因此改變訪問控制符意味著將破壞現有編譯代碼的兼容性。
譯者注:在C++中,Name Mangling 是為了支持重載而加入的一項技術。編譯器將目標源文件中的名字進行調整,這樣在目標文件符號表中和連接過程中使用的名字和編譯目標文件的源程序中的名字不一樣,從而實現重載。