第十二章 類和動態內存分配
靜態成員變量
不能在類聲明中初始化靜態成員變量(聲明描述了如何分配內存,但並不分配內存);
可以在類聲明之外(在方法文件中)用單獨的語句初始化靜態成員變量;
初始化語句必須指出類型,並使用作用域解析運算符,但不使用關鍵字static。
C++僅允許對const整數類型的靜態數據成員和枚舉類型常量在類聲明中初始化。
特殊成員函數
默認構造函數
默認析構函數
默認復制構造函數
(
何時調用:
1. 程序生成對象的副本時(按值傳遞對象或函數返回對象);
2. 編譯器生成臨時對象時(如:三個對象相加(連續運算)時,編譯器會生成臨時對象來保存中間結果)。
默認構造函數逐個賦值非靜態成員(淺賦值),當成員中有指針時(由new初始化),只會復制地址,而不復制內容,當此地址被清理時,此成員所指內容將會亂碼,應定義一個復制內存的復制構造函數(深度復制)
)
默認賦值運算符(只能由類成員函數重載)
地址運算符(隱式地址運算符返回調用對象的地址(this指針))
C++11提供了兩個特殊成員函數:
移動構造函數
移動賦值運算符
字面值0可以表示空指針(C程序員經常使用NULL),C++引入了關鍵字nullptr來表示空指針。
靜態類成員函數
可以將成員函數聲明為靜態的(函數聲明必須包含關鍵字static,如果函數定義是獨立的(非內聯),則不能包含關鍵字static)。
靜態函數在方法文件中的定義相似於普通成員函數。
靜態函數的特點:
1. 不能通過對象調用靜態成員函數(在公有部分聲明的靜態成員函數可以使用類名和作用域解析運算符來調用它, 此時的類名相當於命名空間);
2. 靜態成員函數只能使用靜態數據成員,不能訪問其它數據成員。
使用new初始化對象
Class_name* pclass = new Class_name(value); //value的類型是Type_name(會調用某些構造函數)
使用new初始化的對象在調用delete時,將自動隱式地調用析構函數。
定位到自由存儲區用來存儲對象的定位new運算符在釋放空間的時候:
必須顯式地按對象創建的反向順序使用析構函數,最後再釋放緩存區。
某些老式的編譯器不支持類內嵌套結構體和類。(VS 2010不支持)
成員初始化列表
對於const數據成員,必須在執行到構造函數體之前對它進行初始化,C++使用列表初始化語法來完成此項工作。
const類成員和被聲明為引用的類成員必須使用這種語法。因為引用和const數據成員一樣,只能在創建時進行初始化。
這種語法只能用於構造函數。
C++11允許在類內初始化。
class Classy { int mem1 = 10;//類內初始化 const int mem2 = 20;//類內初始化 //... }; //等價於成員初始化列表 Classy::Classy() : mem1(10), mem(20) { ... } //C++11類內初始化過的類,覆蓋成員函數的默認值 Classy::Classy(int n) : mem1(n) { ... }
類聲明中數據成員的初始化(C++98)
作用域為類的普通變量(可以使用成員初始化列表)
作用域為類的靜態常量(static const,允許在類聲明內初始化)
作用域為類的靜態變量(在方法文件中初始化)
作用域為類的枚舉常量(允許在類聲明內初始化)
作用域為類的const常量(必須使用成員初始化列表)
作用域為類的引用(必須使用成員初始化列表)
用鏈表實現隊列的代碼
類聲明
1 #ifndef CLASS_H_ 2 #define CLASS_H_ 3 #include<iostream> 4 //用鏈表實現隊列(先進先出) 5 //註:鏈表的方向為從前端指向後端,最後一個節點指向空指針(0)。 6 //插入隊列為更新後端節點指針;從隊列中刪除為更新前端節點指針。 7 8 typedef double Item;//Item為項目類型的別名 9 10 struct Node {Item item; Node* next;};//VS2010不允許類內定義結構體 11 12 class Quene 13 { 14 private: 15 // struct Node {Item item; Node* next;}; 16 Node* front; 17 Node* rear; 18 const int MAX_QUENE; 19 int items; 20 public: 21 // Quene(Item ); 22 Quene(); 23 ~Quene(); 24 bool enquene(Item); 25 bool dequene(); 26 void show_items() const; 27 };//第二次犯錯,忘寫‘;’號,報構造函數的錯誤 28 29 #endif
方法文件
1 #include "CLASS.h" 2 3 Quene::Quene() : MAX_QUENE(20) 4 { 5 front = 0;//起點和終點都設置為空指針 6 rear = 0;// 7 items = 0; 8 } 9 10 Quene::~Quene() 11 { 12 Node* temp; 13 int n = 0; 14 if(front != 0) 15 { 16 temp = front; 17 front = front -> next; 18 delete temp; 19 } 20 } 21 22 bool Quene::enquene(Item it) 23 { 24 if(items > MAX_QUENE) 25 return false; 26 Node* temp = new Node; 27 temp -> item = it; 28 temp -> next = 0; 29 if(front == 0) 30 front = temp; 31 else 32 rear -> next = temp; 33 rear = temp; 34 ++items; 35 return true; 36 } 37 38 bool Quene::dequene() 39 { 40 41 if(front == 0) 42 return false; 43 Node* temp = front; 44 front = front -> next; 45 --items; 46 delete temp; 47 if(items == 0) 48 rear = 0; 49 return true; 50 } 51 52 void Quene::show_items() const 53 { 54 std::cout << "have " << items << " ge!" << std::endl; 55 Node *temp; 56 temp = front; 57 std::ios_base::fmtflags orig = std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield); 58 while(temp != 0) 59 { 60 std::cout << temp -> item << " --> " << temp -> next << std::endl; 61 temp = temp -> next; 62 } 63 std::cout.setf(orig); 64 }
練習用的代碼,總結了一些錯誤
頭文件
1 #ifndef a_h_ 2 #define a_h_ 3 4 #include<string> 5 //犯錯2. 將枚舉常量放在了類聲明中,導致在初始化對象時出錯 6 enum sex{boy, girl};//將枚舉常量放在類聲明外面 7 const int S = 10; 8 class Person 9 { 10 private: 11 char* name; 12 static int num_string;//靜態數據成員 13 static int New; 14 sex SEX;//常量 15 public: 16 Person(char*, sex SEX = boy);//構造函數 17 Person();//默認構造函數 18 Person(Person &);//復制構造函數 19 ~Person();//析構函數 20 Person & operator=(Person &);//賦值運算符 21 Person & operator=(char*);//賦值運算符 22 void show_person(); 23 void set_sex(sex); 24 static int Show_num(){return num_string;}//靜態成員函數 25 static int Show_New(){return New;} 26 };//類聲明之後加分號 27 //犯錯3. 類聲明之後的花括號後沒加";"號,註:結構、枚舉、類的大括號後必須加分號 28 #endif
方法文件
1 #include<iostream> 2 #include<cstdlib> 3 #include "a.h" 4 5 int Person::num_string = 0;//初始化靜態成員 6 int Person::New = 0; 7 //構造函數 8 Person::Person(char* nm, sex sx) 9 { 10 std::cout << S << std::endl; 11 int len = strlen(nm); 12 name = new char [len+1]; 13 strcpy(name, nm);//犯錯1. 直接用‘=‘賦值“name = nm”,導致調用delete時出錯 14 SEX = sx; 15 if(sx == boy) 16 { 17 std::cout << "construtor! " << nm << " is a boy." << std::endl; 18 num_string++; 19 } 20 else if(sx == girl) 21 { 22 std::cout << "construtor! " << nm << " is a girl." << std::endl; 23 num_string++; 24 } 25 else 26 std::cout << "The person is bad!!!" << std::endl; 27 } 28 29 30 //默認構造函數 31 Person::Person() 32 { 33 name = new char [1]; 34 name[0] = ‘\0‘; 35 SEX = boy; 36 num_string++; 37 // std::cout << "fault constructor." << std::endl; 38 } 39 40 //復制構造函數 41 Person::Person(Person & person2) 42 { 43 int len = strlen(person2.name); 44 name = new char [len+1]; 45 strcpy(name, person2.name); 46 num_string++; 47 std::cout << "copy constructor." << std::endl; 48 } 49 50 51 //賦值運算符重載 52 Person & Person::operator=(Person & person3) 53 { 54 if(this == &person3) 55 return *this; 56 delete [] name; 57 int len = strlen(person3.name); 58 name = new char [len+1]; 59 strcpy(name, person3.name); 60 std::cout << "object--overload operator \‘=\‘" << std::endl; 61 return *this; 62 } 63 64 65 Person & Person::operator=(char* nm) 66 { 67 delete [] name; 68 int len = strlen(nm); 69 name = new char [len+1]; 70 strcpy(name, nm); 71 std::cout << "char--overload operator \‘=\‘" << std::endl; 72 New++; 73 return *this; 74 } 75 76 77 //析構函數 78 Person::~Person() 79 { 80 if(New > 0) 81 --New; 82 --num_string; 83 std::cout << "destructor!!!!——" << name << std::endl; 84 delete [] name; 85 } 86 87 88 void Person::set_sex(sex se) 89 { 90 SEX = se; 91 } 92 93 void Person::show_person() 94 { 95 char se[20]; 96 if(SEX == boy) 97 strcpy(se, "boy"); 98 else 99 strcpy(se, "girl"); 100 std::cout << name << " --> " << se << std::endl; 101 } 102 103 //int Person::Show_num() 104 //{ 105 // return num_string; 106 //}
實現文件
1 #include<iostream> 2 #include<cstdlib> 3 #include<ctime> 4 #include "a.h" 5 6 int main() 7 { 8 { 9 //復制構造函數和賦值運算符的使用 10 Person BaiLin; 11 Person GongRen("Xiao Wang", sex(1));//將整型轉換為枚舉類型 12 BaiLin = GongRen;//不會調用復制構造函數//註:由於類成員name為指針,因此必須進行深度賦值(默認的賦值運算符是淺賦值,只復制地址,不復制內容) 13 Person LaoShi; 14 LaoShi = "San Mao";//犯錯5. 15 } 16 17 std::cout << "***********-------***********" << std::endl; 18 19 Person *person = new Person [20];//對象數組 20 Person addper; 21 char *Name = new char [20]; 22 int s; 23 int nn = 0; 24 std::cout << "type name: "; 25 26 while(nn++ < 20) 27 { 28 std::cin.get(Name, 20); 29 while(std::cin && std::cin.get() != ‘\n‘) 30 continue; 31 if(!std::cin || Name[0] == ‘\0‘) 32 break; 33 else 34 srand(time(0));//種子//time()接受一個time_t類型的地址,用於存放當前的時間(任務時),0表示空指針 35 int n = rand()%3; 36 sex SEX; 37 if( n == 0) 38 SEX = boy; 39 else 40 SEX = girl; 41 // Person temp(Name, SEX); 42 // person[nn-1] = Person(Name, SEX);//犯錯4. 註:這種寫法將調用析構函數(Person(Name, SEX)創建的Person對象在賦值給對象數組後自動析構) 43 person[nn-1] = Name;//更新New //犯錯5. 重載了賦值運算符(object=object)後,若不定義(object=char*),就不能進行隱式的自動類型轉換(char* --> Person) 44 person[nn-1].set_sex(SEX); 45 std::cout << "type name: "; 46 } 47 48 int n = Person::Show_New(); 49 std::cout << "now have " << n << " person!!" << std::endl; 50 51 // person[0].show_person(); 52 while(--n >= 0) 53 person[n].show_person(); 54 system("pause"); 55 delete [] person; 56 return 0; 57 }
第十二章 類和動態內存分配