一. 知识点总结
继承:
1.定义:在已有类的基础上创建新类的过程。
一个 B 类继承A类,或称从类 A 派生类 B
类 A 称为基类(父类),类 B 称为派生类(子类)
2.类继承关系的语法形式:
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
基类名表 构成
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
注:不论种方式继承基类,派生类都不能直接使用基类的私有成员
3.派生类生成的三个步骤:
①吸收基类成员(全部吸收(构造、析构除外),但不一定可见):
在C++的继承机制中,派生类吸收基类中除构造函数和析构函数之外的全部成员。
②改造基类成员:
通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。
③添加派生类新成员:
仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现。
4.
公有继承
Eg:
#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;
};
5. 重名成员:派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽(hide)了基类的同名成员
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名 :: 成员
6.
Eg:
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 ;
};
6. 基类的初始化:在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
派生类构造函数声明
派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 )
… 对象成员n ( 变元表 ) ;
构造函数执行顺序:基类 à 对象成员à 派生类
Eg:
#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 ; }
7.
派生类构造函数和析构函数的使用原则:
①基类的构造函数和析构函数不能被继承
②如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数
③如果基类无无参的构造函数,派生类必须定义构造函数
④如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
⑤派生类是否定义析构函数与所属的基类无关
8.
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
9. 派生类析构函数
(1)当派生类中不含对象成员时
●在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
●在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
●在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。
Eg:
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;
}
10. 赋值兼容规则:
赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
a.派生类的对象可以赋给基类对象
b.派生类的对象可以初始化基类的引用
c.派生类的对象的地址可以赋给基类类型的指针
赋值兼容的可行性:
通过公有继承,
派生类得到了除了构造、析构函数以外的所有成员且这些成员的访问控制属性也和基类完全相同。这样,它便具备了基类的所有功能。
利用赋值兼容规则
a.派生类的对象可以赋给基类对象(强制类型转换)
b.派生类的对象可以初始化基类的引用
c.派生类的对象的地址可以赋给基类类型的指针
Eg:
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;
虚函数和多态:
一.
多态性(Polymorphism)是指一个名字,多种语义;或界面相同,多种实现。
重载函数是多态性的一种简单形式。
虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。
二.
冠以关键字 virtual 的成员函数称为虚函数
实现运行时多态的关键首先是要说明虚函数,另外,必须用
基类指针调用派生类的不同实现版本
三.虚函数和基类指针
基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员
#include<iostream>
using namespace std ;
class Base
{ public : Base(char xx) { x = xx; }
void who() { cout << "Base class: " << x << "\n" ; }
protected: char x;
} ;
class First_d : public Base
{ public : First_d(char xx, char yy):Base(xx) { y = yy; }
void who() { cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected: char y;
} ;
注意:
一个虚函数,在派生类层界面相同的重载函数都保持虚特性
虚函数必须是类的成员函数
不能将友元说明为虚函数,但虚函数可以是另一个类的友元
析构函数可以是虚函数,但构造函数不能是虚函数
四.虚函数的重载特性
在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
如果仅仅返回类型不同,C++认为是错误重载
如果函数原型不同,仅函数名相同,丢失虚特性
例:
class base
{ public :
virtual void vf1 ( ) ;
virtual void vf2 ( ) ;
virtual void vf3 ( ) ;
void f ( ) ;
} ;
class derived : public base
{ public :
void vf1 ( ) ; // 虚函数
void vf2 ( int ) ; // 重载,参数不同,虚特性丢失
char vf3 ( ) ; // error,仅返回类型不同
void f ( ) ; // 非虚函数重载
} ;
五.纯虚函数和抽象类
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
纯虚函数说明形式:
virtual 类型 函数名(参数表)= 0 ;
一个具有纯虚函数的基类称为抽象类。
Eg:
class point { /*……*/ } ;
class shape ; // 抽象类
{ point center ;
……
public :
point where ( ) { return center ; }
void move ( point p ) {center = p ; draw ( ) ; }
virtual void rotate ( int ) = 0 ; // 纯虚函数
virtual void draw ( ) = 0 ; // 纯虚函数
} ;
…...
shape x ; // error,抽象类不能建立对象
shape *p ; // ok,可以声明抽象类的指针
shape f ( ) ; // error, 抽象类不能作为函数返回类型
void g ( shape ) ; // error, 抽象类不能作为传值参数类型
shape & h ( shape &) ; // ok,可以声明抽象类的引用
二.心得体会
继承可以在代码完成之后,优化代码,继承时我们把几个类共有的数据成员抽出作为基类而去使用,这样可以实现代码的复用,有效提高代码的简洁性,更能提高我们的效率。
但自己很少使用继承,一是没有养成这个意识再就是自己改不好。以后会尝试先用继承写些简单的代码。
虚函数常用于继承,展现了多态性。纯虚函数具体实例可以参考一些登陆系统,登陆系统输入不同类型的账号密码,可以进入不同的界面,管理员界面或者用户界面,通过一个公有的登陆,来登陆两个不同界面。