第十二章 类和动态内存分配
静态成员变量
不能在类声明中初始化静态成员变量(声明描述了如何分配内存,但并不分配内存);
可以在类声明之外(在方法文件中)用单独的语句初始化静态成员变量;
初始化语句必须指出类型,并使用作用域解析运算符,但不使用关键字static。
C++仅允许对const整数类型的静态数据成员和枚举类型常量在类声明中初始化。
特殊成员函数
默认构造函数
默认析构函数
默认复制构造函数
(
何时调用:
1. 将新对象初始化为一个同类对象(显式调用复制构造函数);
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运算符在释放空间的时候:
必须显式地按对象创建的反向顺序使用析构函数,最后再释放缓存区
(因为delete [] 删除缓存区后,并不会主动调用缓存区中对象的析构函数)。
某些老式的编译器不支持类内嵌套结构体和类。(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++11 使用关键字delete实现
类声明中数据成员的初始化(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 }