代码改变世界

C++之继承与派生(2)

2016-03-16 17:55  想打架的蜜蜂  阅读(212)  评论(0编辑  收藏  举报

C++之继承与派生(2)

http://www.cnblogs.com/CaiNiaoZJ/archive/2011/08/09/2131942.html

上一节,主要讲解了有关派生类继承方式的内容。那么今天就来说说派生类的构造函数和析构函数,以及怎么样在派生类中显式访问积累成员。大家都知道,基类的构造函数和析构函数是不能被继承的,因此我们必须在派生类的构造函数中对基类的构造函数所需要的参数进行设置。同样,对于派生类对象的清理工作也需要加入新的析构函数。

  1.那么该如何构造呢?对于简单的派生类,即只有一个基类,且直接派生(多继承将在后续几节中做详细讲解),来讲,如果基类的构造函数没有参数,或者没有显式定义构造函数,那么派生类可以不向基类传递参数,甚至可以不定义构造函数。但是一旦基类含有带参数的构造函数时,派生类必须定义构造函数,并把参数传递给基类构造函数。其一般格式:

  派生类名(参数总表)::基类名(参数子表)

  {

    派生类新增成员的初始化语句;

  }

而析构函数可以有用户自己定义,由于其是不带参数的,所以在派生类中是否要自定义析构函数与它所属基类的析构函数无关。在执行派生类的构造函数时,系统会自动调用基类的析构函数,进行对象清理。下面举例来说明一下简单派生类的构造和析构: 

#include <iostream>
#include <string>

class C
{
private:
std::string c;
public:
C(){};
C(std::string c);
~C();
void showC();
};

C::C(std::string c)
{
this->c=c;
std::cout<<"构造对象c"<<std::endl;
}

C::~C()
{
std::cout<<"清理对象c"<<std::endl;
}
void C::showC()
{
std::cout<<"c="<<this->c<<std::endl;
}


class D:private C
{
private:
std::string d;
public:
D(std::string d,std::string c);
~D();
void showD();
};

D::D(std::string d,std::string c):C(c) //参数传递给基类,也可以不写C(c),那么就调用了C类中无参构造函数
{
this->d=d;
std::cout<<"构造对象d"<<std::endl;
}

D::~D()
{
std::cout<<"清理对象d"<<std::endl;
}

void D::showD()
{
showC();
std::cout<<"d="<<this->d<<std::endl;
}

 

int main()
{
{
D d("ddd","ccc");
d.showD();
}

return 0;
}

结果:

在结果里我们可以看到派生类构造函数和析构函数的调用顺序,当创建派生类对象时,首先调用基类的构造函数,再调用派生类构造函数,而当清理对象时,则刚好相反。

  那么如果当派生类中存在成员对象,那么构造函数和析构函数的调用顺序又如何呢?在解答这个问题之前,我们先看一下当派生类中含有子对象时,构造函数的一般格式:

  派生类名(参数总表):基类名(参数子表),对象名1(参数子表1),对象名2(参数子表2)

  {

    派生类新增成员的初始化语句;

  } 

还是,我们用一个示例来说明,并通过实际的运行结果来解答上述的问题:

#include <iostream>
#include <string>

class C
{
private:
std::string c;
public:
C(){};
C(std::string c);
~C();
void showC();
};

C::C(std::string c)
{
this->c=c;
std::cout<<"构造c值为"<<c<<"的对象c"<<std::endl;
}

C::~C()
{
std::cout<<"清理c值为"<<c<<"的对象c"<<std::endl;
}
void C::showC()
{
std::cout<<"c="<<this->c<<std::endl;
}


class D:private C
{
private:
std::string d;
C c;
public:
D(std::string d,std::string c1,std::string c2);
~D();
void showD();
};

D::D(std::string d,std::string c1,std::string c2):c(c2),C(c1) //参数传递给基类,也可以不写C(c1)或c(c2),那么就调用了C类中无参构造函数
{
this->d=d;
std::cout<<"构造对象d"<<std::endl;
}

D::~D()
{
std::cout<<"清理对象d"<<std::endl;
}

void D::showD()
{
showC();
c.showC();
std::cout<<"d="<<this->d<<std::endl;
}

 

int main()
{
{
D d("ddd","ccc1","ccc2");
d.showD();
}

return 0;
}

结果:

从结果中很清楚的说明了其构造函数和析构函数的调用顺序。另外读者也可以在类D成员对象c后面再声明一个基类对象,运行看看,修改过后其构造和析构的调用顺序又如何?最后一点要补充的是,如果派生类的基类也是派生类,那么每个派生类只需要负责其直接的基类数据成员的初始化。

  2.在定义派生类时,C++里是允许派生类中的成员名和基类中的成员名相同,出现这种情况,我们称派生类成员覆盖了基类中使用相同名称的成员。也就是说当你在派生类中或用对象访问该同名成员时,你所访问只是派生类中的成员,基类中的就自动被忽略。但如果我确实要访问到基类中的同名成员那怎么办,C++中这样规定必须在成员名前加上基类名和作用域标识符"::"。

  3.最后还是一样,我将用一个示例来总结一下今天所讲的内容(开发工具:vs2010):

#include <iostream>
#include <string>

class C
{
public:
std::string c;
C(){};
C(std::string c);
~C();
void showC();
};

C::C(std::string c)
{
this->c=c;
std::cout<<"构造c值为"<<c<<"的对象c"<<std::endl;
}

C::~C()
{
std::cout<<"清理c值为"<<c<<"的对象c"<<std::endl;
}
void C::showC()
{
std::cout<<"c="<<this->c<<std::endl;
}


class D:public C
{
private:
std::string c;//同名数据成员
std::string d;
C c2;
C c3;
public:
D(std::string d,std::string c1,std::string c2);
~D();
void showC();//同名成员函数
void updateC(std::string c);
};

D::D(std::string d,std::string c2,std::string c3):c2(c2),c3(c3),C("ccc1") //参数传递给基类,也可以不写C(c1)或c(c2),那么就调用了C类中无参构造函数
{
this->d=d;
this->c=c2;//覆盖了基类的数据成员c,访问的是派生类的数据数据成员c
std::cout<<"构造对象d"<<std::endl;
}

D::~D()
{
std::cout<<"清理对象d"<<std::endl;
}

void D::showC()//同名成员函数
{
C::showC();//显式访问基类成员函数showC();
c2.showC();
std::cout<<"D::c="<<c<<std::endl;//覆盖了基类的数据成员c
std::cout<<"C::c="<<C::c<<std::endl;//显式访问基类数据成员c
std::cout<<"d="<<this->d<<std::endl;
}

void D::updateC(std::string c)
{
C::c=c;//显式访问基类数据成员c
}

 

int main()
{
{
D d("ddd","ccc2","ccc3");
d.showC();//访问的是派生类中函数成员showC()
d.updateC("ccc4");
d.showC();//同理
}

return0;
}

结果: