时间:2022-09-21 07:04:29
[摘要] 运用new运算符和delete运算符实现动态内存分配,是C++程序分配内存独有的内容。根据对象所在类的不同,创建对象时,调用构造函数的情况各有不同,特别是含有对象成员的派生类对象构造函数的调用顺序比较复杂。很多学生对上述知识点不能正确理解,形成学习障碍。通过参考相关书籍进行大量实验,深入分析研究它们的特点、规律及使用方法,并用于指导教学,收到了好的教学效果。
[关键词] C++ 教学难点 动态分配内存 构造函数调用
在对自考学生进行“C++程序设计”学习指导时,发现许多学生总是不能正确解答“动态分配内存”和“构造函数调用”这两个重要的知识点相关的问题。本文就这两个问题进行讨论。
1 动态分配和释放内存的问题
1.1问题的提出
动态分配内存几乎是所有应用程序的重要组成部分。C++提供的动态分配内存是由new和delete两个运算符实现的,在自考教材中多处提到,其中在第10页例1.3和第82页例4.8两处出现如下:
例1.3 例4.8
double *p; Point *ptr=new Point[2];
p=new double[3];……
……delete [ ]ptr;//释放内存
pelete p;//释放内存
上例中,两次动态定义的均是数组,但采用delete释放内存的方式确不同,且上机运行均能通过编译,从而给人以两种方式均正确的认识。
但是,在2009年10月全国试卷第49题中,对用data =new T[len]定义的动态数组,给出的标准答案只有一个即“delete[]data;”而不是“delete[]data;”或“delete data;”。到底怎样才是正确的呢?请看下面解答。
1.2单个对象内存空间的动态分配和释放
1.2.1动态分配
一般形式:数据类型*指针名=new数据类型(初值);
语句作用:通过new语句,动态分配一个能够存储指定数据类型数据的内存单元,并把分配到的内存初始化为某一个可知的初值,同时返回指向内存的开始处的指针。
注意:初始化部分的类型必须与内存分配的数据类型一致。
1.2.2释放内存
一般形式:delete指针名;
语句作用:告诉系统释放由new分配的单个内存单元。
几点注意:
(1)用new为指针分配内存,称为手工分配内存,这种方式分配的内存不能自动释放(由系统定义变量时分配的内存,当不再使用时,系统将会自动释放。),必须用delete手工释放。
(2)delete必须用在先前已由new成功分配内存的有效指针上,若用在了未用new分配内存的指针上,将会带来严重的问题,比如系统崩溃。
(3)对于一个用new分配内存的指针,只能用一次delete。
例1 double *p=new double(3.5);
……
pelete p;
1.3数组的动态分配及释放
1.3.1动态分配
一般形式:数据类型 *指针名=new数据类型[m];
语句作用:通过new语句动态分配一个由指定的数据类型所要求的连续m个内存单元,并返回指向首个单元开始处的指针。
注意:分配数组时不能给数组初始化,即不能给定初值。
1.3.2释放内存
一般形式:数据类型 []指针名;
语句作用:告诉系统用[]释放动态分配的数组。
注意:
(1)用delete释放一个动态数组时,还必须用[]形式告诉delete释放多大的内空间,否则内存长期占用,很快就会耗尽,造成内存泄漏。
(2)用delete释放由new分配数组时,只需用[],不必在括号中加数字说明数组长度。
例2动态分配对象数组。
#include
using namespace std;
class Point{private:int x,y;public:
Point():x(0),y(0){cout
Point(int a,int b):x(a),y(b){
cout
Point&operator=(Point&a){
if(this==&a) return *this;x=a.x;y=a.y;return *this; }
~Point(){cout
void main(){Point a(0,0),b(1,1),*A=new Point[2];
A[0]=a;A[1]=b;delete []A;}
运行结果:
Iitializing 0,0
Iitializing 1,1
Iitializing default//为数组元素A[0]调用构造函数
Iitializing defaultt//为数组元素A[1]调用构造函数
Delete1,1//为数组元素A[1]调用析构函数
Delete0,0//为数组元素A[0]调用析构函数
Delete1,1
Delete0,0
说明:释放对象数组时,用delete[]形式,使对象数组中每个对象都调用一次析构函数,可将整个数组完整释放。但是,如果将此例中delete[]A;改为delete A;,则运行结果变为:
Iitializing0,0
Iitializing1,1
Iitializing default
Iitializing default
Delete0,0//为数组元素A[0]调用析构函数
Delete1,1
Delete0,0
此时只对数组中第一个元素调用了析构函数,即“delete指针;”命令只能释放指针所指向的单元,不能为对象数组中每个对象都调用一次析构函数。
1.4综述
通过对动态分配和释放单个内存和数组两种情况的讨论可知,“delete指针名;”命令只能释放由new创建的单个存储单元空间,只有用“delete []指针名;”方式才能释放由new创建的动态数组占用的所有空间。
2 含有对象成员的派生类对象调用构造函数的顺序问题
教材在第5章和第6章分别介绍了含有对象成员的类对象和派生类对象调用构造函数的顺序问题,简述如下。
2.1含有对象成员的类对象调用构造函数的顺序
2.1.1知识点
在类的声明中,数据成员可以以另一种类为类型,称为对象成员。
在定义包含对象成员的类对象时,C++编译器为对象成员调用构造函数的顺序是按照对象成员在类中的声明顺序进行的,而与初始化列表中参数顺序无关。
例3对象成员调用构造函数的顺序
#include
using namespace std;
class object{ private:int val; public:
object( ):val(0){
cout
object(int i):val(i){
cout
class container{private: object one;object two;int data; public:
container( ):data(0),one( ),two( ){
cout
container(int i,int j,int k):two(i),one(j){
data=k;cout
void main(){container obj , anObj(5,6,10);}
例3运行结果:
Default constructor for object//为对象成员one调用object
类的无参构造函数
Default constructor for object//为对象成员two调用object
类的无参构造函数
Default constrctor for container
Constructor for object6//为对象成员one调用
object类的有参构造函数
Constructor for object5//为对象成员two调用object
类的有参构造函数
constrctor for container10
例3说明
在类container中,成员one和two都是object类对象,声明顺序是one、two。在有参构造函数初始化列表中one在后two在前,但运行结果表明,系统是先为one调用了object类的有参构造函数,后为two调用object类的有参构造函数。从而验证了前面所述结论。
2.2不含对象成员的派生类对象调用构造函数的顺序
知识点:
如果派生类中没有对象成员,派生类对象定义时,C++编译器也会自动调用派生构造函数,并通过派生构造函数调用直接基类构造函数,其顺序是:先调用基类构造函数,再调用派生类构造函数。
例4源程序
#include
using namespace std;
classPoint{public:Point(){cout
class Rectangle:public Point{
public:Rectangle(){cout
void main(){Rectangle r;}
例4运行结果
Point…//为对象r调用基类Point的构造函数
Rectangle…//为对象r调用派生类Rectange的构造函数
2.3派生类有基类对象成员时派生类对象调用构造函数的顺序
2.3.1问题的提出
当派生类的数据成员中有基类对象成员时,系统将按怎样的顺序调用构造函数呢?先看看例题5。
例5派生中有基类对象成员时,定义派生对象时调用构造函数的顺序
#include
using namespace std;
class Point{private:int x; public:
Point(){x=0;cout
Point(int i){x=i;cout
class Rectangle:public Point{
private:Point a;
public:Rectangle(int i,int j):a(j),Point(i){
cout
void main(){Rectangle r(1,2); }
例5运行结果
Point…1 //为派生类对象r调用基类Point的有参构造函数
为继承的数据成员x赋值。
Point…2//为Point类对象成员a调用Point类的有参构造函数
Rectangle…//为派生类对象r调用派生类Rectangle构造函数
2.3.2结论
由上例可见,当派生类中含有基类对象成员时,调用基类构造函数的顺序是:基类构造函数对象成员应调用的构造函数派生类构造函数。
3 结束语
综上所述,C++动态分配内存是由new和delete两个语句相互配合完成,在使用中应注意分清分配的是单个内存单还是数组,因为它们的空间在释放时方法不同。派生类对象和对象成员调用构造函数是有规律所循的。正确掌握和使用它们,对C++程序设计是非常重要的。
参考文献:
[1]刘振安.C++程序设计.北京:机械工业出版社,2008.86-167.
[2]甘玲,李盘林.解析C++面向对象程序设计.北京:清华大学出版社.
[3]徐士良,葛兵,徐艳.C++程序设计.北京:机械工业出版社,2006.283-345.
[4]曹静,董宁,陈丹.C++面向对象程序设计.北京:中国水力水电出版社.
[5]李春葆,陶红艳,金晶.C++语言程序设计.北京:清华大学出版社.
[6]刘玉英,张怡芳,王涛伟.程序设计基础C++.北京:人民邮电出版社.