歡迎回到 C++ (現代 C++)

自其建立以來,C++ 已成為世界上最常用的程式設計語言之一。 編寫完善的 C++ 程式不但執行快速,而且有效率。 該語言比其他語言更有彈性:它可以在抽象的最高層級運作,並在矽層級下運作。 C++ 提供高度優化的標準連結庫。 它可讓您存取低階硬體功能,以將速度最大化,並將記憶體需求降到最低。 C++ 幾乎可以建立任何類型的程式:遊戲、設備驅動器、HPC、雲端、桌面、內嵌和行動應用程式等等。 即使是其他程式設計語言的連結庫和編譯程式也會以 C++ 撰寫。

C++ 的其中一個原始需求是與 C 語言的回溯相容性。 因此,C++ 一律允許 C 樣式程式設計,具有原始指標、陣列、以 Null 終止的字元字串和其他功能。 它們可能會啟用絕佳的效能,但也可能會產生 Bug 和複雜性。 C++ 的演進強調的功能可大幅減少使用 C 樣式慣用語的需求。 當您需要 C 程式設計工具時,舊的 C 程式設計設施仍然存在。 不過,在新式 C++ 程式代碼中,您應該越來越少地需要這些程式代碼。 新式 C++ 程式代碼更簡單、更安全、更優雅,而且仍然盡可能快。

下列各節提供新式 C++ 主要功能的概觀。 除非另有說明,否則此處所列的功能可在 C++11 和更新版本中使用。 在 Microsoft C++ 編譯程式中,您可以設定 /std 編譯程式選項,以指定要用於項目的標準版本。

資源和智慧型指標

C 樣式程序設計中的其中一個主要 Bug 類別是 記憶體流失。 流失通常是因為呼叫使用 new所配置的記憶體失敗delete所造成。 新式 C++ 強調資源擷取的原則 是初始化 (RAII)。 這個想法很簡單。 資源(堆積記憶體、檔句柄、套接字等)應該 由 對象擁有 。 該物件會在其建構函式中建立或接收新配置的資源,並在解構函式中刪除它。 RAII 原則保證當擁有物件超出範圍時,所有資源都會正確傳回至操作系統。

為了支援輕鬆採用 RAII 原則,C++ 標準連結庫提供三種智慧型手機型指標類型: std::unique_ptrstd::shared_ptrstd::weak_ptr。 智慧型手機會處理其擁有之內存的配置和刪除。 下列範例顯示具有陣列成員的類別,該成員是在呼叫 make_unique()中的堆積上配置。 對和 delete 的呼叫new是由 unique_ptr 類別封裝。 widget當物件超出範圍時,將會叫用unique_ptr解構函式,並釋放配置給陣列的記憶體。

#include <memory>
class widget
{
private:
    std::unique_ptr<int[]> data;
public:
    widget(const int size) { data = std::make_unique<int[]>(size); }
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data gadget member
    // ...
    w.do_something();
    // ...
} // automatic destruction and deallocation for w and w.data

盡可能使用智慧型指標來管理堆積記憶體。 如果您必須明確使用 newdelete 運算符,請遵循RAII的原則。 如需詳細資訊,請參閱物件存留期和資源管理 (RAII)。

std::stringstd::string_view

C 樣式字串是 Bug 的另一個主要來源。 藉由使用 std::stringstd::wstring,您幾乎可以排除與 C 樣式字串相關聯的所有錯誤。 您也可以獲得成員函式的優點,以便搜尋、附加、前面加上等等。 兩者都針對速度進行高度優化。 將字串傳遞至只需要唯讀存取的函式時,在 C++17 中,您可以用於 std::string_view 更高的效能優勢。

std::vector 和其他標準連結庫容器

標準連結庫容器全都遵循 RAII 的原則。 它們為元素的安全周遊提供反覆運算器。 而且,它們已針對效能進行高度優化,並經過徹底測試,以取得正確性。 藉由使用這些容器,您可以消除自定義數據結構中可能引入的 Bug 或效率不足。 而不是原始數位,請在 C++ 中當做循序容器使用 vector

vector<string> apples;
apples.push_back("Granny Smith");

使用 map (not unordered_map) 作為預設的關聯容器。 針對變質和多重案例使用 setmultimapmultiset

map<string, string> apple_color;
// ...
apple_color["Granny Smith"] = "Green";

需要效能優化時,請考慮使用:

  • 未排序的關聯容器,例如 unordered_map。 這些項目的額外負荷和常數時間查閱較低,但較難正確且有效率地使用。
  • vector已排序 。 如需詳細資訊,請參閱演算法

請勿使用 C 樣式陣列。 對於需要直接存取資料的舊版 API,請改用存取子方法,例如 f(vec.data(), vec.size()); 。 如需容器的詳細資訊,請參閱 C++ 標準連結庫容器

標準連結庫演算法

在假設您需要為程式撰寫自定義演算法之前,請先檢閱 C++ 標準連結庫 演算法。 標準連結庫包含許多常見作業的不斷成長演算法,例如搜尋、排序、篩選和隨機化。 數學連結庫很廣泛。 在 C++17 和更新版本中,會提供許多演算法的平行版本。

以下是一些重要的範例:

  • for_each,預設周遊演算法(以及以範圍為基礎的 for 迴圈)。
  • transform,用於不就地修改容器元素
  • find_if,默認搜尋演算法。
  • sortlower_bound和其他預設排序和搜尋演算法。

若要撰寫比較子,請在您可以時使用 strict < 並使用具名 Lambda

auto comp = [](const widget& w1, const widget& w2)
     { return w1.weight() < w2.weight(); }

sort( v.begin(), v.end(), comp );

auto i = lower_bound( v.begin(), v.end(), widget{0}, comp );

auto 而不是明確的類型名稱

C++11 引進 auto 了 用於變數、函式和範本宣告的 關鍵詞。 auto 指示編譯程式推斷對象的類型,因此您不需要明確輸入它。 auto 當推斷型別是巢狀範本時,特別有用:

map<int,list<string>>::iterator i = m.begin(); // C-style
auto i = m.begin(); // modern C++

以範圍為基礎的 for 迴圈

數位和容器上的 C 樣式反覆專案容易編製索引錯誤,而且對類型也很乏味。 若要消除這些錯誤,並讓您的程式代碼更容易閱讀,請使用範圍型 for 迴圈搭配標準連結庫容器和原始數位。 如需詳細資訊,請參閱 以範圍為基礎的 for 語句

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> v {1,2,3};

    // C-style
    for(int i = 0; i < v.size(); ++i)
    {
        std::cout << v[i];
    }

    // Modern C++:
    for(auto& num : v)
    {
        std::cout << num;
    }
}

constexpr 表達式而非宏

C 和 C++ 中的宏是編譯前由預處理器處理的令牌。 宏令牌的每個實例都會在編譯檔案之前,以其定義的值或表達式取代。 宏通常用於 C 樣式程式設計,以定義編譯時間常數值。 不過,宏容易出錯且難以偵錯。 在新式 C++ 中,您應該偏好 constexpr 使用編譯時間常數的變數:

#define SIZE 10 // C-style
constexpr int size = 10; // modern C++

統一初始化

在新式 C++ 中,您可以使用任何類型的大括號初始化。 初始化陣列、向量或其他容器時,這種初始化形式特別方便。 在下列範例中, v2 會使用的三個 實例 S初始化。 v3 會使用三個 S 實例初始化,而 實例本身會使用大括號初始化。 編譯程式會根據 宣告的 v3型別來推斷每個專案的型別。

#include <vector>

struct S
{
    std::string name;
    float num;
    S(std::string s, float f) : name(s), num(f) {}
};

int main()
{
    // C-style initialization
    std::vector<S> v;
    S s1("Norah", 2.7);
    S s2("Frank", 3.5);
    S s3("Jeri", 85.9);

    v.push_back(s1);
    v.push_back(s2);
    v.push_back(s3);

    // Modern C++:
    std::vector<S> v2 {s1, s2, s3};

    // or...
    std::vector<S> v3{ {"Norah", 2.7}, {"Frank", 3.5}, {"Jeri", 85.9} };

}

如需詳細資訊,請參閱 大括弧初始化

移動語意

新式 C++ 提供 行動語意,讓您能夠消除不必要的記憶體複本。 在舊版的語言中,在某些情況下,無法避免複製。 移動作業會將資源的擁有權從某個物件傳輸到下一個物件,而不進行複本。 某些類別擁有資源,例如堆積記憶體、檔句柄等等。 當您實作資源擁有類別時,您可以為其定義 移動建構函式移動指派運算符 。 在不需要複本的情況下,編譯程式會在多載解析期間選擇這些特殊成員。 如果定義了移動建構函式,標準連結庫容器類型會叫用物件上的移動建構函式。 如需詳細資訊,請參閱移動建構函式和移動指派運算元 (C++)。

Lambda 運算式

在 C 樣式程式設計中,函式可以使用函式指標傳遞至另一個函式。 函式指標不方便維護和瞭解。 它們所參考的函式可能會在原始程式碼的其他地方定義,遠非叫用的點。 此外,它們不是類型安全。 新式 C++ 提供 函式物件、覆寫 運算子的 operator() 類別,讓它們能夠像函式一樣呼叫它們。 建立函式物件最方便的方式是使用內嵌 Lambda 表達式。 下列範例示範如何使用 Lambda 運算式傳遞函式物件,函 find_if 式會在向量中的每個元素上叫用該物件:

    std::vector<int> v {1,2,3,4,5};
    int x = 2;
    int y = 4;
    auto result = find_if(begin(v), end(v), [=](int i) { return i > x && i < y; });

Lambda 運算式 [=](int i) { return i > x && i < y; } 可以讀取為「接受類型 int 單一自變數的函式,並傳回布爾值,指出自變數是否大於 x 和小於 y。」請注意,變數 xy 周圍的內容都可以在 Lambda 中使用。 指定 [=] 這些變數會 依值擷 取;換句話說,Lambda 表達式有自己的這些值複本。

例外狀況

新式 C++ 強調例外狀況,而不是錯誤碼,這是報告和處理錯誤狀況的最佳方式。 如需詳細資訊,請參閱 例外狀況和錯誤處理的新式 C++ 最佳做法。

std::atomic

使用 C++ 標準連結庫 std::atomic 結構和相關類型進行線程間通訊機制。

std::variant (C++17)

聯集通常用於 C 樣式程式設計,藉由讓不同類型的成員佔用相同的記憶體位置來節省記憶體。 不過,聯集不是類型安全,而且容易程式設計錯誤。 C++17 將 std::variant 類別引入為比等位更強固且更安全的替代方案。 函 std::visit 式可用來以型別安全的方式存取類型的成員 variant

另請參閱

C++ 語言參考
Lambda 運算式
C++ 標準程式庫
Microsoft C/C++ 語言一致性