原型:void*memcpy(void*dest, const void*src,unsigned int count);
功能:由src所指內存區域復制count個字節到dest所指內存區域。
說明:src和dest所指內存區域不能重疊,函數返回指向dest的指針。
看一下這個函數的解釋:
Example:
/* memcpy example */ #include <stdio.h> #include <string.h> struct { char name[40]; int age; } person, person_copy; int main () { char myname[] = "Pierre de Fermat"; /* using memcpy to copy string: */ memcpy ( person.name, myname, strlen(myname)+1 ); person.age = 46; /* using memcpy to copy structure: */ memcpy ( &person_copy, &person, sizeof(person) ); printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age ); return 0; }
Output:
person_copy: Pierre de Fermat, 46
關於 淺拷貝 和 深拷貝
在對象拷貝過程中,如果沒有自定義拷貝構造函數,系統會提供一個缺省的拷貝構造函數。缺省的拷貝構造函數對於基本類型的成員變量,按字節復制;對於類類型成員變量,調用其相應類型的拷貝構造函數。
淺拷貝
位拷貝,編譯器只是直接將指針的值拷貝過來。多個對象共用同一塊內存資源,若同一塊資源釋放多次,會發生崩潰或者內存泄漏。
示例:
class A { public: A() { name = new char[20]; std::cout << "A()" << std::endl; } ~A(){ std::cout << "~A()" << std::endl; delete name; name = NULL; } private: int num; char* name; }; int main(int argc, char *argv[]) { A a1; A a2(a1); // 淺拷貝 return 0; }
執行結果:程序調用一次構造函數,二次析構函數后,發生崩潰。name指針分配一次內存,該內存卻被釋放兩次,導致程序崩潰。
原因即是上面所述,在對象拷貝過程中,系統發現我們沒有自定義拷貝構造函數,會使用默認缺省構造函數進行淺拷貝。所以對name指針進行拷貝后,出現兩個指針指向同一塊內存。
這個時候我們就需要使用深拷貝。
深拷貝
每個對象擁有自己的資源,顯式提供拷貝構造函數。
class A { public: A() { name = new char[20]; std::cout << "A()" << std::endl; } A(const A &a) { name = new char[20]; memcpy(name, a.name, strlen(a.name)); } ~A(){ std::cout << "~A()" << std::endl; delete name; name = NULL; } private: int num; char* name; }; int main(int argc, char *argv[]) { A a1; A a2(a1); // 淺拷貝 return 0; }
執行結果:程序調用一次構造函數,一次自定義拷貝構造函數,兩次析構函數。兩個對象的指針成員所指內存不同。
總結:
- 淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一塊內存空間;
- 深拷貝不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經深拷貝后的指針是指向兩個不同地址的指針;
拷貝構造函數與賦值函數
這兩個非常容易混淆,常導致錯寫、錯用。拷貝構造函數是在對象被創建時調用的,而賦值函數只能被已經存在了的對象調用。
示例:
string a("hello"); string b("world"); string c = a; // 拷貝構造函數. 風格較差,最好寫成string c(a); c = b; // 賦值函數
c++11拷貝控制
1、= default
可以通過將拷貝控制成員定義為= default,顯式地要求編譯器生成它們的合成版本:
class A { public: A() = default; A(const A&) = default; A &operator=(const A&); ~A() = default; };
當我們在類體內的成員聲明中指定= default時,編譯器生成的合成函數是隱式內聯的(就像在類體中定義的任何其他成員函數一樣)。如果我們不希望合成函數是內聯函數,我們可以在該函數的定義上指定= default,就像在重載=運算符的定義中那樣。
注:我們只能對具有合成版本的成員函數使用= default(即默認構造函數或拷貝控制成員)。
2、= delete 阻止拷貝類對象
在新標准下,可以通過將拷貝構造函數和賦值運算符定義為已刪除函數來阻止復制。已刪除的函數是已聲明的函數,但不能以任何其他方式使用。使用= delete跟隨想要刪除的函數的參數列表來將函數定義為已刪除:
class A { public: A(); A(const A&) = delete; A &operator=(const A&) = delete; ~A(); };
= delete關鍵字既告訴編譯器又告訴代碼閱讀者,故意沒有定義這些成員。
與= default不同,= delete必須出現在已刪除函數的第一個聲明中。從邏輯上講,這種差異源自這些聲明的含義。默認成員只影響編譯器生成的代碼;因此在編譯器生成代碼之前不需要= default。另一方面,編譯器需要知道一個函數被刪除,以禁止試圖使用它的操作。
與= default不同,可以在任何函數上指定= delete(可以在默認構造函數或編譯器可以合成的拷貝控件成員上使用= default)。雖然刪除函數的主要用途是抑制拷貝控制成員,但是當想要引導函數匹配過程時,刪除函數有時也很有用。