C++ Primer Plus 笔记第十二章
类和动态内存分配
本章内容包括:
对类成员使用动态内存分配
隐式和显示复制构造函数
隐式和显示重载赋值运算符
在构造函数中使用 new 所必须完成的工作
使用静态类成员
将定位 new 运算符用于对象
使用指向对象的指针
实现队列抽象数据类型(ADT)
12.1 动态内存和类
C++ 让程序在运行时决定内存分配,而不是在编译时决定;
C++ 使用 new 和 delete 运算符来动态控制内存;
遗憾的是,使用 new 和 delete 运算符将导致许多新的编程问题:
析构函数将是必不可少的;
有时要重载赋值运算符
12.1.1 复习示例和静态类成员
使用 char 指针(而不是 char 数组)来表示姓名:
类声明没有为字符串本身分配存储空间,而是在构造函数中使用 new 来为字符串分配空间;
避免了在类声明中预先定义字符串长度
静态类成员有一个特点: 无论创建了多少对象,程序都只创建一个静态变量副本;
注意: 静态数据成员在类声明中声明,在包含类方法文件中初始化;
初始化时使用作用域运算符来指出静态成员所属的类;
如果静态成员是整型或者枚举型 const,则可以在类声明中初始化
警告: 在构造函数中使用 new 来分配内存时,必须在相应的析构函数中使用 delete 来释放内存;
使用 new [ ] 来分配内存,应该使用 delete [ ] 来释放内存
12.1.2 特殊成员函数
C++ 自动提供的成员函数:
默认构造函数,如果没有定义构造函数;
默认析构函数,如果没有定义;
复制构造函数,如果没有定义;
赋值运算符,如果没有定义;
地址运算符,如果没有定义
默认构造函数不完成任何工作,但使得能够声明数组和未初始化的对象;
默认赋值构造函数和默认赋值运算符使用成员赋值;
默认析构函数不完成任何工作;
隐式地址运算符返回调用对象的地址(即this指针 )
StringBad 类中的问题由 隐式复制构造函数 和 隐式赋值运算符 引起的;
1. 默认构造函数
如果没有提供任何构造函数,C++ 将创建默认的构造函数;
如果定义了构造函数,C++ 将不会定义默认构造函数,如果希望创建对象时不显示的对其进行初始化,则要显示的定义默认构造函数;
只能有一个默认构造函数,编译器不能处理二义性
2. 复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中;
复制构造函数用于初始化过程中,而不是赋值过程中;
类的复制构造函数原型:
Class_name ( const Class_name & );
3. 何时调用复制构造函数
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用:
StringBad ditto ( motto ); // calls StringBad ( const StringBad & )
StringBad metoo = motto; // calls StringBad ( const StringBad & )
StringBad also = StringBad( motto ); // calls StringBad ( const StringBad & )
StringBad * pStringBad = new StringBad ( motto ); // calls StringBad ( const StringBad & )
每当程序生成了对象副本时,编译器都将使用复制构造函数:
函数按值传递对象;
函数返回对象;
编译器生成临时对象
4. 默认的复制构造函数的功能
默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制);
如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象;
静态函数不受影响,因为它们不属于整个类
12.1.3 回到StringBad: 复制构造函数的哪里出现了问题
问题一: 程序使用默认的复制构造函数创建对象,而没有进行计数更新
问题二: 隐式复制构造函数是按值进行复制的,没有生成新的内存空间
1. 定义一个显示复制构造函数以解决问题
解决类设计中这种问题方法是进行深度复制(deep copy):
复制构造函数应当复制字符串并将副本的地址赋给 str 成员,调用析构函数将释放不同的字符串;
StringBad :: StringBad ( const StringBad & st )
{
num_strings ++; // handle static member update
len = st.len; // same length
str= new char [ len + 1 ]; // allot space
std :: strcpy( str, st.str ); // copy string to new location
cout << num_strings << ": \"" << str << "\" object created\n";
}
必须定义复制构造函数的原因,一些类成员是使用 new 初始化的,指向数据的指针而不是数据本身;
警告: 如果类中包含了使用 new 初始化的指针成员,应当定义一个复制构造函数,以指向的数据而不是指针,被称为深度复制
复制的另一种形式(成员复制或浅复制)只是复制指针值,浅复制不会深入“挖掘”以复制指针引用的结构
12.1.4 StringBad 的其他问题: 赋值运算符
C++ 允许类对象赋值, 这是通过自动为类重载赋值运算符实现的,原型如下:
Class_name & Class_name :: operator = ( const Class_name & );
接受并返回一个指向类对象的引用
1. 赋值运算符的功能以及何时使用它
将已有的对象赋给另一个对象时,将使用重载的赋值运算符
初始化对象时,并不一定会使用赋值运算符,使用复制构造函数
StringBad metoo = knot; 实现时可能分两步来处理这条语句:
使用复制构造函数创建一个临时对象;
通过赋值将临时对象的值复制到新对象中
解决赋值的问题
StringBad & StringBad :: operator = ( const StringBad & st )
{
if( this == &st )
return *this;
delete [ ] str;
len = st.len;
str = new char [len + 1];
std :: strcpy( str, st.str );
return *this;
}
12.2 改进后的新 String 类
添加一些说明 String 类工作原理的方法:
int length() const {return len;}
friend bool operator < ( const String &st1, const String &st2 );
friend bool operator > ( const String &st1, const String &st2 );
friend bool operator == ( const String &st1, const String &st2 );
firend operator >> (istream & is, String & st);
char & operator [ ] (int i);
const char & operator [ ] (int i) const;
static int HowMany();
C++ 11 引入新关键字 nullptr,用于表示空指针
12.2.2 比较成员函数
比较操作符重载应用接受两个参数的标准 strcmp() 函数:
如果第一个参数位于第二个参数之前,返回一个负值;
如果第一个参数位于第二个参数之后,返回一个正值;
如果两个参数相等,返回0;
12.2.3 使用括号表示法访问字符
C++ 中,两个中括号组成一个运算符——中括号运算符,可以使用方法 operator [ ] ( );
中括号运算符,一个操作数位于第一个中括号的前面,另一个操作数了位于两个中括号之间; // city[0]
operator 是一个 String 对象,对于 operator [4]:
C++查找名称和特征标于此相同的方法: String :: operator [ ] ( int i );
将 opera[4] 替换为 opera.operator[ ] (4);
opera 对象调用该方法,数组下标成为该函数的参数
12.2.4 静态类成员函数
可以将成员函数声明为静态的:
不能通过对象调用静态成员函数,如果静态成员函数实在公有部分声明的,则可以使用类名和作用域解析运算符调用;
静态成员函数不与特定的对象相关联,因此只能使用静态数据成员
12.2.5 进一步重载赋值运算符
提高将常规字符串复制到 String 对象的效率,最简单的方法是重载赋值运算符,使之能够使用常规字符串
程序清单 12.4 string1.h
1 #ifndef STRING1_H_ 2 #define STRING1_H_ 3 #include<iostream> 4 using std::ostream; 5 using std::istream; 6 7 class String 8 { 9 private: 10 char *str; 11 int len; 12 static int num_strings; 13 static const int CINLIM = 80; // cin input limit 14 public: 15 // 构造函数与其他方法 16 String(const char * s); // 构造函数 17 String(); // 默认构造函数 18 String(const String &); // 复制构造函数 19 ~String(); // 析构函数 20 int length() const { return len; } 21 22 // 重载操作符方法 23 String & operator = (const String &); 24 String & operator = (const char *); 25 char & operator[](int i); 26 const char & operator[](int i) const; 27 28 // 重载操作符的友元 29 friend bool operator < (const String & st1, const String & st2); 30 friend bool operator > (const String & st1, const String & st2); 31 friend bool operator == (const String & st1, const String & st2); 32 friend ostream & operator << (ostream & os, const String & st); 33 friend istream & operator >> (istream & is, String & st); 34 35 // 静态成员函数 36 static int HowMany(); 37 }; 38 39 #endif
程序清单 12.5 string1.cpp
1 #pragma warning(disable:4996) 2 #include<cstring> 3 #include"string1.h" 4 using std::cin; 5 using std::cout; 6 7 // 初始化静态类成员 8 int String::num_strings = 0; 9 10 // 静态方法 11 int String::HowMany() 12 { 13 return num_strings; 14 } 15 16 // 类方法**************************************************** 17 18 // 构造函数传入C标准字符串参数 19 String::String(const char * s) 20 { 21 len = std::strlen(s); 22 str = new char[len + 1]; 23 std::strcpy(str, s); 24 num_strings++; 25 } 26 27 // 默认构造函数 28 String::String() 29 { 30 len = 4; 31 str = new char[1]; 32 str[0] = '\0'; 33 num_strings++; 34 } 35 36 // 复制构造函数 37 String::String(const String & st) 38 { 39 num_strings++; 40 len = st.len; 41 str = new char[len + 1]; 42 std::strcpy(str, st.str); 43 } 44 45 // 析构函数 46 String::~String() 47 { 48 --num_strings; 49 delete[] str; 50 } 51 52 53 54 // 重载操作符方法***************************************** 55 56 // 从 String 类到 String 类赋值 57 String & String::operator = (const String & st) 58 { 59 if (this == &st) 60 return *this; 61 delete[] str; 62 len = st.len; 63 str = new char[len + 1]; 64 std::strcpy(str, st.str); 65 return *this; 66 } 67 68 // 从 C字符串 到 String 类赋值 69 String & String::operator = (const char * s) 70 { 71 delete[] str; 72 len = std::strlen(s); 73 str = new char[len + 1]; 74 std::strcpy(str, s); 75 return *this; 76 } 77 78 // 从非 const String 类读写字符 79 char & String::operator[] (int i) 80 { 81 return str[i]; 82 } 83 84 // 从 const String 类读取字符 85 const char & String::operator[] (int i) const 86 { 87 return str[i]; 88 } 89 90 // 重载操作符友元 91 bool operator < (const String &st1, const String &st2) 92 { 93 return (std::strcmp(st1.str, st2.str) < 0); 94 } 95 96 bool operator > (const String &st1, const String &st2) 97 { 98 return st2 < st1; 99 } 100 101 bool operator == (const String &st1, const String &st2) 102 { 103 return (std::strcmp(st1.str, st2.str) == 0); 104 } 105 106 // 字符串输出 107 ostream & operator << (ostream & os, const String & st) 108 { 109 os << st.str; 110 return os; 111 } 112 // 字符串快速并且直接输入 113 istream & operator >> (istream & is, String & st) 114 { 115 char temp[String::CINLIM]; 116 is.get(temp, String::CINLIM); 117 if (is) 118 st = temp; 119 while (is && is.get() != '\n') 120 continue; 121 return is; 122 }
程序清单 12.6 sayings1.cpp
1 #include<iostream> 2 #include"string1.h" 3 const int ArSize = 10; 4 const int MaxLen = 81; 5 6 int main() 7 { 8 using std::cout; 9 using std::cin; 10 using std::endl; 11 String name; 12 cout << "Hi, What`s your name?\n>>"; 13 cin >> name; 14 15 cout << name << ", please enter up to " << ArSize << " short sayings <empty line to quit>:\n"; 16 String sayings[ArSize]; // 对象数组 17 char temp[MaxLen]; 18 19 int i; 20 for (i = 0; i < ArSize; i++) 21 { 22 cout << i + 1 << ": "; 23 cin.get(temp, MaxLen); 24 while (cin && cin.get() != '\n') 25 continue; 26 if (!cin || temp[0] == '\0') // empty line? 后半部分用于旧版本的检测空行 27 break; 28 else 29 sayings[i] = temp; 30 } 31 int total = i; // total # of lines read 32 33 if (total > 0) 34 { 35 cout << "Here are your sayings:\n"; 36 for (i = 0; i < total; i++) 37 cout << sayings[i][0] << ": " << sayings[i] << endl; 38 39 int shorest = 0; 40 int first = 0; 41 for (i = 1; i < total; i++) 42 { 43 if (sayings[i].length() < sayings[shorest].length()) 44 shorest = i; 45 if (sayings[i] < sayings[first]) 46 first = i; 47 } 48 cout << "Shortest saying:\n" << sayings[shorest] << endl; 49 cout << "First alphabetically:\n" << sayings[first] << endl; 50 cout << "This program used " << String::HowMany() << " String objects. Bye.\n"; 51 } 52 else 53 cout << "No input! Bye.\n"; 54 55 return 0; 56 57 }
12.3 在构造函数中使用 new 时应注意的事项
在构造函数中使用 new 来初始化指针成员,则应在析构函数使用 delete;
new 和 delete 必须互相兼容。new 对应于 delete,new [ ] 对应于 delete [ ];
只有一个析构函数,如果有多个构造函数,必须以相同的方式使用 new,所有的构造函数都必须与它兼容;
delete (无论带不带 [ ])可以用于空指针(NULL、0、nullptr);
定义一个复制构造函数,通过深度复制将对象初始化为另一个对象:
String :: String ( const String & st )
{
num_string++; // 更新静态成员
len = st.len;
str = new char [ len + 1 ]; // 分配新地址空间
std::strcpy(str,st.str); // 把字符串复制到新的地址
}
定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象:
String & String :: operator = ( const String & st )
{
if ( this == &st ) // 检查自我赋值情况
return *this;
delete [ ] str; // 释放成员指针以前指向的内存
len = st.len;
str = new char [ len + 1 ]; // 为复制数据分配内存,而不是仅仅数据的地址
std :: strcpy( str,st.str );
return *this; // 返回一个指向调用对象的引用
}
12.3.2 包含类成员的类的逐成员复制
对于包含类成员的类,类的类成员使用动态内存分配:
默认的逐成员复制和赋值行为有一定的智能;
逐成员复制将使用成员类型定义的复制构造函数和赋值运算符
如果类因其他成员需要定义复制构造函数和赋值运算符,这些函数必须显示地调用 String 和 string 的复制构造函数和赋值运算符
12.4 有关返回对象的说明
当成员函数或独立的函数返回对象时,有几种返回方式可供选择:
指向对象的引用;
指向对象的 const 引用;
const 引用;
12.4.1 返回指向 const 对象的引用
使用 const 引用的常见原因是旨在提高效率:
返回对象将调用复制构造函数,而返回引用不会;
引用指向的对象应该在调用函数执行时存在;
参数被声明为 const 引用,返回类型必须为 const;
12.4.2 返回指向非 const 对象的引用
operator = () 的返回值用于连续赋值:
Sring s1 ( "Good stuff" );
String s2, s3;
s3 = s2 = s1;
返回类型不是 const 因为方法 operator = () 返回一个指向 s2 的引用,可以对其进行修改。返回引用可以不调用复制构造函数
operator << ( ) 的返回值用于串接输出:
String s1( "Good stuff" );
cout << s1 << "is coming!";
operator << ( cout, s1 ) 的返回值成为一个用于显示字符串 " is coming! "的对象;
返回类型必须为 ostream &,而不能是 ostream,因为 ostream 没有公有的复制构造函数
12.4.3 返回对象
如果返回的对象是调用函数中的局部变量,不能按引用返回它,被调用函数执行完毕后,局部对象将调用其析构函数,引用指向的对象不再存在;
返回对象而不是引用,将调用复制构造函数
12.4.4 返回方式总结
如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用,将使用复制构造函数来生成返回的对象;
如果方法或函数返回一个没有公有复制构造函数的类的对象,它必须返回这种对象的引用;
有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,首选引用,因为效率高
12.5 使用指向对象的指针
C++ 经常使用指向对象的指针;
程序清单12.7 sayings2.cpp
1 #include<iostream> 2 #include<cstdlib> 3 #include<ctime> 4 #include"string1.h" 5 6 const int ArSize = 10; 7 const int MaxLen = 81; 8 9 int main() 10 { 11 using namespace std; 12 String name; 13 cout << "Hi, what`s your name?\n"; 14 cin >> name; 15 16 cout << name << ", please enter up to " << ArSize << " short saying <empty line to quit>\n"; 17 String sayings[ArSize]; 18 char temp[MaxLen]; 19 int i; 20 for (i = 0; i < ArSize; i++) 21 { 22 cout << i + 1 << ": "; 23 cin.get(temp, MaxLen); 24 while (cin && cin.get() != '\n') 25 continue; 26 if (!cin || temp[0] == '\0') 27 break; 28 else 29 sayings[i] = temp; 30 } 31 32 int total = i; 33 if (total > 0) 34 { 35 cout << "Here are your sayings:\n"; 36 for (i = 0; i < total; i++) 37 cout << sayings[i] << "\n"; 38 String * shortest = &sayings[0]; 39 String * first = &sayings[0]; 40 for (i = 0; i < total; i++) 41 { 42 if (shortest->length() > sayings[i].length()) 43 shortest = &sayings[i]; 44 if (*first > sayings[i]) 45 first = &sayings[i]; 46 } 47 cout << "Shortest saying:\n" << *shortest << endl; 48 cout << "First saying:\n" << *first << endl; 49 srand(time(0)); 50 int choice = rand() % total; // pick index at random 51 String * favorite = new String(sayings[choice]); 52 cout << "My favorite sayings \n" << *favorite << endl; 53 delete favorite; 54 } 55 else 56 cout << "Not much to say, eh?\n"; 57 cout << "Bye.\n"; 58 59 return 0; 60 }
使用 new 初始化对象,如果 Class_name 是类, value 的类型为 Type_name:
Class_name * pclass = new Class_name(value);
12.5.1 再谈 new 和 delete
12.5.2 指针和对象小结
使用对象指针注意:
使用常规表示法来声明指向对象的指针:
String * glamour;
可以将指针初始化为已有的对象:
String * first = &sayings[0];
可以使用 new 来初始化指针,这将创建一个新对象(使用构造函数):
String * favorite = new String (sayings[choice]);
String * gleep = new String // 使用默认构造函数
String * glop = new String ( "my my my " ) // 使用构造函数 String ( const char * )
String * favorite = new String ( sayings[ choice ] ) // 使用复制构造函数 String ( const String & )
可以使用 -> 运算符通过指针访问类方法:
shortest -> length();
可以对对象指针应用解除引用运算符 (*)来获得对象:
*first
12.5.3 再谈定位 new 运算符
12.6 复习各种技术
12.6.1 重载 << 运算符
要重新定义 << 运算符,以便将它和 cout 一起用来显示对象的内容,定义下面友元运算符函数:
ostream & operator << ( ostream & os, const c_name & obj )
{
os << . . . ; // display object contents
return os;
}
其中 c_name 为类名, 如果该类能够返回所需内容的公有方法,则可以在运算符中使用这些方法,这样便不用将它们设置为友元函数了
12.6.2 转换函数
要将单个值转换为类类型,需要定义类构造函数:
c_name ( type_name value ); // c_name为类名, type_name是要转换的类型名称
要将类转换为其他类型,需要创建类成员函数:
operator type_name ( ); // type_name 为要转换的类型
使用转换函数时要小心,可以在声明构造函数时使用关键字 explicit,防止被用于隐式转换
12.6.3 其构造函数使用 new 的类
如果使用 new 运算符来分配类成员指向的内存,在设计时应采取一些措施:
对于指向的内存是由 new 分配的所有类成员,都应在析构函数中对其使用 delete,该运算符将释放分配的内存;
如果析构函数通过对指针类成员使用 delete 来释放内存,则每个构造函数都应当使用 new 来初始化指针,或设置为空指针;
构造函数使用 new[ ] 或 new,不能混用;
new [ ] 对应于 delete [ ] , new 对应于 delete;
应定义一个分配内存(而不是使用指针指向已有内存)的复制构造函数。这样程序能将类对象初始化为另一个类对象;
应定义一个重载赋值运算符的类成员函数,这样程序能将类对象赋值给另一个类对象
12.7 队列模型
队列特征:
队列存储有序的项目序列;
队列所能容纳的项目数有一定的限制;
应当能够创建空队列;
应当能够检查队列是否为空;
应当能够检查队列是否为满;
应当能够在队尾添加项目;
应当能够在队首删除项目;
应当能够确定队列中的项目数目
1. Queue类的接口
2. Queue类的实现
确定队列的数据结构——链表:
struct Node
{
Item item;
struct Node * next;
}
嵌套结构和类:
在类声明中声明的结构、类或枚举被称为是被嵌套在类中,其作用域为整个类;
3. 类方法
类构造函数应提供类成员的值,对于非静态 const 数据成员,必须在执行到构造函数体之前,创建对象时进行初始化;
C++ 提供: 成员初始化列表:
成员初始化列表有逗号分隔的初始化列表组成(前面带冒号);
通常,初始值可以是常量或构造函数的参数列表中的参数,不限于初始化常量;
只有构造函数可以使用这种初始化列表语法;
引用于 const 数据类似,只能在被创建时进行初始化;
对于简单数据成员,使用成员初始化列表和在函数体中使用赋值没有什么区别
注意:
这种格式只能用于构造函数;
必须使用这种格式来初始化非静态 const 数据成员;
必须使用这种格式来初始化引用数据成员;
数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与初始化器中的顺序无关
警告: 不能将成员初始化列表语法用于构造函数之外的其他类方法
成员初始化列表使用的括号的方式也可用于常规初始化:
int games = 162;
double talk = 2.71828;
替换为:
int games(162);
double talk(2.71828);
这使得初始化内置类型就像初始化类对象一样
C++ 11允许类内初始化:
class Classy
{
int mem1 = 10;
const int mem2 = 20;
. . .
}
禁止复制构造函数和赋值运算符的方法:
1. 将所需的方法定义为伪私有方法:
class Queue
{
private:
Queue ( const Queue & q ): qsize(0) { }
Queue & operator = ( const Queue & q ) { return *this; }
}
避免了本来将自动生成的默认方法定义;
方法是私有的,所以不能被广泛使用
2. C++ 11提供了另一种禁用方法的方式——使用关键字 delete
12.7.2 Customer类
程序清单12.10 queue.h
1 #ifndef QUEUE_H_ 2 #define QUEUE_H_ 3 4 class Customer 5 { 6 private: 7 long arrive; 8 int processtime; 9 public: 10 Customer() { arrive = processtime = 0; } 11 void set(long when); 12 long when() const { return arrive; } 13 int ptime() const { return processtime; } 14 }; 15 16 typedef Customer Item; 17 18 class Queue 19 { 20 private: 21 struct Node { Item item; struct Node * next; }; 22 enum {Q_SIZE = 10}; 23 Node * front; 24 Node * rear; 25 int items; 26 const int qsize; 27 Queue(const Queue & q) : qsize(0) {} 28 Queue & operator =(const Queue & q) { return *this; } 29 public: 30 Queue(int qs = Q_SIZE); 31 ~Queue(); 32 bool isempty() const; 33 bool isfull() const; 34 int queuecount() const; 35 bool enqueue(const Item & item); 36 bool dequeue(Item & item); 37 }; 38 #endif
程序清单12.11 queue.cpp
1 #include"queue.h" 2 #include<cstdlib> 3 4 // Queue methods 5 Queue::Queue(int qs) : qsize(qs) 6 { 7 front = rear = NULL; 8 items = 0; 9 } 10 11 Queue::~Queue() 12 { 13 Node * temp; 14 while (front != NULL) 15 { 16 temp = front; 17 front = front->next; 18 delete temp; 19 } 20 } 21 22 bool Queue::isempty() const 23 { 24 return items == 0; 25 } 26 27 bool Queue::isfull() const 28 { 29 return items == qsize; 30 } 31 32 int Queue::queuecount() const 33 { 34 return items; 35 } 36 37 // add item to queue 38 bool Queue::enqueue(const Item & item) 39 { 40 if (isfull()) 41 return false; 42 Node * add = new Node; 43 add->item = item; 44 add->next = nullptr; 45 items++; 46 if (front == NULL) 47 front == add; 48 else 49 rear->next = add; 50 rear = add; 51 return true; 52 } 53 // Place front item into item variable and remove from queue 54 bool Queue::dequeue(Item & item) 55 { 56 if (isempty()) 57 return false; 58 item = front->item; 59 items--; 60 Node * temp = front; 61 front = front->next; 62 delete temp; 63 if (items == 0) 64 rear = nullptr; 65 return true; 66 } 67 68 // time set to a random value in the range 1 - 3 69 void Customer::set(long when) 70 { 71 processtime = std::rand() % 3 + 1; 72 arrive = when; 73 }
12.8 总结
在类构造函数中,可以使用 new 为数据分配内存,然后将内存地址赋给类成员,这样便可以处理长度不同的字符串;
在类构造函数中使用 new 如果对象包含成员指针,指向的内存由 new 分配:
释放保存对象的内存并不会自动释放成员指针指向的内存;
应在类析构函数中使用 delete 来释放分配的内存
如果对象包含指向 new 分配的内存的指针成员,则将对象初始化为另一个对象,或将一个对象赋值给另一个对象会出现问题:
浅复制, 没有创建指针指向内容的副本
解决方法:定义一个特殊的复制构造函数来重新定义初始化,并重载赋值运算符:
新的定义都将创建指向数据的副本,并使新对象指向这些副本,旧对象与新对象都将引用独立的相同的数据
对象的存储持续性为自动或外部时,在它不再存在时将自动调用其析构函数;
如果使用 new 运算符为对象分配内存,并将地址赋给一个指针,则当使用 delete 时自动为对象调用析构函数;
如果使用定位 new 运算符为类对象分配内存,则必须显示的为该对象调用析构函数:
方法是使用指向该对象的指针调用析构函数的方法
C++ 允许在类中包含结构、类和枚举定义,这些嵌套类型的作用域为整个类;
C++ 为类构造函数提供了一种可用来初始化数据成员的特殊语法:成员初始化列表
如果数据是非静态 const 成员或引用,则必须采用这种格式
C++11 允许类内初始化,与成员初始化列表等价,使用成员初始化列表的构造函数将覆盖相应的类内初始化