C++编程学习笔记 复习/拾遗 8

继承的应用

#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!

管理步骤
  1. 创建一个控制台类型的项目L9.3,带一个main.cpp文件,其内容是main函数
  2. 在L9.3项目中为Mammal类创建2个文件(Ammal.h和Ammal.cpp文件),为Dog类创建2个文件(Dog.h和Dog.cpp文件)

在项目名L9.3,右键单击选择新建文件,添加新文件,如图:在这里插入图片描述
文件->新建->新建一个类,如图:
系统自动加了框架代码
在这里插入图片描述

  1. 输入基类名Mammal
  2. 勾选构造函数与析构函数
  3. 默认文件名Mammal.cpp及
    Mammal.h
    在这里插入图片描述
  1. 输入子类名
  2. 勾选从其它类继承
  3. 选择继承方式
  4. 输入父类名
    在这里插入图片描述
赋值兼容规则(向上转型)

一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:

  • 派生类的对象可以被赋值给基类对象。
  • 派生类的对象可以初始化基类的引用。
  • 指向基类的指针也可以指向派生类,即一个公有派生类对象的指针值可以赋值给(或初始化)一个基类指针。
  • 利用这样的指针或引用,只能访问派生类对象中从基类继承过来的成员,无法访问派生类的自有成员。
    在这里插入图片描述注意:派生类的拷贝构造函数如果要重新定义,其参数只有一个,即派生类对象引用,根据赋值兼容规则,派生类对象可以用来初始化基类的拷贝构造函数的基类对象引用。

在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();。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值