您的位置:首页 > 其它

继承与派生

2016-11-19 08:03 169 查看
定义:类的继承,是新的类从已有类那里得到已有的特性,从已有类产生新类的过程就是类的派生,原有的类称为基类,产生的新类称为派生类;一个派生类,可以同时有多个基类,这种情况称为多继承;派生类成员是指除了从基类继承来的所有成员之外,新增加大的数据和函数成员;

一.派生类生成过程

1.吸收基类成员

派生类实际上包含了它的全部基类中除构造函数和析构函数之外的所有成员;

2.如果派生类声明了一个和某基类成员同名的新成员(如果是成员函数,则参数表也要相同,参数不同的情况属于重载),派生类的新成员就隐藏了外层同名成员,这叫做同名隐藏;

3.添加新成员

增加数据和函数成员,实现必要的新增功能;

二.访问控制

1.共有继承

当类的继承方式为公有继承时,基类的公有成员和保护成员的访问属性在派生类中不变,而无论是派生类的成员还是派生类的对象都无法直接访问基类的私有成员;

#include<iostream>
#include<cstdlib>

class Point								//基类Point类的定义
{
public:
Point(float x,float y)				//构造函数
{
this->x=x;
this->y=y;
}
float getX() const
{
return x;
}
float getY() const
{
return y;
}
private:
float x,y;
};

class Rectangle:public Point			//派生类定义部分
{
public:
Rectangle(float x,float y,float w,float h):Point(x,y)		//构造函数
{
this->w=w;
this->h=h;
}
float getH() const
{
return h;
}
float getW() const
{
return w;
}
private:
float w,h;
};

int main(void)
{
Rectangle a(1,2,3,4);
std::cout<<a.getX()<<a.getY()<<a.getH()<<a.getW()<<"\n";
system("pause");
return 0;
}
收获:

(1).直接在形参表里初始化只限于构造函数和复制构造函数;

(2).若Point类的构造函数初始化列表x=0,y=0,当初始化Rectangle时,只传一个y进去的话,最终的结果为x=2,y=0,如果没有初始化为0,则因少一个参数编译错误;

(3)从基类那里继承成员,可以理解为,派生类有了可以访问基类成员的权利,而不是直接在派生类中产生一个副本;

2.私有继承

当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中;经过私有继承后,所有基类的成员都成了派生类的私有成员或不可直接访问的成员,如果进一步派生的话,基类的全部成员就无法在新的派生类中直接被访问(言外之意,公有继承是可以的,他的‘孙子’有访问‘爷爷’成员的权利),实际上阻止了基类功能的继续派生;

#include<iostream>
#include<cstdlib>

class Point								//基类Point类的定义
{
public:
Point(float x,float y)				//构造函数
{
this->x=x;
this->y=y;
}
float getX() const
{
return x;
}
float getY() const
{
return y;
}
private:
float x,y;
};

class Rectangle:private Point			//派生类定义部分(私有继承)
{
public:
Rectangle(float x,float y,float w,float h):Point(x,y)		//构造函数
{
this->w=w;
this->h=h;
}
float getH() const
{
return h;
}
float getW() const
{
return w;
}
//!!!!!!!!!!!!!!!!!!!
float getX() const
{
return Point::getX();
}
float getY() const
{
return Point::getY();
}
//!!!!!!!!!!!!!!!!!!!
private:
float w,h;
};

int main(void)
{
Rectangle a(1,2,3,4);
std::cout<<a.getX()<<a.getY()<<a.getH()<<a.getW()<<"\n";
system("pause");
return 0;
}
收获:

(1)当没有//!!!!!!!!!下面的两个函数,主函数中的a就无法调用Point中的getX();

(2)即使在Rectangle的作用域中的函数,也无法访问基类Point的私有成员,无论是公有继承还是私有继承,故其实继承,只是给了派生类访问基类的权利,并没在派生类中产生副本,而私有继承与公有继承不同的是,它继承来的东西,无法在类外直接访问了;如上程序中原有的外部接口getX和getY被派生类封装和隐蔽起来,用了上面程序的方法后,就可使Point的外部接口‘出现’;

(ps:在访问Rectangle的getX时,由于新增的函数比基类同名函数有更小的作用域,因此在调用时,自然会使用派生类的函数)

三.类型兼容规则

定义:类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,替代情况包括:

(1)派生类的对象隐式转换为基类对象(即用派生类对象中从基类继承来的成员,逐个赋值给基类对象的成员);

(2)派生类的对象可以初始化基类的引用;

(3)派生类的指针可以隐式转换为基类的指针;

B b1,*pb1;    //基类
D d1;             //派生类
b1=d1;
B &rb=dl;
pb1=&dl;       //指针pb1虽然指向了对象dl,但是通过pb1只能只用继承下来的基类成员


在替代完成之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员

#include<iostream>
#include<cstdlib>

class Base1				//基类Base1定义
{
public:
void display() const
{
std::cout<<"Base1::display()"<<std::endl;
}
};

class Base2:public Base1		//公有派生类Base2定义
{
public:
void display() const
{
std::cout<<"Base2::display()"<<std::endl;
}
};

class Derived:public Base2		//公有派生类Derived定义
{
public:
void display() const
{
std::cout<<"Derived::display()"<<std::endl;
}
};

void fun(Base1 *ptr)					//参数为指向基类的指针
{
ptr->display();
}

int main(void)
{
Base1 base1;
Base2 base2;
Derived derived;

fun(&base1);
fun(&base2);
fun(&derived);
system("pause");
return 0;
}

四.派生类的构造和析构函数

1.构造函数

(1)构造派生类的对象时,就要对基类的成员对象和新增成员对象进行初始化;首先调用基类的构造函数,来初始化它们的数据成员,然后按照构造函数初始化列表中指定的方式初始化派生类新增的成员对象,最后再执行派生类构造函数的函数体;对于使用默认构造函数(即没有手写构造函数或没有参数)的基类,可以不给出类名,系统自动初始化,不用给出‘提示’,成员对象的初始化也如此;

(2)如果对基类初始化时,需要调用基类的带有形参表的构造函数时,派生类必须声明构造函数,提供一个将参数传递给基类构造函数的途径;当派生类没有显式构造函数时,系统会生成一个默认构造函数,该默认构造函数会使用基类的默认构造函数对继承自基类的数据初始化,并调用类  类型  的成员对象的默认构造函数,对这些成员对象初始化;

(3)执行顺序

调用基类构造函数按照它们被继承时声明的顺序(从左至右)
派生类新增成员的初始化,按照它们在类中声明的顺序
执行函数体的内容
class Base1
{
public:
Base1(int i) {;}			//基类Base1,构造函数有参数
};

class Base2
{
public:
Base2(int j) {;}			//基类Base2,构造函数有参数
};

class Base3
{
public:
Base3() {;}			//基类Base3,构造函数无参数
};

class Derived:public Base2,public Base1,public Base3
{
public:
/*初始化顺序:Base2->Base1->Base3->member1->member2->member3,没有Base3和member3,因为Base3的构造函数没有参数,
初始化列表的顺序执行顺序无关!!*/
Derived(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base2(b)
{
;
}
private:
Base1 member1;
Base2 member2;
Base3 member3;
};
2.复制构造函数
如果要为派生类编写复制构造函数,一般需要为基类相应的复制构造函数传递参数
(ps:凡是出现新的派生类都需要“重新构造”基类)
3.析构函数
派生类的析构函数只需在函数体中负责把派生类新增的非对象成员进行清理即可,系统会自己调用基类即对象成员的析构函数对基类及对象成员进行清理,清理顺序与构造顺序相反;

五.派生类成员的标识与访问
如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将隐藏所有基类的同名成员,若派生类的函数与基类的函数同名但具有不同的参数数量或参数类型,不属于函数重载,因为只有在相同的作用域中定义的函数才可以重载;通过对象名  .  成员名的方法可以唯一标识和访问派生类新增成员,但若派生类中没有这个同名成员,这个方法就没有办法唯一标识成员,因为从不同基类继承过来的同名成员具有相同的作用域(即作用域范围相同,没有包含与被包含的关系,如基类包含派生类);
#include<iostream>
#include<cstdlib>

class Base0
{
public:
int var0;
void fun0() {std::cout<<"Member of Base0"<<std::endl;}
};

class Base1:public Base0
{
public:
int var1;
};

class Base2:public Base0
{
public:
int var2;
};

class Derived:public Base1,public Base2
{
public:
int var;	void fun() {std::cout<<"Member of Derived"<<std::endl;}
};

int main(void)
{
Derived d;
d.Base1::var0=2;
d.Base1::fun0();
d.Base2::var0=3;
d.Base2::fun0();
system("pause");
return 0;
}
收获:其实Base0类的函数成员fun0的代码始终只有一个副本,之所以调用fun0函数时仍使用基类名,是因为调用非静态函数是总是针对特定的对象(this指针),上述中Derived类的对象中存在两个Base()类的子对象(可能是每次新构造一个对象,基类就会为之生成一个"子对象"),因此调用fun0()函数时,需要使用Base1或Base2加以限定,以明确针对哪个Base0对象调用;

六.虚基类及其派生类构造函数

在派生类的对象中,同名数据成员在内存中同时拥有多个副本,同一个函数名会有多个映射,若将共同基类设置为虚基类,这时从不同的路径继承来的同名数据成员在内存中就只有一个副本,同一个函数名也只有一个映射(即不管创造多少个派生类,基类都只会因此产生一个子对象);
#include<iostream>
#include<cstdlib>

class Base0
{
public:
Base0(int var):var0(var) {;}
int var0;
void fun0() {std::cout<<"Member of Base0"<<std::endl;}
};

class Base1:virtual public Base0
{
public:
Base1(int var):Base0(var) {;}
int var1;
};

class Base2:virtual public Base0
{
public:
Base2(int var):Base0(var) {;}
int var2;
};

class Derived:public Base1,public Base2
{
public:
Derived(int var):Base0(var),Base1(var),Base2(var) {;}
int var;
void fun() {std::cout<<"Member of Derived"<<std::endl;}
};

int main(void)
{
Derived d(1);
d.var=2;
d.fun();
d.fun0();	//因为基类只产生了一个对象,所以,通过d唯一的调用仅此一个的Base0子对象
system("pause");
return 0;
}
注意:此案例中并不会初始化var0三次,因为c++编译器“决定”,建立一个对象时,如果这个对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类(Derived)的构造函数通过调用虚基类的构造函数进行初始化的,该派生类的其它基类(Base1、Base2)对虚基类构造函数的调用都自动被忽略;
构造一个类的对象的一般顺序:

(有直接或间接的虚基类时)执行虚基类构造函数->>执行其他基类的构造函数->>执行新增类类型的成员对象的构造函数->>基本数据类型的初始化->>执行函数体

彩蛋(using补充)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: