STL之map操作的幾點疑惑

一、map下標操作所導致的初始化(副作用)


當在程序中使用下標操作時,會發生什麼呢?下標代表的是鍵,當這個鍵在map中已經存在的話,則下標操作可以獲得該鍵所關聯的值;當這個鍵在map中不存在的話,將執行下面的操作:

1、調用值類型mapped_type的默認構造函數來初始化與該鍵關聯的值,再將這個鍵值對插入到map中;
2、然後再在新的map中獲取該鍵關聯的值來執行用戶的操作。


如下代碼:


void main()
{
	map<string, int> my_words;
	my_words.insert(make_pair("liz1", 1));
	my_words.insert(make_pair("liz2", 2));

	int count = my_words["liz3"];
	cout<<count<<endl;
	cout<<my_words.count("liz3")<<endl;

	system("pause");
}

當執行my_words["liz3"]操作時,先獲得int的默認初始值0(如果爲類類型則爲默認構造函數),然後與liz3構成一個新的pair<string, int>類型的鍵值對,並將該鍵值對插入到my_words中,然後在在新的map中獲取到鍵"liz3"所關聯的值,此時應該爲0了。


二、map的三種insert操作及其返回值的作用


在順序容器中,我們在使用insert操作的時候總是需要表明插入的位置和需要插入的值。而在map中,則有一些不同。

1、map容器不需要我們標註插入的位置。這與map容器的底層結構有關係。map的實現數據結構爲紅黑樹,紅黑樹具有二叉排序樹的性質,能夠根據需要插入的值來尋找插入的位置。
2、map中的鍵總是唯一不重複的,因此,對於map中已經存在的鍵則需要直接忽視。


接下來我們詳解一下map的三種插入操作:


1、map.insert(value)


其中value的類型爲value_type(鍵值對)。如果value.first(鍵)在map中已經存在,則map不變;如果不存在,則插入value。返回值爲一個pair類型,包含一個迭代器和一個bool對象,來標識是否進行了插入操作;

  • 當鍵不存在時,我們將value直接插入map。與前面的下標操作相比較,無需進行任何的初始化操作;
  • 對於insert的返回值,是一個pair類型,包括一個迭代器和一個bool對象。此迭代器指向的是map中鍵爲value.first的元素,bool標識是否插入了value。假設value.first在map中已經存在了,則迭代器指向map中已經存在的鍵爲value.first的元素,bool設置爲false,表示沒有發生插入操作;假設value.first在map中不存在,則先插入,然後再將迭代器指向該元素,bool設置爲true。下面以兩段程序來說明這點。
void main()
{
	map<string, int> my_words;
	my_words.insert(make_pair("liz1", 1));
	my_words.insert(make_pair("liz2", 2));

	pair<map<string, int>::iterator, bool> returnPtr = my_words.insert(make_pair("liz1", 8));  //插入的鍵在my_words中已經存在
	
	cout<<returnPtr.first->first<<"  "<<returnPtr.first->second<<endl;  //迭代器指向的是my_words中已經存在的鍵爲liz1的鍵值對
	cout<<"insert happen? : "<<returnPtr.second<<endl;  //沒有發生插入操作,bool應該爲false
	

	system("pause");
}


運行結果爲:




首先看程序,my_words中已經存在了鍵爲liz1的鍵值對,當試圖插入一個鍵爲liz1的鍵值對時,my_words將保持不變,並且給函數返回一個pair類型的返回值,包含一個指向my_words中已經存在的鍵值對,另一個爲false,表明沒有發生插入操作。


void main()
{
	map<string, int> my_words;
	my_words.insert(make_pair("liz1", 1));
	my_words.insert(make_pair("liz2", 2));

	pair<map<string, int>::iterator, bool> returnPtr = my_words.insert(make_pair("liz3", 8));  //插入的鍵在my_words中不存在
	
	cout<<returnPtr.first->first<<"  "<<returnPtr.first->second<<endl;  //迭代器指向的插入之後鍵爲liz3的鍵值對
	cout<<"insert happen? : "<<returnPtr.second<<endl;  //發生了插入操作,bool爲true
	

	system("pause");
}


運行結果爲:




首先看程序,插入的鍵值對中鍵爲liz3,這個鍵在my_words中不存在,則insert函數會將該鍵值對插入my_words中,然後再將已經插入my_words中的該鍵值對的迭代器返回,並且bool值爲true。


2、map.insert(beg, end)


前面又說到,map容器的插入元素可以根據自己的值自行尋找需要插入的位置,因此不需要在函數參數列表中標註需要插入的位置,所以這裏的beg和end這對迭代器所標註的是將要插入的鍵值對,首先有要求,該迭代器的所指向的數據需爲map的value_type類型的鍵值對。針對與這對迭代器範圍內的鍵值對,如果在map中存在,則map不改變;如果不存在則插入。這裏的返回類型均爲void類型。

3、map.insert(iter, value)(指定查找起點)


先講一下這個函數的含義:如果value.first鍵不在map中,以迭代器iter爲起點來搜尋value的存儲位置,再插入;反之,map保持不變。返回值是map中指向鍵爲value.first的鍵值對。

三個函數中我最弄不明白的就是這個函數了。前面我一直在強調的一點是map容器可以根據插入值來自行尋找插入位置,因此,用戶無需再在函數參數列表中表明插入的位置。但是這個函數似乎與我強調的這點相反。其實,這裏的iter有其特有的作用。

對於第一個insert函數,在嘗試插入value時,需要通過value的鍵來尋找其插入位置,回憶平衡二叉樹的查找過程,總是從根結點開始比較,一直往下查找直到葉子結點。這裏,假如現在的map的二叉樹的深度非常大,其值是從1到10000,然後我現在要插入的元素是9999,並且我們已經直到了8000已經插入了,那麼,我們肯定是從8000結點開始查找,而不是傻傻地還從0開始查找。


下面通過一段程序來測試一下這第一個和第三個insert的運行時間,按照理論,第三個insert的效率要高一些,運行時間短一些。程序如下:


/*
在for循環中執行insert操作是希望將時間可見,否則總是爲0
*/
void main()
{
	map<int, int> my_map;
	pair<map<int, int>::iterator, bool> returnPtr;
	
	for(long i = 0; i < 1000000; ++i)
	{
		my_map.insert(make_pair(i, 8));
	}

	clock_t beg = clock();
	for(i = 0; i < 1000000; ++i)
		returnPtr = my_map.insert(make_pair(1000001 + i, 8));  
	clock_t end = clock();
	double time = (double)(end - beg)/CLOCKS_PER_SEC;
	cout<<"insert(value)的運行時間: "<<time<<endl;

	
	beg = clock();
	map<int, int>::iterator ptr = returnPtr.first; 
	for(i = 0; i < 1000000; ++i)  
		ptr = my_map.insert(ptr, make_pair(1000001 + i, 8));  //每次都從上一次插入位置爲起點開始尋找插入位置
	end = clock();
	time = (double)(end - beg)/CLOCKS_PER_SEC;
	cout<<"insert(iter, value)的運行時間: "<<time<<endl;
}

運行結果如下:




從運行結果可以看出,第三種指定查找起點的insert函數的效率要好很多。

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