返回介绍

12.5 使用指向对象的指针

发布于 2024-10-08 23:14:10 字数 7560 浏览 0 评论 0 收藏 0

C++程序经常使用指向对象的指针,因此,这里来练习一下。程序清单 12.6 使用数组索引值来跟踪最短的字符串和按字母顺序排在最前面的字符串。另一种方法是使用指针指向这些类别的开始位置,程序清单 12.7 使用两个指向 String 的指针实现了这种方法。最初,shortest 指针指向数组中的第一个对象。每当程序找到比指向的字符串更短的对象时,就把 shortest 重新设置为指向该对象。同样,first 指针跟踪按字母顺序排在最前面的字符串。这两个指针并不创建新的对象,而只是指向已有的对象。因此,这些指针并不要求使用 new 来分配内存。

除此之外,程序清单 12.7 中的程序还使用一个指针来跟踪新对象:

这里指针 favorite 指向 new 创建的未被命名对象。这种特殊的语法意味着使用对象 saying [choice]来初始化新的 String 对象,这将调用复制构造函数,因为复制构造函数(const String &)的参数类型与初始化值(saying [choice])匹配。程序使用 srand( )、rand( ) 和 time( ) 随机选择一个值。

程序清单 12.7 sayings2.cpp

使用 new 初始化对象

通常,如果 Class_name 是类,value 的类型为 Type_name,则下面的语句:

将调用如下构造函数:

这里可能还有一些琐碎的转换,例如:

另外,如果不存在二义性,则将发生由原型匹配导致的转换(如从 int 到 double)。下面的初始化方式将调用默认构造函数:

下面是程序清单 12.7 中程序的运行情况:

由于该程序随机选择用户输入的格言,因此即使输入相同,显示的结果也可能不同。

12.5.1 再谈 new 和 delete

程序清单 12.4、程序清单 12.5 和程序清单 12.7 组成的程序在两个层次上使用了 new 和 delete。首先,它使用 new 为创建的每一个对象的名称字符串分配存储空间,这是在构造函数中进行的,因此析构函数使用 delete 来释放这些内存。因为字符串是一个字符数组,所以析构函数使用的是带中括号的 delete。这样,当对象被释放时,用于存储字符串内容的内存将被自动释放。其次,程序清单 12.7 中的代码使用 new 来为整个对象分配内存:

这不是为要存储的字符串分配内存,而是为对象分配内存;也就是说,为保存字符串地址的 str 指针和 len 成员分配内存(程序并没有给 num_string 成员分配内存,这是因为 num_string 成员是静态成员,它独立于对象被保存)。创建对象将调用构造函数,后者分配用于保存字符串的内存,并将字符串的地址赋给 str。然后,当程序不再需要该对象时,使用 delete 删除它。对象是单个的,因此,程序使用不带中括号的 delete。与前面介绍的相同,这将只释放用于保存 str 指针和 len 成员的空间,并不释放 str 指向的内存,而该任务将由析构函数来完成(参见图 12.4)。

图 12.4 调用析构函数

在下述情况下析构函数将被调用(参见图 12.4)。

  • 如果对象是动态变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。因此,在程序清单 12.3 中,执行完 main() 时,将调用 headline[0]和 headline[1]的析构函数;执行完 callme1( ) 时,将调用 grub 的析构函数。
  • 如果对象是静态变量(外部、静态、静态外部或来自名称空间),则在程序结束时将调用对象的析构函数。这就是程序清单 12.3 中 sports 对象所发生的情况。
  • 如果对象是用 new 创建的,则仅当您显式使用 delete 删除对象时,其析构函数才会被调用。

12.5.2 指针和对象小结

使用对象指针时,需要注意几点(参见图 12.5):

  • 使用常规表示法来声明指向对象的指针:

  • 可以将指针初始化为指向已有的对象:

  • 可以使用 new 来初始化指针,这将创建一个新的对象(有关使用 new 初始化指针的细节,请参见图 12.6):

  • 对类使用 new 将调用相应的类构造函数来初始化新创建的对象:

图 12.5 指针和对象

图 12.6 使用 new 创建对象

  • 可以使用->运算符通过指针访问类方法:

  • 可以对对象指针应用解除引用运算符(*)来获得对象:

12.5.3 再谈定位 new 运算符

本书前面介绍过,定位 new 运算符让您能够在分配内存时能够指定内存位置。第 9 章从内置类型的角度讨论了定位 new 运算符,将这种运算符用于对象时情况有些不同,程序清单 12.8 使用了定位 new 运算符和常规 new 运算符给对象分配内存,其中定义的类的构造函数和析构函数都会显示一些信息,让用户能够了解对象的历史。

程序清单 12.8 placenew1.cpp

该程序使用 new 运算符创建了一个 512 字节的内存缓冲区,然后使用 new 运算符在堆中创建两个 JustTesting 对象,并试图使用定位 new 运算符在内存缓冲区中创建两个 JustTesting 对象。最后,它使用 delete 来释放使用 new 分配的内存。下面是该程序的输出:

和往常一样,内存地址的格式和值将随系统而异。

程序清单 12.8 在使用定位 new 运算符时存在两个问题。首先,在创建第二个对象时,定位 new 运算符使用一个新对象来覆盖用于第一个对象的内存单元。显然,如果类动态地为其成员分配内存,这将引发问题。

其次,将 delete 用于 pc2 和 pc4 时,将自动调用为 pc2 和 pc4 指向的对象调用析构函数;然而,将 delete[]用于 buffer 时,不会为使用定位 new 运算符创建的对象调用析构函数。

这里的经验教训与第 9 章介绍的相同:程序员必须负责管用定位 new 运算符用从中使用的缓冲区内存单元。要使用不同的内存单元,程序员需要提供两个位于缓冲区的不同地址,并确保这两个内存单元不重叠。例如,可以这样做:

其中指针 pc3 相对于 pc1 的偏移量为 JustTesting 对象的大小。

第二个教训是,如果使用定位 new 运算符来为对象分配内存,必须确保其析构函数被调用。但如何确保呢?对于在堆中创建的对象,可以这样做:

但不能像下面这样做:

原因在于 delete 可与常规 new 运算符配合使用,但不能与定位 new 运算符配合使用。例如,指针 pc3 没有收到 new 运算符返回的地址,因此 delete pc3 将导致运行阶段错误。在另一方面,指针 pc1 指向的地址与 buffer 相同,但 buffer 是使用 new []初始化的,因此必须使用 delete [ ]而不是 delete 来释放。即使 buffer 是使用 new 而不是 new []初始化的,delete pc1 也将释放 buffer,而不是 pc1。这是因为 new/delete 系统知道已分配的 512 字节块 buffer,但对定位 new 运算符对该内存块做了何种处理一无所知。

该程序确实释放了 buffer:

正如上述注释指出的,delete [] buffer;释放使用常规 new 运算符分配的整个内存块,但它没有为定位 new 运算符在该内存块中创建的对象调用析构函数。您之所以知道这一点,是因为该程序使用了一个显示信息的析构函数,该析构函数宣布了“Heap1”和“Heap2”的死亡,但却没有宣布“Just Testing”和“Bad Idea”的死亡。

这种问题的解决方案是,显式地为使用定位 new 运算符创建的对象调用析构函数。正常情况下将自动调用析构函数,这是需要显式调用析构函数的少数几种情形之一。显式地调用析构函数时,必须指定要销毁的对象。由于有指向对象的指针,因此可以使用这些指针:

程序清单 12.9 对定位 new 运算符使用的内存单元进行管理,加入到合适的 delete 和显式析构函数调用,从而修复了程序清单 12.8 中的问题。需要注意的一点是正确的删除顺序。对于使用定位 new 运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。

程序清单 12.9 placenew2.cpp

该程序的输出如下:

该程序使用定位 new 运算符在相邻的内存单元中创建两个对象,并调用了合适的析构函数。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文