class with pointer menbers

string_test.cpp

  1. 1 #include "string.h"
  2. 2 #include <iostream>
  3. 3
  4. 4 using namespace std;
  5. 5
  6. 6 int main()
  7. 7 {
  8. 8 String s1("hello");
  9. 9 String s2("world");
  10. 10
  11. 11 String s3(s2);
  12. 12 cout << s3 << endl;
  13. 13
  14. 14 s3 = s1;
  15. 15 cout << s3 << endl;
  16. 16 cout << s2 << endl;
  17. 17 cout << s1 << endl;
  18. 18 }

string.h

  1. 1 #ifndef __MYSTRING__
  2. 2 #define __MYSTRING__
  3. 3
  4. 4 class String
  5. 5 {
  6. 6 public:
  7. 7 String(const char* cstr=0);
  8. 8 String(const String& str);
  9. 9 String& operator=(const String& str);
  10. 10 ~String();
  11. 11 char* get_c_str() const { return m_data; }
  12. 12 private:
  13. 13 char* m_data;
  14. 14 };
  15. 15
  16. 16 #include <cstring>
  17. 17
  18. 18 inline
  19. 19 String::String(const char* cstr)
  20. 20 {
  21. 21 if (cstr) {
  22. 22 m_data = new char[strlen(cstr)+1];
  23. 23 strcpy(m_data, cstr);
  24. 24 }
  25. 25 else {
  26. 26 m_data = new char[1];
  27. 27 *m_data = '\0';
  28. 28 }
  29. 29 }
  30. 30
  31. 31 inline
  32. 32 String::~String()
  33. 33 {
  34. 34 delete[] m_data;
  35. 35 }
  36. 36
  37. 37 inline
  38. 38 String& String::operator=(const String& str)
  39. 39 {
  40. 40 if (this == &str)
  41. 41 return *this;
  42. 42
  43. 43 delete[] m_data;
  44. 44 m_data = new char[ strlen(str.m_data) + 1 ];
  45. 45 strcpy(m_data, str.m_data);
  46. 46 return *this;
  47. 47 }
  48. 48
  49. 49 inline
  50. 50 String::String(const String& str)
  51. 51 {
  52. 52 m_data = new char[ strlen(str.m_data) + 1 ];
  53. 53 strcpy(m_data, str.m_data);
  54. 54 }
  55. 55
  56. 56 #include <iostream>
  57. 57 using namespace std;
  58. 58
  59. 59 ostream& operator<<(ostream& os, const String& str)
  60. 60 {
  61. 61 os << str.get_c_str();
  62. 62 return os;
  63. 63 }
  64. 64
  65. 65 #endif

注意的點

  • string_test.cpp:11拷貝構造,14拷貝賦值
  • string.h:8拷貝構造(建構函式接受自己),9拷貝賦值,10解構函式
  • 不知道將來要建立的物件多大,所以只放一根指標,再動態建立空間放物件
  • 類中帶指標,要關注三個特殊函式(Big Three)
  • 結束符號“\0”判斷字串結束
  • 21判斷是否空指標
  • 22:new:,動態分配一塊記憶體
  • 31-35:delete,解構函式,防止記憶體洩露,變數離開作用域時自動呼叫
  • 11的get_c_str()是為了配合輸出函式

拷貝構造(copy ctor)

  • String a("Hello"); String b("World"); b = a;
  • 如果沒有構造拷貝,會發生b和a的指標都指向“Hello”,“World”沒有指標指向,導致記憶體洩露
  • 而且a、b一個改動會影響另一個,別名在程式設計中是危險的事
  • 這種拷貝方式稱為“淺拷貝”,是編譯器的預設版本
  • copy ctor 實現“深拷貝”,建立足夠的新空間存放藍本
  • String s2(s1); 與 String s2 = s1; 效果相同

拷貝賦值(copy op =)

  • String s1("hello"); String s2(s1); s2 = s1;
  • 過程:43刪除左值--44開闢新空間--45拷貝右值
  • 40-41:檢測自我賦值(self assignment),來源端和目的端是否相同
  • 這步的意義不只是提高效率,不這樣寫會出錯
  • 因為43會把原空間刪掉,導致45行復制的時候訪問空指標

棧(stack)和堆(heap)

  • {Complex c1(1,2);}
  • {Complex* p = new Complex(3);
  • delete p;}
  • stack:存在於某作用域(scope)的一塊記憶體空間,呼叫函式時,函式本身即形成一個stack用來防止它接受的引數,以及返回地址,離開作用域後,解構函式被自動呼叫(aoto object)
  • heap:system heap,作業系統提供的一塊global記憶體空間,程式可以動態分配(dynamic allocated)從中獲得若干區域,使用完後需手動釋放空間
  • {static Complex c1(1,2);}
  • 靜態物件,離開作用域後物件仍存在,組用域是整個程式
  • 全域性物件,寫在任何作用域之外(全域性作用域之中),也可看做一種靜態物件
  • new:先分配memory,再呼叫ctor,分解為以下三個函式
  • void* mem = operator new(sizeof(Complex)); // 分配記憶體,operator new呼叫malloc(n)
  • p = static_cast<Complex*>(mem); // 轉型
  • p->Complex::Complex(1,2); // 建構函式,Complex::Complex(pc,1,2);
  • delete:先呼叫解構函式,再釋放記憶體
  • String::~String(p); // 把字串裡面動態分配的記憶體刪掉(字串本身只是指標m_data)
  • operator delete(p); // 內部呼叫free(p),刪掉字串本身的指標

new到底分配多少記憶體

  • 除錯模式下加上debug header 32byte,正常模式下中間為資料(一根指標4byte),上下為cookies 2*4byte,湊夠16的倍數
  • 動態分配所得的array
  • new []:array new
  • delete[]:array delete
  • array new 一定要搭配 array delete
  • String* p = new String[3]; delete[] p; // 喚起3次dtor
  • String* p = new String[3]; delete p; // 喚起1次dtor,另外2個指標指向的動態記憶體資料無法刪除(注意指標是可以刪除的)
  • 雖然new complex的時候不會產生此問題,但也要注意搭配使用,養成好習慣

參考:

const在函式前面與後面的區別

https://blog.csdn.net/qq_25800311/article/details/83054129