日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

using(别名)和range based for

發布時間:2024/4/18 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 using(别名)和range based for 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

using(別名)替代typedef

關鍵字

using

語法

別名聲明是具有下列語法的聲明:

using?標識符?attr(可選)?=?類型標識?;?(1)

template?<?模板形參列表?>using?標識符?attr(可選)?=?類型標識?;(2)?

attr(C++11)?-???可選的任意數量屬性的序列

標識符?-???此聲明引入的名字,它成為一個類型名?(1)?或一個模板名?(2)

模板形參列表??-???模板形參列表,同模板聲明

類型標識????-???抽象聲明符或其他任何合法的?類型標識(可以引入新類型,如?類型標識?中所注明)。類型標識?不能直接或間接涉指?標識符。注意,標識符的聲明點處于跟在?類型標識?之后的分號處。

解釋

大家都知道,在 C++ 中可以通過 typedef 重定義一個類型:

typedef unsigned int uint_t;

被重定義的類型并不是一個新的類型,僅僅只是原有的類型取了一個新的名字。因此,下面這樣將不是合法的函數重載:

void func(unsigned int);void func(uint_t); // error: redefinition

使用 typedef 重定義類型是很方便的,但它也有一些限制,比如,無法重定義一個模板。

想象下面這個場景:

typedef?std::map<std::string,?int>?map_int_t;typedef std::map<std::string, std::string> map_str_t;

我們需要的其實是一個固定以 std::string 為 key 的 map,它可以映射到 int 或另一個 std::string。然而這個簡單的需求僅通過 typedef 卻很難辦到。

因此,在 C++98/03 中往往不得不這樣寫:

template <typename Val> struct str_map { typedef std::map<std::string, Val> type; }; // ... str_map<int>::type map1; // ...

一個雖然簡單但卻略顯煩瑣的 str_map 外敷類是必要的。這明顯讓我們在復用某些泛型代碼時非常難受。

現在,在 C++11 中終于出現了可以重定義一個模板的語法。請看下面的示例:

template <typename Val> using str_map_t = std::map<std::string, Val>; // ... str_map_t<int> map1;

這里使用新的 using 別名語法定義了 std::map 的模板別名 str_map_t。比起前面使用外敷模板加 typedef 構建的 str_map,它完全就像是一個新的 map 類模板,因此,簡潔了很多。

實際上,using 的別名語法覆蓋了 typedef 的全部功能。先來看看對普通類型的重定義示例,將這兩種語法對比一下:

// 重定義unsigned int typedef unsigned int uint_t; using uint_t = unsigned int; // 重定義std::map typedef std::map<std::string, int> map_int_t; using map_int_t = std::map<std::string, int>;

可以看到,在重定義普通類型上,兩種使用方法的效果是等價的,唯一不同的是定義語法。

typedef 的定義方法和變量的聲明類似:像聲明一個變量一樣,聲明一個重定義類型,之后在聲明之前加上 typedef 即可。這種寫法凸顯了 C/C++ 中的語法一致性,但有時卻會增加代碼的閱讀難度。比如重定義一個函數指針時:

typedef void (*func_t)(int, int);

與之相比,using 后面總是立即跟隨新標識符(Identifier),之后使用類似賦值的語法,把現有的類型(type-id)賦給新類型:

using func_t = void (*)(int, int);

從上面的對比中可以發現,C++11 的 using 別名語法比 typedef 更加清晰。因為 typedef 的別名語法本質上類似一種解方程的思路。而 using 語法通過賦值來定義別名,和我們平時的思考方式一致。

下面再通過一個對比示例,看看新的 using 語法是如何定義模板別名的。

/* C++98/03 */ template <typename T> struct func_t { typedef void (*type)(T, T); }; // 使用 func_t 模板 func_t<int>::type xx_1; /* C++11 */ template <typename T> using func_t = void (*)(T, T); // 使用 func_t 模板 func_t<int> xx_2;

從示例中可以看出,通過 using 定義模板別名的語法,只是在普通類型別名語法的基礎上增加 template 的參數列表。使用 using 可以輕松地創建一個新的模板別名,而不需要像 C++98/03 那樣使用煩瑣的外敷模板。

需要注意的是,using 語法和 typedef 一樣,并不會創造新的類型。也就是說,上面示例中 C++11 的 using 寫法只是 typedef 的等價物。雖然 using 重定義的 func_t 是一個模板,但 func_t<int> 定義的 xx_2 并不是一個由類模板實例化后的類,而是 void(*)(int, int) 的別名。

因此,下面這樣寫:

void foo(void (*func_call)(int, int));void foo(func_t<int> func_call); // error: redefinition

同樣是無法實現重載的,func_t<int> 只是 void(*)(int, int) 類型的等價物。

細心的讀者可以發現,using 重定義的 func_t 是一個模板,但它既不是類模板也不是函數模板(函數模板實例化后是一個函數),而是一種新的模板形式:模板別名(alias template)。

其實,通過 using 可以輕松定義任意類型的模板表達方式。比如下面這樣:

template <typename T>using type_t = T;// ...type_t<int> i;

type_t 實例化后的類型和它的模板參數類型等價。這里,type_t<int> 將等價于 int。

?

range based for

熟悉C#或者python的人都知道在C#和python中存在一種for的使用方法不需要明確給出容器的開始和結束條件,就可以遍歷整個容器。C++11吸取了他們的優點,引入了這種方法也就是基于范圍的For(Range-Based-For)。

值得一說的是,如果沒有range-based-for,我們還是可以遍歷容器的。它是對for的擴展。

比如C98時候我們可以這樣:

int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };for (int i = 0; i < 10; i++) cout << arr[i];

或者遍歷容器,這樣:

std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};for?(std::vector<int>::iterator?itr?=?vec.begin();?itr?!=?vec.end();?itr++) std::cout << *itr;

不過有了C++11, 我們就可以這樣:

std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};for?(auto?n?:vec) std::cout << n; int?arr[10]?=?{?1,?2,?3,?4,?5,?6,?7,?8,?9,?10?};for?(auto?n?:?arr)????std::cout?<<?n;

乍一看,是不是顯得代碼非常簡潔?確實,這可能也是為啥引入這個概念。不過我覺得range-based-for最主要的作用可能就是“一統江湖”。有了它,不管你是數組,map,vector還是其他容器, 都可以用它來遍歷。

下面看看它的語法以及解釋:

//語法屬性(可選) for ( 范圍聲明 : 范圍表達式 ) 循環語句//解釋{ auto && __range = 范圍表達式 ; for (auto __begin = 首表達式, __end = 尾表達式 ; __begin != __end; ++__begin) { 范圍聲明 = *__begin; 循環語句 }}

range-based-for看起來那么美好,其實在應用的時候還是要有一些需要注意的事情:

  • range-base-for 默認是只讀遍歷,也就是說下邊的例子

  • std::vector<int> vec {1,2,3,4,5,6,7,8,9,10}; cout << "修改前" << endl; for (auto n :vec) std::cout << n++; cout << endl; cout << "修改后" << endl; for (auto j : vec) std::cout << j; // 輸出:修改前12345678910修改后12345678910

    這也許并不是我們想要的,如果要修改就要將遍歷的變量聲明為引用型。

    也就是將代碼改成這樣:

    for (auto& n :vec) std::cout << n++;

    2. 在遍歷容器的時候,auto自動推導的類型是容器的value_type類型,而不是迭代器。

    之前提過range-based-for的實現方式,其中有范圍表達式是自動推導的

    auto && __range = 范圍表達式 ;

    舉個例子,如果你遍歷的容器是map,那么value_type是std::pair,也就是說val的類型是std::pair類型的,因此需要使用val.first,val.second來訪問數據。

    std::map<string, int> map = { { "a", 1 }, { "b", 2 }, { "c", 3 } }; for (auto &val : map) cout << val.first << "->" << val.second << endl;

    此外,使用基于范圍的for循環還要注意一些容器類本身的約束,比如set的容器內的元素本身有容器的特性就決定了其元素是只讀的,哪怕的使用了引用類型來遍歷set元素,也是不能修改器元素的,看下面例子:

    set<int> ss = { 1, 2, 3, 4, 5, 6 }; for (auto& n : ss) cout << n++ << endl;

    3. 不能迭代的時候修改容器, 下面的代碼運行時候可能崩潰。

    vector<int> vec = { 1, 2, 3, 4, 5, 6 }; int main(){ for (auto &n : vec) { cout << n << endl; vec.push_back(7); }}

    由于在遍歷容器的時候,在容器中插入一個元素導致迭代器失效了,因此,基于范圍的for循環和普通的for循環一樣,在遍歷的過程中如果修改容器,會造成迭代器失效。

    4. 循環陷阱

    說這個問題之前,還是要把它的實現再次粘貼過來(C++17以前):

    { auto && __range = 范圍表達式 ; for (auto __begin = 首表達式, __end = 尾表達式 ; __begin != __end; ++__begin) { 范圍聲明 = *__begin; 循環語句 }}

    舉個例子:

    #include <iostream>#include <string> using namespace std; struct MyClass{ string text = "MyClass"; string& getText() { return text; }}; int main(){ for (auto ch : MyClass().text) { cout << ch; } cout << endl;}

    輸出結果就是

    MyClass

    但是我們把代碼改成下邊的樣子, 結果什么都不會輸出,程序直接退出

    for (auto ch : MyClass().getText()) { cout << ch; }

    為什么會出現這樣的現象呢?答案就在于range-based-for實現的時候有這樣一句話:

    auto && __range = 范圍表達式 ;

    如果“范圍表達式”返回臨時量,則其生存期被延續到循環結尾,如綁定到轉發引用?__range?所示,要注意的是“范圍表達式”中任何臨時量生存期都不被延長。

    for (auto& x : foo().items()) { /* .. */ } // 若 foo() 返回右值則為未定義行為

    原始的例子中,range_expression是 "MyClass().text",MyClass()是臨時對象,同時 "MyClass()" 這個表達式是右值。所以,"MyClass().text" 這個表達式也是右值,"MyClass().text" 這個對象是臨時對象中的一部分。所以,在 "auto && __range = range_expression;" 這個語句中,auto會被推導為 "std::string"。初始化右值引用為臨時對象的一部分時,可以延長整個臨時對象的生存期,在引用被銷毀時臨時對象才會被銷毀。所以for循環可以正常執行。

    但是在修改過后,range_expression是 "MyClass().getText()"。同樣地,MyClass()是臨時對象,"MyClass()" 這個表達式是右值。但是 "getText()" 的返回類型為 "string&",所以,"MyClass().getText()" 這個表達式是左值。所以,在 "auto && __range = range_expression;" 這個語句中,auto會被推導為 "string &",語句等價于 "string & __range = range_expression;" 。雖然"MyClass().getText()" 這個對象是臨時對象中的一部分,但是在初始化非const的左值引用時,不會延長臨時對象的生存期,所以在這個初始化語句結束的同時MyClass()這個臨時對象就被銷毀了,__range成為了野引用,所以后面的循環語句可能會出現內存錯誤。

    要解決這個問題,可以利用C++20 “初始化語句”變通解決:

    for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK

    總結

    以上是生活随笔為你收集整理的using(别名)和range based for的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。