STL之序列式容器(二)、array容器

一、array的用法及初始化

array<T,N> 模板定義了一種相當於標準數組的容器類型。它是一個有 N 個 T 類型元素的固定序列。除了需要指定元素的類型和個數之外,它和常規數組沒有太大的差別。顯然,不能增加或刪除元素。
模板實例的元素被內部存儲在標準數組中。和標準數組相比,array 容器的額外開銷很小,但提供了兩個優點:

  • 如果使用 at(),當用一個非法的索引訪問數組元素時,能夠被檢測到,因爲容器知道它有多少個元素,
  • 這也就意味着數組容器可以作爲參數傳給函數,而不再需要單獨去指定數組元素的個數。

使用 array 容器類型時,需要在源文件中包含頭文件 array。數組容器非常好用,這裏有一個示例,展示瞭如何創建具有 100 個 double 型元素的 array<>:

std::array<double,100> data;

如果定義了一個 array 容器,卻沒有爲元素指定初始值,那麼元素就不會被初始化;但是可以如下所示,將它們初始化爲 0 或者和默認元素類型等效的值:

std::array<double, 100> data {};

使用該語句初始化後,容器中所有的元素都會變爲 0.0。形參 N 必須是一個常量表達式(constant expression)並且容器中元素的個數不能變。當然,當創建 array 容器的實例時,要像創建常規數組那樣,對元素進行初始化:

std::array<double, 10> values {0.5, 1.0, 1.5, 2.0};

初始化器列表中的 4 個值用於初始化前 4 個元素,其餘的元素都將爲 0。圖 1 說明了這一點。
圖一
通過調用數組對象的成員函數 fill(),可以將所有元素設成給定值。例如:

values.fill(3.1415926);

fill() 函數將所有元素都設爲傳入的實參值。

二、array獲取(訪問)元素

可以通過在方括號中使用索引表達式汸問和使用數組容器的元素,這和標準數組的訪問方式相同,例如:

values[4] = values[3] + 2.O*values[1];

第 5 個元素的值被賦值爲右邊表達式的值。像這樣使用索引時,因爲沒有做任何邊界檢査,所以,如果使用越界的索引值去訪問或存儲元素,就不會被檢測到。爲了能夠檢查越界索引值,應該使用成員函數 at():

values.at (4) = values.at(3) + 2.O*values.at(1);

這和前一條語句的功能相同,除了當傳給 at() 的索引是一個越界值時,這時會拋出 std::out_of_rang 異常。應該總是使用 at(),除非確定索引沒有越界。這也產生了一個疑問,爲什麼 operator 的實現沒有進行邊界檢查?答案是因爲性能。如果每次訪問元素,都去檢查索引值,無疑會產生很多開銷。當不存在越界訪問的可能時,就能避免這種開銷。
數組對象的 size() 函數能夠返回 size_t 類型的元素個數值,所以能夠像下面這樣去計算數組所有元素的和:

double total {};
for(size_t i {} ; i < values.size() ; ++i)
{
    total += values[i];
}

size() 函數的存在,爲數組容器提供了標準數組所沒有的優勢,數組元素能夠知道它包含多少元素。接受數組容器作爲參數的函數,只需要通過調用容器的成員函數 size(),就能得到元素的個數。不需要去調用 size() 函數來判斷一個數組容器是否爲空。如果容器中沒有元素的話,成員函數 empty() 會返回 true:

if(values.empty())
    std::cout << "The container has no elements.\n";
else
    std::cout << "The container has "<< values.size()<<"elements.\n";

然而,我們很難想象數組容器沒有元素的情形,因爲當生成一個數組容器時,它的元素個數就固定了,而且無法改變。生成空數組容器的唯一方法是,將模板的第二個參數指定爲 0,這種情況基本不可能發生。然而,對於其他元素可變或者元素可刪除的容器來說,它們使用 empty() 時的機制是一樣的,因此爲它們提供了一個一致性的操作。
對於任何可以使用迭代器的容器,都可以使用基於範圍的循環,因此能夠更加簡便地計算容器中所有元素的和:

double total {};
for(auto&& value : values)
    total += value;

當然,通過把容器作爲參數傳給函數,也能達到同樣的效果。數組容器的成員函數 front() 和 back() 分別返回第一個元素和最後一個元素的引用。成員函數 data() 同樣能夠返回 &from(),它是容器底層用來存儲元素的標準數組的地址,一般不會用到。
模板函數 get<n>() 是一個輔助函數,它能夠獲取到容器的第 n 個元素。模板參數的實參必須是一個在編譯時可以確定的常量表達式,所以它不能是一個循環變量。它只能訪問模板參數指定的元素,編譯時會對它進行檢查。get<n>() 模板提供了一種不需要在運行時檢查,但能用安全的索引值訪問元素的方式。下面展示如何使用它:

std::array<std::string, 5> words {"one","two","three”,"four","five" };
std::cout << std::get<3>(words) << std::endl; // Output words[3]
std::cout << std::get<6>(words) << std::endl; // Compiler error message!

下面是一個示例,展示了關於數組容器你到目前爲止所學到的知識:

/**示例 1**/
#include <iostream> // For standard streams
#include <iomanip>  // For stream manipulators
#include <array>    // For array<T,N>
int main()
{
    const unsigned int min_wt {100U};
    const unsigned int max_wt {250U};
    const unsigned int wt_step {10U};
    const size_t wt_count {1 + (max_wt - min_wt) / wt_step};
    const unsigned int min_ht {48U};
    const unsigned int max_ht {84U};
    const unsigned int ht_step {2U};
    const size_t ht_count { 1 + (max_ht - min_ht) / ht_step };
    const double lbs_per_kg {2.20462};
    const double ins_per_m {39.3701};
    std::array<unsigned int, wt_count> weight_lbs;
    std::array<unsigned int, ht_count> height_ins;
    // Create weights from lOOlbs in steps of lOlbs
    for (size_t i{}, w{min_wt} ; i < wt_count ; w += wt_step, ++i)
    {
        weight_lbs.at(i) = w;
    }
    //Create heights from 48 inches in steps of 2 inches
    unsigned int h{min_ht};
    for(auto& height : height_ins)
    {
        height = h;
        h += ht_step;
    }
    //Output table headings
    std::cout << std:: setw (7) <<" |";
    for (const auto& w : weight_lbs)
        std::cout << std:: setw (5) << w<<"11";
    std::cout << std::endl;
    // Output line below headings
    for (size_t i{1} ; i < wt_count ; ++i)
        std::cout<<"---------";
    std::cout << std::endl;
    double bmi {};
    unsigned int feet {};
    unsigned int inches {};
    const unsigned int inches_per_foot {12U};
    for (const auto& h : height_ins)
    {
        feet = h / inches_per_foot;
        inches = h % inches_per_foot;
        std::cout << std::setw (2) <<feet <<"'"<<std::setw (2) << inches <<"\""<<"|";
        std::cout << std::fixed <<std::setprecision(1);
        for (const auto& w : weight_lbs)
        {
            bmi = h / ins_per_m;
            bmi = (w / lbs_per_kg) / (bmi*bmi);
            std::cout << std:: setw (2) <<""<<bmi <<" |";
        }
        std::cout << std::endl;
    }
    for (size_t i {1} ; i < wt_count ; ++i)
        std::cout << "---------";
    std::cout << "\nBMI from 18.5 to 24.9 is normal" << std::endl;
}

本節中,不再展示這個示例的輸出結果,因爲可能會佔據很多篇幅。這裏有兩套參數,每套定義了 4 個有關身高、體重範圍的常量,它們被包含在 BMI 表中。因爲身高、體重都是整數、非負數,所以存放在數組容器中的身高和體重都是 unsigned int 類型的元素。
在循環中,可以用適當的值初始化容器。第一個循環展示了 at() 函數的使用,這裏也可以放心地使用 weight_lbs[i]。接下來的兩個循環分別輸出了表的列頭,以及一條用來分隔表頭和表的橫線。數據表是以循環嵌套的方式生成的。外循環遍歷身高並輸出英尺和英寸的最左一列的身高。內循環遍歷體重,輸出當前身高每行的 BMI 值。

三、array迭代器及用法

數組模板定義了成員函數 begin() 和 end(),分別返回指向第一個元素和最後一個元素的下一個位置的隨機訪問迭代器。如前面章節所述,隨機訪問迭代器具有最多的功能,能使用它進行全部的操作。可以在循環中顯式地使用迭代器來設置 height_ins 容器的值:

unsigned int h {min_ht};
auto first = height_ins.begin();
auto last = height_ins.end () ;
while (first != last)
{
    *first++=h;
    h += ht_step;
}

迭代器對象是由 array 對象的成員函數 begin() 和 end() 返回的。使用 auto 時不需要擔心迭代的實際類型,除非你要自己考慮,在本例中它們是 std::array<unsigned int,19>::iterator 類型,這意味着 iterator 類型被定義在 array<T,N> 類型中。可以看出,可以像使用普通指針那樣上使用迭代器對象。在保存了元素值後,使用後綴 ++ 運算符對 first 進行自增。當 first 等於 end 時,所有的元素都被設完值,循環結束。
如前面章節所述,最好用全局的 begin() 和 end() 函數來從容器中獲取迭代器,因爲它們是通用的,first 和 last 可以像下面這樣定義:

first = std::begin(height_ins);
auto last = std::end (height_ins);

記住,當迭代器指向容器中的一個特定元素時,它們沒有保留任何關於容器本身的信息,所以我們無法從迭代器中判斷,它是指向 array 容器還是指向 vector 容器。容器中的一段元素可以由迭代器指定,這讓我們有了對它們使用算法的可能。
定義在 algorithm 頭文件中的 generate() 函數模板,提供了用函數對象計算值、初始化一切元素的可能。我們可以像這樣重寫之前用來初始化 height_ins 容器的代碼段:

unsigned int height {};
std::generate(std::begin(height_ins), std::end(height_ins),[height, &min_ht, &ht_step]()mutable
{ return height += height == 0 ? min_ht : ht_step; });

爲容器元素設置值的語句現在減少到兩條,也不再需要顯式的循環語句。第一條語句定義了一個變量用來保存元素的初始值。genemte() 的前兩個參數分別是開始迭代器和結束迭代器,用來指定需要設置值的元素的範圍。第三個參數是一個 lambda 表達式。lambda 表達式以引用的方式捕獲 min_hi、ht_step。mutable 關鍵使 lambda 表達式能夠更新 height 局部副本的值,它是以值引用的方式捕獲的。
在 return 語句中,lambda 第一次執行後,height 的局部副本的值被設爲 min_ht。然後,隨着 lambda 的每次調用,height 都會增加 ht_step。 在 lambda 表達式中,以值引用的方式捕獲的變量局部副本的值會被一直保存,這一機制正好滿足了我們的要求。
假定要用連續的遞增值初始化一個數組容器,這裏有一個函數模板 iota() 可以做到,它定義在頭文件 numeric 中。這裏有一個它的用法示例:

std::array<double, 10> values;
std::iota(std::begin(values), std::end(values),10.0);
    elements to 10.0 to 19.0

前兩個參數是迭代器,用來定義需要設置值的元素的範圍。第三個參數是第一個元素要設置的值,通過遞增運算生成了隨後每一個元素的值。iota()函數的使用並不僅限於數值。元素可以設爲任意類型的值,只要這些類型支持 operator++()。
注意,不要忘記算法是獨立於容器類型的,對於任何具有指定類型迭代器的容器來說,算法都可以應用到它們的元素上。generate() 和 iota() 函數模板只需要正向迭代器,所以用來指定任何容器的元素範圍的迭代器都能發揮作用。
容器定義了成員函數 cbegin() 和 cend(),它們可以返回 const 迭代器。當只想訪問元素時,應該使用 const 迭代器。對於 non-const 迭代器,最好使用全局的 cbegin 和 cend() 來獲取。全局函數或成員函數 rbegin() 和 rend() 可以分別得到指向最一個元素和第一個元素前一個位置的反向迭代器。函數 crbegin() 和 crend() 可以返回 const 反向迭代器。我們可以用反向迭代器以逆序方式處理元素。例如:

std::array<double, 5> these {1.0, 2.0, 3.0, 4.0, 5.0};
double sum {};
auto start = std::rbegin(these);
auto finish = std::rend(these);
while(start != finish)
    sum += *(start++};
std::cout << "The sum of elements in reverse order is " << sum << std::endl;

在循環中,從最後一個元素幵始,我們計算出了所有元素的和。結束迭代器指向第一個元素之前的位置,所以 sum 加上第一個元素後,循環就結朿了。在反向迭代器上使用遞增運算符,會讓迭代器用一種和普通正向迭代器移動方向相反的方式移動。我們也可以使用 for 循環:

for (auto iter = std::rbegin (these); iter != std::rend(these); ++iter)
    sum += *iter;

因爲數組容器實例的元素個數固定,所以無法使用插入迭代器。插入迭代器通常用來向容器中添加元素。

四、array元素的比較

可以用任何比較運算符比較兩個數組容器,只要它們有相同的大小,保存的是相同類型的元素,而且這種類型的元素還要支持比較運算符。示例如下:

std::array<double,4> these {1.0, 2.0, 3.0, 4.0};
std::array<double,4> those {1.0, 2.0, 3.0, 4.0};
std::array<double,4> them {1.0, 3.0, 3.0, 2.0};
if (these == those) std::cout << "these and those are equal." << std::endl;
if (those != them) std::cout << "those and them are not equal."<< std::endl;
if (those < them) std::cout << "those are less than them."<< std::endl;
if (them > those) std::cout << "them are greater than those." << std::endl;

容器被逐元素地比較。對 ==,如果兩個數組對應的元素都相等,會返回 true。對於 !=,兩個數組中只要有一個元素不相等,就會返回 true。這也是字典中單詞排序的根本方式,兩個單詞中相關聯字母的不同決定了單詞的順序,即比較兩個數組大小時,會依次比較他們對應的元素,只要第一組比較出大小後,後面就不用繼續比較了,如those和them比較,當比較到第二組元素時,就已經比較出大小,那麼不在繼續剩餘元素的比較。這個代碼片段中所有的比較運算都是 true。所以當它們執行時,會輸出 4 條消息。
不像標準數組,只要它們存放的是相同類型、相同個數的元素,就可以將一個數組容器賦給另一個。例如:

them = those;

賦值運算符左邊的數組容器的元素,將會被賦值運算符心邊的數組容器的元素覆蓋。

參考鏈接:http://c.biancheng.net/view/412.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章