《C++ Primer Plus》学习笔记7
第12章 类和动态内存分配
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
1、动态内穿和类
1)静态数据成员在类声明中声明,在包含类方法的文件中初始化,初始化时使用作用域操作符来指出静态成员所属的类,但如果静态成员是整型或枚举型const,则可以在类声明中初始化。
2)new分配足够的内存的时候一般都是这种形式
len = strlen(s);
str = new char[len + 1];
这样做的目的是使分配的内存能够存储包含空字符的字符串。
3)在构造函数中使用new来分配内存时候,必须在相应析构函数中使用delete来释放内存,如果使用new[]来分配内存,则应使用delete[]包含中括号来释放内存。
4)隐式成员函数
C++自动提供下面这些成员函数:
默认构造函数;
复制构造函数;
赋值操作符;
默认析构函数;
地址操作符;
5)改进后的新string类
①修订后的默认构造函数
String::String()
{
len = 0;
str = new char[1];//为什么不使用str = new char上面两种方式分配的内存量相同,区别在于前者和析构函数兼容,后者不兼容。析构函数中包含delete[] str;
str[0] = ‘\0‘;
}
②比较成员函数
要实现字符串比较的函数,最简单的方法是使用标准的strcmp()函数,如果按照字母顺序,第一个参数位于第二个参数之前,则该函数返回一个负值;如果两个字符串相同,则返回0;如果第一个参数位于第二个参数之后,则返回一个正值。
bool operator< (const String &st1, const String &st2)
{
if(std::strcmp(st1.str, st2.str) > 0)
return true;
else
return false;
}
因为内置的 > 操作符返回一个布尔值,所以可以继续简化为
bool operator< (const String &st1, const String &st2)
{
return (std::strcmp(st1.str, st2.str) < 0);
}
bool operator> (const String &st1, const String &st2)
{
return st2.str < st1.str;
}
bool operator== (const String &st1, const String &st2)
{
return (std::strcmp(st1.str, st2.str) == 0);
}
6)如果编译器没有实现布尔类型的话,可以使用int代替bool,0代替false,1代替true。如果编译器不支持静态类常量,可以用枚举来定义CINLIM
7)在构造函数中使用new时应该注意的事项
①如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete
②new和delete必须相互兼容,new对应delete,new[]对应delete[]
③如果有多个构造函数,则必须以相同的方式使用new,要么带中括号,要不都不带,因为只有一个析构函数,因此所有的构造函数都必须与它兼容,不过,可以在一个构造函数中使用new来初始化指针,而在另一个构造函数中将指针初始化为空,这个是因为delete可以用于空指针。
8)有关返回对象的说明
①返回指向const对象的引用
使用const引用的常见原因是旨在提高效率。
//version1
Vector Max(const Vector & v1, const Vector & v2)
{
if(v1.magval() > v2.magval())
return v1;
else
return v2;
}
//version2
const Vector & Max(const Vector & v1, const Vector & v2)
{
if(v1.magval() > v2.magval())
return v1;
else
return v2;
}
注意:返回对象将调用复制构造函数,但是返回将不会。效率高
引用指向的对象应该再调用函数执行时存在;
v1和v2都被声明为const引用,因此返回类型必须是const,这样才能匹配
②使用指向对象的指针
String * favorite = new String(sayings[choice]);
这不是为要存储的字符串分配内存,而是为对象分配内存;也就是说,为保存字符串地址的str指针和len成员分配内存,程序员并没有给num_string成员分配内存,因为num_string成员是静态成员,它独立于对象被保存。
③下面几种情况析构函数将被调用
如果对象是动态变量,则当执行完定义该对象的程序块时候,将调用该对象的析构函数;
如果对象是静态变量(外部、静态、静态外部或来自名称空间),则在程序借结束时候将调用对象的析构函数
如果对象是用new创建的,则仅当显式使用delete删除对象时,析构函数才会被调用
9)指针和对象的小结
①使用常规法来声明指向对象的指针
String * glamour;
②可以将指针初始化为指向已有的对象
String * first = &sayings[0];
③可以使用new来初始化指针,这将创建一个新的对象
String * favorite = new String(saying[choice])
④对类使用new将调用相应的类构造函数来初始化新的创建对象
String * gleep = new String;
String * glop = new String("my my my");
String * favorite = new String(sayings[choice]);
⑤可以使用->操作符通过指针访问类方法
if(sayings[i].length() < shortest -> length())
⑥可以对对象指针应用解除引用操作符()来获得对象
if(sayings[i] < first)
first = &sayings[i];
10)布局new操作符
能够让您在分配内存时指定内存位置
在布局new操作符来为对象分配内存,必须确保其析构函数被调用。
对于堆中创建的对象,可以使用
delete pc2;
对于使用布局new操作符创建的对象,不能像下面这么做
delete pc1;
原因在于delete可与常规new操作符配合使用,但不能与布局new操作符配合使用。
解决方法显式地为使用布局new操作符创建的对象调用析构函数,采用pc1 ->~JustTesting();
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
总结:处理各种与类相关问题的编程技术
1)重载<<操作符
要重新定义<<操作符,以便将它与cout一起用来显示对象的内容,定义下面友元操作符函数:
ostream & operator<< (ostream & os, const c_name & obj)
{
os << ……; //display object contents
return os;
}
2)转换函数
要将单个值转换为类类型,需要创建原型如下所示的类构造函数
c_name(type_name value) 其中c_name为类名 type_name是要转换的类型的名称
要将类转换为其他类型,需要创建原型如下所示的类成员函数
operator type_name();虽然该函数没有声明返回类型,但应返回所需要类型的值
使用转换函数要小心,可以在声明构造函数时候使用关键字explicit,以防止被用于隐式转换。
2、队列模拟
队列是一个抽象的数据类型(ADT),可以存储有序的项目序列,新项目被添加在队尾,并且可以删除队首的项目。队列与堆栈不一样的是堆栈在同一端进行添加和删除,这使得栈是一个后进先出的结构,而队列是先进先出的。
1)Queue类的实现
因为队列操作每删除第一个元素后,需要将余下的所有元素向前移动一位;故不适合用数组来完成。所有我们这里采用链表,链表是由节点序列构成,每个节点中都包含要保存到链表信息以及一个指向下一节点的指针。对于队列来说,数据部分多事一个Item类型的值,使用下面结构来表示节点
struct Node
{
Item item;
struct Node * next;
}
2)嵌套结构和类
在类声明中声明的机构、类或枚举被称为是被嵌套在类中,其作用域为整个类,这种声明不会创建数据对象,而只是指定了可以在类中使用的类型
C++允许在类中包含结构、类、枚举定义作用域为整个类,这就意味着他们被局限于类中,并不会与别的发生冲突。
3)小技巧,怎么表示一个随机过程平均每6分钟来一个客户
bool newcustomer(double x)
{
return (std::rand() * x/RAND_MAX < 1);//RAND_MAX实在cstdlib中定义的,是rand()可能返回的最大值,所以rand() * x/RAND_MAX的值将在0-6之间
}
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
小结:
如果对象包含指向new分配的内存的指针成员,则将一个对象初始化为另一个对象,或将一个对象赋给另一个对象,也会出现问题,在默认情况下,C++做个对成员进行初始化和赋值,这就意味着被初始化或被赋值的对象的成员与原始对象完全相同,如果原始对象的成员指向一个数据块,则副本成员将指向同一个数据块。解决方法是定义一个特殊的复制构造函数来重新定义初始化,并重载赋值操作符,新的定义将创建指向数据的副本,并使新对象指向这些副本,这样,旧对象和新对象都将应用独立的相同的数据,不会重叠。对于每种情况,最终母的是执行深度复制,也就是说复制数据,而不仅仅是复制指向数据的指针。