C++之模板類(Template) 容器之map 及 對象副本 深/淺拷貝 等問題



這是C++學習過程當中,比較難理解抽象的知識,如今總結歸納於此,望對你們有所幫助:數組


通常地當咱們只想知道一個值是否存在時,set 最有用處;但願存儲也可能修改一個相關的值時,map 最爲有用.安全

   map提供一個鍵值對容器,在map(也叫關聯數組)中咱們提供一個鍵/值對,鍵用來索引,而值用做被存儲和檢索的數據.函數

   在使用mapset時兩個最主要的動做是向裏面放入元素以及查詢元素是否存在.性能

 

首先要包含頭文件學習

#include <map>spa

 

定義並生成map指針

爲定義map對象咱們至少要指明鍵和值的類型例如對象

map<string, int>   word_count;//定義名爲word_count的map,鍵值爲string類型,value爲int類型索引

class employee;內存

map<int, employee*>    personnel;//定義名爲personnel的map,鍵值爲int,value爲employee指針

或者使用類型定義

typedef     map <string,   int>   MY_MAP;

MY_MAP   word_count;

 

定義了map 之後下一步工做就是加入鍵/值元素對

方法一:

word_count[ string("Anna") ] = 1;

1一個未命名的臨時string 對象被構造並傳遞給與map 類相關聯的下標操做符,這個對象用Anna 初始化,

2word_count 中查找Anna 項,沒有找到

3 一個新的鍵/值對被插入到word_count 中。固然鍵是一個string對象持有「Anna」。可是值不是1 而是0

4 插入完成接着值被賦爲1

用下標操做符把map 初始化至一組元素集合,會使每一個值都被初始化爲缺省值。而後再被賦值爲顯式的值,若是元素是類對象並且它的缺省初始化和賦值的運算量都很大。就會影響程序的性能

方法二:

word_count.insert(map<string,int>::value_type( string("Anna"), 1 ));

其做用是建立一個pair對象,接着將其直接插入map

typedef  map<string,int>::value_type    valType;

word_count.insert( valType( string("Anna"), 1 ));

方法三:

my_Map.insert(pair <string,int> ( "c ",3));

方法四:

my_Map.insert(make_pair( "d ",4));

方法五:

爲插入必定範圍內的鍵/值元素,咱們能夠用另外一個版本的insert()方法。它用一對iterator做爲參數。

map< string, int > word_count;

// ... fill it up

map< string, int > word_count_two;

// 插入全部鍵/值對

word_count_two.insert(word_count.begin(),word_count.end());

 

查找並獲取map 中的元素。 

下標操做符給出了獲取一個值的最簡單方法例如

// map<string,int> word_count;

int count = word_count[ "wrinkles" ];

可是隻有當map 中存在這樣一個鍵的實例時該代碼纔會表現正常若是不存在這樣的實例使用下標操做符會引發插入一個實例在本例中鍵/值對

string( "wrinkles" ), 0

被插入到word_countcount 被初始化爲0

有兩個map 操做可以發現一個鍵元素是否存在並且在鍵元素不存在時也不會引發插入實例

1   count(keyValue)

count()返回mapkeyValue 出現的次數固然對於map

而言返回值只能是01 若是返回值非0 咱們就能夠安全地使用下標操做符例如

int count = 0;

if ( word_count.count( "wrinkles" ))

count = word_count[ "wrinkles" ];

2   find(keyValue)

若是實例存在則find()返回指向該實例的iterator 若是不存在則返回等於end()iterator 例如

int count = 0;

map<string,int>::iterator it = word_count.find( "wrinkles" );

if ( it != word_count.end() )

count = (*it).second;

指向map中元素的iterator指向一個pair對象(下文有對pair的介紹)其中first擁有鍵,second擁有值

 

map 進行迭代

咱們能夠經過對由begin()end()兩個迭代器標記的全部元素進行迭代來作到這一點。

typedef map<string,int> tmap;

tmap::iterator iter = text_map->begin(),

iter_end = text_map->end();

while ( iter != iter_end )

{ cout << "word: " << (*iter).first << " ("<<(*iter).second<<")";

 

map中的元素進行刪除

map 中刪除元素的erase()操做有三種變化形式。

爲了刪除一個獨立的元素咱們傳遞給erase()一個鍵值或iterator(刪除的是當前迭代到的那個元素), 爲了刪除一列元素咱們傳遞給erase()一對lieator

一個map 只能擁有一個鍵值的唯一實例,爲了給同一個鍵提供多個項咱們必須使用multimap。

 

map的其餘操做

my_Map.size()        返回元素數目
my_Map.empty()       判斷是否爲空
my_Map.clear()       清空全部元素

 

PS:一些問題解惑:

 

Q:如下兩種定義方式,在進行map.clear()的時候,內存釋放上有啥不一樣?
     typedef map<CString,CFileAttribute> MAPStr2FileAttr;
     typedef map<CString,CFileAttribute *> MAPStr2FileAttr;
 

A:

   clear()至關於earse(m.begin(),   m.end());

   若定義的map的存儲對象是一個類對象:

   拷貝是整個STL(Standard Template Library,標準模板庫)工做的方式,因此容器中保存的是副本,而不是要添加的對象自己。對象原件在副本拷貝存放結束後即可以結束生命,而在使用clear()的時候,對象副本會去走到析構函數,進行對象內部的內存釋放。clear()後,對象拷貝被析構,剩下的只是raw   memory,即沒有被初始化的內存,它們將被歸還到stl的內存分配器alloc裏的(記得嗎,vector/list等全部容器都有一個alloc參數的,就是這東西),容器自己已經再也不擁有這塊內存了。內存歸還了,只不過不是歸還入系統堆而已。(除了vector不能(自動)釋放內存,其它STL容器都會在每個erase動做後釋放一塊內存。)

   若定義的map存儲對象是一個類對象的指針:

   這時通常不能採用clear()函數,由於clear()不會自動釋放原來對象所佔用的內存。這時可使用erase()輔助delete語句進行元素刪除和內存釋放。

  上面這句話我是這樣理解的,由於存入的是指針,這個指針指向一塊區域(new出來的,eg:class A, A *a = new A()),可是畢竟map裏面的value值是個指針,就是一個地址而已,所以在clear的時候只是把指針清除掉了,而指針指向的內容依舊存在。所以通常要在clear以前先釋放掉這些個指針指向的空間。

   另外使用的是類對象指針時,還須要維護這個指針不受到破壞。

 

小結:

  若是用容器存副本,則容器銷燬的時候,副本也會自動被刪除。

   若是用容器存指針,則容器銷燬的時候,不會刪除這些指針所指向的對象,所以必須先手工刪除完畢以後,再銷燬容器。

 

 

Q:  對由key獲得的value對象,沒有辦法改變其中的數值嗎??

    例以下代碼:

    //srcfileAttribute.nIndex初始爲0
    CFileAttribute srcfileAttribute = m_mapKeyVsFile[「abc」];
    srcfileAttribute.nIndex++;
    可是再次
    CFileAttribute srcfileAttribute = m_mapKeyVsFile[「abc」];
    發現這個srcfileAttribute.nIndex仍是0;並無變成1,
    這是爲何呢??
    難道說我不能這樣直接改map裏面的value值嗎?必須刪除從新insert一個??

 

A:

    srcfileAttribute = m_mapKeyVsFile[「abc」];此時srcfileAttrbute是經過map中值的拷貝構造函數構造的一個新的對象,這個副本的改變不影響map中的值,要改變map中的值能夠直接m_mapKeyVsFile[「abc」].nIndex++;

    若是用容器存副本,其存入、取出的過程是經過拷貝構造函數賦值操做符來進行的。。。

 

 

 

Q:   我採用map<CString,CFileAttribute> 這種方式,既map的value值爲CFileAttribute對象,
     可是在進行insert的時候程序卻報錯...

     例如如下代碼:
     typedef map<CString,CFileAttribute> MAPStr2FileAttr;
     MAPStr2FileAttr m_mapKeyVsFile;

     szFilePath = "abc";
     CFileAttribute FileAttr;
     FileAttr.m_str = "nidfjasdkljfsdk";

     m_mapKeyVsFile.insert(pair <CString,CFileAttribute> (szFilePath, FileAttr));

     在執行insert的時候提示 _BLOCK_TYPE_IS_VALID 的錯誤,從網上查了下發現把CFileAttribute 的析構函數弄掉就OK了...

    
這是爲何呢??不是存入map的是原對象的副本嗎,怎麼還會涉及到析構函數??

 

A:

   若是用容器存副本,其存入、取出的過程是經過拷貝構造函數和賦值操做符(詳解查看operator操做符重載來進行的。

  若是你沒有顯式地提供這二者,則使用缺省的拷貝構造函數和賦值操做符,其實現方式爲:內存複製。例如:倘若你沒有提供CFileAttribute::operator=(重載賦值操做符),那麼語句fileAttribute1 = fileAttribute2就至關於:memcpy(&fileAttribute1, &fileAttribute2, sizeof(CFileAttribute))。若是你的CFileAttribute僅包含簡單變量,例如:
class CFileAttribute
{
  int i;
  double d;
};
那麼memcpy沒什麼不妥。但倘若你的CFileAttribute中包含(或者嵌套包含)指針,那麼就可能有問題了,由於指針的複製並不等於內存塊的複製,所以你必須顯示地提供一個CFileAttribute::operator=,而且在其中把指針所對應的內存塊也複製一遍,這纔是真正的副本(此時這兩個副本內的指針反而是不相等的,由於分別指向不一樣的內存塊),其中任何一個副本的銷燬(通常會在析構函數中把其指針所指向的內存塊同時銷燬)都不會影響到另外一個副本的獨立存在,既採用的是深拷貝(詳解參看深拷貝與淺拷貝)。
你的CFileAttribute::m_str顯然是CString類型,而CString內部固然是一個指針,所以缺省的、memcpy方式的拷貝構造函數以及賦值操做符必定會出問題。你必須顯式提供本身的拷貝構造函數以及賦值操做符。

 

 

 

PS:我在使用map模板類時碰到的一個問題:

使用map時vc6竟然跳出了上百個warning C4786,上網查找才知道這個是vc的bug,因爲stl裏用的字符串過長,vc搞不定,呵呵,雖然能夠不去管,可是若是程序出錯誤,要在上百條的warning找到錯誤信息仍是很困難的。有人說用#pragma warning(disable: 4786)能夠去掉,可是實驗了一下竟然不行,後來發現一個老外說要把這句話放到全部stl頭的include以前,因而照辦,果真能夠,^_^老外就是敬業~~


 

 

**************************************************************************************

 

 

pair

pair同 map、set、mulitmap、multiset同樣是關聯容器

1 pair的應用

pair是將2個數據組合成一個數據,當須要這樣的需求時就可使用pair,如stl中的map就是將key和value放在一塊兒來保存。另外一個應用是,當一個函數須要返回2個數據的時候,能夠選擇pair。 pair的實現是一個結構體,主要的兩個成員變量是first second 由於是使用struct不是class,因此能夠直接使用pair的成員變量。

std::pair模型

template   <class T1,  class T2>  
  struct   pair  
  {  
      T1   first;  
      T2   second;  
  };   
  

2 make_pair函數

template pair make_pair(T1 a, T2 b) { return pair(a, b); }

很明顯,咱們可使用pair的構造函數也可使用make_pair來生成咱們須要的pair。通常make_pair都使用在須要pair作參數的位置,能夠直接調用make_pair生成pair對象很方便,代碼也很清晰。另外一個使用的方面就是pair能夠接受隱式的類型轉換,這樣能夠得到更高的靈活度。靈活度也帶來了一些問題如:

std::pair<int, float>(1, 1.1);

std::make_pair(1, 1.1);

是不一樣的,第一個就是float,而第2個會本身匹配成double。

相關文章
相關標籤/搜索