再上一篇:10.2 析构函数
上一篇:10.3 实现类型转换和拷贝的构造函数
主页
下一篇:第十一章 继承和派生类
再下一篇:11.2 初始化基类成员
文章列表

10.4 构造函数与对象成员

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

在定义一个新类时,可把一个已定义类的对象作为该类的成员。产生这新定义类的对象时,也要对它的对象成员进行初始化,且只能通过这新类的构造函数来对它的所有数据成员初始化。对对象成员进行初始化,也必须调用其对象成员的构造函数来实现。

例10.17 初始化对象成员。

#include

class A {

int x,y;

public:

A(int a,int b)

{

x=a;y=b;

}

void Show()

{ cout<< "x="<

}

};

class B{

int Length,Width;

public:

B(int a,int b)

{

Length=a;Width=b;

}

void Show()

{ cout<< "Length="<

}

};

class C{

int r,High;

A a1; //D

B b1; //E

public:

C(int a,int b, int c, int d,int e,int f):a1(e,f),b1(c,d) //F

{

r=a;High=b;

}

void Show()

{ cout<< "r="<

a1.Show();

b1.Show();

}

};

void main(void)

{

C c1(25,35,45,55,65,100); //G

c1.Show();

}

执行程序后输出以下3行:

r=25 High=35

x=65 y=100

Length=45 Width=55

D行定义了类A的对象a1,E行定义类B的对象b1。执行G行,产生类C的对象c1时,调用类C的构造函数,该构造函数在初始化对象c1时,又分别调用类A和类B的构造函数来初始化c1的对象成员a1和b1。在类C的构造函数的参数中,e和f作为类A的构造函数产生对象a1的初始化参数,c和d作为类B的构造函数产生对象b1的初始化参数。F行中的类C的构造函数:

C(int a,int b, int c, int d,int e,int f)

参数a,b,c,d,e,f是形参,而F行中的:

a1(e,f)

其中的e和f是调用类A的构造函数的实参,这二个参数在类C的构造函数中的顺序是任意的,这由a1(e,f)中的参数的名字确定使用构造函数C( )中的哪一个参数,而不管它是第几个形参。

在一个类的定义中,说明对象成员的一般格式为:

class ClassName {

ClassName1 c1;

ClassName2 c2;

......

ClassNamen cn;

public:

ClassName(args);

......

};

其中,ClassName1,ClassName2,......,ClassNamen是已经定义了的类名。为了初始化对象成员c1,c2,...,cn,类ClassName的构造函数要调用这些对象成员所对应类的构造函数,则类ClassName的构造函数的形式为:

ClassName::ClassName(args):c1(args1),c2(args2),......,cn(agrsn)

{

...... //对其它成员的初始化

}

把冒号后用逗号隔开的,对c1,c2,......,cn初始化的列表称为成员初始化列表,其中的参数表依次为调用相应成员所在类的构造函数时应提供的参数(实参)。这些参数通常来自于类 ClassName 的构造函数的形式参数“args”。初始化对象成员的参数(实参)可以是表达式。当然,也可以仅对部分对象成员进行初始化。

注意,在args中的形参必须带有类型说明,而在对象成员初始化列表中的每一个参数不要类型说明,并且均可为表达式。

对对象成员的构造函数的调用顺序取决于这些对象成员在类中说明的顺序,与它们在成员初始化列表中的顺序无关。

当建立类ClassName的对象时,先调用各个对象成员的构造函数,初始化相应的对象成员,然后才执行类ClassName的构造函数,初始化类ClassName中的其它成员。析构函数的调用顺序与构造函数正好相反。

例10.18 说明构造函数与析构函数的调用顺序。

#include

class Obj{

int val;

public:

Obj( )

{

val =0;

cout<

}

Obj(int i)

{

val =i;

cout<

}

~Obj( )

{

cout<<"调用Obj的构析构函数!\n";

}

};

class Con{

Obj one,two;

int data;

public:

Con()

{

data=0;

cout<

}

Con(int i,int j,int k):two(i+j),one(k)

{

data=i;

cout<

}

~Con()

{

cout<<"调用Con的构析构函数!\n";

}

};

void main(void)

{

Con c(100,200,400);

}

执行程序后输出以下6行:

400 调用Obj的构造函数!

300 调用Obj的构造函数!

100 调用Con的构造函数!

调用Con的构析构函数!

调用Obj的构析构函数!

调用Obj的构析构函数!

请读者自行分析程序的输出结果。

习题与思考10

1.构造函数和析构函数的作用是什么?为什么构造函数允许重载而析构函数不允许重载?

2.定义一个描述复数的类,数据成员有:实部和虚部;成员函数有:输出复数,构造函数完成数据的初始化。构成一个完整的程序,能测试数据与成员函数的正确性。3.定义一个描述学生基本情况的类,数据成员包括:姓名,学号,数学、英语、物理和C++成绩;成员函数:输出数据,构造函数可完成所有数据的初始化,修改每一个数据成员的函数,求出总成绩和平均成绩的函数。构成一个完整的程序,能测试数据与成员函数的正确性。

4.写出下面程序的执行结果:

#include

class A{

int m;

char *p1, *p2;

public:

A(int n)

{ m= n;

cout<<"调用构造函数A(int)\n";

}

A(double x)

{ if(x<0) m=x-0.5; else m=x+0.5;

cout<<"调用构造函数A(double)\n";

}

void Print( )

{ cout<<"m="<

~A(){cout<<" called ~A()\n"; }

};

void main(void )

{

A a(25), b(-200.75);

b.Print();

A c(0);

c= A(20);

}

5.为习题10.1~10.3中的类定义拷贝初始化构造函数。6.设定义一个类:

class Array{

int SizeI; //整型数组的大小

int PointI; //整型数组中实际存放的元素个数

int sizeR; //实型数组的大小

int PointR; //实型数组中实际存放的元素个数

int *pi; //指向整型数组,动态分配内存空间

int *pr; //指向实型数组,动态分配内存空间public:

Array(int si=100, int sr=200);

void put(int n); //将n加入整型数组中

void put(float x); //将x加入实型数组中

int Geti(int index); //取整型数组中的第index个元素

float Getr(int index); //取实型数组中的第index个元素

~Array(); //析构函数

void Print( ) ; //分别输出整型和实型数组中的所有元素

};

构成完整的程序,即完成该类成员函数的定义和测试程序的设计。构造函数 Array(intsi=100, int sr=200)中,si和sr分别为整型数组和实型数组的大小。

7. 下面定义一个存放字符串的线性表的类:

class LinearStr{

int Size; //字符串指针数组的大小

int Point; //字符串指针数组中实际存放的元素个数

char **strp; //指向字符串指针数组的指针

public:

LinearStr(int n=100)

{

if( n==0) {

Size= Point=0;

strp =0;

}

else {

Size=n;

Point=0;

strp= new char * [n];

for( int i=0; i

}

}

~LinearStr()

{

for(int i; i

if(strp[i] ) delete [] strp[i];

}

……;

};

参照例9.8处理一个线性表的要求,补充成员函数和测试程序,构成一个完整的程序。