C++ 寫時拷貝 3

http://blog.csdn.net/ljianhui/article/details/22895505

字符串一種在程序中經常要使用到的數據結構,然而在C中卻沒有字符串這種類型。在C++中,為了方便字符串的使用,在STL中提供了一個string類。該類維護一個char指針,并封裝和提供各種的字符串操作。

一、為什么要實現隱式公享寫時拷貝
試想一下,如果我們要自己實現一個string類,最簡單的方式是什么?就是讓每一個string類的實例維護一個在內存中獨立的字符數組,每個string對象各不相干。這樣一個對象的任何變化都不會影響到其他的對象。這樣做的好處就是處理簡單,不易出錯,但是這樣做的缺點卻是內存的使用量、程序的效率也低。
例如,對于如下的例子:
[cpp]?view plain?copy
  1. int?swap(string?&x,?string?&y)??
  2. {??
  3. ????string?tmp(x);??
  4. ????x?=?y;??
  5. ????y?=?tmp;??
  6. }??
在上面的代碼中,我們需要做的事情非常簡單,只是想交換一下兩個string對象的值而已。然而,如果我們采用上面所說的實現方式,一共在內存上進行了三次的用new來創建一個新的數組并復制數據,同時還會調用兩次的delete[]。我們花了這么大的力氣才完成了這樣一個簡單的動作。而隱式共享寫時復制的內存管理策略卻可以解決這樣的問題。雖然C++11用&&運算符解決了上述問題,但是在程序中使用大量的string的副本,而不改變其值的情況還是不少的。例如string數組,刪除一個元素后的移動操作。

二、隱式共享寫時復制的實現思想
什么是隱式共享寫時拷貝呢?就是當用一個string對象初始化另一個string對象或把一個string對象賦值給另一個string對象時,它們內部維護的指針其實指向了內存中的同一個字符數組,這就是隱式共享。

使用這種方法,上述的代碼就不需要調用new來創建數組也不需要復制,也不會調用delete[](如果不是很明白也不要緊,看完實現代碼和后自然就明白了)。然后兩個指針指向同一個對象很容易引發錯誤,當其中一個對象執行析構函數釋放掉其內部指針指向的內存時,另一個對象卻對此完全不知情,可能會引用一個不存在的內存,從而讓程序崩潰。所以為了方便資源的管理,我們引用智能指針的思想,為每個內存中的字符數組(用new創建,存在于堆中)添加一個引用計數used,表示有多少個對象引用這個塊內存(即字符數組)。當一個對象析構時,它會把引用計數used減1,當used為0時,表示沒有對象引用這塊內存,從而把這塊內存釋放掉。當然由于used也要在對象中共享,所以它也是一個堆中的數據,每個對象有一個指向它的指針。

而當一個string對象需要改變它的值時,例如
[cpp]?view plain?copy
  1. string?s1("abc");??
  2. string?s2(s1);??
  3. string?s3("edf");??
  4. s2?+=?s3;??
此時,s1和s2指向了堆內存中的同一個字符數組,而當s2的值要改變時,因為如果直接在其指向的內存中修改,則會影響到對象s1,所以為了讓s2的操作不影響到s1,s2會在重新new出一塊內存,然后先把之前所引用的字符數組的數據復制到新的字符數組中,然后再把s3中的字符數據復制到新的字符數組中。這就是寫時拷貝。注意,同時還要把之前指向的內存的引用計數減1(因為它指向了新的堆中的字符數組),并在堆中重新new一個塊內存,用于保存新的引用計數,同時把新的字符數組的引用計數置為1。因為此時只有一個對象(就是改變值的對象)在使用這個內存。

三、代碼實現及設計要點詳解
說了這么多,還是來看看代碼的實現吧,為了與標準C++的string類區別開來,這樣采用第一個字母大寫來表示自定義的字符串類String。

源代碼可以點擊下面的連接下載:
http://download.csdn.net/detail/ljianhui/7143351

其頭文件_stringv2.h如下:
[cpp]?view plain?copy
  1. #ifndef?_STRINGV2_H_INCLUDED??
  2. #define?_STRINGV2_H_INCLUDED??
  3. /***?
  4. String類的部分實現,采用的內存管理策略是:隱式共享,寫時復制?
  5. 實現方法:與智能指針的實現類似?
  6. ***/??
  7. class?String??
  8. {??
  9. ????public:??
  10. ????????String();??
  11. ????????String(const?String&?s);??
  12. ????????String(const?char?*pc,?size_t?len);??
  13. ????????String(const?char?*pc);??
  14. ????????~String();??
  15. ????????String&?operator=(const?String?&s);??
  16. ????????String&?operator=(const?char?*s);??
  17. ????????String&?operator+=(const?String?&rhs);??
  18. ????????String&?operator+=(const?char?*rhs);??
  19. ????????void?clear();??
  20. ????????size_t?getLength()const?{return?_length;}??
  21. ????????const?char*?cstr()const?{return?_cstr;}??
  22. ????private://function??
  23. ????????void?_initString(const?char?*cstr,?size_t?len);??
  24. ????????void?_decUsed();??
  25. ????????char*?_renewAndCat(const?char?*cstr,?size_t?len);??
  26. ????????void?_addString(const?char?*cstr,?size_t?len);??
  27. ????????void?_addAssignOpt(const?char?*cstr,?size_t?len);??
  28. ????private://data??
  29. ????????char?*_cstr;??
  30. ????????size_t?*_used;??
  31. ????????size_t?_length;??
  32. ????????size_t?_capacity;??
  33. };??
  34. String?operator+(const?String?&lhs,?const?String?&rhs);??
  35. std::ostream&?operator?<<(std::ostream?&os,?const?String?&s);??
  36. std::istream&?operator?>>(std::istream?&in,?String?&s);??
  37. #endif?//?_STRINGV2_H_INCLUDED??
從上面的String的數據成員,我們可以看到String在其內部維護一個指向堆內存的字符數組的char指針_cstr和一個指向堆內存中字符數組的引用計數的size_t指針_used。本類并沒有實現String的所有操作,只是實現了大部分的初始化和String跟寫操作有關的函數。

注意:為了說明的方便,我會使用s._cstr等方式來指明一個成員變量所屬的對象,或使用*s._cstr等方式來引用一個對象的指針成員所指的內存。但這并不是說在類的外面訪問成員變量,只是為了說明的方便和清晰而已。為了方便代碼的閱讀,類的成員變量或私有函數都以下劃線“_”開頭。

下面就來一個函數一個函數地解釋其實現方式。
1)默認構造函數
[cpp]?view plain?copy
  1. String::String():??
  2. ????_cstr(NULL),??
  3. ????_used(new?size_t(1)),??
  4. ????_length(0),??
  5. ????_capacity(0)??
  6. {??
  7. }??
這里需要注意的地方就是,在默認初始化中,我們并不使用new來申請內存,而是直接把_cstr置為NULL,這樣做是因為我們不知道程序接下來會做什么動作,貿然為其分配內存是不合理的。例如,對于如下操作,則無需分配內存,
[cpp]?view plain?copy
  1. String?s1("abc");??
  2. String?s2;??
  3. s2?=?s1;??
根據隱式共享的原則,只需要把s2._cstr的值賦為s1._cstr即可。而為什么沒有為對象分配內存,而*_used的值卻為1呢?這里只要是為了操作的統一,考慮上面的語句s2 = s1,其產生的操作應該是把s2._used所指向的內存數據(引用計數)的值減1,因為s2._cstr不再指向原先的字符數據。s1._used所指向的內存數據的值加1。若*s2._userd的值為0,就釋放s2._userd和s2._cstr所指向的內存。而如果在這里,*s2._userd的初始值為0,0減1就會變成-1,而_userd是一個無符號整數的指針,它的值就會變成2^32-1,從而讓程序運行的結果不符合我們的預想。而*s2._userd的初始值為1則可完美地避免這個問題。

2)復制構造函數
[cpp]?view plain?copy
  1. String::String(const?String?&s):??
  2. ????_cstr(s._cstr),??
  3. ????_used(s._used),??
  4. ????_length(s._length),??
  5. ????_capacity(s._capacity)??
  6. {??
  7. ????++*_used;??
  8. }??
本函數非常易懂,就是把s的成員的值全部復制給*this即可,但是由于多了*this這個對象引用s的字符數組,所以應該把該字符數組的引用計數加1。注意,此時this->_used和s._used指向了同一個對象。

3)帶C字串參數的構造函數
[cpp]?view plain?copy
  1. String::String(const?char?*cstr,?size_t?len)??
  2. {??
  3. ????if(cstr?==?NULL)??
  4. ????????return;??
  5. ????size_t?str_len?=?strlen(cstr);??
  6. ????if(len?<=?str_len)??
  7. ????{??
  8. ????????_initString(cstr,?len);??
  9. ????}??
  10. }??
  11. void?String::_initString(const?char?*cstr,?size_t?len)??
  12. {??
  13. ????if(cstr?==?NULL)??
  14. ????????return;??
  15. ????_cstr?=?new?char[len?+?1];??
  16. ????memcpy(_cstr,?cstr,?len);??
  17. ????_cstr[len]?=?0;??
  18. ????_used?=?new?size_t(1);??
  19. ????_length?=?len;??
  20. ????_capacity?=?len;??
  21. }??
該函數非常簡單,由于是構造函數,而且使用的參數是C風格的字符串,所以默認為其字符串一定不是某個對象所引用的字符數組,所以直接為其分配內存,并復制字符。非常明顯,因為是該對象第一個創建該字符數組的,所以其引用為1.

4)帶C風格字符串的構造函數
[cpp]?view plain?copy
  1. String::String(const?char?*cstr)??
  2. {??
  3. ????if(cstr?==?NULL)??
  4. ????????return;??
  5. ????size_t?len?=?strlen(cstr);??
  6. ????_initString(cstr,?len);??
  7. }??
其實現與上原理相同,只是參數不同,不再詳述。

5)析構函數
[cpp]?view plain?copy
  1. String::~String()??
  2. {??
  3. ????_decUsed();??
  4. }??
  5. void?String::_decUsed()??
  6. {??
  7. ????--*_used;??
  8. ????if(*_used?==?0)??
  9. ????{??
  10. ????????if(_cstr?!=?NULL)??
  11. ????????{??
  12. ????????????delete[]?_cstr;??
  13. ????????????_cstr?=?NULL;??
  14. ????????????_length?=?0;??
  15. ????????????_capacity?=?0;??
  16. ????????}??
  17. ????????delete?_used;??
  18. ????????_used?=?NULL;??
  19. ????}??
  20. }??
_decUsed()函數可以說是該類內存釋放的管理函數,可以看到,每當一個對象被析構時,其指向的堆中的字符數組的引用計數就會減1,當引用計數為0時,就釋放字符數組和引用計數。

6)賦值操作函數
[cpp]?view plain?copy
  1. String&?String::operator=(const?String?&s)??
  2. {??
  3. ????++*(s._used);??
  4. ????_decUsed();??
  5. ????_cstr?=?s._cstr;??
  6. ????_length?=?s._length;??
  7. ????_capacity?=?s._capacity;??
  8. ????return?*this;??
  9. }??
該賦值操作函數的參數一個本類的對象,該類賦值操作函數第一個要避免的就是自身賦值的問題,在有指針存在的類中是特別要重視這個問題,而在這個String類也不可例外。為什么這樣說呢?因為我們調用賦值操作函數時,必須要減少左值的引用計數,增加右值的引用計數,這個在第1)點已經說過了,而如果是自身賦值的話,在減少其引用計數時,其引用計數可能為0,從而導致字符數組的釋放,從而讓_cstr指針懸空(delete[]掉了,卻在賦值的過程中,重新賦為delete[]前的值,即_cstr的值沒有在賦值過程中改變)。

一般的程序的做法是判斷參數的地址與this是否相等來避免自身賦值,而這里卻可以采用巧妙的策略來避免這個問題,可以看到上面的代碼并沒有if判斷語句。我們首先對*s._used加1,這樣*s._used至少為2,然后再對*(this->_used)減1,這樣即使s與*this是同一個對象,也可以保證*(this->_used)的值至少為1,不會變為0,從而讓字符數組不會被釋放。因為復制是使用隱式共享的,所以直接復制指針,使指針_cstr其指向與s同一個存在中的字符數組并復制其他的數據成員即可。

同時,我們還要記得返回當前對象的引用。

7)重載的賦值操作函數
[cpp]?view plain?copy
  1. String&?String::operator=(const?char?*cstr)??
  2. {??
  3. ????if(cstr?!=?NULL)??
  4. ????{??
  5. ????????_decUsed();??
  6. ????????size_t?len?=?strlen(cstr);??
  7. ????????_initString(cstr,?len);??
  8. ????}??
  9. ????return?*this;??
  10. }??
該賦值操作函數的參數一個C風格的字符串,因而不會發生自身賦值的問題。與String(const char *cstr)函數相似,唯一不同的是使用賦值操作函數時,對象已經存在,所以要調用_decUsed來減少該對象的_cstr原先指向的字符數組的引用計數。然后生成根據cstr創建一個全新的字符數組。并返回當前對象的引用。

8)重載+=操作符,實現字符串連接
[cpp]?view plain?copy
  1. String&?String::operator+=(const?String?&s)??
  2. {??
  3. ????_addAssignOpt(s._cstr,?s._length);??
  4. ????return?*this;??
  5. }??
  6. String&?String::operator+=(const?char?*cstr)??
  7. {??
  8. ????if(cstr?!=?NULL)??
  9. ????????_addAssignOpt(cstr,?strlen(cstr));??
  10. ????return?*this;??
  11. }??
  12. ??
  13. void?String::_addAssignOpt(const?char?*cstr,?size_t?len)??
  14. {??
  15. ????if(*_used?==?1)??
  16. ????????_addString(cstr,?len);??
  17. ????else??
  18. ????{??
  19. ????????_decUsed();??
  20. ????????_cstr?=?_renewAndCat(cstr,?len);??
  21. ????????_used?=?new?size_t(1);??
  22. ????}??
  23. }??
  24. void?String::_addString(const?char?*cstr,?size_t?len)??
  25. {??
  26. ????//本函數,只有在引用計數為1時,才可用??
  27. ????if(*_used?!=?1)??
  28. ????????return;??
  29. ????if(len?+?_length?>?_capacity)??
  30. ????{??
  31. ????????char?*ptr?=?_renewAndCat(cstr,?len);??
  32. ????????delete[]?_cstr;??
  33. ????????_cstr?=?ptr;??
  34. ????}??
  35. ????else??
  36. ????{??
  37. ????????strncat(_cstr,?cstr,?len);??
  38. ????????_length?+=?len;??
  39. ????}??
  40. }??
  41. char*?String::_renewAndCat(const?char?*cstr,?size_t?len)??
  42. {??
  43. ????size_t?new_len?=?len?+?_length;??
  44. ????size_t?capacity?=?new_len;??
  45. ????capacity?+=?(capacity?>>?1);??
  46. ????char?*ptr?=?new?char[capacity+1];??
  47. ????if(_cstr?!=?NULL)??
  48. ????????memcpy(ptr,?_cstr,?_length);??
  49. ????ptr[_length]?=?0;??
  50. ????_length?=?new_len;??
  51. ????_capacity?=?capacity;??
  52. ????strncat(ptr,?cstr,?len);??
  53. ????return?ptr;??
  54. }??
+=是一個復雜的操作,也是我實現時琢磨得最久的操作。因為它是寫的操作,根據寫時拷貝的原則,它需要減少其原先字符數組的引用計數,同時創建一個新的字符數組來儲存增加長度后的字符串。并且該對象所引用的字符數組可能只有該對象自己在引用,也就是說其引用計數為1,此時減少其引用計數還可能導致原先字符數組的釋放,從而丟失數據,并在使用指向原先字符數組的指針進行數據復制時發生錯誤。所以引用計數是否為1應該采用不同的策略。

而當引用計數為1時,我們可以認為該對象獨立享有該字符數組,可以對其進行任何操作而不影響其他對象,這時,我們可以把字符串直接追加到已經的字符數組的后面,而這樣做可能因為字符數組的容量不夠而不能進行,這時為字符數組的重新分配合適的空間。

當引用計數不為1時,我們首先調用_decUsed()來減少原字符數組的引用計數,然后調用_renewAndCat來連接并產生新的字符數組,然后重置_cstr的指向,并new一個新的引用計數,初始值置為1.

上面的函數中,_renewAndCat的功能就是分配新的字符數組,同時把原先的字符數據復制到新的字符數組中,再在新的字符數組中追加字符串,返回新的字符數組的首地址。_addString是只有當引用計數為1時才能調用的函數,其字符數組足以容納連接后的字符串,則直接連接,若不能,則調用_renewAndCat重新分配合適的字符數組,并進行復制,最后,把舊的字符數組delete[]掉,再把_cstr賦值為_renewAndCat創建的新字符數組的首地址。

注:本人認為,如果一個字符串對象做了一次+=運算,那么它很可能會很快做第二次,所以在分配內存時,我采用了預分配的策略,每次分配都分配連接完成后的字符串的長度的1.5倍。這樣當下一次執行+=時,字符數組就可能有足夠多的容量來保存連接后的字符串,而不用重新分配和復制。而且我們注意到,當調用+=一次之后,*_used肯定為1,即下次運行+=時,是極有可能直接加到字符數組的后面的。

為了提高程序的運行效率,在進行1.5倍的預分配時,沒有使用浮點數乘法,更沒有使用除法,而是采用了移位運算,如下:
int capacity = len;
capacity += (capacity >> 1)
其對應的數學表達式為:“x = capacity + capacity/2;capacity = x”。因為右移運算相當于除以2,這樣就實現了乘以1.5的運算操作。

9)清空字符串
[cpp]?view plain?copy
  1. void?String::clear()??
  2. {??
  3. ????_decUsed();??
  4. ????_cstr?=?NULL;??
  5. ????_used?=?new?size_t(1);??
  6. ????_length?=?0;??
  7. ????_capacity?=?0;??
  8. }??
該函數用于清除字符串對象引用的字符數組的數據,所以我們只需要調用_decUsed函數,減少對象所引用的字符數組的引用計數,并把其他成員變量設置為默認的值即可。即與默認構造函數所設置的值一致。


注:以下函數不是String的成員函數
10)重載+操作符
[cpp]?view plain?copy
  1. String?operator?+(const?String?&lhs,?const?String?&rhs)??
  2. {??
  3. ????String?stemp(lhs);??
  4. ????stemp?+=?rhs;??
  5. ????return?stemp;??
  6. }??
該函數的實現可以借助上面實現的+=操作符,先用第一個對象rhs復制構造一個臨時對象stemp,然后通過把第二個參數追加到臨時對象stemp上,返回stemp即可簡單輕松地實現+操作符的重載。

11)重載輸出操作符
[cpp]?view plain?copy
  1. ostream&?operator?<<?(ostream?&os,?const?String?&s)??
  2. {??
  3. ????os<<s.cstr();??
  4. ????return?os;??
  5. }??
12)重載輸入操作符
[cpp]?view plain?copy
  1. istream&?operator?>>?(istream?&in,?String?&s)??
  2. {??
  3. ????const?int?BUFFER_SIZE?=?256;??
  4. ????char?buffer[BUFFER_SIZE];??
  5. ????char?*end?=?buffer?+?BUFFER_SIZE?-1;??
  6. ????s.clear();??
  7. ????do??
  8. ????{??
  9. ????????//用于判斷是否讀完輸入內容,因為如果還未讀取的輸入字符數大于buffer??
  10. ????????//的容量,則buffer的最后一個字符會被get函數置為'\0'??
  11. ????????*end?=?'#';??
  12. ????????in.get(buffer,?BUFFER_SIZE);??
  13. ????????s?+=?buffer;??
  14. ????}while(*end?==?'\0');??
  15. ????in.get();??
  16. ????return?in;??
  17. }??
實現輸入操作符的重載的一個困難之處就是我們不知道用戶要輸入的字符串的長度,也就不知道應該分配一個多大的緩沖區來接收輸入的字符。所以在這里,設置一個一定大小的緩沖,采用循環讀取,連續添加到字符串對象中的方法來實現。那么如何知道該循環讀取輸入多少次呢?也就是說,怎么知道已經把所有的輸入字符讀取完畢呢?在這里,我使用了一個標準輸入流istream的get成員函數,該成員函數從輸入流中讀取指定個數的字符或遇到輸入流結束而返回,注意最后它會自動加入一個空字符‘\0’作為結束。這個空字符也作為讀入的字符數量的計數。例如,如果有一個大小為6的char型數組作為buffer,從標準輸入流中讀入6個字符,實際上只會從標準輸入中讀入最多5個字符(因為可能遇到流結束),并把空字符‘\0’加入到buffer的末尾。

所以我們可以把buffer的最后一個字節,設置成我們自己特定的一個字符(只是是非‘\0’即可),如這里的'#',然后讀入buffer大小的字符數。若還沒有讀取完畢,我們設置的這個特殊的字符會被空字符'\0'覆蓋,我們從而知道,還沒讀取完標準輸入的數據。若我們設置的特殊字符沒有被覆蓋,就說明,讀到的數據不足以填滿buffer,也就是說,我們已經沒有數據可讀了,從而可以判斷已經讀取完所有輸入的字符。

注:輸入也是一個寫的操作,并且會把對象之前的內容覆蓋掉,所以在輸入到對象之前,要先調用clear成員函數,把對象清空。

四、測試代碼
[cpp]?view plain?copy
  1. #include?<iostream>??
  2. #include?"_stringv2.h"??
  3. using?std::cin;??
  4. using?std::cout;??
  5. using?std::cin;??
  6. using?std::endl;??
  7. int?main()??
  8. {??
  9. ????String?s1;??
  10. ????s1?=?"abc";??
  11. ????{??
  12. ????????String?s2(s1);??
  13. ????????s2?+=?s1;??
  14. ????????cout?<<?s2?<<?endl;??
  15. ????}??
  16. ????String?s3(s1);??
  17. ????cin?>>?s3;??
  18. ????cout?<<?s3?<<?endl;??
  19. ????cout?<<?s1?<<?endl;??
  20. ????String?s4?=?s1?+?s3;??
  21. ????cout?<<?s4?<<?endl;??
  22. }??
運行結果如下:

五、代碼分析
首先定義一個String的對象s1,s1調用默認構造函數,生成一個默認的對象,然后調用賦值操作函數,為s1分配堆內存字符數組。對象s2是以對象s1的樣本復制構造出來的對象,其作用域只在花括號內。我們可以看到s2的改變并沒有影響到s1。其他的調用也一樣,從而可以看到是實現了隱式共享,寫時拷貝。從運行的結果可以看出,一切的運行都是沒有問題的,與標準庫的string的輸出一致。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383778.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383778.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383778.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++類模板實例化條件

&#xff08;我不想了解這個&#xff0c;可是考試要考。。。。 并不是每次使用模板類都會實例化一個類 聲明一個類模板的指針和引用不會引起類模板的實例化如果檢查這個指針或引用的成員時時&#xff0c;類模板會實例化定義一個對象的時候需要有類的定義&#xff0c;會實例化…

C++ String類寫時拷貝 4

http://blog.51cto.com/zgw285763054/1839752 維基百科&#xff1a; 寫入時復制&#xff08;英語&#xff1a;Copy-on-write&#xff0c;簡稱COW&#xff09;是一種計算機程序設計領域的優化策略。其核心思想是&#xff0c;如果有多個調用者&#xff08;callers&#xff09;同時…

C++筆試復習

基礎知識點 C中對象數組在定義的時候全部進行實例化&#xff08;與Java不同&#xff0c;Java相當于只是定義了一個指針數組&#xff0c;沒有進行實例化&#xff09; 程序的三種基本控制結構是&#xff1a;順序結構、循環結構、選擇結構 一個C程序開發步驟通常包括&#xff1a…

C++函數默認參數

聲明是用戶可以看到的部分&#xff0c;客戶非常信任地使用這個特性&#xff0c;希望得到一定的結果&#xff0c;但是你在實現里使用了不同的缺省值&#xff0c;那么將是災難性的。因此編譯器禁止聲明和定義時同時定義缺省參數值。 類的成員函數的參數表在聲明時默認參數位于參…

C語言鏈表各類操作詳解

http://blog.csdn.net/pf4919501/article/details/38818335鏈表概述   鏈表是一種常見的重要的數據結構。它是動態地進行存儲分配的一種結構。它可以根據需要開辟內存單元。鏈表有一個“頭指針”變量&#xff0c;以head表示&#xff0c;它存放一個地址。該地址指向一個元素。…

Java筆試復習

Java程序運行 Java程序的執行必須經過編輯、編譯和運行三個步驟 編輯指編寫代碼&#xff0c;最終形成后綴名為.java的Java源文件編譯指使用Java編譯器&#xff08;javac指令&#xff09;將源文件翻譯為二進制代碼&#xff0c;編譯后生成后綴名為.class的字節碼文件&#xff0c…

數據結構之自建算法庫——鏈棧

http://blog.csdn.net/sxhelijian/article/details/48463801本文針對數據結構基礎系列網絡課程(3)&#xff1a;棧和隊列中第4課時棧的鏈式存儲結構及其基本運算實現。 按照“0207將算法變程序”[視頻]部分建議的方法&#xff0c;建設自己的專業基礎設施算法庫。 鏈棧算法庫采用…

Java類名與包名不區分大小寫

剛才寫了一個簡單的Java程序&#xff0c;經過測試得到一個令人震驚的結論&#xff1a;Java類名和包名是不區分大小寫的 可以看一下這個例子&#xff1a; package Test;class aBcdEfG {}class AbCdefg {}public class TTT {public static void main(String[] args){AbCdefg tm…

epoll實現高并發聊天室

http://blog.csdn.net/qq_31564375/article/details/51581038項目介紹 本項目是實現一個簡單的聊天室&#xff0c;聊天室分為服務端和客戶端。本項目將很多復雜的功能都去掉了&#xff0c;線程池、多線程編程、超時重傳、確認收包等等都不會涉及。總共300多行代碼&#xff0c;讓…

BZOJ2809-左偏樹合并

Description 在一個忍者的幫派里&#xff0c;一些忍者們被選中派遣給顧客&#xff0c;然后依據自己的工作獲取報償。在這個幫派里&#xff0c;有一名忍者被稱之為 Master。除了 Master以外&#xff0c;每名忍者都有且僅有一個上級。為保密&#xff0c;同時增強忍者們的領導力&a…

處理大并發之一 對epoll的理解,epoll客戶端服務端代碼

http://blog.csdn.net/zhuxiaoping54532/article/details/56494313處理大并發之一對epoll的理解&#xff0c;epoll客戶端服務端代碼序言&#xff1a;該博客是一系列的博客&#xff0c;首先從最基礎的epoll說起&#xff0c;然后研究libevent源碼及使用方法&#xff0c;最后研究n…

epoll詳解

http://blog.csdn.net/majianfei1023/article/details/45772269歡迎轉載&#xff0c;轉載請注明原文地址&#xff1a;http://blog.csdn.net/majianfei1023/article/details/45772269一.基本概念&#xff1a;1.epoll是什么&#xff1a;epoll是Linux內核為處理大批量文件描述符而…

數據分割-并查集+set

小w來到百度之星的賽場上&#xff0c;準備開始實現一個程序自動分析系統。 這個程序接受一些形如xixj 或 xi≠xj 的相等/不等約束條件作為輸入&#xff0c;判定是否可以通過給每個 w 賦適當的值&#xff0c;來滿足這些條件。 輸入包含多組數據。 然而粗心的小w不幸地把每組數據…

linux c++線程池的實現

http://blog.csdn.net/zhoubl668/article/details/8927090?t1473221020107 線程池的原理大家都知道&#xff0c;直接上代碼了^_^ Thread.h [cpp] view plaincopy #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #inc…

樹啟發式合并入門

所謂啟發式合并&#xff0c;就是一種符合直覺的合并方法&#xff1a;將小的子樹合并在大的子樹上。 這些問題一般是相似的問題背景&#xff1a;都是樹上的計數問題&#xff0c;都不能直接從上往下進行暴力&#xff0c;都需要從下往上計數時對子樹信息進行運算從而得到父親節點的…

鏈棧基本操作

http://blog.csdn.net/jwentao01/article/details/46765517###;棧基本概念&#xff1a; 棧&#xff08;stack&#xff09;是限定在表尾進行插入和刪除操作的線性表&#xff08;或單鏈表&#xff09;。 //只能在一段進行插入和刪除&#xff0c;因此不存在&#xff0c;在中間進行…

Linux網絡編程---I/O復用模型之select

https://blog.csdn.net/men_wen/article/details/53456435Linux網絡編程—I/O復用模型之select 1. IO復用模型 IO復用能夠預先告知內核&#xff0c;一旦發現進程指定的一個或者多個IO條件就緒&#xff0c;它就通知進程。IO復用阻塞在select或poll系統調用上&#xff0c;而不是阻…

UVa12633-Super Rooks on Chessboard-容斥+FFT

題目大意就是給你一個R*C的棋盤&#xff0c;上面有超級兵&#xff0c;這種超級兵會攻擊 同一行、同一列、同一主對角線的所有元素&#xff0c;現在給你N個超級兵的坐標&#xff0c;需要你求出有多少方塊是不能被攻擊到的(R,C,N<50000) 遇到這種計數問題就要聯想到容斥&#…

Linux網絡編程---I/O復用模型之poll

https://blog.csdn.net/men_wen/article/details/53456474Linux網絡編程—I/O復用模型之poll 1.函數poll poll系統調用和select類似&#xff0c;也是在指定時間內輪詢一定數量的文件描述符&#xff0c;以測試其中是否有就緒者。 #include <poll.h>int poll(struct pollfd…

FFT模板

整理了一下&#xff0c;自己寫了一下模板 const double PIacos(-1.0); struct complex {double r,i;complex(double _r0,double _i0):r(_r),i(_i){}complex operator (const complex &b) {return complex(rb.r,ib.i);}complex operator -(const complex &b) {return c…