继承的应用
#include <iostream>
using namespace std;
class A
{
public:
void f(int i)
{ cout<<i<<endl; }
void g()
{ cout<<"A\n"; }
};
class B:private A
{
public:
void h()
{ cout<<"B\n"; }
A::f;
//A::f; 该语句的含义是将类A 中的公有成员f()
从私有继承方式的派生类B 中申明为公有的
};
程序中A::f; 的含义是将类A 中的公有成员f() 从私有继承方式的派生类B 中申明为公有的,B 的派生类对象可以访问成员f()。因此在main()中,语句b.f(10); 是正确的。该语句称为访问申明,它是私有继承方式中的一种调用机制,即在私有继承方式下,用于恢复名字的访问权限。
int main()
{
B b;
b.f(10);//true
b.g(); //error
b.h();//true
return 0;
}
语句b.g(); 是错误访问,有编译错。若注释该语句,运行结果为:10
B
将class B:private A 改为class B:public A,则无编译错,原程序输出为:10
A
B
Dog类(派生类)从Ammal类(基类)派生的过程:
- 吸收基类成员:继承后,派生类继承基类的除了构造函数和析构函数之外的成员。本例中Dog类继承了基类的Mammal类中的GetAge()、GetWeight()、SetAge()、SetWeight()、Speak()、itsAge、itsWeight;
- 改造基类成员:当派生类的同名属性和行为具有和基类不同的特征时,就要在派生类中重新声明或者定义,赋予新的含义,从而完成对基类成员的改造。这样就隐藏了基类中的同名成员(同名重写/覆盖规则)。本例中Dog类与Speak()函数进行改造,因为并不是所有动物都有共同语言。
- 添加新成员:派生类中添加新成员使得派生类在功能上有所扩展。本例中Dog类添加了新的类成员itsColor、GetColor()、SetColor( ),从而实现了派生类Dog在功能上的扩展。
基于项目的多文件管理
//Ammal.h
#include <iostream>
enum MyColor{BLACK, WHITE};
class Mammal
{
public:
Mammal();
~ Mammal();
Mammal(int age, int weight):itsAge(age),itsWeight(weight){}
int GetAge(){return itsAge;}
int GetWeight(){return itsWeight;}
void SetAge(int age){itsAge = age;}
void SetWeight(int weight){itsWeight = weight;}
void Speak(){cout<<"Mammal language!"<<endl;}
protected:
int itsAge; //年龄
int itsWeight; //体重
};
//Ammal.cpp文件中
#include "Ammal.h"
using namespace std;
Mammal::Mammal()
{
}
Mammal::~Mammal()
{
}
//Dog.h
#include "Ammal.h"
using namespace std;
class Dog : public Mammal
//公有继承方式
{
public:
Dog();
~Dog();
MyColor GetColor(){return itsColor;}
void SetColor(MyColor color){itsColor = color;}
void Speak(){cout<<"Dog language!"<<endl;}
protected:
MyColor itsColor;
};
//Dog.cpp
#include "Dog.h"
using namespace std;
Dog::Dog()
{
}
Dog::~Dog()
{
}
//main.cpp
#include "Dog.h"
using namespace std;
int main()
{
Dog dog;
dog.SetAge(25);
dog.SetWeight(50);
dog.SetColor(WHITE);
cout<<"Ddog age = "<<dog.GetAge()<<endl;
cout<<"Dog weight = "<<dog.GetWeight()<<endl;
cout<<"Dog color = "<<dog.GetColor()<<endl;
dog.Speak();
return 0;
};
运行结果:
Dog age = 25
Dog weight = 50
Dog color = 1
Dog language!
管理步骤
- 创建一个控制台类型的项目L9.3,带一个main.cpp文件,其内容是main函数
- 在L9.3项目中为Mammal类创建2个文件(Ammal.h和Ammal.cpp文件),为Dog类创建2个文件(Dog.h和Dog.cpp文件)
在项目名L9.3,右键单击选择新建文件,添加新文件,如图:
文件->新建->新建一个类,如图:
系统自动加了框架代码
- 输入基类名Mammal
- 勾选构造函数与析构函数
- 默认文件名Mammal.cpp及
Mammal.h
- 输入子类名
- 勾选从其它类继承
- 选择继承方式
- 输入父类名
赋值兼容规则(向上转型)
一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:
- 派生类的对象可以被赋值给基类对象。
- 派生类的对象可以初始化基类的引用。
- 指向基类的指针也可以指向派生类,即一个公有派生类对象的指针值可以赋值给(或初始化)一个基类指针。
- 利用这样的指针或引用,只能访问派生类对象中从基类继承过来的成员,无法访问派生类的自有成员。
注意:派生类的拷贝构造函数如果要重新定义,其参数只有一个,即派生类对象引用,根据赋值兼容规则,派生类对象可以用来初始化基类的拷贝构造函数的基类对象引用。
在C++中,这与Java不同,Java系统会自动调用子类中的方法。因为Java中默认是虚函数,动态绑定机制能识别出对象转型前的类型。而C++中虚函数必须加virtual。所以Java中没有赋值兼容规则。
例9.4:赋值兼容示例,要求用类的公有继承方法输出圆信息,包括半径和圆心。
#include <iostream>
using namespace std;
class Point
{protected:
double x,y;
public:
Point(double a=0,double b=0)
{x=a;y=b; }
void Show()
{cout<<x<<","<<y<<endl; }
};
class Circle:public Point
{
public:
Circle(double a=0,double b=0,double c=0):Point(a,b)
{ r=c; }
void Show();
private:
double r;
};
void Circle::Show()
{ cout<<"半径="<<r<<endl<<"圆心=";
cout<<x<<","<<y<<endl;//等价于p.Show();
}
int main()
{
Circle c1(100,100,10);//定义派生类对象
Point p1=c1,*p2=&c1,&p3=c1;//基类对象、对象指针和对象引用
p1.Show();
p2->Show();//赋值兼容规则
p3.Show();
return 0;
}
运行结果:100,100
100,100
100,100
组合与继承的比较
- “包含 has-a”关系用组合来表达
- “属于is-a”关系用继承来表达
- 在更多的时候,组合关系比继承更能使系统具有高度的灵活性,可维护行,并且提高系统的可重用性。
许多时候都要求将组合与继承两种技术结合起来使用,创建一个更复杂的类。
圆类设计与分析-平台9.9题
设有一个Point类,有数据成员x和y。另有一个Color类,有数据成员a。
要求从Point类公有派生出Circle类,增加了数据成员r和颜色对象p,这3个类都定义了Show函数输出其数据信息。要求Circle类的定义采用组合与继承技术结合。
#include <iostream>
using namespace std;
enum MyColor{BLACK, WHITE,RED,YELLOW,GREEN};
class Point
{protected:
double x,y;
public:
Point(double a,double b)
{x=a;y=b;
cout<<"调用Point类带参构造函数"<<endl;
}
Point()
{x=0;y=0;
cout<<"调用Point类无参构造函数"<<endl;
}
void Show()
{ cout<<x<<","<<y; }
};
class Color
{
protected:
MyColor a;
public:
Color(MyColor b)
{a=b;
cout<<"调用Color类带参构造函数"<<endl;
}
Color()
{a=BLACK;
cout<<"调用Color类无参构造函数"<<endl;
}
Color(Color &r)
{a=r.a;
cout<<"调用Color类拷贝构造函数"<<endl;
}
void Show()
{
cout<<"颜色=";
switch (a)
{
case 0:cout<<"BLACK"<<endl;break;
case 1:cout<<"WHITE"<<endl;break;
case 2:cout<<"RED"<<endl;break;
case 3:cout<<"YELLOW"<<endl;break;
case 4:cout<<"GREEN"<<endl;break;
default:cout<<"QITA"<<endl;
}
}
};
class Circle:public Point
{
public:
Circle(double a,double b,double c,Color &d):Point(a,b),p(d)
{ r=c; }
void Show();
private:
double r;
Color p;
};
void Circle::Show()
{
cout<<"半径="<<r<<endl<<"圆心=("<<x<<","<<y<<")"<<endl;
p.Show();
}
int main()
{
Color b(RED);
Circle c1(100,100,10,b);//定义圆对象
c1.Show();//调用成员函数
return 0;
}
//组合和继承结合时,先调用基类构造函数,再调用组合对象的构造函数,最后调用派生类的构造函数。
运行结果:
调用Color类带参构造函数
调用Point类带参构造函数
调用Color类拷贝构造函数
半径=10
圆心=(100,100)
颜色=RED
基类的成员函数在派生类中重载
例9.6 基类的成员函数在派生类中重载示例1
#include <iostream>
using namespace std;
class A
{ int a;
public:
void fn()
{a=0;
cout<<"A::"<<a<<endl;
}
void fn(int a)
{this->a=a;
cout<<"A::"<<a<<endl;
}
};
class B : public A
{
int b;
};
int main()
{ B b; //B类没有对fn函数进行同名覆盖
b.fn(3);
b.fn();
return 0;
}
结果:
A::3
A::0
例9.7:基类的成员函数在派生类中重载示例2-在例9.6基础上改
#include <iostream>
using namespace std;
class A
{ int a;
public:
void fn()
{cout<<"A::0"<<endl;}
void fn(int a)
{this->a=a;
cout<<"A::"<<a<<endl;}
};
class B : public A
{
public:
void fn(int a) //B类对fn函数进行同名覆盖
{cout<<"B::"<<a<<endl;}
};
int main()
{ B b;
b.fn(3);
b.fn();
return 0;
}
结果:
错误信息:no matching function for call to 'B::fn()'
例9.8:基类的成员函数在派生类中重载示例2修改
#include <iostream>
using namespace std;
class A
{ int a;
public:
void fn()
{cout<<"A::0"<<endl;}
void fn(int a)
{this->a=a;
cout<<"A::"<<a<<endl;}
};
class B : public A
{
public:
void fn(int a) //同名覆盖
{cout<<"B::"<<a<<endl;}
void fn() //同名覆盖
{A::fn();//用类名::,将隐藏的A类函数调用
}
};
int main()
{ B b;
b.fn(3);//同名覆盖,调用B类重写的函数
b.A::fn(3);//用类名::,将隐藏的A类函数调用
b.fn();//同名覆盖,间接调用隐藏的A类函数
return 0;
}
当派生类写一个和基类同名(无论参数列表相同或不相同)的函数时,此时发生的动作叫“覆盖”。覆盖的意思,就是基类的同名函数,在派生类内,将变得无法直接调用(但可以间接调用)。
要访问A的重载函数,一种方式是直接用基类名::函数,可以将隐藏的基类函数调用,比如:b.A::fn(3); 还有一种方法是间接调用,在派生类中再写一个同名函数,里面代码用基类名::函数间接调用,比如:b.fn();。