类的动态内存分配和释放
C++能够在程序运行时决定内存的分配,而不是只在编译阶段,因此,就可以根据程序的需要,而不是根据一系列严格的存储类型规则来使用内存,C++使用new和delete运算符来动态控制内存,但是,在类中使用这些运算符会导致许多新的问题,在这种情况下,析构函数就是必不可少的,下面我们通过一个小程序来看一看这些问题,这个程序是在类中使用new和delete来存储释放字符串
我们将函数声明写在一个头文件,即stringbad.h
在头文件里定义了一个类,名为Stringbad
在类函数定义文件中,即stringbad.cpp
我们在主函数文件main.cpp中,测试函数传递类的引用以及类的值
特殊成员函数
复制构造函数
我们上面所说的问题其实就是由特殊的成员函数引起的,这些成员函数都是编译器进行自动定义的,就Stringbad而言,这些函数的行为与我们类的设计不符,C++会自动提供下列的成员函数
更准确的说,如果程序使用对象的方式要求我们这样做,编译器就是生成上述最后三个函数的定义,例如,如果我们要将一个对象赋值给另一个对象,编译器会提供赋值运算符的定义,可以看出Stringbad类中的问题是由隐式复制构造函数和隐式的赋值运算符引起的,因此,我们可以自己提供一个参数符合的复制构造函数,就不会出现这样的问题,也可以对赋值运算符进行显示的重载,防止其中的某个部分不能使用
对于复制构造函数的说明:
赋值运算符
对于赋值运算符重载的说明
应用实例
我们可以根据上面的知识将我们的所写的代码进行改进补充,重新体会类的各种用法,并完成一些字符串的基本操作
在构造函数使用new的注意事项
在构造函数使用new和delete注意事项如下:
有关返回对象的说明
当成员函数或者独立函数返回对象时,有几种返回方式可以选择,即可以返回对象的引用,指向对象的const引用或者const对象
返回指向const对象的引用
返回指向非const对象的引用
返回对象
总结
使用指向对象的指针
我们可以使用一个指针来指向一个类的对象,当我们使用对象指针时,需要注意以下几点:
定位new运算符
定位new运算符能够能够让我们在分配内存时指定内存的位置,但是,将定位new运算符用于类的情况却有所不同,我们下面通过一个测试的例子来看看
我们在这个程序中使用new定位运算符已经普通的new运算符来开辟内存空间,并且测试它们调用构造函数和析构函数的情况,运行代码结果如下:
我们可以看出,在这个例子中,我们的构造函数被调用了四次,而析构函数只被调用了一次,并且还出现了段错误,这个原因是什么呢?并且如何进行修改?
原因如下:
delete可以与常规的new运算符配合使用,但是不能与new运算符配合使用,因为常规new运算符开辟了内存空间,而定位new运算符是在别处开辟的内存空间
我们在使用类对象的指针时说过,当我们使用new去创建类的对象时,会去调用构造函数,只有当我们显式的去使用delete时,程序才会去调用析构函数,因为我们要修改后的代码为
delete []p;
delete p2;
delete p4;
程序运行后的结果为
此时我们可以看出,析构函数被调用了两次,即p2和p4调用了析构函数,因为delete [] p释放使用常规new运算符分配的整个内存块,但它并没有为定位new运算符在该内存块中创造的对象调用析构函数,因此,我们需要显式地为定位new运算符创建的对象调用析构函数,显示调用析构函数时,必须知道要销毁的对象,在我们添加上之后
delete p2;
delete p4;
p3->~A();
p1->~A();
delete [] p;
程序的运行结果如下所示:
在这里,我们可以看出,p3的C将原来p1里的A给覆盖掉了,这是因为我们两次使用new定位运算符时指向的内存空间的起始位置是一样的,所以后面p3调用时就会将原本的内容进行覆盖,要解决这个问题,我们只需要在p3使用new定位运算符时,让其进行偏移一个类的大小就可以解决
A *p3 = new(p + sizeof(A)) A("C",6);
提示:
成员初始化列表
当我们在类的私有成员里设置了一个const常量,那么我们就必须在构造函数的初始化列表中对其进行初始化,否则就会出错
对于我们的代码而言,num是一个常量,所以我们可以对其进行初始化,但是不能给它赋值,当我们调用构造函数时,对象会将在括号中的代码执行前就被建立,即我们没有进行a = anum;和num = anum + 1;操作之前,我们的程序就已经为a和num分配了内存空间,这个分配内存空间的过程实际就是相当于我们对a和num进行初始化的过程,只是我们没有给它们一个确定的值,而当我们开始执行a = anum;和num = anum + 1;时,我们进行的是常规的赋值操作,而不是初始化,因此,对于const数据成员,必须在执行到构造函数之前就对其进行初始化
C++提供了一种特殊的语法来完成这个过程,即成员列表初始化
成员初始化列表的要求如下