?教学目的与要求:
了解派生类对象创建和释放的过程 。
掌握派生类的构造函数和析构函数的构造规则 。
教学内容提要:
1,派生类的构造函数和析构函数的执行顺序;
2,派生类的构造函数和析构函数的构造规则;
3,调整基类成员在 派生类中的访问属性
教学重点:派生类的构造函数和析构函数的构造规则 。
教学难点:派生类的构造函数和析构函数的构造规则 。
教学进度,P123~P136
教学过程:
第 13 讲 派生类的构造函数与析构函数对于派生类,由于其中包含有从基类继承来的和派生类中新声明的数据成员,而派生类和它的基类都有相应的构造函数和析构函数。
【 13.1派生类构造函数和析构函数的执行顺序 】
通常情况下,
当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数 ;
当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
例 13.1 基类和派生类的构造函数及析构函数的执行顺序 。
#include<iostream.h>
class Base{
public:
Base(){ cout<<"Constructing base class\n"; }
//基类的构造函数
~Base(){ cout<<"Destructing baes class\n"; }
//基类的析构函数
};
class Derive:public Base{
public:
Derive(){cout<<"Constructing derived class\n";}
//派生类的构造函数
~Derive(){cout<<"Destructing derived class\n";}
//派生类的析构函数
};
main()
{ Derive op; return 0; }
class B:public A
{
public:
B();
~ B();
void setz(int c){z=c;}
int getz(){return z;}
int getsum(){return x+gety()+z;}
private:
int z;
};
A::A():x(1),y(2)
{
cout<<“调用类 A的构造函数”
<<endl;
}
#include<iostream.h>
class A
{
public:
A();
~ A();
void setx(int a){x=a;}
void sety(int b){y=b;}
int getx() const{return x;}
int gety() const{return y;}
protected:
int x;
private:
int y;
};
例 13.2 调用构造函数和析构函数
A::~ A()
{
cout<<″调用类 A的析构函数 ″<<endl;
}
B::B():z(3)
{
cout<<″调用类 B的构造函数 ″<<endl;
}
B::~ B()
{
cout<<″调用类 B的析构函数 ″<<endl;
}
void main()
{
B b;
cout<<″X=″<<b.getx()<<″\tY=″<<b.gety()
<<″\tZ=″<<b.getz()<<endl;
cout<<″X+Y+Z=″<<b.getsum()<<endl;
}
(续)
在创建派生类的对象时,系统将首先调用其基类的构造函数来初始化从基类中继承的数据成员,然后调用派生类自身的构造函数初始化在派生类中新声明的数据成员。 终止对象时,
析构函数的执行顺序则正好相反,即先调用派生类的析构函数清除派生类中新声明的数据成员,再调用基类的析构函数清除从基类中继承的数据成员。
注调用类 A的构造函数调用类 B的构造函数
X=1 Y=2 Z=3
X+Y+Z=6
调用类 B的析构函数调用类 A的析构函数
(续)
1,在 C++中,派生类构造函数的一般格式为,
派生类名 (参数总表 ):基类名 (参数表 )
{
// 派生类新增成员的初始化语句
};
通过为派生类定义一个带有初始化列表的构造函数。
【 13.2派生类构造函数和析构函数的构造规则 】
例 13.3 当基类含有带参数的构造函数时,派生类构造函数的构造方法 。
#include<iostream.h>
class Base {
public:
Base(int n) //基类的构造函数
{
cout<<"Constructing base class\n";
i=n;
}
~Base() //基类的析构函数
{ cout<<"Destructing base class\n"; }
void showi()
{ cout<<i<<endl; }
private:
int i;
};
class Derive,public Base{
public:
Derive(int n,int m):Base(m) // 定义派生类构造函数时,
{ // 缀上基类的构造函数
cout<<"Constructing derived class"<<endl; j=n; }
~Derive() //派生类的析构函数
{ cout<<"Destructing derived class"<<endl; }
void showj(){ cout<<j<<endl;}
private:
int j;
};
main()
{ Derive obj(50,60);
obj.showi(); obj.showj();
return 0;
}
派生类名::派生类名(参数总表):基类(参数表),对象成员 1(对象成员参数表 1),对象成员 2(对象成员参数表 2),…,
对象成员 n(对象成员参数表 n)
{
派生类中新声明的数据成员初始化语句
}
参数总表 应包括初始化基类数据成员、内嵌对象数据成员及其他数据成员所需的全部数据。
是需要用参数初始化的 基类名、对象成员名及各自对应的参数表,
基类名和对象成员名 之间的顺序可以是任意的,且对于使用默认构造函数的基类和对象成员,可以不列出基类名和对象成员名。
对象成员 是指在派生类中新声明的数据成员,它是属于另外一个类的对象。对象成员必须在初始化列表中初始化。
2、当派生类中含有内嵌对象成员时,其构造函数的一般形式为,
( 1) 调用派生类析构函数。
( 2) 调用派生类中新增对象成员的析构函数,顺序与它们在派生类中声明的顺序相反。
( 3) 调用基类的析构函数。
析构函数的执行顺序与构造函数正好相反。其顺序是:
( 1) 调用基类的构造函数。
( 2) 调用对象成员的构造函数,调用顺序是按照它们在 派生类声明中的顺序。
( 3) 派生类的构造函数体中的内容。
派生类构造函数执行的一般顺序如下:
#include <iostream.h>
class A
{
public:
A(int i)
{
a=i;
cout<<” constructor called.A\n” ;
}
~A() { cout<<” Destructor called,A\n” ;}
void print() { cout<<a<<’,’ ; }
int Geta( ) {return a;}
private,int a;
};
例 13.5
classB:publicA
{
public:
B(int i,intj,int k);
~B() {cout<<”Destructorcalled B\n”;}
void print( )
{ A::print( );
cout<<b<<’,’<<aa.Geta()<<endl;
}
private,intb;
A aa;
};
B::B(inti,intj,int k):A(i),aa(j)
{
b=k;
cout<<”constructorcalled,B\n”;
}
void main()
{
B obj(3,4,5);
obj.print();
}
constructor called,A
constructor called,A
constructor called,B
3,5,4
Destructor called B
Destructor called A
Destructor called A
【 13.3 调整基类成员在 派生类中的访问属性 】
【 13.3.1 同名成员 】
C++中允许在派生类中声明的成员与基类中的成员名相同。
如果在派生类中定义了与基类成员同名的成员,则称派生类成员覆盖了基类的同名成员。
class X
{ public,
int f();
};
class Y:public X
{ public:
int f();
void g()
{ f();
}
};
…
Y obj;
Obj.f();
//调用的派生类中的 f()
//调用的派生类中的 f()
如果要访问基类中声明的同名的成员,则必须在成员名前加上基类名和作用域运算符,如下格式:基类名::
成员名。
class X
{ public,
int f();
};
class Y:public X
{ public:
int f();
void g()
{ X:,f();
}
};
…
Y obj;
Obj.X::f();
//调用的基类中的 f()
//调用的基类中的 f()
参考教材 例 4.9
#include<iostream.h>
class A
{
public:
void setx(int a){x=a;}
void sety(int b){y=b;}
int getx() const{return x;}
int gety() const{return y;}
protected:
int x;
private:
int y;
};
class B:private A
{
public:
void setBx(int a){setx(a);}
void setBy(int b){sety(b);}
例 13.7
【 13.3.2 访问声明 】
int getBx(){return getx();}
int getBy(){return gety();}
int getsum(){return x+gety();}
};
void main()
{
B b;
cout<<″X=″<<b.getBx()<<″\ tY=″<<b.getBy()<<endl;
cout<<″X+Y=″<<b.getsum()<<endl;
}
(续)
b.setBx(2);
b.setBy(3);
etx(2); //error
ety(3); //error
上述程序是派生类对象通过调用派生类中的公有成员函数间接访问基类中的成员,但有时程序员可能希望基类的某些成员可被派生类对象直接访问。为此 C++提供了称为访问声明的特殊机制,可个别调整基类的某些成员,使之在派生类中保持原有的访问属性。
#include<iostream.h>
class A
{
public:
void setx(int a){x=a;}
void sety(int b){y=b;}
int getx() const{return x;}
int gety() const{return y;}
protected:
int x;
private:
int y;
};
class B:private A
{
public:
void setBx(int a){setx(a);}
void setBy(int b){sety(b);}
int getBx(){return getx();}
int getBy(){return gety();}
A::setx;
A::sety;
int getsum(){return x+gety();}
};
void main()
{
B b;
b.setx(5);
b.sety(6);
cout<<″X=″<<b.getBx()<<″\
tY=″<<b.getBy()<<endl;
cout<<″X+Y=″<<b.getsum()<<endl;
}
访问声明的方法是把基类中的公有成员或保护成员直接写在派生类类体中的同名段中,同时冠以基类名和作用域运算符。
2、访问声明中只含有不带类型和参数的函数名或变量名。
如上述访问声明写成下列形式都是错误的:
void A::setx;
void A::setx();
A::setx();
注意
1、数据成员也可以使用访问声明。
3、访问声明不能改变原来在基类中的成员性质。
4,对于基类中的重载函数名,访问声明将对基类中所有同名函数起作用,这意味着对于重载函数使用访问声明时要慎重。
小结
1,派生类的构造函数和析构函数的执行顺序
2,派生类的构造函数和析构函数的构造规则
3,调整基类成员在 派生类中的访问属性作业:
P158 4.4,4.10
了解派生类对象创建和释放的过程 。
掌握派生类的构造函数和析构函数的构造规则 。
教学内容提要:
1,派生类的构造函数和析构函数的执行顺序;
2,派生类的构造函数和析构函数的构造规则;
3,调整基类成员在 派生类中的访问属性
教学重点:派生类的构造函数和析构函数的构造规则 。
教学难点:派生类的构造函数和析构函数的构造规则 。
教学进度,P123~P136
教学过程:
第 13 讲 派生类的构造函数与析构函数对于派生类,由于其中包含有从基类继承来的和派生类中新声明的数据成员,而派生类和它的基类都有相应的构造函数和析构函数。
【 13.1派生类构造函数和析构函数的执行顺序 】
通常情况下,
当创建派生类对象时,首先执行基类的构造函数,随后再执行派生类的构造函数 ;
当撤消派生类对象时,则先执行派生类的析构函数,随后再执行基类的析构函数。
例 13.1 基类和派生类的构造函数及析构函数的执行顺序 。
#include<iostream.h>
class Base{
public:
Base(){ cout<<"Constructing base class\n"; }
//基类的构造函数
~Base(){ cout<<"Destructing baes class\n"; }
//基类的析构函数
};
class Derive:public Base{
public:
Derive(){cout<<"Constructing derived class\n";}
//派生类的构造函数
~Derive(){cout<<"Destructing derived class\n";}
//派生类的析构函数
};
main()
{ Derive op; return 0; }
class B:public A
{
public:
B();
~ B();
void setz(int c){z=c;}
int getz(){return z;}
int getsum(){return x+gety()+z;}
private:
int z;
};
A::A():x(1),y(2)
{
cout<<“调用类 A的构造函数”
<<endl;
}
#include<iostream.h>
class A
{
public:
A();
~ A();
void setx(int a){x=a;}
void sety(int b){y=b;}
int getx() const{return x;}
int gety() const{return y;}
protected:
int x;
private:
int y;
};
例 13.2 调用构造函数和析构函数
A::~ A()
{
cout<<″调用类 A的析构函数 ″<<endl;
}
B::B():z(3)
{
cout<<″调用类 B的构造函数 ″<<endl;
}
B::~ B()
{
cout<<″调用类 B的析构函数 ″<<endl;
}
void main()
{
B b;
cout<<″X=″<<b.getx()<<″\tY=″<<b.gety()
<<″\tZ=″<<b.getz()<<endl;
cout<<″X+Y+Z=″<<b.getsum()<<endl;
}
(续)
在创建派生类的对象时,系统将首先调用其基类的构造函数来初始化从基类中继承的数据成员,然后调用派生类自身的构造函数初始化在派生类中新声明的数据成员。 终止对象时,
析构函数的执行顺序则正好相反,即先调用派生类的析构函数清除派生类中新声明的数据成员,再调用基类的析构函数清除从基类中继承的数据成员。
注调用类 A的构造函数调用类 B的构造函数
X=1 Y=2 Z=3
X+Y+Z=6
调用类 B的析构函数调用类 A的析构函数
(续)
1,在 C++中,派生类构造函数的一般格式为,
派生类名 (参数总表 ):基类名 (参数表 )
{
// 派生类新增成员的初始化语句
};
通过为派生类定义一个带有初始化列表的构造函数。
【 13.2派生类构造函数和析构函数的构造规则 】
例 13.3 当基类含有带参数的构造函数时,派生类构造函数的构造方法 。
#include<iostream.h>
class Base {
public:
Base(int n) //基类的构造函数
{
cout<<"Constructing base class\n";
i=n;
}
~Base() //基类的析构函数
{ cout<<"Destructing base class\n"; }
void showi()
{ cout<<i<<endl; }
private:
int i;
};
class Derive,public Base{
public:
Derive(int n,int m):Base(m) // 定义派生类构造函数时,
{ // 缀上基类的构造函数
cout<<"Constructing derived class"<<endl; j=n; }
~Derive() //派生类的析构函数
{ cout<<"Destructing derived class"<<endl; }
void showj(){ cout<<j<<endl;}
private:
int j;
};
main()
{ Derive obj(50,60);
obj.showi(); obj.showj();
return 0;
}
派生类名::派生类名(参数总表):基类(参数表),对象成员 1(对象成员参数表 1),对象成员 2(对象成员参数表 2),…,
对象成员 n(对象成员参数表 n)
{
派生类中新声明的数据成员初始化语句
}
参数总表 应包括初始化基类数据成员、内嵌对象数据成员及其他数据成员所需的全部数据。
是需要用参数初始化的 基类名、对象成员名及各自对应的参数表,
基类名和对象成员名 之间的顺序可以是任意的,且对于使用默认构造函数的基类和对象成员,可以不列出基类名和对象成员名。
对象成员 是指在派生类中新声明的数据成员,它是属于另外一个类的对象。对象成员必须在初始化列表中初始化。
2、当派生类中含有内嵌对象成员时,其构造函数的一般形式为,
( 1) 调用派生类析构函数。
( 2) 调用派生类中新增对象成员的析构函数,顺序与它们在派生类中声明的顺序相反。
( 3) 调用基类的析构函数。
析构函数的执行顺序与构造函数正好相反。其顺序是:
( 1) 调用基类的构造函数。
( 2) 调用对象成员的构造函数,调用顺序是按照它们在 派生类声明中的顺序。
( 3) 派生类的构造函数体中的内容。
派生类构造函数执行的一般顺序如下:
#include <iostream.h>
class A
{
public:
A(int i)
{
a=i;
cout<<” constructor called.A\n” ;
}
~A() { cout<<” Destructor called,A\n” ;}
void print() { cout<<a<<’,’ ; }
int Geta( ) {return a;}
private,int a;
};
例 13.5
classB:publicA
{
public:
B(int i,intj,int k);
~B() {cout<<”Destructorcalled B\n”;}
void print( )
{ A::print( );
cout<<b<<’,’<<aa.Geta()<<endl;
}
private,intb;
A aa;
};
B::B(inti,intj,int k):A(i),aa(j)
{
b=k;
cout<<”constructorcalled,B\n”;
}
void main()
{
B obj(3,4,5);
obj.print();
}
constructor called,A
constructor called,A
constructor called,B
3,5,4
Destructor called B
Destructor called A
Destructor called A
【 13.3 调整基类成员在 派生类中的访问属性 】
【 13.3.1 同名成员 】
C++中允许在派生类中声明的成员与基类中的成员名相同。
如果在派生类中定义了与基类成员同名的成员,则称派生类成员覆盖了基类的同名成员。
class X
{ public,
int f();
};
class Y:public X
{ public:
int f();
void g()
{ f();
}
};
…
Y obj;
Obj.f();
//调用的派生类中的 f()
//调用的派生类中的 f()
如果要访问基类中声明的同名的成员,则必须在成员名前加上基类名和作用域运算符,如下格式:基类名::
成员名。
class X
{ public,
int f();
};
class Y:public X
{ public:
int f();
void g()
{ X:,f();
}
};
…
Y obj;
Obj.X::f();
//调用的基类中的 f()
//调用的基类中的 f()
参考教材 例 4.9
#include<iostream.h>
class A
{
public:
void setx(int a){x=a;}
void sety(int b){y=b;}
int getx() const{return x;}
int gety() const{return y;}
protected:
int x;
private:
int y;
};
class B:private A
{
public:
void setBx(int a){setx(a);}
void setBy(int b){sety(b);}
例 13.7
【 13.3.2 访问声明 】
int getBx(){return getx();}
int getBy(){return gety();}
int getsum(){return x+gety();}
};
void main()
{
B b;
cout<<″X=″<<b.getBx()<<″\ tY=″<<b.getBy()<<endl;
cout<<″X+Y=″<<b.getsum()<<endl;
}
(续)
b.setBx(2);
b.setBy(3);
etx(2); //error
ety(3); //error
上述程序是派生类对象通过调用派生类中的公有成员函数间接访问基类中的成员,但有时程序员可能希望基类的某些成员可被派生类对象直接访问。为此 C++提供了称为访问声明的特殊机制,可个别调整基类的某些成员,使之在派生类中保持原有的访问属性。
#include<iostream.h>
class A
{
public:
void setx(int a){x=a;}
void sety(int b){y=b;}
int getx() const{return x;}
int gety() const{return y;}
protected:
int x;
private:
int y;
};
class B:private A
{
public:
void setBx(int a){setx(a);}
void setBy(int b){sety(b);}
int getBx(){return getx();}
int getBy(){return gety();}
A::setx;
A::sety;
int getsum(){return x+gety();}
};
void main()
{
B b;
b.setx(5);
b.sety(6);
cout<<″X=″<<b.getBx()<<″\
tY=″<<b.getBy()<<endl;
cout<<″X+Y=″<<b.getsum()<<endl;
}
访问声明的方法是把基类中的公有成员或保护成员直接写在派生类类体中的同名段中,同时冠以基类名和作用域运算符。
2、访问声明中只含有不带类型和参数的函数名或变量名。
如上述访问声明写成下列形式都是错误的:
void A::setx;
void A::setx();
A::setx();
注意
1、数据成员也可以使用访问声明。
3、访问声明不能改变原来在基类中的成员性质。
4,对于基类中的重载函数名,访问声明将对基类中所有同名函数起作用,这意味着对于重载函数使用访问声明时要慎重。
小结
1,派生类的构造函数和析构函数的执行顺序
2,派生类的构造函数和析构函数的构造规则
3,调整基类成员在 派生类中的访问属性作业:
P158 4.4,4.10