C++通过类派生(Class Derivation)的机制支持继承(Inheritance)。允许程序员在保持原有类特性的基础上进行扩展,增加功能,派生出新类。
继承的方式有以下2种:单一继承和多重继承。
派生类的定义中包括子类新增加的成员和继承父类需要重写的成员。C++允许在派生类中重新声明和定义这些成员函数,使这些函数具有新的功能,称之为重写或覆盖。重写函数起屏蔽、更新作用,取代基类成员,完成新功能。
测试一:子类继承基类的所有属性和函数,设基类函数和数据均为public类型,并且以public方式被继承,做如下测试。例如:
// test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
class CBox
{
public:
CBox()
{
cout<<"调用CBox类的构造函数;"<<endl;
}
void SetLength(double len) //设置盒子的长
{
cout<<"CBox类中设置盒子的长;"<<endl;
length = len;
}
void SetWidth(double w) //设置盒子的宽
{
cout<<"CBox类中设置盒子的宽;"<<endl;
width = w;
}
void SetHeight(double h) //设置盒子的高
{
cout<<"CBox类中设置盒子的高;"<<endl;
heigth = h;
}
void ShowBox() //显示盒子长、宽、高的成员函数
{
cout<<"CBox类中显示盒子数据;"<<endl;
cout<<"length="<<length<<endl
<<"width="<<width<<endl
<<"heigth="<<heigth<<endl;
}
~CBox()
{
cout<<"调用CBox类的析构函数;"<<endl;
}
public:
double length,width,heigth;
};
class CColorbox:public CBox
{
public:
CColorbox()
{
cout<<"调用CColorbox类的构造函数;"<<endl;
}
~CColorbox()
{
cout<<"调用CColorbox类的析构函数;"<<endl;
}
};
void test()
{
CColorbox colorbox;
colorbox.SetHeight(10);
colorbox.SetWidth(5);
colorbox.SetLength(20);
colorbox.ShowBox();
}
int _tmain(int argc, _TCHAR* argv[])
{
test();
system("pause");
return 0;
}
运行结果:
首先调用基类的构造函数,再调用子类构造函数。析构时先析构子类后析构基类。子类继承了基类的所有公有函数和成员,除了基类的构造、析构函数。
测试二:子类重写基类的函数
子类中重写基类的函数后,默认调用的是子类的函数!!!
1、子类中添加基类中同名但不同参数的函数,则基类的带参数的Setheight(double h)函数首先被调用!!!
则编译出错test.cpp(72): error C2660: “CColorbox::SetHeight”: 函数不接受 1 个参数:
class CColorbox:public CBox
{
public:
CColorbox()
{
cout<<"调用CColorbox类的构造函数;"<<endl;
}
void SetHeight() //新添加的
{
cout<<"重载:调用CColorbox类的与基类同名但不同参数的SetHeight函数;"<<endl;
}
~CColorbox()
{
cout<<"调用CColorbox类的析构函数;"<<endl;
}
};
void test()
{
CColorbox colorbox;
colorbox.SetHeight(10);
colorbox.SetHeight(); //新添加的
colorbox.SetWidth(5);
colorbox.SetLength(20);
colorbox.ShowBox();
}
,才能够对其(子类继承的函数)进行重载。
class CColorbox:public CBox
{
public:
CColorbox()
{
cout<<"调用CColorbox类的构造函数;"<<endl;
}
void SetHeight(double h) //新添加。删除此函数,则子类中就不存在SetHeight()
{
cout<<"调用CColorbox类的与基类同名同参数的SetHeight函数;"<<endl;
heigth = h;
}
void SetHeight()
{
cout<<"重载:调用CColorbox类的与基类同名但不同参数的SetHeight函数;"<<endl;
}
~CColorbox()
{
cout<<"调用CColorbox类的析构函数;"<<endl;
}
};
调用结果:子类中重写基类的函数后,默认调用的是子类的函数!!!
3、在子类中添加新的函数和数据成员,调用子类新功能:
class CColorbox:public CBox
{
public:
CColorbox()
{
cout<<"调用CColorbox类的构造函数;"<<endl;
}
void SetHeight(double h) //删除此函数,则子类中就不存在SetHeight()
{
cout<<"调用CColorbox类的与基类同名同参数的SetHeight函数;"<<endl;
heigth = h;
}
void SetHeight()
{
cout<<"重载:调用CColorbox类的与基类同名但不同参数的SetHeight函数;"<<endl;
}
void SetColor(int c)
{
cout<<"CColorbox类中设置盒子的颜色;"<<endl;
color = c;
}
void ShowColBox()
{
cout<<"调用CColorbox类中ShowColBox();"<<endl;
cout<<"color="<<color<<endl; //访问自己的私有成员
cout<<"length"<<length<<endl;
}
~CColorbox()
{
cout<<"调用CColorbox类的析构函数;"<<endl;
}
public:
int color;
};
void test()
{
CColorbox colorbox;
colorbox.SetHeight(10);
colorbox.SetHeight();
colorbox.SetWidth(5);
colorbox.SetLength(20);
colorbox.SetColor(1);
colorbox.ShowBox();
colorbox.ShowColBox();
}
运行结果:
三、数据成员的重载
因为成员重载后,基类和子类中各有一份相同的数据成员,调用那一个成员就成了一个需要讨论的问题,测试时采用对于类成员的显式调用完成显示。
实验结果证明:基类函数会调用基类的数据成员,子类的成员函数默认调用子类的数据成员!!!
class CColorbox:public CBox
{
public:
CColorbox()
{
cout<<"调用CColorbox类的构造函数;"<<endl;
}
void SetColLength(double len) //子类设置长度函数
{
cout<<"CColorbox类中设置盒子的长;"<<endl;
length = len;
}
void ShowColBox() //显示盒子长、宽、高的成员函数
{
cout<<"显示基类长度;"<<endl;
cout<<"length = "<<CBox::length<<endl;
cout<<"显示子类长度;"<<endl;
cout<<"length = "<<CColorbox::length<<endl;
}
~CColorbox()
{
cout<<"调用CColorbox类的析构函数;"<<endl;
}
public:
int length; //添加同名成员变量
};
void test()
{
CColorbox colorbox;
colorbox.SetLength(20); //调用基类函数设置长度
colorbox.SetColLength(10); //调用子类函数设置长度
colorbox.ShowColBox();
}
显示运行结果:
1、若将test中调用基类的赋值函数注释掉,则显示基类长度中,成员变量为随机值;
2、若要调用基类的长度成员,则要进行显式引用,即CBox::length。
派生类的生成
仔细分析派生新类这个过程,实际是经历了以下步骤:
第一步是继承基类的成员,不论是数据成员,还是成员函数,除构造函数与析构函数外全部接收(即全部拷贝,虽然子类中覆盖后只是显示调用,但仍可调用),全部成为派生类的成员。
子类中访问基类成员的拷贝方式为:子类对象.基类名::基类成员。如:
colorbox.CBox::length。
第二步是重写基类成员。如果派生类声明了一个与基类成员函数相同的成员函数时,派生类中的新成员则屏蔽了基类同名成员,类似函数中的局部变量屏蔽全局变量。称为同名覆盖(Override)。
由code1分析:
1、子类中重写了基类中的同名函数,则基类的同名函数不会再出现在子类。
1)重写:void SetHeight(double h);或void SetHeight();则基类的void SetHeight(double h)函数将不能在子类中显式调用。 2)若子类中需要重载SetHeight函数,则需要先重写与基类同参、同名、同返回值的函数,再进行重载。
2、子类中重写了基类中的同名数据,则该数据成员在子类中也存在了一份拷贝,如 length,则,默认方式是:继承过来的基类函数调用基类中的拷贝length,若用子类函数则调用子类中的length。
第三步是定义新成员。新成员必须与基类成员不同名,是派生类自己的新特性。派生类新成员的加入使得派生类在功能上有所发展。这一步是继承与派生的核心特征。
第四步是重写构造函数与析构函数。因为派生类不继承基类的构造函数与析构函数,并且派生类的需要对新添加的数据成员进行必要的初始化,所以构造函数与析构函数需要重写。