第 12 章 类和动态内存分配
12.1 动态内存和类
通常最好在程序运行时,而不是编译时决定内存的分配。
不能在类声明中初始化静态变量,因为类声明仅仅提供了类的属性而没有分配内存,因此只能在类实现中初始化。将静态变量声明为 const
或转换为枚举类型则可以在声明中初始化。
如果没有声明,则类会自动生成部分成员函数,即默认构造函数、默认析构函数、复制构造函数、赋值运算符、地址运算符(返回 this
指针的值)、移动构造函数、移动赋值运算符。
复制构造函数用于将一个定义好的对象复制到新创建的对象中,其原型为 ClassName(const ClassName &);
,新建一个对象并初始化为已有对象时会调用复制构造函数,Example e1(e2); Example e1 = e2; Example e1 = Example(e2); Example *e1 = new Example(e2);
都会调用复制构造函数。
按值传递意味着创建原始变量的一个副本,创建临时变量时(函数参数等)也会调用复制构造函数。
默认复制构造函数逐个复制非静态成员,如果成员本身是类对象,则会调用该类的复制构造函数来复制成员对象,成员复制也被称为浅复制。
浅复制存在隐含的问题,即如果类成员中有指针对象,则只会复制对象的地址而不会新构造一个对象再指向它,因此当复制对象指针指向内存被释放后,浅复制的对象中的指针将指向一个已经被释放的内存空间进而导致错误。解决这个问题的办法是进行深复制,显式地定义复制构造函数创建一个相同的对象,再创建新的指针指向新的对象。
如果一些类成员是使用 new
初始化的指向数据的指针,则必须定义复制构造函数。
将已有的对象赋给另一个对象时将使用重载的赋值运算符,类似 Example e1 = e2;
的初始化对象有可能先用复制构造函数创建一个临时对象,再用重载赋值运算符将临时变量赋给 e1
,也有可能只调用复制构造函数。
重载赋值运算符与复制构造函数类似,逐个复制非静态成员,如果成员本身是类,则通过该类的赋值运算符复制成员。
赋值运算符的重载与复制构造函数的重载类似,都需要进行深复制。不同的地方在于重载赋值运算符需要先检查是否为自我复制,如果是则可以直接返回 *this
,另外还需要释放已经申请的空间,因为赋值后会分配新的空间。
12.2 改进后的新 String
类
使用 nullptr
而不是 0 表示空指针。
有时需要提供仅供 const
对象使用的 const
函数,C++ 区分常量和非常量函数的特征标。
不能通过对象调用静态函数,需要通过类名和作用域解析运算符调用。静态函数只能使用静态成员。
12.3 在构造函数中使用 new
时应注意的事项
使用 new
初始化成员指针时应该在析构函数中使用 delete
;如果有多个构造函数则必须使用同样的方式使用 new
,都使用中括号或都不使用,因为析构函数只有一个,所有构造函数都需要和它兼容;自定义显式复制构造函数实现深复制;自定义显式重载赋值运算符检查自我赋值或释放内存并实现深复制。
12.4 有关返回对象的说明
返回对象将调用复制构造函数,而返回引用不会。引用指向的对象应该在调用函数执行时存在。
重载赋值运算符与和重载 cout
一起使用的 <<
运算符一般返回指向非 const
对象的引用。
如果返回的是函数内的局部变量,则不应该返回对象的引用,因为函数执行完成后内部对象会调用析构函数,对象不再存在,它的引用自然也没有意义了。通常重载运算符需要返回对象而不是引用。
12.5 使用指向对象的指针
使用 new
创建对象时,首先为对象分配内存,再调用构造函数依次将参数复制到分配的内存单元中,然后创建对象变量,最后把分配内存的地址赋给变量。
delete
可以与常规的 new
运算符配合使用,但不能和定位 new
一起使用。这时销毁对象需要显式地调用析构函数,即 obj->~Object();
,销毁一系列对象时应按照与创建顺序相反的顺序,因为新对象可能依赖旧对象,而且仅当所有定位 new
对象都被销毁后才可以销毁存储这些对象的缓存区。
12.6 复习各种技术
12.7 队列模拟
必须使用初始化列表初始化非静态 const
成员或引用成员,因为它们只能被初始化而不能被赋值。
定义私有复制构造函数和重载赋值运算符可以避免意外调用。
标签:12,对象,运算符,重载,复制,动态内存,赋值,Primer,构造函数 From: https://www.cnblogs.com/futureknight/p/17189946.html