歡迎回到 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_ptr
、 std::shared_ptr
和 std::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
盡可能使用智慧型指標來管理堆積記憶體。 如果您必須明確使用 new
和 delete
運算符,請遵循RAII的原則。 如需詳細資訊,請參閱物件存留期和資源管理 (RAII)。
std::string
和 std::string_view
C 樣式字串是 Bug 的另一個主要來源。 藉由使用 std::string
和 std::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
) 作為預設的關聯容器。 針對變質和多重案例使用 set
、 multimap
和 multiset
。
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
,默認搜尋演算法。sort
、lower_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
。」請注意,變數 x
和 y
周圍的內容都可以在 Lambda 中使用。 指定 [=]
這些變數會 依值擷 取;換句話說,Lambda 表達式有自己的這些值複本。
例外狀況
新式 C++ 強調例外狀況,而不是錯誤碼,這是報告和處理錯誤狀況的最佳方式。 如需詳細資訊,請參閱 例外狀況和錯誤處理的新式 C++ 最佳做法。
std::atomic
使用 C++ 標準連結庫 std::atomic
結構和相關類型進行線程間通訊機制。
std::variant
(C++17)
聯集通常用於 C 樣式程式設計,藉由讓不同類型的成員佔用相同的記憶體位置來節省記憶體。 不過,聯集不是類型安全,而且容易程式設計錯誤。 C++17 將 std::variant
類別引入為比等位更強固且更安全的替代方案。 函 std::visit
式可用來以型別安全的方式存取類型的成員 variant
。
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應