第八章 继承

 第八章    继承
8.1 类之间的关系
继承:在已有类的基础上创建新类的过程
一个 B 类继承A类,或称从类 A 派生类 B
类 A 称为基类(父类),类 B 称为派生类(子类)
8.2 基类和派生类
类继承关系的语法形式
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
基类名表 构成
访问控制 基类名1, 访问控制 基类名2,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
8 .2 .1 访问控制
不论种方式继承基类,派生类都不能直接使用基类的私有成员
①派生类的生成过程
派生类的生成过程经历了三个步骤:
●吸收基类成员(全部吸收(构造、析构除外),但不一定可见)
●改造基类成员
●添加派生类新成员
(1) 吸收基类成员
●在C++的继承机制中,派生类吸收基类中除构造函数
和析构函数之外的全部成员。
(2) 改造基类成员
通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。
(3) 添加新成员
●仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现。
②派生类对象结构
对于下面的继承关系:
class Father
{
int a,b;
public:
 // 成员函数
};
class Son:public Father
{
int c;
public:
// 成员函数
};
(1).公有制继承
#include<iostream>
using namespace std ;
class A
{ public :
void get_XY() { cout << "Enter two numbers of x, y : " ; cin >> x >> y ; }
void put_XY() { cout << "x = "<< x << ", y = " << y << '\n' ; }
protected: int x, y ;
};
class B : public A
{ public :
int get_S() { return s ; };
void make_S() { s = x * y ; }; // 使用基类数据成员x,y
protected: int s;
};
class C : public B
{ public :
void get_H() { cout << "Enter a number of h : " ; cin >> h ; }
int get_V() { return v ; }
void make_V() { make_S(); v = get_S() * h ; } // 使用基类成员函数
protected: int h, v;
};
8.2.2 重名成员
① 派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽(hide)了基类的同名成员
②在派生类中使用基类的同名成员,显式地使用类名限定符:
类名 :: 成员
8.2.3 派生类中访问静态成员
①基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员)
②根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
③派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员       或通过对象访问 对象名 . 成员
8.3 基类的初始化
①在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
② 派生类构造函数声明为
派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 )
… 对象成员n ( 变元表 ) ;
③ 构造函数执行顺序:基类 →对象成员 →派生类
3.派生类构造函数和析构函数的定义规则
      派生类构造函数和析构函数的使用原则
①基类的构造函数和析构函数不能被继承
②如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数
③如果基类无无参的构造函数,派生类必须定义构造函数
④如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
⑤派生类是否定义析构函数与所属的基类无关
㈠派生类的构造函数的定义
●派生类的数据成员既包括基类的数据成员,也包括派生类
新增数据成员。
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
㈡派生类析构函数
(1)当派生类中不含对象成员时
●在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
●在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数
8.4 继承的应用实例
例8-8 考察一个点、圆、圆柱体的层次结构
Point

Circle

Cylinder
列:
#include<iostream>
#include <iomanip.h>
using namespace std ;
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;
}
int main(){
Point p ( 72, 115 ) ; //定义点对象并初始化
cout << "The initial location of p is " << p << endl ;
p.setPoint ( 10, 10 ) ; //置点的新数据值
cout << "\nThe new location of p is " << p << endl ; //输出数据
Circle c ( 2.5, 37, 43 ) ; //定义圆对象并初始化
cout<<"\nThe initial location and radius of care\n"<<c<<"\nArea = "<<c.area()<<"\n" ;
c.setRadius ( 4.25 ) ;
c.setPoint ( 2, 2 ) ;
cout<<"\nThe new location and radius of care\n"<<c<<"\nArea = "<<c.area()<< "\n" ;
Cylinder cyl ( 5.7, 2.5, 12, 23 ) ; //定义圆柱体对象并初始化
cout << "\nThe initial location, radius ang height of cyl are\n" << cyl<< "Area = " << cyl.area() << "\nVolume = " << cyl.volume() << '\n';
cyl.setHeight ( 10 ) ; cyl.setRadius ( 4.25 ) ; cyl.setPoint ( 2, 2 ) ;
cout << "\nThe new location, radius ang height of cyl are\n" << cyl<< "Area = " << cyl.area() << "\nVolume ="<<cyl.volume()<< "\n" ;
}
8.5 多继承
1. 一个类有多个直接基类的继承关系称为多继承
2.多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
2.类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加自己的成员
8.5.1 多继承的派生类构造和访问
1.多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。
2.执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
3.一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
㈠多继承的构造函数
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)
{
// 派生类新增成员的初始化语句
}
㈡多继承方式下构造函数的执行顺序:
多继承方式下构造函数的执行顺序:
●先执行所有基类的构造函数
●再执行对象成员的构造函数
●最后执行派生类的构造函数
处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的基类顺序与派生类构造函数中所定义的成员初始化列表顺序没有关系。
内嵌对象成员的构造函数执行顺序与对象在派生类中声明的顺序一致
㈢多继承的析构函数
●析构函数名同样与类名相同,无返回值、无参数,而且其定义方式与基类中的析构函数的定义方式完全相同。
●功能是在派生类中对新增的有关成员进行必要的清理工作。
●析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理。
●赋值兼容规则指在程序中需要使用基类对象的任何地方,
都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
  a 派生类的对象可以赋给基类对象
  b 派生类的对象可以初始化基类的引用
  c 派生类的对象的地址可以赋给基类类型的指针赋值兼容的可行性
㈣通过公有继承,
派生类得到了除了构造、析构函数以外的所有成员且这些成员的访问控制属性也和基类完全相同。这样,它便具备了基类的所有功能。
利用赋值兼容规则
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;
●在替代之后,派生类对象就可以作为基类的对象使用,
但只能使用从基类继承的成员。
一个派生类对象也是一个基类对象,一个基类对象可派上用场的地方,派
生类对象一样可派上用场。反之则不然。
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 &”
}
*赋值兼容应注意的问题
(1),声明为指向基类的指针可以指向他的共有派生类的对象,但不可以指向他的私有派生类的对象,
列入:
classB{.......};
class D:private B {.....};
B b1,*pbl;Dd1;

pb1=&b1;//合法,基类B的对象b1和B的指针

pb1=&d1;//非法,不允许将基类指针指向他的私有派生类对象












  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值