variable templates
變量模板。這個特性允許模板被用于定義變量,就像之前模板可以用于定義函數或類型一樣。變量模板為模板編程帶來了新的靈活性,特別是在定義泛化的常量和元編程時非常有用。
變量模板的基本語法
變量模板的聲明遵循以下基本語法:
template<typename T>
constexpr T someValue = /* some value depending on T */;
這里,T
是一個類型參數,someValue
是根據T
變化的一個變量。
示例:定義一個泛化的π值
考慮一個簡單的例子,我們想要定義一個依據類型變化的π值,以便能夠獲取不同精度的π值(例如,float
,double
,long double
等)。
#include <iostream>template<typename T>
constexpr T pi = T(3.1415926535897932385);int main() {std::cout << "pi<float> = " << pi<float> << std::endl;std::cout << "pi<double> = " << pi<double> << std::endl;std::cout << "pi<long double> = " << pi<long double> << std::endl;return 0;
}
這個例子中,我們定義了一個變量模板pi
,其類型是模板參數T
。因此,我們可以請求pi
的不同類型版本,獲取相應精度的π值。
注意事項
- 類型完整性:變量模板在實例化時要求其類型參數必須是完整類型。
- 模板參數限制:和函數或類模板一樣,變量模板不能推導其模板參數。
generic lambdas
泛型Lambda表達式,這大大增強了Lambda的靈活性和通用性。泛型Lambda允許Lambda的參數類型自動推導,更重要的是,它允許Lambda使用模板參數。這意味著,你可以寫出更為通用的Lambda表達式,它們能夠處理多種不同類型的數據,而不需要為每種類型編寫特定的代碼。
泛型Lambda的基本用法
在C++11中,Lambda參數的類型必須是顯式聲明的。而在C++14中,你可以使用auto
關鍵字,讓編譯器自動推導參數的類型。這不僅僅是一個簡化的語法糖,它實際上隱式地將Lambda轉換成了一個模板。
例如,一個可以比較兩個值的Lambda在C++14中可以寫成:
auto compare = [](auto a, auto b) {return a < b;
};
這個Lambda可以用于比較任意兩個可以進行<
操作的類型的值。
實現原理
當編譯器遇到泛型Lambda的時候,它實際上為Lambda自動生成了一個模板類。每次調用泛型Lambda時,編譯器根據傳遞給Lambda的參數類型,實例化出一個特定的模板實例。這就是C++14泛型Lambda如何提供類型通用性的基本原理。
使用場景
泛型Lambda可以在很多場合下簡化代碼,特別是與STL算法一起使用時。例如,使用泛型Lambda在一個容器中查找一個元素:
std::vector<int> v = {1, 2, 3, 4, 5};
int to_find = 3;
auto found = std::find_if(v.begin(), v.end(), [to_find](auto elem) { return elem == to_find; });
這段代碼使用了泛型Lambda來定義查找條件,這使得相同的Lambda可以應用于不同類型的容器和元素。
注意事項
-
捕獲表達式的自動類型推導:泛型Lambda可以捕獲外部變量,但是這些捕獲的變量類型必須在Lambda表達式創建時就已經確定。這意味著不能捕獲泛型Lambda自身作為變量,因為在創建時Lambda的類型還未確定。
-
Lambda不能被重載或特化:與函數模板不同,Lambda表達式不能被重載或特化。這意味著同一個Lambda表達式必須能夠處理所有它可能被調用的的類型。
-
遞歸調用:泛型Lambda表達式不能直接遞歸調用自己,因為
auto
類型參數不允許在Lambda內部推導其自身類型
lambda init-capture
C++14引入了一個新的Lambda表達式特性:初始化捕獲(init-capture),也被稱為廣義Lambda捕獲或Lambda初始化。這項特性讓我們能夠在Lambda表達式的捕獲列表中,直接定義并初始化新的變量。這提供了更加靈活的變量捕獲方式,允許在Lambda表達式創建時構造新的對象或執行復雜的初始化。
基本語法
初始化捕獲的基本語法是在Lambda捕獲列表中使用[name = expression]
形式,其中name
是在Lambda體中使用的新變量的名稱,而expression
是用于初始化該變量的表達式。例如:
int x = 42;
auto lambda = [y = x + 1] { return y; };
std::cout << lambda(); // 輸出 43
在這個例子中,通過初始化捕獲創建了一個名為y
的新變量,并將其初始化為x + 1
的值。y
在Lambda內部可用,就像任何其他捕獲的變量一樣。
特點與用途
初始化捕獲的引入解決了C++11中存在的一些局限性:
-
移動語義支持:在C++11中,Lambda只能通過值或引用捕獲外部變量,不能方便地捕獲通過移動語義獲得的對象。C++14通過初始化捕獲支持移動語義,使得能夠移動而非復制對象到Lambda表達式內部。
auto p = std::make_unique<int>(42); auto lambda = [ptr = std::move(p)] { return *ptr; };
-
復雜的初始化:初始化捕獲允許在Lambda捕獲列表中執行復雜的表達式計算,無需在Lambda外部單獨聲明和初始化變量。
-
避免不必要的捕獲:有時我們希望Lambda表達式依賴于從外部變量派生的值,而不直接依賴這些外部變量本身。通過初始化捕獲,可以避免不必要地捕獲外部變量,減少資源捕獲的開銷。
注意事項
- 初始化捕獲只能創建拷貝或通過移動語義創建的變量。這意味著,你不能通過初始化捕獲來直接聲明一個引用類型的變量。例如,以下代碼是不合法的:
int x = 42;
auto lambda = [&y = x] { return y; }; // 錯誤:不能通過初始化捕獲來創建引用
relaxed restrictions on constexpr functions
在C++14中,對constexpr
函數的限制相較于C++11有了顯著放寬。constexpr
函數或變量表達的是它們可以在編譯時被求值,這對于提高程序的性能和安全性非常重要。在C++11中,標記為constexpr
的函數受到了很多限制,使得它們的用途相對有限。然而,C++14放寬了這些限制,使得constexpr
函數變得更加靈活和強大。
C++11中的constexpr
函數限制包括:
- 函數體內只能包含一個單一的返回語句。
- 不能有任何變量聲明。
- 不能有任何循環或分支語句。
C++14放寬的限制:
-
多條語句:C++14允許
constexpr
函數包含多條語句,使得你可以在constexpr
函數中執行更復雜的計算。 -
局部變量:允許在
constexpr
函數內部聲明和使用局部變量,只要這些變量的類型也都是字面類型(Literal Type)。 -
循環和分支:在C++14中,
constexpr
函數現在可以使用循環和條件分支,這大大增加了它們的靈活性和能力。 -
遞歸:由于允許使用多條語句和條件分支,
constexpr
函數現在也支持遞歸調用。
示例對比:
C++11中的constexpr
函數:
constexpr int factorial(int n) {return n <= 1 ? 1 : (n * factorial(n - 1));
}
C++14中的constexpr
函數:
constexpr int factorial(int n) {int result = 1;for(int i = 1; i <= n; ++i) {result *= i;}return result;
}
在C++14的例子中,我們利用了局部變量、循環,以及允許在constexpr
函數中包含多條語句的特性。
注意點:
- 即使C++14放寬了對
constexpr
函數的限制,但是為了在編譯時求值,函數的所有輸入和邏輯仍然需要在編譯時是已知的。 constexpr
函數也可以在運行時被調用,如果其參數在編譯時不是已知的。在這種情況下,它們就像普通函數那樣工作。
binary literals
二進制字面量,使得開發者可以直接在代碼中以二進制形式表示整數。這一特性對于底層編程、位操作或者對內存布局有特定要求的場景尤為有用。在C++14之前,表達二進制數據通常需要使用十六進制或八進制,盡管這些方法也相對直觀,但直接使用二進制字面量無疑能讓代碼的意圖更加明顯。
使用方法
二進制字面量通過在數字前加0b
或0B
前綴來表示。后續緊跟二進制數字(0或1)。
// C++14 二進制字面量示例
int a = 0b1010; // 等于十進制的10
int b = 0B0011; // 等于十進制的3
這種表示方法使得表達和理解涉及位操作的代碼變得更加直觀和簡潔。
注意事項
-
前綴規定:二進制字面量必須以
0b
或0B
前綴開始。沒有此前綴,數字會被解釋為十進制數。 -
類型默認為
int
:就像十進制和十六進制字面量一樣,未經明確指定的二進制字面量將被視為int
類型。如果字面量超出了int
類型的范圍,這可能導致未定義行為或編譯錯誤。
digit separators
數字分隔符,允許在數值字面量中使用單引號'
作為分隔符,以提高數值的可讀性。這對于編寫涉及大數值的代碼特別有用,使得這些大數值更容易閱讀和理解。
使用方法
數字分隔符可以在整數、浮點數以及二進制、八進制、十六進制數字字面量中使用。分隔符可以放置在數字之間的任何位置,但不能放置在數字的開頭或結尾。此外,分隔符不能連續使用。
以下是一些使用數字分隔符的示例:
long long earth_population = 7'800'000'000; // 世界人口約78億
unsigned long distance_to_moon = 384'400; // 月球距離地球大約384,400千米
double avogadro_number = 6.022'140'76e23; // 阿伏伽德羅常數
int binary_data = 0b1010'0110'1101'0011; // 二進制字面量
注意事項
-
位置限制:數字分隔符(單引號
'
)不能放置在數值字面量的開頭或結尾。此外,分隔符之間至少要有一位數字,不允許連續使用分隔符。 -
前綴后立即使用:對于二進制(
0b
或0B
)、八進制(0
)、十六進制(0x
或0X
)字面量,分隔符不能緊跟數值前綴后立即出現。比如,0x'FF
是不合法的,正確的寫法應該是0xFF
。 -
浮點數及科學記數法:在浮點數和使用科學記數法的字面量中,分隔符同樣不能用在小數點或指數標識符(
e
或E
)之前或之后立即出現。
return type deduction for functions
函數返回類型推導,允許編譯器從函數體中推導函數的返回類型,而不需要顯式地指定。這一特性大大提高了C++語言的靈活性和表達力,尤其是在編寫模板代碼和Lambda表達式時。
使用方法
要使用函數返回類型推導,可以在函數聲明中使用auto
作為返回類型。編譯器會根據函數體中return
語句的類型來自動推導整個函數的返回類型。
auto Add(int x, int y) {return x + y; // 返回類型根據返回值推導為int
}auto GetMessage() {return "Hello, World!"; // 返回類型根據返回值推導為const char*
}
-
如果函數包含多個
return
語句,那么所有return
語句的返回類型必須相同,或者必須有一個明確的、可以自動轉換的公共類型,否則會導致編譯錯誤。 -
函數不能僅由一個不返回任何值的
return
語句構成,這樣編譯器無法推導出返回類型。 -
在某些復雜的情況下,顯式指定返回類型可能更加清晰和直觀。
適用場景
函數返回類型推導特別適用于如下場景:
-
模板編程:在模板編程中,函數的返回類型可能依賴于模板參數,使用
auto
可以避免復雜的類型聲明。 -
Lambda表達式:Lambda表達式在C++11中引入時就支持返回類型推導,C++14將這一特性擴展到了所有函數。
-
減少冗余:在某些函數的返回類型非常明顯的情況下,使用返回類型推導可以減少代碼的冗余,并提高代碼的簡潔性和可讀性。
注意事項
-
一致的返回類型:如果函數包含多個
return
語句,所有return
語句必須具有相同的類型,或者它們的類型能夠隱式轉換為一個共同的類型。否則,編譯器將無法推導出一個確定的返回類型,導致編譯錯誤。 -
無返回值的情況:使用
auto
作為返回類型時,函數體不能僅包含不返回值的return
語句(例如,空的return;
)。這是因為編譯器需要通過return
語句的類型來推導函數的返回類型。 -
遞歸函數的限制:對于遞歸函數,編譯器可能無法在所有情況下準確推導出返回類型。
aggregate classes with default non-static member initializers
在C++14中,聚合類(Aggregate classes)的特性得到了增強,允許包含默認的非靜態成員初始化器(Default non-static member initializers)。這意味著聚合類除了原有的特點,比如沒有用戶定義的構造函數(User-defined constructors)、沒有私有或保護的非靜態數據成員、沒有基類和虛函數等,現在還可以為其非靜態成員指定默認初始值。
聚合類定義
在C++14之前,聚合類主要用于簡單地將數據組合在一起,沒有復雜的類初始化語義。具體來說,一個聚合類滿足以下條件:
- 沒有用戶定義的構造函數
- 沒有私有或保護的非靜態數據成員
- 沒有虛函數
- 沒有基類
C++14中的變化
C++14允許聚合類的非靜態數據成員擁有默認的初始化器。這增加了聚合類的靈活性和易用性,允許它們在定義時指定成員的初始狀態,而不需要用戶定義的構造函數。
struct Point {int x{0}; // 默認初始化為0int y{0};
};Point p; // p.x和p.y都默認初始化為0
Point q = {1, 2}; // 通過聚合初始化設置x為1,y為2
std::make_unique
內存管理工具——std::make_unique
。這個函數模板用于創建并返回一個指向動態分配對象的std::unique_ptr
,簡化了動態內存分配的過程并幫助避免內存泄漏。
使用方法
std::make_unique
函數模板的基本語法如下:
std::unique_ptr<T> make_unique<Args>(Args&&... args);
其中,T
是動態分配對象的類型,Args
是構造函數的參數列表。
示例:
auto ptr = std::make_unique<int>(42); // 創建一個指向int的std::unique_ptr并初始化為42struct Point {int x;int y;
};auto point = std::make_unique<Point>(Point{1, 2}); // 創建一個指向Point結構體的std::unique_ptr并初始化其成員
注意事項
-
只能創建std::unique_ptr:
std::make_unique
只能用于創建std::unique_ptr
,無法創建std::shared_ptr
。對于需要使用std::shared_ptr
的情況,應當使用std::make_shared
或手動分配。 -
無法自定義刪除器:與直接使用
std::unique_ptr
構造函數不同,std::make_unique
無法指定自定義的刪除器。如果需要自定義內存釋放方式,需要通過std::unique_ptr
的構造函數來實現。 -
僅適用于單個對象:
std::make_unique
僅用于分配單個對象的內存,并不能用于分配數組。分配數組需要使用std::make_unique
替代品std::make_unique_for_overwrite
或手動分配。 -
不支持初始化列表構造函數:
std::make_unique
不支持通過初始化列表構造對象,只能傳遞參數給對象的構造函數來創建對象。 -
不支持動態數組:使用
std::make_unique
創建的對象并不支持動態數組,因為std::unique_ptr
不支持動態數組的內存釋放。 -
類類型限制:要使用
std::make_unique
創建的對象類型,必須是完整的對象類型,不能是不完整類型或抽象類型。
std::shared_timed_mutex and std::shared_lock
在C++14標準中,引入了std::shared_timed_mutex
和std::shared_lock
,這兩個類分別是用于多線程讀寫操作的共享互斥鎖和共享鎖,提供了更靈活和高效的并發控制機制。
std::shared_timed_mutex
std::shared_timed_mutex
是一個支持讀寫操作的共享互斥鎖,允許多個線程同時讀取共享數據,但在寫入操作時會獨占資源。
特點:
-
共享讀取:多個線程可以同時獲取共享鎖(讀取鎖),允許并發讀取數據。
-
獨占寫入:在寫操作時,會獨占資源,防止并發寫入數據造成數據競爭。
-
超時支持:支持超時等待功能,可以在一定時間內獲取鎖或放棄操作。
使用std::shared_timed_mutex
可以實現多線程讀寫操作的并發控制。以下是std::shared_timed_mutex
的基本用法示例:
創建和初始化:
#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_timed_mutex mutex;int shared_data = 0;
讀取操作:
void read_data() {std::shared_lock<std::shared_timed_mutex> lock(mutex);std::cout << "Reading data: " << shared_data << std::endl;
}
寫入操作:
void write_data(int value) {std::unique_lock<std::shared_timed_mutex> lock(mutex);shared_data = value;std::cout << "Writing data: " << shared_data << std::endl;
}
主函數示例:
int main() {// 創建讀線程和寫線程std::thread reader1(read_data);std::thread reader2(read_data);std::thread writer(write_data, 42);// 等待線程執行完畢reader1.join();reader2.join();writer.join();return 0;
}
在上述示例中,通過std::shared_lock
和std::unique_lock
鎖定std::shared_timed_mutex
實現了讀和寫的并發控制。多個讀線程可以同時訪問共享數據shared_data
,而寫線程在進行寫操作時會獨占資源,防止讀寫競爭。
std::shared_lock
std::shared_lock
是用于管理共享互斥鎖的智能鎖類,它提供了對共享鎖的自動獲取和釋放,簡化了多線程編程中關于共享鎖的操作。
特點:
-
自動加鎖和解鎖:
std::shared_lock
在構造時自動獲取共享鎖,在析構時自動釋放共享鎖,保證鎖的正確使用。 -
共享鎖支持:
std::shared_lock
可以管理對std::shared_timed_mutex
的共享鎖,支持多線程并發讀取數據。 -
適用于多讀單寫場景:適用于多個線程同時讀取數據,但需要獨占寫入數據的場景,提高了讀操作的并發性能。
std::shared_lock
是C++14標準中用于管理共享互斥鎖的智能鎖類,它提供了對共享鎖的自動獲取和釋放,能夠簡化多線程編程中的共享數據訪問操作。以下是使用std::shared_lock
的基本示例:
示例代碼:
#include <iostream>
#include <thread>
#include <shared_mutex>std::shared_timed_mutex mutex;
int shared_data = 0;void read_data() {std::shared_lock<std::shared_timed_mutex> lock(mutex);std::cout << "Reading data: " << shared_data << std::endl;
}void write_data(int value) {std::unique_lock<std::shared_timed_mutex> lock(mutex);shared_data = value;std::cout << "Writing data: " << shared_data << std::endl;
}int main() {// 創建讀線程和寫線程std::thread reader1(read_data);std::thread reader2(read_data);std::thread writer(write_data, 42);// 等待線程執行完畢reader1.join();reader2.join();writer.join();return 0;
}
-
在主函數中,創建了兩個讀線程
reader1
和reader2
,以及一個寫線程writer
。 -
read_data()
函數中使用std::shared_lock
來鎖定std::shared_timed_mutex
,實現共享鎖的獲取,允許多個線程同時讀取共享數據shared_data
。 -
write_data(int value)
函數中使用std::unique_lock
來鎖定std::shared_timed_mutex
,實現獨占鎖的獲取,確保寫入操作時獨占資源,防止數據競爭。 -
主函數中啟動讀線程和寫線程,并通過
join()
等待線程執行完畢。 -
在讀取數據時,通過
std::shared_lock
獲取共享鎖來讀取共享數據,而在寫入數據時,通過std::unique_lock
獲取獨占鎖來寫入數據。
std::integer_sequence
std::integer_sequence
是C++14引入的一個模板類,用于表示編譯時整數序列。這個實用的模板類使得編譯時的整數操作(比如索引列表、解包元組等)變得簡單和類型安全。它常常與模板元編程和編譯時計算一起使用,為C++模板提供了強大的編譯時序列處理能力。
std::integer_sequence基本概念
std::integer_sequence<T, Ints...>
是一個編譯時整數序列的類型,其中T
是整數類型(如int
, size_t
等),而Ints...
則是一系列T
類型的編譯時常量。
用法示例
假如你想在編譯時生成一個整數序列(例如,0到N-1),可以使用std::make_integer_sequence
和std::integer_sequence
來實現這一點。這種能力非常方便當你需要基于索引進行模板展開或操作元組時。
示例代碼
下面是一個利用std::integer_sequence
和std::make_integer_sequence
展開函數參數并打印元組內容的示例:
#include <iostream>
#include <tuple>// 函數模板,用于打印元組中的每個元素
template<typename Tuple, std::size_t... I>
void printTupleImpl(const Tuple& t, std::index_sequence<I...>) {((std::cout << (I == 0 ? "" : ", ") << std::get<I>(t)), ...);
}// 打印元組的主函數模板
template<typename... Args>
void printTuple(const std::tuple<Args...>& t) {printTupleImpl(t, std::make_index_sequence<sizeof...(Args)>{});
}int main() {auto t = std::make_tuple(1, "two", 3.14); // 一個包含不同類型元素的元組std::cout << "Tuple contains: ";printTuple(t); // 打印元組內容std::cout << std::endl;return 0;
}
特性和用途
-
編譯時索引生成:利用
std::make_integer_sequence
或std::make_index_sequence
生成一個編譯時索引序列,以便于模板元編程和編譯時算法中進行迭代或索引操作。 -
元組訪問與展開:結合
std::get
和std::index_sequence
可以實現編譯時的元組訪問和元素展開,這對于泛型編程中的參數解包特別有用。 -
模板元編程:
std::integer_sequence
和相關工具使得在模板元編程中處理整數序列變得更加直觀和類型安全。
通過std::integer_sequence
,C++14進一步增強了模板編程的能力,為編譯時的序列操作提供了強大的工具。
std::exchange
std::exchange
是 C++14 標準庫中引入的一項非常實用的功能。它位于 <utility>
頭文件中。這個函數模板允許你將一個變量的值替換為另一個新值,同時返回變量原來的值。這種操作在很多情況下是非常便利的,尤其是在需要更新變量值的同時,又需要保留變量原來的值時。
std::exchange 的函數原型
template< class T, class U = T >
T exchange( T& obj, U&& new_value );
obj
: 是一個將要被新值替換的對象的引用。new_value
: 是將要被設置給obj
的新值。可以是右值引用(采用移動語義),這樣可以避免不必要的復制操作。- 返回值:函數返回
obj
的原始值。
使用示例
下面是一個使用 std::exchange
的簡單示例,展示了如何使用它交換變量的值:
#include <iostream>
#include <utility>
#include <vector>int main() {int old_value = 42;int new_value = 84;// 使用 std::exchange 更新 old_value,同時獲取其原始值int original_value = std::exchange(old_value, new_value);std::cout << "Original value: " << original_value << std::endl; // 輸出:42std::cout << "New value: " << old_value << std::endl; // 輸出:84// 也可以用于復雜類型,比如在替換 std::vector 內容時std::vector<int> vec = {1, 2, 3};std::vector<int> new_vec = {4, 5, 6};auto old_vec = std::exchange(vec, std::move(new_vec));// vec 現在 包含 new_vec 的內容,而 new_vec 是空的for (int v : vec) std::cout << v << " "; // 輸出:4 5 6std::cout << std::endl;return 0;
}
std::quoted
std::quoted
是 C++14 中引入的,位于 <iomanip>
頭文件中的一個實用功能。它提供了一種便捷的方法來對輸入和輸出流進行引用和解引用操作,這在處理包含空格或分隔符的字符串時格外有用。它確保了字符串的完整性,在讀取和寫入文件或處理用戶輸入時特別有用。這個功能的出現簡化了程序員在處理文本數據時需要編寫的引號管理與轉義字符的工具代碼。
std::quoted
的基本用法
std::quoted
的主要目的是確保字符串數據在經過輸入輸出操作時,能夠保持它們的字面值,并且正確處理其中的特殊字符。它會自動為字符串添加引號,并且將內部的特殊字符(如引號本身)進行轉義,確保字符串在存儲和傳輸時不會損失信息。
函數簽名
template< class CharT, class Traits, class Allocator >
std::basic_ostream<CharT, Traits>& operator<< (std::basic_ostream<CharT, Traits>& os, const std::basic_string<CharT, Traits, Allocator>& str );template< class CharT, class Traits, class Allocator >
std::basic_istream<CharT, Traits>& operator>> (std::basic_istream<CharT, Traits>& is, std::basic_string<CharT, Traits, Allocator>& str );
- 第一個函數簽名表示在輸出流中使用
std::quoted
來寫入字符串。 - 第二個函數簽名表示在輸入流中使用
std::quoted
來讀取字符串。
示例代碼
#include <iostream>
#include <sstream>
#include <iomanip> // 導入 std::quotedint main() {std::string input = R"(John Doe "Programmer")";std::stringstream sstream;// 寫入字符串時加上引號和必要的轉義sstream << std::quoted(input);std::cout << sstream.str() << std::endl; // 輸出:"John Doe \"Programmer\""// 從流中讀取字符串時解除引號和轉義std::string output;sstream >> std::quoted(output);std::cout << output << std::endl; // 輸出:John Doe "Programmer"return 0;
}
使用場景
- 當你需要向文件或其他輸出流寫入字符串,并想保證即使字符串中包含引號或特殊字符也能正確寫入和讀取時,使用
std::quoted
。 - 在處理來自用戶的輸入,特別是在需要保留輸入字符串中的空格和引號時。
通過使用 std::quoted
,你能夠更簡單地處理文本數據,無需擔心引號管理、轉義符等細節問題。這在讀寫配置文件、處理CSV格式數據、或進行網絡通信時特別有用,因為這些場合下經常會遇到需要精確管理字符串邊界和特殊字符的情況。
and many small improvements to existing library facilities, such as
C++14 不僅引入了一些全新的功能和改進,還對現有庫設施進行了許多小的改進。這些改進提高了標準庫的實用性和靈活性。以下是一些值得注意的改進:
1. 類型推導和 auto
改進
- 返回類型推導:C++14 改善了函數返回類型的推導能力,使得在編寫函數時,可以省略返回類型,并讓編譯器自動推導。
- 泛型 Lambda:C++14 允許在 lambda 表達式中使用
auto
關鍵字作為參數類型,從而使 Lambda 表達式具有泛型能力。
2. std::make_unique
C++14 在 <memory>
頭文件中引入了 std::make_unique
函數模板,作為創建 std::unique_ptr
實例的首選方式。這一改進補充了 C++11 中引入的 std::make_shared
,使得智能指針的創建更加完整。
3. 數字字面量改進
C++14 引入了對二進制字面量的支持,允許直接用二進制形式表示整數。此外,引入了整型字面量和浮點型字面量的單字符后綴,用于指定更具體的類型。
4. 容器和算法的改進
std::cbegin
和std::cend
:用于獲取容器的 const 迭代器,即使容器本身是非 const 的。- 容器訪問函數:如
std::at
函數,增加了對訪問數組元素的安全性。 - 算法改進:例如,
std::copy_n
,std::all_of
,std::any_of
,std::none_of
等標準算法被改進,使其使用起來更加方便。
5. 編譯時整數序列
通過 std::integer_sequence
和 std::make_integer_sequence
等,C++14 引入了編譯時整數序列的支持,這在模板元編程中非常有用。
6. 定制化點
C++14 引入了定制化點 (customization points),通過定義特殊的自定義命名空間,開發者可以更加靈活地控制 ADL (Argument-Dependent Lookup) 的行為。
7. 其他語言和庫改進
- 棄用屬性:引入了
[[deprecated]]
屬性,允許標記過時的代碼,提示使用者注意。 - 特化
std::get<>()
:對std::tuple
的std::get<>()
函數進行了特化,使得可以通過類型來獲取元素。 - 著色器語言靜態數據成員:允許著色器語言 (如 OpenGL Shading Language) 著色器和 C++ 代碼共享靜態數據成員。
這些是 C++14 引入的許多小改進中的一部分示例。這些改進的加入,使得 C++ 更加現代化,靈活性和實用性都得到了提升。開發者能夠借助這些新特性和改進,編寫出更簡潔、更高效、更易于維護的代碼。
two-range overloads for some algorithms
在 C++14 標準中,標準庫算法獲得了一項重要的增強:一些算法添加了所謂的“雙范圍重載”版本。這意味著,對于某些算法,你現在可以直接將兩個獨立的范圍當做參數傳入,而不是之前的單個范圍加上目標開始迭代器。這一改進簡化了算法的使用,并使得代碼更易于閱讀和理解。
雙范圍算法的使用場景
雙范圍重載在比較兩個容器、復制一個容器到另一個容器、以及在兩個容器之間進行移動、交換時特別有用。它們允許你直接指定源范圍和目標范圍,而不必指定起始和結束迭代器。
主要的雙范圍算法
以下是 C++14 中增加雙范圍重載的一些主要算法:
std::equal
: 用于比較兩個范圍內的元素是否全部相等。C++14 之前,你需要提供兩個范圍的起始迭代器和第一個范圍的結束迭代器。C++14 允許你直接傳入兩個范圍。std::mismatch
: 查找兩個范圍中第一對不匹配的元素。在 C++14 之前,使用此算法同樣需要指定兩個范圍的起始迭代器和第一個范圍的結束迭代器。C++14 使得直接傳入兩個范圍成為可能。std::copy
: C++14 為std::copy
添加了重載版本,允許兩個范圍作為參數,簡化了從一個容器向另一個容器復制元素的操作。
示例:使用 std::equal
的雙范圍版本
#include <algorithm>
#include <vector>
#include <iostream>int main() {std::vector<int> vec1 = {1, 2, 3, 4, 5};std::vector<int> vec2 = {1, 2, 3, 4, 5};// 使用 C++14 中的雙范圍重載bool isEqual = std::equal(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());// 使用 C++14 簡化的雙范圍調用形式bool isEqualSimplified = std::equal(vec1.begin(), vec1.end(), vec2.begin());std::cout << "The vectors are " << (isEqual ? "equal." : "not equal.") << std::endl;std::cout << "Simplified call: The vectors are " << (isEqualSimplified ? "equal." : "not equal.") << std::endl;return 0;
}
type alias versions of type traits
在 C++14 中,對類型特征(type traits)的改進之一是引入了類型別名版本,這些別名提供了更簡潔的語法來訪問類型特征的結果。類型特征是模板元編程中的一種工具,它允許在編譯時對類型進行查詢和操作。在 C++11 中,類型特征通常通過繼承自 std::integral_constant
的模板結構體實現,其結果可以通過::value
成員訪問。而 C++14 通過提供類型別名簡化了這個過程,使得獲取類型特征的結果更加直接。
類型別名類型特征的優點
相對于 C++11 風格的類型特征,C++14 引入的類型別名版本提供了以下優勢:
- 簡潔的語法:不需要通過
::value
或::type
來訪問結果,可以直接使用類型別名得到想要的結果。 - 提高代碼的可讀性:代碼更為簡潔,意圖更加明顯,提高了代碼的可讀性和易維護性。
示例
比較 C++11 和 C++14 在使用類型特征時的不同。
C++11 風格
#include <type_traits>int main() {// 使用C++11風格的類型判斷static_assert(std::is_integral<int>::value, "int is an integral type");// 獲取添加const修飾符后的類型typedef std::add_const<int>::type MyConstIntType;
}
C++14 風格
#include <type_traits>int main() {// 使用C++14風格的類型別名static_assert(std::is_integral_v<int>, "int is an integral type"); // 注意使用_v后綴// 使用類型別名獲取添加const修飾符后的類型using MyConstIntType = std::add_const_t<int>; // 注意使用_t后綴
}
主要類型別名特征
C++14 為許多常用的類型特征引入了類型別名,這里列舉一些例子:
- 類型屬性:例如
std::is_integral_v<T>
,直接獲得布爾值結果,而不是std::is_integral<T>::value
。 - 類型修改器:例如
std::add_const_t<T>
,得到修改后的類型,而不是std::add_const<T>::type
。 - 類型關系:例如
std::is_same_v<T, U>
,直接得到比較結果,而不是std::is_same<T, U>::value
。
user-defined literals for basic_string, duration and complex
C++14 中為 std::basic_string
、std::chrono::duration
和 std::complex
引入了用戶自定義字面量(User-Defined Literals, UDL),這是對 C++11 中用戶自定義字面量功能的擴展。用戶自定義字面量允許字面值直接定義自己的處理方式,使得代碼更加清晰、直觀。
std::basic_string
的用戶自定義字面量
在 C++14 中,通過在字符串字面值后添加 s
后綴,可以直接創建 std::string
實例。這一功能由頭文件 <string>
提供。
#include <iostream>
#include <string>using namespace std::string_literals;int main() {// 使用用戶自定義字面量創建 std::stringauto myString = "Hello, world!"s;std::cout << myString << std::endl;return 0;
}
這樣的用戶自定義字面量不僅適用于 std::string
,也適用于 std::wstring
、std::u16string
和 std::u32string
,分別通過在字面值后添加 ws
、s16
和 s32
后綴來創建。
std::chrono::duration
的用戶自定義字面量
C++14 對 std::chrono
庫進行了增強,引入了表示時間的用戶自定義字面量,使得表示特定時間間隔變得更加直觀。這些字面量定義在頭文件 <chrono>
中。
#include <iostream>
#include <chrono>using namespace std::chrono_literals;int main() {auto oneSecond = 1s; // std::chrono::seconds 類型auto halfAMinute = 30s; // 同樣是 std::chrono::seconds 類型auto oneDay = 24h; // std::chrono::hours 類型std::cout << "One day has " << oneDay.count() << " hours." << std::endl;return 0;
}
std::complex
的用戶自定義字面量
C++14 也為復數類型 std::complex
引入了用戶自定義字面量。通過在整數或浮點字面值后添加 i
、if
或 il
后綴,可以創建 std::complex
類型的實例,表示復數。這一功能由頭文件 <complex>
提供。
#include <iostream>
#include <complex>using namespace std::complex_literals;int main() {auto myComplex = 3.0 + 4i; // 創建 std::complex<double> 實例std::cout << "Real part: " << myComplex.real() << ", Imaginary part: " << myComplex.imag() << std::endl;return 0;
}
【大廠面經、學習筆記、實戰項目、大綱路線、講解視頻 領取文檔】C++校招實習、社招、面試題
https://docs.qq.com/doc/DR2N4d25LRG1leU9Q
C/C++Linux服務器開發/高級架構師學習資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)領取君羊739729163
合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!