1、c++类的继承和派生机制
继承指的是从先辈出得到属性和行为的特征,也就是新类从已有的类那里得到已有的特性,反过来看,从已有类产生新类的过程就是类的派生。因此类的继承和派生是一个成对出现的概念,原来的类称为基类或者父类,产生的新类称为派生类或者子类,派生类同样可以作为基类派生新的类,这样就可以建立一个具有共同特性的对象家族,实现代码的重用。派生新类的过程一般包括,吸收父类的成员,调整父类的成员,添加新的成员。
2、派生类的定义:
class 派生类名称:继承方式 第一个基类名,继承方式 第二个基类名称,...,继承方式 第n个基类名称
{
派生类成员声明;
};
例:
class Linequ: public Matrix //公有派生类Linequ定义 { public: Linequ(int dims=2); //构造函数 ~Linequ(); //析构函数 void setLinequ(double *a,double *b);//方程赋值 void printL(); //显示方程 int Solve(); //全选主元高斯消去法求解方程 void showX(); //显示方程的解 private: //私有数据 double *sums; //方程右端项 double *solu; //方程的解 };
其中
Linequ是派生类的名称;public是继承方式,继承方式主要的作用是控制从基类继承的成员的访问属性(另外还有 private 和protected两种继承方式,此处未使用)父类是Matrix;花括号{}里面的内容是派生类的成员声明;
这里需要注意的是一个派生类也可以同时继承多个基类,这叫做多继承,像上面的代码例子是只继承类一个Matrix基类,就是单继承的。另外派生出来的新类也可以继续作为基类再次派生新类。
3、派生类生成的过程
派生类主要有三个步骤,吸收父类的成员,调整父类的成员,添加新的成员。
吸收父类成员
就是将基类中的成员,除了构造函数和析构函数之外,全都接收到派生类中,因此构造函数和析构函数是不能被继承的。
调整基类成员包括两个方面,
一个是基类成员的访问控制问题,主要依靠派生列定义时的继承方式来控制;第二个是对基类数据成员或函数成员的隐藏,隐藏的方式就是在新类中声明一 个和基类中数据或者 函数同名的成员,这样派生类中的该成员就实现了对基类数据成员或函数成员的隐藏。如果在派生类中或者通过派生类的对象,直接使用成员名就只能访问到派 生类中声明的同 名函数,无法基类中的数据成员或函数成员,从而实现了对基类成员的隐藏。
添加新成员是继承和派生机制的核心
因为根据实际情况给派生类添加新的适当的数据和成员,就可以实现新的功能,例如派生类没有继承基类的构造函数和析构函数,所以就要在派生 列中加入新的构造函数和析构 函数来完成初始化和扫尾工作。
4、派生类的访问控制
派生类的访问控制是通过类的继承方式实现的,类的继承方式主要有public(公有继承),protected(保护继承),private(私有继承)。按照这三种继承方式,从基类继承的基类成员在 派生类中的访问属性也是不同的,这里的访问属性不同主要是从两方面来说的,首先是派生类中的新增成员对从基类继承的成员的访问,其次是派生类外部,即通过派生类声明的对象访问从 基类继承的成员函数。
第一:公有继承 方式
当类的继承为公有继承方式时,基类的公有和保护成员的访问成员在派生类中仍然作为派生类的公有成员和保护成员,派生类的其他成员可以直接访问。在类之外只能通过派生类的对象访问 从基类继承的公有成员。但是无论是派生类的成员还是派生类的对象都无法直接访问基类的私有成员。
第二:私有继承方式
当类的继承为私有继承时,基类的公有和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问。但是类外部的派生类对象无法直接访问它们。但是为了在私有继承的情 况下保证基类的一部分外部接口特征能够在派生类中存在,可以在派生类中声明基类的同名函数,利用派生类对基类的访问能力,把基类的成员函数功能搬过来,同时我们也要知道,根据同 名隐藏原则,派生类在调用时调用的肯定是派生类的同名函数。
第三:保护继承方式
当类的继承为保护继承时,访问控制属性同私有继承方式相同,但是如果派生类作为新的基类,继续派生时,私有继承和保护继承就有区别啦!例如B类以私有继承的方式继承了A类,B类又 派生了C类,那么C类的成员和对象都不能访问从A类中继承的成员。如果B类是以保护继承的方式继承了A类,那么A类中的公有和保护成员在B类中就是保护成员,若是B类再派生出C类,那 么A类中的公有和保护成员就被C类间接继承后,有可能是保护的或者私有的(是B到C的派生方式决定),因此C类中的成员可能访问从A中继承的成员。好绕呀!
从继承的访问权限可以看出,如果类A中含有保护成员member_of_protected,那么对于这个类A定义的对象a来讲,A类中的保护成员member_of_protected和私有成员 member_of_privated是不可访问的。但是如果A类派生了子类AA,那么对于子类AA来说,公有成员和保护成员具有相同的访问权限。也就是A类中保护成员可能被它的派生类访问,但是 一定不可能被程序中的普通函数或者与A类平行的其他类访问。这样就实现了成员的隐藏和共享,实现了代码的高效利用。
4、类型兼容规则
类型兼容性规则指的是在任何需要基类的对象的地方,都可以使用公有派生类的对象来替代。派生类的对象可以赋值给基类的对象 ,可以初始化基类的引用,派生对象的地址可以赋值给指 向基类的指针。这个的具体应用据说在多态特性中,等学到多态在具体描述~~~~~(偷个懒~~~~)
5、派生类的构造函数和析构函数
构造函数
因为基类的构造函数和析构函数是不能被继承的,所以在派生类中,如果要对派生类新增的成员进行初始化,就必须为派生类添加新的构造函数和析构函数,另外需要注意的是,派生类的构 造函数只负责对派生类的新增成员函数记性初始化,对于所有从基类继承下来的成员,其初始化工作还是由基类的构造函数完成的。同样对派生类对象的清理工作也需要加入新的析构函数。 因为派生类的成员有所有基类的成员和派生类的新增成员,若是派生类中还内嵌了其他类对象,那么派生类的成员还会间接包括这些对象的成员,概括起来就是,构造派生类的对象时,需要 对基类的成员,新增成员和内嵌对象的成员进行初始化。
派生类的构造函数语法为:
派生类名::派生类名(参数总表):基类名1(参数表1),...,基类名n(参数表n),内嵌对象名1(内嵌对象参数表1),...,内嵌对象名m(内嵌对象参数表m)
{
派生类新增成员的初始化语句;
}
需要注意的是,虽然参数总表要求给出基类,新增类成员,内嵌成员对象的全部参数,但是实际使用时可以根据需要自行选择给出,不一定都给出。当一个类同时继承多个基类时,对于所有 需要给予参数初始化的基类,都要显示给出基类的名称和参数,对于使用默认构造函数的基类,可以不给出类名。同样对于对象成员,如果使用默认构造函数,也不需要给出对象名和参数 表。对于单继承的,只需要给出基类名称就可以啦!
如果基类声明了但有形参数表的构造函数,派生类就应当声明构造函数,提供一个将参数传递给基类构造函数的途径,保证基类初始化时有必要的数据。
在构造派生类构造函数的执行次序是:
(1)调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);(也就是按照“基类名1(参数表1),基类名2(参数表2),...,基类名n(参数表n)”这个顺序!!)
(2)调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序;
(3)调用派生类的构造函数中的内容
拷贝构造函数
对于一个类,如果程序员没有编写拷贝构造函数,编译系统就会自动生成一个默认的拷贝构造函数。如果编写拷贝构造函数,那么需要给基类相应的拷贝构造函数传递参数,例如,C类是B类的派生类,C类的拷贝构造函数形式如下:
C::C(C &c1):B(c1)
{
...
}
析构函数
派生类析构函数的声明方法与没有继承关系的类中析构函数的声明方法完全相同,但是执行次序和构造函数正好相反,首先对派生类新增普通成员清理,然后对派生类新增成员清理,最后 对所有基类继承类的成员清理。
6、派生类成员的标志和访问
派生类成员分为四种:
(1)不可访问的成员
从基类私有成员的继承来的成员。
(2)私有成员
从基类继承的成员和新增的成员
(3)保护成员
新增成员或者从基类继承来的
(4)公有成员
作用域分辨符
作用域分辨符是“::”,它的最用是限定访问成成员所在的类的名称,一般形式是:
基类名::成员名;//数据成员
基类名::成员名(参数名);//函数成员
作用域分辨符唯一识别成员的过程,在类的派生结构层中,基类的成员和派生类新增成员都具有类作用域,两者的最用范围是相互包含的,派生类在内层。此时,如果派生类声明了一个和 基类成员同名的新成员,派生的新成员就隐藏了外层同名成员,直接使用成员名只能访问到派生类的成员,在没有虚函数的情况下,若是派生类声明了与基类成员函数同名的新函数,即使 函数的参数表不同,从基类继承的同名函数的所有承载形式也都会被隐藏。如果要访问被隐藏的成员,就需要使用作用域分辨符和基类名来限定。因此,“对象名.成员名”这种方式可以唯一 标识和访问派生类的新增成员,基类的同名成员可以使用基类名和作用域分辨符访问。因此在派生类即建立派生类对象的模块中,派生类新增成员如果隐藏了基类的同名成员,这是使用“对 象名.成员名”的方式,就只能访问到派生类的新增成员函数,对基类同名成员函数的访问只能通过基类名和作用域分辨符来实现,也就是说,必须明确告诉系统要使用哪个基类的成员。
总之原则是:
派生的新类中如果有和它所继承的基类中相同的成员,那么原来基类中的成员在这个派生类中就被隐藏,要想访问基类中的这些隐藏成员就要用到作用域来 指定基类,继承多个基类也是如此。
7、虚基类
以前讨论多继承的都是所有基类之间没有继承关系,但是如果这个条件不满足呢?如果某个派生类的部分或者全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级中 继承来的成员就拥有相同的名称,因此派生类会产生同名现象,虽然可以通过作用域来限定,但是这样多份相同的成员增加了内存的开销,如何解决呢?我们可以通过虚基类来解决。
虚基类就是从不同路径继承过来的额同名数据成员在内存中只有一个拷贝,同一个函数名也只用一个映射,虚基类的声明语法:
class 派生类名: virtual 继承方式 基类名
其中虚基类的关键字作用范围只对紧跟其后的基类起作用。
但是虚基类不是这么简单的,用到再说吧!!