一、心得体会
之前的代码在定义类时,如果需要多次用到某一个类,都需要反复定义使用它,但继承的学习却弥补这一缺点。集成式面向对象程序设计中软重用的关键技术。继承机制使用已经定义的类作为基础建立新的类定义,新的类时原有类的数据及操作与新类所增加的数据及操作的组合。新的类把原有类作为基类引用,而不需要修改原有类的定义。新定义的类作为派生类引用。这种课扩充、可重用技术大大降低了大型软件的开发难度。
二、内容及实例
8.1类之间的关系
继承:在已有类的继承上创建新类的过程
一个B类继承A类,或称从类A派生类B。类A称为基类(父类),类B称为派生类(子类)
8.2基类和派生类
C++中,描述类继承关系的语句格式为:
class 派生类名:基类名表
{
数据成员和成员函数说明
};
基类名表 构成
访问控制 基类名1,访问控制 基类名2,...,访问控制 基类名n
访问控制表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
8.2.1访问控制
不论哪种方式继承基类,派生类都不能直接使用基类的私有成员
1.派生类的生成过程
(1)吸收基类成员
在c++的继承机制中,派生类吸收基类中除析构函数和析构函数之外的全部成员。
(2)改造基类成员
通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。
(3)添加新成员
仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现。
例:
#include<iostream>
using namespace std;
class A
{
public:
int a;
int b;
private:
int c;
protected:
int d;
};
class B:public A
{
int c;
};
int main()
{
cout<<"size of A is"<<sizeof(A);
cout<<"size of B is"<<sizeof(B);
}
1.公有继承
以公有方式继承的派生类,基类的public和protected成员在派生类中的性质不变。即派生类中可以使用基类中定义的public和protected成员;并且,基类的公有成员也是派生类对象的借口,可以在类模块之外被访问。
2.私有继承
以私有方式继承的派生类,基类的public和protected成员会成为派生类的私有成员,即基类中定义的public和protected成员只能在私有继承的派生类中可见,而不能再类外使用。
3.保护继承
保护把基类的公有成员和保护成员作为派生类的保护成员,使其在派生类中被屏蔽。保护继承和私有继承方式在程序设计中应用较少。原因是,继承的目的是软件重用,如果有需要屏蔽的成员,通常在类中被定义为私有的或保护的成员。
4.访问声明
C++提供一种访问调节机制,是一些本来在派生类中不可见的成员变为可访问的,称为访问声明。
访问声明的格式为:
基类名::成员
8.2.2 重名成员
C++允许派生类的成员与基类成员重名。在派生类中访问重名成员是,屏蔽基类的同名成员。如果要在派生类中使用基类的同名成员,可以显式地使用作用域符指定,格式如下:
类名::成员
1.重名数据成员
如果在派生类中定义了与基类相同的名字的数据成员,根据继承规则,在建立派生类对象时,系统会分别建立不同的存储空间。
例:
class base
{
public :
int a , b ;
} ;
class derived : public base
{
public :
int b , c ;
} ;
void f ()
{ derived d ;
d.a = 1 ;
d.base :: b = 2 ;
d.b = 3 ;
d.c = 4 ;
};
2.重名成员函数
在派生类中定义与基类同名的成员函数,称为在派生类中重载基类的成员函数。由调用形式指示this指针的不同类型,调用不同版本的成员函数。
8.2.3 派生类中访问静态成员
如果在基类中定义了静态成员,这些静态成员将在类体系中被共享,根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质。
1.基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中静态成员)
2.根据静态成员自身的方法特性和派生类的继承方式,在类层次体系中具有不同的访问性质
3.派生类中访问静态成员,用以下形式显式说明:
类名::成员
或通过对象访问 对象名.成员
例:
#include<iostream>
using namespace std;
class B
{
public:
static void Add() { i++ ; }
static int i;
void out() { cout<<"static i="<<i<<endl; }
};
int B::i=0;
class D : private B
{
public:
void f()
{ i=5;
Add();
B::i++;
B::Add();
}
};
int main()
{ B x;
D y;
x.Add();
x.out();
y.f();
cout<<"static i="<<B::i<<endl;
cout<<"static i="<<x.i<<endl;
//cout<<"static i="<<y.i<<endl;
}
8.3 基类的初始化
1.在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
2.派生类构造函数声明为
派生类构造函数(变元素):基类(变元素),对象成员(变元素)
...对象成员n(变元素);
3.构造函数执行顺序:基类 对象成员 派生类
例:
#include<iostream>
using namespace std ;
class parent_class
{ int data1 , data2 ;
public :
parent_class ( int p1 , int p2 ) { data1 = p1; data2 = p2; }
int inc1 () { return ++ data1; }
int inc2 () { return ++ data2 ; }
void display () {cout << "data1=" << data1 << " , data2=" << data2 << endl ; }
};
class derived_class : private parent_class
{ int data3 ;
parent_class data4 ;
public:
derived_class ( int p1 , int p2 , int p3 , int p4 , int p5 ): parent_class ( p1 , p2 ) , data4 ( p3 , p4 )
{ data3 = p5 ; }
int inc1 ( )
{
return parent_class :: inc1 ( ) ;
}
int inc3 ( )
{
return ++ data3 ;
}
void display ( )
{
parent_class :: display ( ) ; data4.display ( ) ;
cout << "data3=" << data3 << endl ;
}
} ;
int main ( )
{
derived_class d1 ( 17 , 18 , 1 , 2 , -5 ) ;
d1 . inc1 ( ) ;
d1 . display ( ) ;
}
4.派生类构造函数和析构函数的定义规则
派生类构造函数和析构函数的使用原则
1.基类的构造函数和析构函数不能被继承
2.如果基类没有定义构造函数或有无参的构造函数,派生类也可以不用定义构造函数
3.如果派生类无无参的构造函数,派生类必须定义构造函数
4.如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
5.派生类是否定义析构函数与所属的基类无关
5.派生类构造函数的定义
派生类的数据成员既包括基类的数据成员,也包括派生类新增数据成员。
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
6.派生类析构函数
(1)当派生类中不含对象成员时
在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。
例:
class B
{
public:
B() { cout<<"B()"<<endl; }
~B() { cout<<"~B()"<<endl; }
};
class D : public B
{
public:
D(){ cout<<"D()"<<endl; }
~D() { cout<<"~D()"<<endl; }
};
int main()
{
D d;
return 0;
}
8.4 继承的应用实例
例 考察一个点、圆、圆柱体的层次结构 。首先定义点类Point,然后从Point类派生圆类Circle,最后从Circle类派生圆柱体类Cylinder。
class Point
{ friend ostream &operator<< (ostream &, const Point &);
public:
Point( int = 0, int = 0 ) ; // 带默认参数的构造函数
void setPoint( int, int ) ; // 对点坐标数据赋值
int getX() const { return x ; }
int getY() const { return y ; }
protected:
int x, y; // Point类的数据成员
};
class Circle : public Point
{ friend ostream &operator<< (ostream &, const Circle &); // 友元函数
public:
Circle(double r=0.0, int x=0, int y=0); // 构造函数
void setRadius(double); /*置半径*/
double getRadius() const; /*返回半径*/
double area() const; // 返回面积
protected: double radius; // 数据成员,半径
};
class Cylinder:public Circle
{ friend ostream & operator<<(ostream &, const Cylinder &); // 友元函数
public:
Cylinder(double h=0.0, double r=0.0, int x=0, int y=0); // 构造函数
void setHeight(double); /* 置高度值*/
double getHeight() const; /* 返回高度值*/
double area() const; /* 返回面积*/
double volume() const; /* 返回体积*/
protected:
double height; // 数据成员,高度
};
// Point 类的成员函数
// 构造函数,调用成员函数对 x,y作初始化
Point::Point ( int a, int b )
{ setPoint ( a , b ) ; }
// 对数据成员置值
void Point :: setPoint ( int a, int b )
{
x = a ;
y = b ;
}// 重载插入算符,输出对象数据
ostream &operator<< ( ostream &output , const Point &p )
{
output << '[' << p.x << "," << p.y << "]" ;
return output ;
}
// Circle 类的成员函数
// 带初始化式构造函数,首先调用基类构造函数
Circle::Circle( double r, int a, int b ): Point( a, b )
{
setRadius ( r );
}// 对半径置值
void Circle::setRadius ( double r )
{
radius = ( r >= 0 ? r : 0 );
}// 返回半径值
double Circle::getRadius() const { return radius; }
// 计算并返回面积值
double Circle::area() const { return 3.14159 * radius * radius ; }
// 输出圆心坐标和半径值
ostream & operator<< ( ostream &output, const Circle &c)
{
output << "Center = " << '[' << c.x << "," << c.y << "]" << "; Radius = "<< setiosflags(ios::fixed|ios::showpoint) << setprecision(2) << c.radius ;
return output ;
}
Cylinder::Cylinder(double h, double r, int x, int y):Circle(r,x,y)
{
setHeight(h);
}
void Cylinder::setHeight(double h)
{
height = ( h >= 0 ? h : 0 );
}
double Cylinder::getHeight() const { return height; }
double Cylinder::area()const{ return 2*Circle::area()+2*3.14159*radius*height; }
double Cylinder::volume() const { return Circle::area()*height; }
ostream &operator<< ( ostream &output, const Cylinder &cy )
{
output << "Center = " << '[' << cy.x << "," << cy.y << "]" << "; Radius = "
<< setiosflags(ios::fixed|ios::showpoint) << setprecision(2) << cy.radius
<< "; Height = " << cy.height << endl ;
return output;
}
8.5 多继承
1.一个类有多个直接基类的继承关系称为多继承
2.多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
3.类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加自己的成员
8.5.1多继承的派生类构造和访问
(1).多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。
(2).执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
(3).一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
1.多继承的简单应用
class Base1
{ public:
Base1(int x) { value = x ; }
int getData() const { return value ; }
protected:
int value;
};
class Base2
{ public:
Base2(char c) { letter=c; }
char getData() const { return letter;}
protected:
char letter;
};
class Derived : public Base1, public Base2
{ friend ostream &operator<< ( ostream &, const Derived & ) ;
public :
Derived ( int, char, double ) ;
double getReal() const ;
private :
double real ;
};
int main()
{ Base1 b1 ( 10 ) ;
Base2 b2 ( 'k' ) ;
Derived d ( 5, 'A', 2.5 ) ;
:
return ;
}
2.多继承的构造函数
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)
{
// 派生类新增成员的初始化语句
}
3.多继承方式下构造函数的执行顺序:
先执行所有基类的构造函数
再执行对象成员的构造函数
最后执行派生类的构造函数
处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的基类顺序与派生类构造函数中所定义的成员初始化列表顺序没有关系。内嵌对象成员的构造函数执行顺序与对象在派生类中声明的顺序一致。
4.多继承的析构函数
●析构函数名同样与类名相同,无返回值、无参数,而且其定义方式与基类中的析构函数的定义方式完全相同。
●功能是在派生类中对新增的有关成员进行必要的清理工作。
●析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理。
5.赋值兼容规则
●赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
a 派生类的对象可以赋给基类对象
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
6.赋值兼容的可行性
(1)通过公有继承,派生类得到了除了构造、析构函数以外的所有成员且这些成员的访问控制属性也和基类完全相同。这样,它便具备了基类的所有功能。
(2)利用赋值兼容规则
a 派生类的对象可以赋给基类对象(强制类型转换)
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
例如,下面声明的两个类:
class Base{
…
};
class Derived:public Base{
…
};
根据赋值兼容规则, 以下几种情况是合法的:
(1) 可以用派生类对象给基类对象赋值。例如:
Base b;
Derived d;
b=d;
这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值。
(2) 可以用派生类对象来初始化基类的引用。例如:
Derived d;
Base &br=d;
(3) 可以把派生类对象的地址赋值给指向基类的指针。例如:
Derived d;
Base *bptr=&d;
这种形式的转换,是在实际应用程序中最常见到的。
(4) 可以把指向派生类对象的指针赋值给指向基类对象的指针。例如:
Derived *dptr,obj; dptr=&obj;
Base *bptr=dptr;
7.赋值兼容规则的特点
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
一个派生类对象也是一个基类对象,一个基类对象可派上用场的地方,派生类对象一样可派上用场。反之则不然。
class Person {…};
class Student : public Person { … };
void eat(Person &p){};
void study(Student &s){};
void main( )
{
Person p;
Student s;
eat(p); // OK
eat(s); // OK 学生也要吃饭
study(s); // OK
study(p); // error C2664: “study”: 不能将参数 1 从“Person”转换Student &”
}
8.赋值兼容应注意的问题
(1)声明为指向基类的指针可以指向它的公有派生类的对象,但不允许指向它的私有派生类的对象。例如:
class B {…};
class D:private B {…};
B b1,*pbl;D d1;
pb1=&b1; //合法,基类B的对象b1和B类的指针
pb1=&d1; //非法,不允许将基类指针指向它的私有派生类对象
(2)允许将一个声明为指向基类的指针指向其公有派生类对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象。
(3) 声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类的定义的成员。