1,vector是啥?
是具有動態大小的數組,具有順序。能夠存放各種類型的對象。相比於固定長度的數組,運行效率稍微低一些,不過很方便。
2,咋用?
聲明:
vector <int> vi; //vector<類型>標識符 vector <int> vii(10); //Vector<類型>標識符(容量),這句話的意思是聲明一個vector對象名字叫vii,初始大小是10
常用方法:
#include "pch.h" #include <algorithm> using namespace std; int main() { vector<int>vi; vi.push_back(1); vi.push_back(2); //向隊列的最后添加數據,1和2 vi.pop_back(); //去掉隊列的最后一個數據 int vilen = vi.size(); //隊列的實際長度 vi.clear(); //清除隊列中所有的數據 vi.push_back(1); vi.push_back(2); vi.push_back(3); vi.push_back(4); //加點數據 for (int i = 0; i < vilen; i++) { printf("%d\n", vi[i]); } //普通方法遍歷隊列輸出內容 vector<int>::iterator it; //聲明一個迭代器 for (it = vi.begin(); it != vi.end(); it++) { printf("iterator value is %d \n", *it); } //利用迭代器遍歷隊列 for (auto itt : vi) { printf("%d\n", itt); } //c++11的新遍歷方法,利用auto sort(vi.begin(), vi.end()); //sort 需要頭文件 #include <algorithm> //把隊列按照從小到大的順序排序 for (int i = 0; i < vi.size(); i++) { printf("%d\n", vi[i]); } reverse(vi.begin(), vi.end()); //把隊列按照從大到小的順序排序 for (int i = 0; i < vi.size(); i++) { printf("%d\n", vi[i]); } vector<vector<int> > obj; //定義一個二維數組,約等於python中的:[[1,2],[1,2],[1,2]] vector<vector<int> > obj(5, vector<int>(6)); //這樣也是可以的,語法不同而已, return 0; }
3,隊列支持的用法查詢
1.push_back 在數組的最后添加一個數據
2.pop_back 去掉數組的最后一個數據
3.at 得到編號位置的數據
4.begin 得到數組頭的指針
5.end 得到數組的最后一個單元+1的指針
6.front 得到數組頭的引用
7.back 得到數組的最后一個單元的引用
8.max_size 得到vector最大可以是多大
9.capacity 當前vector分配的大小
10.size 當前使用數據的大小
11.resize 改變當前使用數據的大小,如果它比當前使用的大,者填充默認值
12.reserve 改變當前vecotr所分配空間的大小
13.erase 刪除指針指向的數據項
14.clear 清空當前的vector
15.rbegin 將vector反轉后的開始指針返回(其實就是原來的end-1)
16.rend 將vector反轉構的結束指針返回(其實就是原來的begin-1)
17.empty 判斷vector是否為空
18.swap 與另一個vector交換數據
4,特殊聲明一個用法
C++11中,針對順序容器(如vector、deque、list),新標准引入了三個新成員:emplace_front、emplace和emplace_back,這些操作構造而不是拷貝元素。當調用push或insert成員函數時,我們將元素類型的對象傳遞給它們,這些對象被拷貝到容器中。而當我們調用一個emplace成員函數時,則是將參數傳遞給元素類型的構造函數。emplace成員使用這些參數在容器管理的內存空間中直接構造元素。應該是代碼執行會變得更快。看例子:
#include <iostream> #include <algorithm> #include <vector> using namespace std; class A { public: int hehe; A(int i); }; int main() { A a(1); A b(2); vector<A>vi = { a,b }; //創建個隊列 vi.emplace_back(101); //直接用101構造一個實例塞到隊列中 vi.push_back(5); //先生成一個實例,然后拷貝到隊列中。 for (auto itt : vi) { printf("%d\n", itt.hehe); } return 0; } A::A(int i) { hehe = i; //printf("%d\n", hehe); };
5,vector高級用法(這個厲害了,能夠整塊內存轉存為vector)
#include <vector> using namespace std; //此用法可以用於把整塊圖片數據讀取到一個vector中 int main() { unsigned char *hehe = NULL; hehe = (unsigned char *)malloc(10); printf("查看指針指向的內存的大小%d\n",_msize(hehe)); //先去申請一塊10字節的內存,申請成功以后返回的是指向該內存的指針,否則返回null vector<unsigned char> vi(hehe,hehe+10); //vector的傳入參數分別是某塊內存的開始地址和結束地址, printf("%d \n",vi.size()); free(hehe); //malloc獲取的內存記得釋放呦 return 0; }
6,vector中存放指針 vs vector中存放數據 vs vector中存放智能指針
最近遇到了一個問題,業務需求新建一個全局隊列,一個線程向全局隊列中添加數據,另一個線程從隊列中取數據,簡稱,生產者消費者模型。那么問題來了,我是向vector中直接存放局部變量的值呢?還是直接存放指針呢?來吧,寫個代碼測試一下。
1)把指向局部變量的指針添加到vector中,實踐證明這種方法不可取。
#include "pch.h" #include <iostream> #include <string> #include <vector> #include <memory> using namespace std; using namespace cv; //以下是局部變量的普通指針添加到數組中 int prodoucer1(vector<string *> &xc); int prodoucer1(vector<string *> &xc) { string str = "1234"; string str1 = "abc"; //新建倆局部變量 string *str3 = &str; string *str4 = &str1; //新建指向局部變量的指針 cout << str3 << "修改前str3 " << *str3 << endl; cout << str4 << "修改前str4 " << *str4 << endl; xc.push_back(str3); xc.push_back(str4); //把指針添加到隊列中 str3 = &str1; //改變str3指針的指向 cout << str3 << "修改中str3 " << *str3 << endl; return 0; } int main(int argc, char *argv[]){ vector<string *> vi; int rlt = prodoucer1(vi); for (auto i:vi) { cout <<i<<"修改后 "<< *i << endl; } //修改以后i的地址可以拿到,但是i的值已經拿不到了。因為指針指向的內容是局部變量,已經回收掉了 return 0; }
在此,得出結論,如果你要使用vector存放指針,請保證指針指向的內容不會被自動回收。
2)vector中存放數據,實踐證明push_back這是值拷貝
#include "pch.h" #include <iostream> #include <string> #include <vector> #include <memory> using namespace std; using namespace cv; //以下是局部變量的string添加到隊列中的function int prodoucer(vector<string> &xc); int prodoucer(vector<string> &xc){ string str1 ="1234"; string str2 = "abcd"; string str3 = "xyz"; cout << &str1 << "before str1 " << str1 << endl; cout << &str2 << "before str2 " << str2 << endl; cout << &str3 << "before str3 " << str3 << endl; //輸出結果: //00000027BDFDFB18before str1 1234 //00000027BDFDFAF8before str2 abcd //00000027BDFDFAD8before str3 xyz xc.push_back(str1); xc.push_back(str2); xc.push_back(str3); str3= "hehehe"; cout << &str3 << "changeing str3 " << str3 << endl; //00000027BDFDFAD8changeing str3 hehehe return 0; } int main(int argc, char *argv[]){ vector<string> vi; int rlt = prodoucer(vi); cout << " out side the fun" << endl; cout <<&vi[0]<<"using "<< vi[0] << endl; cout << &vi[1] << "using " << vi[1] << endl; cout << &vi[2] << "using " << vi[2] << endl; //打印出來的是: //000001F43005DFB0using 1234 //000001F43005DFD0using abcd //000001F43005DFF0using xyz return 0; }
str1在局部變量中的內存地址原本是fb18,添加到vector中以后,再取出來地址就變成了dfb0,但是前后值沒變,所以我認為這屬於值拷貝
3)vector中存放智能指針,沒有問題,而且智能指針的指向的數據的地址沒有改變,
#include "pch.h" #include <iostream> #include <string> #include <vector> #include <memory> using namespace std; using namespace cv; //以下是局部變量的智能指針添加到隊列中的function int prodoucer(vector<shared_ptr<string>> &xc); int prodoucer(vector<shared_ptr<string>> &xc){ shared_ptr<string> str1 = make_shared<string>("1234"); shared_ptr<string> str2 = make_shared<string>("abcd"); shared_ptr<string> str3 = make_shared<string>("xyz"); cout << str1 << "before str1 " << *str1 << endl; cout << str2 << "before str2 " << *str2 << endl; cout << str3 << "before str3 " << *str3 << endl; //三個變量的地址分別是:e0,a0,60,值就是上面寫的這些 xc.push_back(str1); xc.push_back(str2); xc.push_back(str3); str3= make_shared<string>("hehehe"); cout << str3 << "changeing str3 " << *str3 << endl; //把地址60上的內容變為“hehehe” return 0; } int main(int argc, char *argv[]){ vector<shared_ptr<string>> vi; int rlt = prodoucer(vi); cout << " out side the fun" << endl; for (auto i:vi) { cout <<i<<"using "<< *i << endl; } //循環中能打印出來的是:e0,1234 a0,abcd 60,xyz //很明顯局部的智能指針放到隊列中以后,地址沒變,數據也沒變,所以push_back的操作相當於把智能指針指向的數據塊的引用增加了,而且作用域提升到了全局變量 //push局部變量到vector的操作相當於是對實例本身進行值拷貝,但是更加科學的是,局部變量指向的數據塊並沒有真正地被復制了一遍,而是生命周期變得和vector一樣長了 return 0; }
原本我不明白,現在我明白了。
首先要明白shared_ptr是個啥?是個類,我創建:shared_ptr<string> hehe;hehe是一個類實例,這個類實例采用的創建模板是string,使用sizeof函數查看,你就會發現所有智能指針的大小都是16字節,所有的string大小都是32字節。
那么問題來了,為什么智能指針只有16字節,卻能夠‘放’很多數據呢?大概流程是這樣的:創建實例 --------> new 一塊內存存放數據(模板傳遞的是string就開辟32字節以上,模板是int就開辟4字節以上)---------->實例中相關的屬性存好(這其中包括但是不僅限:new出來的內存的地址,值得一提的是這個實例有個很牛的方法,把自己裝得很像一個指針,)
如何裝得自己很像指針?第一,只要你打印實例hehe,我就把我存的源數據的地址給你打印出來。第二,你如果對我使用取值符號(比如:*hehe),我就把源數據的內容給你。但是這只是偽裝出來的,為什么這么說?因為你可以打印一下&hehe,這樣你就能得到這個實例的實際存儲位置了。不信你看:
int prodoucer(vector<shared_ptr<string>> &xc); int prodoucer(vector<shared_ptr<string>> &xc) { shared_ptr<string> str1 = make_shared<string>("1234"); shared_ptr<string> str2 = make_shared<string>("abcd"); shared_ptr<string> str3 = make_shared<string>("xyz"); cout << &str1 << "before str1 " << *str1 << endl; cout << &str2 << "before str2 " << *str2 << endl; cout << &str3 << "before str3 " << *str3 << endl; //內容是這樣的: //000000458013FC10before str1 1234 //000000458013FC00before str2 abcd //000000458013FBF0before str3 xyz xc.push_back(str2); xc.push_back(str3); str3 = make_shared<string>("hehehe"); cout << &str3 << "changeing str3 " << *str3 << endl; //000000458013FBF0changeing str3 hehehe return 0; } int main(int argc, char *argv[]) { vector<shared_ptr<string>> vi; int rlt = prodoucer(vi); cout << " out side the fun" << endl; for (auto i : vi) { cout << &i << "using " << *i << endl; } //循環中能打印出來的是: //000000458013FC70using 1234 //000000458013FC70using abcd //000000458013FC70using xyz return 0; }
所以,你看到了,整個流程是這樣的:創建智能指針(這其中包括開辟了一塊自帶引用計數的內存存儲源數據,然后新建了一個智能指針的實例指向存數據的內存),當push_back的時候,先值拷貝了一個實例(新實例仍舊指向原來的那塊源數據,數據被引用次數加1,現在是2),然后局部function走完了,回收了局部變量(源數據塊上引用數量減1,現在是1),全局變量的vector中仍舊保存了智能指針的實例,所以源數據的引用不會歸0,不會被釋放。
4,將一個局部的帶指針屬性的實例添加到隊列中,那會發生什么?
#include <iostream> #include <string> #include <vector> #include <memory> using namespace std; class fruit { public: char *color ; }; int prodoucer(vector<fruit> &xc); int prodoucer(vector<fruit> &xc) { fruit apple; apple.color = "red"; fruit pear; pear.color = "yellow"; cout << &apple<< "before str1 " << apple.color << endl; printf("%p \n", apple.color); printf("%p \n", "red"); cout << &pear << "before str2 " << pear.color<< endl; //內容是這樣的: // 0000009BC7AFFA00before str1 red // 00007FF764D03358 // 00007FF764D03358 // 0000009BC7AFFA08before str2 yellow xc.push_back(apple); xc.push_back(pear); apple.color = "green"; printf("%p \n", apple.color); //00007FF764D03390 return 0; } int main(int argc, char *argv[]) { vector<fruit> vi; int rlt = prodoucer(vi); cout << " out side the fun" << endl; cout << &vi[0] << "using " << vi[0].color << endl; printf("%p \n",vi[0].color); printf("%p \n", "red"); cout << &vi[1] << "using " << vi[1].color << endl; //循環中能打印出來的是: // 0000028205C1F110using red // 00007FF764D03358 // 00007FF764D03358 // 0000028205C1F118using yellow return 0; }
說明一下:
第一個問題:為什么“red"這個字符串不管在局部還是在全局,在實例內還是單獨打出來地址永遠都是58呢?個人懷疑是因為它在靜態區,或者是因為雙引號的鍋,但是目前不能確定。
第二個問題,實例的地址前后改變了,這從側面佐證了push_vector確實是值拷貝,但是實例中如果帶指針,指向的數據究竟能不能帶到全局變量中呢?這個實驗看不出來,我們換一個
#include <iostream> #include <string> #include <vector> #include <memory> using namespace std; //using namespace cv; class fruit { public: string *color ; }; int prodoucer(vector<fruit> &xc); int prodoucer(vector<fruit> &xc) { fruit apple; string color1 = "red"; apple.color = &color1; printf("%p \n", apple.color); printf("%p \n", color1); //問題一,以上這兩個地址打印出來為什么不一樣啊喂? // 0000008065AFF730 // 0000008065AFF6C0 string color2 = "yellow"; fruit pear; pear.color = &color2; cout << &apple << "value: " << *apple.color; printf("address:%p \n", apple.color); cout << &pear << "value: " << *pear.color; printf("address:%p \n", pear.color); //內容是這樣的: // 0000008065AFF6E0value: redaddress:0000008065AFF730 // 0000008065AFF6E8value: yellowaddress:0000008065AFF710 xc.push_back(apple); xc.push_back(pear); string color3 = "green"; apple.color = &color3; cout << &apple << " changing value: " << *apple.color; printf("address:%p \n", apple.color); //0000005364EFF8E0 changing value: greenaddress:0000005364EFF8F0 return 0; } int main(int argc, char *argv[]) { vector<fruit> vi; int rlt = prodoucer(vi); cout << " out side the fun" << endl; cout << &vi[0] << "using value:" << *vi[0].color ; printf(" address:%p \n",vi[0].color); //printf("%p \n", "red"); cout << &vi[1] << "using value: " << *vi[1].color ; printf(" address:%p \n", vi[1].color); //循環中能打印出來的是: // 000001905C540290using value : address:0000005364EFF930 // 000001905C540298using value : address:0000005364EFF910 return 0; }
以上這個例子,基本證明了,即使是當作類屬性,在值拷貝的時候指針指向的內容也是不會被拷貝的。所以終極結論是:
當進行值拷貝的時候,指向局部變量的普通指針是不可靠的。智能指針的可靠的。
然后我就又有一個問題了,opencv中有個重要的類叫mat,mat占96字節,mat保存了一個屬性是這樣的:uchar *data;看起來是個普通指針,因此是否可以把局部的mat push到vector中呢?the truth is it does ok .but why?據說在堆上,但是在堆上的數據為啥不能被回收???namen