本文轉載自【微信公眾號:羽林君,ID:Conscience_Remains】經微信公眾號授權轉載,如需轉載與原文作者聯繫
前言
今天繼續肝C++,一入C++深似海。越學越有意思。今天給大家帶來一篇c++vector的介紹,難以置信這篇文章寫了我三天,不過總算整理完畢,現在分享給大家。
模板類vector 和 array是數組的替代品。模板類vector 類似於string類,也是一種動態數組。 在 c++ 中,vector 是一個十分有用的容器。它能夠像容器一樣存放各種類型的對象,簡單地說,vector是一個能夠存放任意類型的動態數組,能夠增加和壓縮數據。
在C++ primer plus 這本書中關於vectir不是進行一次性介紹的,而是分別在不同板塊使用vctor而去介紹的,今天我就融合起來介紹一些vector的使用。
vector做一個模板類
C++語言既有類模板,也有函數模板,其中vector是一個類模板。只有對C++有一定深入的理解才能寫出模板。
模板本身不是類或是函數,相反可以將模板看作編譯器生成類或函數編寫的一份說明。編譯器根據模板創建類或函數的過程叫做實例化,當使用模板時,需要指出編譯器應把類或者函數實例化成何種類型。
對於類模板來說,我們通過提供一些額外的信息,來指定模板到底實例化成什麼樣的類,需要提供哪些信息由模板決定。而提供信息的方式如下所示,即在模板名字後面跟一對尖括號,在括號上面放上信息。
以vector為例子:
vector<int> ivec; //ivec保存int類型的對象
vector<Sales_item> Sales_vec;//保存Sales_item類型的對象
vector<vector<string>> file; //一個二維數組,該元素的vector對象
上面的例子中,編譯器根據模板vector生成了三種不同的類型:vector<int>,vector<Sales_item>和vector<vector<string>>。
vector是模板而非類型,由vector生成的類型必須包含vector中的元素類型,例如vector<int>,int就是vector元素的類型。
vector能容納大部分類型的對象作為參數,但是因為引用不是對象,所以不存在包含引用的vector。
vector<int &> int; //是錯誤的
定義和初始化vector類型
vector<T> v1; //v1是一個空vector,它潛在的元素是T類型的,執行默認初始化
vector<T> v2(v1); //v2中包含有v1所有元素的副本
vector<T> v2 = v1;//等價於v2(v1),v2中包含有v1所有元素的副本
vector<T> v3(n,val);//v3包含了n個重複的元素,每個元素的初始值都是val
vector<T> v4(n); //v4包含了n個重複執行了值初始化的對象
vector<T> v5{a,b,c...};//v5包含了初始值個數的元素,每個元素被賦予相應的初始值
vector<T> v5={a,b,c...};//等價於 v5{a,b,c...};
看到第一個初始化例子,初始化了一個空vector,看上去空vector好像沒什麼用處。但是別忘了,vector是一個數組,在程序運行中,我們是可以很高效的往vector對象中添加元素。事實上,vector最常用方式就是先定義一個空vector,然後當運行時獲取到元素,再逐一添加。
當然也可以在定義vector對象時指定元素的初始值。例如,允許一個vector對象的元素拷貝給另一個vector對象。此時,新vector對象的元素就是原vector對象對應的副本。注意兩個vector對象的類型必須相同。
vector<int> ivec; //初始狀態為空
vector<int> ivec2(ivec); //把ivec的值拷貝給ivec2
vector<int> ivec3 = ivec; //把ivec的元素拷貝給ivec3
vector<string> svec(ivec2); //錯誤:svec的元素時string對象,不是int
列表初始化vector對象
列表初始化即使用花括號括起來的0個或多個初始元素值被賦給vector對象:
vector<string> article = {"a","an","the"};
vector<string> article1 = ("a","an","the");//錯誤 不能放置於圓括號內
創建指定數量的元素:
還可以用vector對象容納的元素數量和所有元素的統一初始值來初始化vector對象:
vector<int> ivec(10,-1); //10個int類型的元素,每個都被初始化為-1
vector<string> svec(10,"hi!");//10個sting類型的元素,每個都被初始化為"hi!"
vector迭代器功能
要訪問順序容器和關聯容器中的元素,需要通過「迭代器(iterator)」進行。迭代器是一個變量,相當於容器和操縱容器的算法之間的中介。迭代器可以指向容器中的某個元素,通過迭代器就可以讀寫它指向的元素。從這一點上看,迭代器和指針類似。
不過和指針不一樣的是,獲取迭代器不是使用取地址符,有迭代器的類型同時返回迭代器的成員。比如,這些類型都擁有名為begin和end的成員,其中begin負責返回指向第一個元素的迭代器,
auto b = v.begin(), c = v.end(); //b表示v的第一個元素 c表示v尾元素的下一個位置
end成員則負責返回指向容器"尾元素的下一個位置"的迭代器。這樣的迭代器
指示的是容器的一個不存在的"尾後"元素。
*iter //返回迭代器iter所指元素的應用
iter->mem //解應用iter並獲取該元素名為mem的成員,等價於(*iter).mem
++iter //令iter指向容器中的下一個元素
--iter //令iter指向容器的上一個元素
舉例子:依次輸出text的每一行直至遇到第一個空白行為止
for(auto it = text.cbegin(); it != text.cend()&& !it->empty();++it)
count << *it <<endl
注:cbegin()和cend()是C++11新增的,它們返回一個const的迭代器,不能用於修改元素。
vector當作容器
一個容器就是一些特定類型對象的集合。順序容器類型有vector(可變大小數組,支持快速隨機訪問,在尾部之外的位置插入或刪除元素可能很慢)、deque(雙端隊列,支持快速隨機訪問,在頭尾插入/刪除元素很快)、list(雙向列表,只支持雙向順序訪問,在list中任何位置進行插入/刪除操作速度都很快)、forward_list(單向列表,只支持單向順序訪問,在列表中任何位置進行插入/刪除操作速度都很快)、array(固定大小數組,支持快速隨機訪問,不能添加或刪除元素)、string(與vector類似的容器,但專門用於保存字符,隨機訪問快,在尾部插入/刪除速度快)。
向vector對象中添加元素
對vector對象來說,直接初始化的方式適用於三種情況:初始值已知且數量較少、初始值是另一個vector對象的副本、所有元素的初始值都一樣。然後更常見的情況是:創建一個vector對象時並不清楚實際所許需要的元素個數,元素的值也無法確定。還有些時候即使元素的初值已知,但如果這些值的總量較大且各不相同,那麼在創建vector對象的時候執行初始化操作也會顯得過於繁瑣。
舉個例子:如果想創建一個vector對象令其包含從0到9共10個元素,使用列表初始化的方法很容易做到這一點;但如果vector對象所包含的元素是從0到99或者0到999呢?這時候通過列表初始化把所有元素都一一羅列出來就不太合適了。對於此例來說,更好的處理方法是先創建一個空的vector,然後在運行時再利用vector的成員函數push_back向其中添加元素。push_back負責把一個值當成vector對象的尾元素"壓到(push)"vector對象的"尾端(back)",例如:
string word;
vector<string> text;//空vector對象
while(cin>>word)//cin>>word 是對word 進行賦值
{
text.push_back(word);//把word添加到text後面
}
在這上面有進一步優化的空間就是使用emplace_back(順序容器(如vector、deque、list)新標準引入了三個新成員:emplace_front、emplace和emplace_back,這些操作構造而不是拷貝元素。這些操作分別對應push_front、insert和push_back,允許我們將元素放置在容器頭部、一個指定位置之前或容器尾部。)
在容器尾部添加一個元素,這個元素原地構造,不需要觸發拷貝構造和轉移構造。而且調用形式更加簡潔,直接根據參數初始化臨時對象的成員。
當調用push或insert成員函數時,我們將元素類型的對象傳遞給它們,這些對象被拷貝到容器中。而當我們調用一個emplace成員函數時,則是將參數傳遞給元素類型的構造函數。emplace成員使用這些參數在容器管理的內存空間中直接構造元素。
emplace_back在引入右值引用,轉移構造函數,轉移複製運算符之前,通常使用push_back()向容器中加入一個右值元素(臨時對象)的時候,首先會調用構造函數構造這個臨時對象,然後需要調用拷貝構造函數將這個臨時對象放入容器中。原來的臨時變量釋放。這樣造成的問題是臨時變量申請的資源就浪費。
所以現在我們可以用emplace_back替換push_back使用,上面例子就可以這麼表示:
text.emplace_back(word);//把word添加到text後面
其他的vector操作
v.empty() //
v.size()
v[n] //返回v中第n個位置上的元素引用
不能用下標形式添加元素
v[idex] = a; //錯誤
此外還有好多vector屬於容器的操作,大家可以參考容器使用的函數,都是一樣的:具體使用另一位朋友寫的很詳細,我就不多做贅述了,大家可以去看看《vector容器!》
v.capacity();//容器的容量
v.size();//返回容器中的元素個數
v.resize(int num);//重新指定容器的長度為num,若容器變長,則以默認值填充新位置;如果容器變短,則末尾超出容器長度的元素被刪除
v.resize(int num, eles);
v.insert(const_iterator pos,ele);//迭代器指向位置pos插入元素els
v.erase(const_iterator pos);//刪除迭代器指向的元素
v.erase(const_iterator start,const_iteartor end);//刪除迭代器從start到end之間的元素
v.clear();//刪除容器中所有元素
vector當作參數
在C++裡很多時候我們會遇到函數想返回兩個以上結果的情況,這時候可以用數組(vector)、類來作為容器返回,也可以聲明一個全局變量的數組,將數值存放在數組裡解決。
使用引用來解決,將vector的引用在函數間傳遞
這是一個例子,假設我要傳入一個數,我的函數的功能是返回這個數後面十個數的序列。
#include<iostream>
#include<vector>
using namespace std;
/*
輸入一個數,返回這個數後面的十個數字序列
注意參數的這個 & 符號不能省略
*/
void getSequence(int num,vector<int>& sequence){
for(int i=0;i<10;i++){
sequence.push_back(i+num);
}
}
int main(){
int num=9;
vector<int> sequence;
//在主調函數這邊,直接傳入該vector變量
getSequence(num,sequence);
//訪問該vector的值的時候,也是直接訪問即可
for(vector<int>::iterator it=sequence.begin();it!=sequence.end();it++){
cout<<*it<<endl;
使用vector注意事項:
1、如果你要表示的向量長度較長(需要為向量內部保存很多數),容易導致內存洩漏,而且效率會很低;
2、Vector 作為函數的參數或者返回值時,需要注意它的寫法:
double Distance(vector<int>&a,vector<int>&b)
其中的「&」絕對不能少!!
文件處理和vector應用
主要是嘗試在文件中記錄和讀取信息,中間用到了vector,C++導出excel表格的過程太過繁瑣,所以這裡直接用很簡單的方法導出一個.csv的文本文件,該文件也可用excel打開。
#include<string>
#include<fstream> // 文件流
#include<sstream>
using namespace std;
int main()
//寫文件
ofstream outFile;
outFile.open("data.csv", ios::out);
outFile << "name" << ',' << "age"<< ',' << "hobby" << endl;
outFile << "Mike" << ',' << 18 << ','<< "paiting" << endl;
outFile << "Tom" << ',' << 25 << ','<< "football" << endl;
outFile.close();
ifstream inFile("data.csv", ios::in);
string lineStr;
vector< vector<string> > strArray; //vector 類型文string
while(getline(inFile, lineStr)) // 從 inFile 中讀取一行,放到 lineStr 中
{
cout<< lineStr<<endl;
stringstream ss(lineStr); //讀取內容放置在 ss流 中, 括號相當於初始化
string str;
vector<string> lineArray;
// 按照逗號分隔
while(getline(ss, str, ',')) // ss 中, 按照 「,」 逗號分割將ss 分割成一個個str
{
lineArray.push_back(str); // 將字符串放置到 line Array
cout<< str<<endl;
}
strArray.push_back(lineArray);
getchar();
return 0;
outFile.open("data.csv", ios::out);前面的雙引號內容為csv文件路徑,若沒有輸入文件路徑,則在編譯器默認路徑下生成一個csv文件。『