算法工程师应该熟知的C++高级用法
1. lambda表達式(C11)
1.1 lambda表達式的組成
[=]/*1*/ ()/*2*/ mutable/*3*/ throw()/*4*/ -> int/*5*/ {}/*6*/- capture子句
- 參數列表(optional)
- 可變規范(optional)
- 異常定義(optional)
- 返回類型(optional)
- 函數體
1.2 即看即用
語法:
[capture](parameters)->return-type {body}[]叫做捕獲說明符
parameters參數列表
->return-type表示返回類型,如果沒有返回類型,則可以省略這部分。
我們可以這樣輸出"hello,world"
auto func = [] () { cout << "hello,world"; }; func(); // now call the function變量捕獲與lambda閉包實現
string name; cin >> name; [&](){cout << name;}();lambda函數能夠捕獲lambda函數外的具有自動存儲時期的變量。函數體與這些變量的集合合起來叫閉包。
[ ] 不截取任何變量
[&} 截取外部作用域中所有變量,并作為引用在函數體中使用
[=] 截取外部作用域中所有變量,并拷貝一份在函數體中使用
[=, &foo] 截取外部作用域中所有變量,并拷貝一份在函數體中使用,但是對foo變量使用引用
[bar] 截取bar變量并且拷貝一份在函數體重使用,同時不截取其他變量
[x, &y] x按值傳遞,y按引用傳遞
[this] 截取當前類中的this指針。如果已經使用了&或者=就默認添加此選項。
1.3 基本概念和用法
c++中,普通的函數是長這樣子的:
ret_value function_name(parameter) option { function_body; }比如:
int get_value(int a) const {return a++;}lambda表達式定義了一個匿名函數,并且可以捕獲所定義的匿名函數外的變量。它的語法形式是:
[ capture ] ( parameter ) option -> return_type { body; };其中:
- capture 捕獲列表
- parameter 參數列表
- option 函數選項
- return_type 函數返回值類型
- body 函數體
比如:
// defination auto lamb = [](int a) -> int { return a++; };// usage std::cout << lamb(1) << std::endl; // output: 2組成lambda的各部分并不是都必須存在的:
所以一個最簡單的lambda表達式可以是下面這樣,這段代碼是可以通過編譯的:
int main() {[]{}; // lambda expressionreturn 0; }捕獲列表
捕獲列表是lambda表達式和普通函數區別最大的地方。[]內就是lambda表達式的捕獲列表。一般來說,lambda表達式都是定義在其他函數內部,捕獲列表的作用,就是使lambda表達式內部能夠重用所有的外部變量。捕獲列表可以有如下的形式:
- [] 不捕獲任何變量
- [&] 以引用方式捕獲外部作用域的所有變量
- [=] 以賦值方式捕獲外部作用域的所有變量
- [=, &foo] 以賦值方式捕獲外部作用域所有變量,以引用方式捕獲foo變量
- [bar] 以賦值方式捕獲bar變量,不捕獲其它變量
- [this] 捕獲當前類的this指針,讓lambda表達式擁有和當前類成員同樣的訪問權限,可以修改類的成員變量,使用類的成員函數。如果已經使用了&或者=,就默認添加此選項。
捕獲列表示例:
#include <iostream>class TLambda { public:TLambda(): i_(0) { }int i_;void func(int x, int y) {int a;int b;// 無法訪問i_, 必須捕獲this,正確寫法見l4auto l1 = [] () {return i_;};// 以賦值方式捕獲所有外部變量,這里捕獲了this, x, yauto l2 = [=] () {return i_ + x + y;};// 以引用方式捕獲所有外部變量auto l3 = [&] () {return i_ + x + y;};auto l4 = [this] () {return i_;};// 以為沒有捕獲,所以無法訪問x, y, 正確寫法是l6auto l5 = [this] () {return i_ + x + y;};auto l6 = [this, x, y] () {return i_ + x + y;};auto l7 = [this] () {return ++i_;};// 錯誤,沒有捕獲a變量auto l8 = [] () {return a;};// a以賦值方式捕獲,b以引用方式捕auto l9 = [a, &b] () {return a + (++b);};// 捕獲所有外部變量,變量b以引用方式捕獲,其他變量以賦值方式捕獲auto l10 = [=, &b] () {return a + (++b);}} };int main() {TLambda a;a.func(3, 4);return 0; }引用和賦值,就相當于函數參數中的按值傳遞和引用傳遞。如果lambda捕獲的變量在外部作用域改變了,以賦值方式捕獲的變量則不會改變。按值捕獲的變量在lambda表達式內部也不能被修改,如果要修改,需使用引用捕獲,或者顯示的指定lambda表達式為 mutable。
// [=] int func(int a);// [&] int func(int& a);// ------------------------------------------int a = 0; auto f = [=] {return a;}; // 按值捕獲 a += 1; // a被修改 cout << f() << endl; // output: 0// ------------------------------------------int a = 0; auto f = [] {return a++;}; // Error auto f = [] () mutable {return a++;}; // OK1.4 lambda表達式的類型
上面一直使用auto關鍵字來自動推導lambda表達式的類型,那作為強類型語言的c++,這個lambda表達式到底是什么類型呢?lambda的表達式類型在c++11中被稱為『閉包類型(Closure Tyep)』,是一個特殊的、匿名的、非聯合(union)、非聚合(aggregate)的類類型。可以認為它是一個帶有operator()的類,即防函數(functor)。因此可以使用std::function 和std::bind來存儲和操作lambda表達式。
std::function<int (int)> f = [](int a) {return a;}; std::function<int (int)> f = std::bind([](int a){return a;}, std::placeholders::_1); std::cout << f(22) << std::endl;對于沒有捕獲任何變量的lambda,還可以轉換成一個普通的函數指針。
using func_t = int (*)(int); func_t f = [](int a){return a;}; std::cout << f(22) << std::endl;2. if的高級寫法
if (a!=b,b!=c,a!=c)C++的if語句使用逗號表達式,逗號表達式與加減乘除本質上是一樣的, 它的求值是從左向右依次對表達式求值,
整個表達式的結果取逗號表達式中最后一個表達的的結果, 如果非零, 就會使 if 成立!
上面相當于:
3. 左值與右值(C11)
本質上說,左值是可以被尋址的值,即左值一定對應內存中的一塊區域,而右值既可以是內存中的值,也可以是寄存器中的一個臨時變量。一個表達式被用作左值時,使用的是它的地址,而被用作右值時,使用的是它的內容。
在cpp11中,右值又可以進一步分為純右值(pure rvalue)和將亡值(expiring value);其中純右值指的是臨時變量或者不和變量關聯的字面量;其中臨時變量包括非引用類型的函數返回值,比如 int f()的返回值,和表達式的結果,比如(a+b)結果就是一個臨時變量;字面量比如10,“abc”這些。將亡值是cpp11新增的,適合右值引用相關的概念,包括返回右值引用T&&的函數的返回值,std::move的返回值。
如何準確的判斷一個表達式是不是左值呢?
- 如果這個表達式可以出現在等號的左邊,一定是左值;
- 如果可以對一個表達式使用&符號去地址,它一定是左值;
- 如果它“有名字”,則一定是左值;
形如 const T&的常量左值引用是個萬金油的引用類型,其可以引用非常量左值,常量左值和右值。而形如T&的非常量左值引用只能接受非常量左值對其進行初始化。
int &a = 2; # 非常量左值引用綁定到右值,編譯失敗 int b = 2; # 非常量左值變量 const int &c = b; # 常量左值引用綁定到非常量左值,編譯通過 const int d = 2; # 常量左值 const int &e = c; # 常量左值引用綁定到常量左值,編譯通過 const int &b =2; # 常量左值引用綁定到右值,編程通過右值引用通常不能綁定任何左值,如果要綁定左值,需要使用std::move()將左值轉換為右值;
int a; int&& r1 = a; //非法,a是左值; int&& r2 = std::move(a); //合法,通過std::move()將左值轉換為右值。移動語義是C++11新增的重要功能,其重點是對右值的操作。右值可以看作程序運行中的臨時結果,右值引用可以避免復制提高效率下表列出了在C++11中各種引用類型可以引用的值的類型。值得注意的是,只要能夠綁定右值的引用類型,都能夠延長右值的生命期。
4. 重載匹配
5. costexpr
5.1 costexpr變量
一般來說,在日益復雜的系統中確定變量的初始值到底是不是常量表達式并不是一件容易的事情。為了解決這個問題C++11允許將變量聲明為costexpr類型以便由編譯器驗證變量的值是否是一個常量表達式
變量聲明為constexpr類型,就意味著一方面變量本身是常量,也意味著它必須用常量表達式來初始化:
constexpr int mf = 20; constexpr int limit = mf + 1; constexpr float y{108};constexpr int i; // Error! Not initialized int j = 0;constexpr int k = j + 1; //Error! j not a constant expression如果初始值不是常量表達式,就會發生編譯錯誤
5.2 costexpr函數
除了能用常量表達式初始化constexpr變量聲明為,還可以使用consstexpr函數。它是指能用于常量表達式的函數,也就是它的計算結果可以在編譯時確定。。
例如下面的計算階乘的constexpr函數。
constexpr long long factorial(int n){return n <= 1? 1 : (n * factorial(n - 1));}constexpr long long f18 = factorial(20);可以用它來初始化constexp變量。所有計算都在編譯時完成,比較有趣的是像溢出這樣的錯誤也會在編譯期檢出
定義的方法就是在返回值類型前加constexpr關鍵字。但是為了保證計算結果可以在編譯期就確定,函數體中只能有一條語句,而且該語句必須是return語句,因此,下面:
constexpr int data(){const i = 1;return i; }無法通過編譯。不過一些不會產生實際代碼的語句在常量表達式函數的使用下,可以通過編譯,比如說static_assert,using、typedef之類的。因此,下面:
constexpr int f(int x){static_assert(0 == 0, "assert fail.");return x; }可以通過編譯。
- 在使用前必須已經有定義。
5.3 if constexpr
我們知道,constexpr將表達式或函數編譯為常量結果。一個很自然的想法是,如果我們把這一特性引入到條件判斷中去,讓代碼在編譯時就完成分支判斷,豈不是能讓程序效率更高。C++17支持if constexpr(表達式),允許在代碼中聲明常量表達式的判斷條件,如下:
#include <iostream>template<typename T> auto print_type_info(const T & t){if constexpr (std::is_integral<T>::value){return t + 1;}else{return t + 0.01;} }int main() {std::cout << print_type_info(5) << "\n";std::cout << print_type_info(5.5) << "\n"; }6. Cpp函數指針
在Cpp中,函數名不可以作為形參或者返回參數,但是函數指針可以
#include <iostream> #include<initializer_list>using namespace std;void show(string s, int i) {while (i--) cout << s << endl; }// 返回函數指針 // decltype返回的是函數類型,而函數只能返回函數指針 decltype(show) *getShow() {return show; }auto getShow2() -> void (*)(string, int) {return show; }// F是函數名 typedef decltype(show) F; // PF是函數指針 typedef decltype(show) *PF;using FF = decltype(show); using PFF = decltype(show) *;using FFF = void(string, int); using PFFF = void (*)(string, int);void recvFunc(void(*f)(string, int), string s, int i) {f(s, i); }void recvFunc2(PFF f, string s, int i) {f(s, i); }void recvFunc3(F *f, string s, int i) {f(s, i); }int main() {void (*f1)(string, int);void (*f2)(string, int);// 對于函數指針,show 和 &show是等價的f1 = show;f2 = &show;f1("Hello", 1);f2("World !", 2);F *f3 = show;PF f4 = show;f3("Hello", 1);f4("World !", 2);FF *f5 = show;PFF f6 = show;f5("Hello", 1);f6("World !", 2);recvFunc(show, "Do!", 3);recvFunc2(show, "HA!", 3);recvFunc3(show, "LA!", 3);FFF *f7 = show;PFFF f8 = show; }7. std::string_view(C17)
std::string_view是C++ 17標準中新加入的類,正如其名,它提供一個字符串的視圖,即可以通過這個類以各種方法“觀測”字符串,但不允許修改字符串。由于它只讀的特性,它并不真正持有這個字符串的拷貝,而是與相對應的字符串共享這一空間。即——構造時不發生字符串的復制。同時,你也可以自由的移動這個視圖,移動視圖并不會移動原定的字符串。
const char *cstr_pointer = "pointer"; char cstr_array[] = "array"; std::string stdstr = "std::string";std::string_viewsv1(cstr_pointer, 5), // 使用C風格字符串-指針構造,并指定大小為5sv2(cstr_array), // 使用C風格字符串-數組構造sv3("123456", 4), // 使用字符串字面量構造,并指定大小為4sv4(stdstr), // 使用std::string構造sv5("copy"sv); // 使用拷貝構造函數構造(sv是std::string_view字面量的后綴)std::cout<< sv1 << endl // point<< cstr_pointer << endl // pointer<< sv2 << endl // array<< sv3 << endl // 1234<< sv4 << endl // std::string<< sv5 << endl; // copy7.1 span(C20)
std::span是指向一組連續的對象的對象, 是一個視圖view, 不是一個擁有者owner
一組連續的對象可以是 C 數組, 帶著大小的指針, std::array, 或者std::string
#include <ranges>#include <vector>#include <iostream>#include <span>#include <format>?void printSpan(std::span<int> container){std::cout << std::format("container size: {} \n", container.size());for (auto ele : container){std::cout << ele << " ";}std::cout << std::endl;std::cout << std::endl;}??int main(){std::vector v1{1, 2, 3, 4, 5, 8};std::vector v2{9, 2, 4, 2, 6, 78};?std::span<int> dynamicSpan(v1);std::span<int, 6> staticSpan(v2);?printSpan(dynamicSpan);printSpan(staticSpan);}8. stack、queue和priority_queue
8.1 stack(棧)
首先,你得寫個頭文件:
#include <stack>那么如何定義一個棧呢?
stack <類型> 變量名接下來是一些關于棧的基本操作~
stack <int> s;(以這個為例子)1.把元素a加入入棧:s.push(a);
2.刪除棧頂的元素:s.pop();
3.返回棧頂的元素:s.top();
4.判斷棧是否為空:s.empty();(為空返回TRUE)
5.返回棧中元素個數:s.size();
6.把一個棧清空:(很抱歉沒有這個函數,你得寫這些:)
…詳情請參照古月居
總結
以上是生活随笔為你收集整理的算法工程师应该熟知的C++高级用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 世纪互联携手微软公有云Azure是我国企
- 下一篇: 【运营】Google search co