c++ 踩坑大法好 復合數據類型------vector


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM