第十二章 类和动态内存分配

前面说过,通常最好在程序运行时确定诸如使用多少内存的问题,方法就是使用new来创建动态内存并在使用完之后delete掉。怎么对类成员使用动态内存分配就是本章的主要内容了。这本书到这里也逐步加深了。


1. 动态内存和类

在类中使用new和delete运算符对类成员进行动态内存分配看似很强大,但代价是使用起来会导致许多新的问题,所以用起来必须小心。

1.1 这一小节举了一个StringBad类的例子,之所以叫Bad是因为这个类有缺陷。首先不管缺陷,这个类定义了一个静态类成员

static int num_string。

静态成员有一个特点,无论创建了多少对象,程序都只创建一个静态类变量副本,也就是所有类对象共享这个静态成员。另外不能在类声明中初始化静态成员变量,这是因为类声明只是描述如何分配内存,而不分配内存。所以初始化必须在类声明外,比如方法文件中。但是如果静态数据成员为整形或枚举型const,则可以在类声明中初始化。在类声明外初始化静态成员也很简单,使用类名加::限定符即可,例如书中

int StringBad::num_strings = 0;

StringBad有两个构造函数,一个析构函数。两个构造函数基本相同,只是默认构造函数把C++赋给新建的对象,而带参数的构造函数把参数赋给新建的对象。构造函数里面使用了new char[len+1] 来分配内存,这段内存的大小根据参数(或者默认值C++)的长度来决定。因为使用了new,所以析构函数里面使用了delete[]来释放这个内存。


咋一看这样好像没什么问题。但程序没有以我们期待的方式使用析构函数。首先主函数使用了一个以StringBad类对象的值传递的函数callme2(headline2), headline是一个StringBad类的对象。当子函数开始执行结束时,子函数调用了析构函数,之后headline2指向的值就不对了。然后当程序使用StringBad sailor = sports和knot = headline1时候又出现了问题。


1.2 特殊成员函数

上面一小节的问题是由特殊成员函数引起的,这些成员函数是自动定义的,所以可能与类设计不符。具体的说,C++自动提供了下面这些成员函数:

默认构造函数,如果没有定义构造函数;

默认析构函数;如果没有定义;

复制构造函数,如果没有定义;

赋值运算符,如果没有定义;

地址运算符;如果没有定义;

这样一看StringBad类中的问题是由隐式复制构造函数和隐式赋值运算符引起的。


默认构造函数已经讲了,如果没有定义构造函数,程序会自己定义一个空函数作为默认构造函数;如果定义了带参数的构造函数,则还必须定义一个不带参数的构造函数来作为默认构造函数。但如果带参数的构造函数都使用默认值,也可以作为默认构造函数。


复制构造函数用于将一个对象复制到新创建的对象中,即用于初始化过程中(包括上例callme2(headline2)按值传递参数),其原型通常如下:

Class_name(const Class_name &)

新建一个对象并将其初始化为同类现有对象是,这个函数将被调用,比如下面四种情况的任意一种:

StringBad ditto(motto);

StringbBad metoo = motto;

StringBad also = StringBad(motto)

StringBad * pStringBad = new StringBad(motto)

注意这个时候没有调用普通的构造函数,只调用了复制构造函数。

另外就是前面讲过的每当程序生成了对象副本是,编译器都将使用复制构造函数


1.3 StringBad类里面表示knot和sailor调用了复制构造函数,没有调用普通构造函数,但退出时调用了析构函数。析构函数在任何对象过期是都将被调用,而不管对象是如何被创建的。

同时因为使用了赋值语句sailor = sport,delete sailer 的char str[]时,把sport的char str[]也delete掉了,因为它们指向相同的内存。


上面这些问题的解决办法显然是必须得自己定义一个复制构造函数,正如书中所给出的一样


1.4 上面的问题同样也会出现在赋值上,所以还应写一个复制函数,赋值函数是将一个对象的成员值赋给一个已经存在的对象,而不是一个新创建的对象,这点和复制构造函数不同。


2. 就是修订后的String类了

首先是可以把默认构造函数修改了

str = new char[1]. 

之所以改成包含一个元素的数组,是为了和析构函数delete[] str对应

然后修订后的string类使用了几个友元函数对操作符>, <, ==, >>等进行了重载来比较或者输出string的不同对象,最好定义了使用中括号表示访问字符串的某个字符。


之后这个修订的String类定义了一个静态函数声明(包含关进子static),作用是调用前面声明的静态数据变量,也只能使用静态数据成员


最好如果想把一个C风格字符串赋给一个String对象,最好再写一个有针对性的赋值运算符的重载函数,以保证析构函数按预期的调用


3. 至此我们可以看出一个类只有一个析构函数,所以必须创建足够多的构造函数来保证new出来的内存能正确delete。如果new的是[],delete也要带[]。特别是应定义一个复制构造函数和复制运算符。


4. 当成员函数或独立的函数返回对象时,可以返回指向对象的引用,指向对象的const引用或const对象


如果方法或函数要返回局部对象,则应返回对象么若不是指向对象的引用,这个时候将使用复制构造函数来返回对象。如果方法或函数要返回一个没有公有复制构造函数的类(如ostream类)的对象,它必须分那好一个指向这种对象的引用。最后,有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首先选择引用,因为其效率更高。


5. 使用new初始化对象

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

Class_name * pclass = new Class_name(value); 可以定义一个指向这个对象的指针。只适合将调用如下构造函数:

Class_name(Type_name);

然后调用一个复制构造函数来初始化*pclass;

当这个时候程序结束时还必须的加一行delete pclass来delete掉这个指向Class_name对象的指针,同时对象的析构函数也会被自动调用。


最好本章总结了一下各个要点,然后给出了一个队列的例子,可以自己敲一边应该就明白了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值