現代 C++泛指的是從 C++11 之后的 C++標準. 從 C++11 開始, C++標準委員會實行班車制, 沒三年發布一個新版本, 如果一個功能在新版本發布之前已經準備好, 則可以加入該版本中, 否則延后到下一個版本.
語言核心
自 C++11 開始, 語言語法層面加了許多語法糖, 還有增加了一些新語法.使得 C++語言更加簡潔, 更加易讀, 同時更不容易出錯.
auto
自動類型推導
auto
允許程序員在聲明變量的時候讓編譯器自動推導變量的類型, 而不需要顯式聲明變量的類型.
#include <string>int main() {auto x = 1;auto s = "Hello";auto str = std::string("World");// C++11 之前std::string::iterator it1 = str.begin();// C++11 之后auto it = str.begin();
}
在if/switch
中聲明變量
增加了語法糖, 允許在if/switch
中聲明一個變量, 這樣一方面簡化寫法, 另一方面限制了變量的可見范圍, 提高安全性.
#include <fcntl.h>
#include <unistd.h>#include <iostream>char getOption() { return 'y'; }
int main() {if (auto ret = open("output.txt", O_RDONLY); ret != -1) {std::cout << "open file success" << std::endl;close(ret);}switch (auto c = getOption(); c) {case 'y':[[fallthrough]];case 'Y':std::cout << "yes" << std::endl;break;default:break;}
}
Range based for loop
增加了語法糖, 可以簡化for
循環寫法, 并同時提升安全性.
#include <vector>
#include <print>int main() {// 遍歷 vectorstd::vector v{1, 2, 3};for (auto num : v) {std::println("{}", num);}// 遍歷數組int array[] = {1, 2, 3};for (auto num : array) {std::println("{}", num);}
}
結構化綁定
#include <map>
#include <print>
#include <string>int main() {std::map<std::string, int> map = {{"a", 1}, {"b", 2}};for (auto [key, value] : map) {std::println("{}:{}", key, value);}// bind to tupleauto tuple = std::make_tuple(1, 2, 3);auto [a, _, c] = tuple;std::println("a={}", a);
}
統一初始化
支持通過花括號{}
初始化任意類型的變量. 對泛型編程的場景幫助很大.
#include <string>
#include <vector>int main() {int a{0};std::string str{"Hello"};struct S {std::string name;float num;S(std::string s, float f) : name(s), num(f) {}};S s1{"Alan", 2.7};std::vector<S> v{s1, {"Bob", 85.9}};
}
進一步閱讀: C++ 類成員初始化發展歷程(從 C++11 到 C++20)
移動語義
移動語義是 C++11 引入的, 主要用來減少不必要的拷貝, 提升性能. 對于一些臨時變量, 或者說不會再使用的變量, 則可以采用移動語義來減少拷貝.
#include <string>
#include <vector>std::string GetStr() { return "Hello"; }
int main() {std::vector<std::string> vec;auto s = GetStr();vec.emplace_back(s); // copyvec.emplace_back(s + "World"); // movevec.emplace_back(GetStr()); // movevec.emplace_back(std::move(s)); // movereturn 0;
}
進一步閱讀: C++ 必知必會: 移動語義(Move Semantics)
lambdas 表達式
在編程中經常會用到一些臨時函數或者工具函數, 只在局部使用, 并且功能簡單. 以往的方式會造成聲明語使用位置相距比較遠, 影響可讀性. lambda 表達式可以允許我們定義一個臨時的函數, 并且可以省略函數名.
#include <algorithm>
#include <string>
#include <vector>int main() {struct Record {std::string s1;std::string s2;};std::vector<Record> v = {{"A", "1"}, {"B", "0"}, {"a", "2"}};std::sort(v.begin(), v.end(), [](const auto& p1, const auto& p2) {return p1.s1 < p2.s1 || (p1.s1 == p2.s1 && p1.s2 < p2.s2);});
}
進一步閱讀: C++ Lambda 表達式: 簡潔與高效的完美結合
constexpr
constexpr
用來聲明一個常量表達式, 可以在編譯期計算出結果, 并且可以避免運行時計算. 相比起宏定義, constexpr
更加類型安全, 因為本質上講define
是字符串替換, 沒有類型檢查.
#define SIZE 64
constexpr int size = 64;
進一步閱讀:
- C++ constexpr, consteval 和 constinit 簡要介紹
- C++ 中的 const 和 constexpr: 深入對比與最佳實踐
標準庫
多線程編程
- 線程(
std::thread/std::jthread
). C++11 引入了線程, 并在 C++20 加入了滿足 RAII 要求的jthread
. 詳情參考: C++20 std::jthread 完全指南 - 簡化多線程編程與線程管理 - 原子變量(
std::atomic
): 是一種特殊的變量類型, 它支持原子操作, 這些操作在多線程環境下是不可分割的, 也就是不會被其他線程的操作打斷. 這有助于避免多線程訪問共享資源時產生的數據競爭問題, 保證數據安全. 進一步閱讀: Modern C++ 中的 std::atomic 簡介 - 互斥鎖(
std::mutex
): 是一種同步機制, 它保證多個線程對共享資源的訪問是互斥的. 進一步閱讀: 現代 C++鎖介紹 - Latch 和 Barrier: 線程同步機制, 它們可以確保多個線程在某個事件發生之前不會繼續執行. 進一步閱讀: C++ Latch 和 Barrier: 新手指南
工具類
string_view
: 輕量級視圖, 它只包含對數據的引用, 不包含數據本身. 可以簡單理解為包含了字符串的指針和長度. 詳情參考: 深入理解 C++ std::string_view — 高效字符串操作的利器optional
: 顧名思義, 這個工具包含的數據可能為空, 通常用于表示一個可選的值. 進一步閱讀: 現代 C++ 必備知識: 解鎖 std::optional, std::variant 和 std::anyvariant
: 是對于傳統的union
的一種更安全的替代. 可以保存多個類型的值, 但是只能存儲其中一個.any
:any
相對于一個特定類型的值就好比void *
對于一種特定類型的指針.any
可以被賦值為任何類型, 但是只能讀取為原始類型.expected
: 對于返回值+錯誤提示的一種通用表示, 在成功的情況下返回一個可用的值, 否則返回對應的錯誤類型. 進一步閱讀: 解讀 C++23 std::expected 函數式寫法
總結
本文是對現代 C++新特性的一個簡單介紹, 只包含了筆者認為的常用特性, 對細節感興趣的朋友請查閱相關資料進一步閱讀. 這里也有我按版本整理的新特性介紹: 現代 C++, 有興趣的讀者可以進一步瀏覽.