C++学习 之 继承(笔记)
1.继承基础:
继承就像是生物里的遗传与变异,即派生类(子代)从基类(父代)那里继承基类的某些特性(遗传),并在此基础上拥有自己的特性(变异)。
C++派生语法:
class Base//定义一个基类
{
//...基类成员
};
class Derived:access-specifier Base//定义一个派生类
{
//...派生类成员
}
其中access-specifier可以是public、private、protected(表示派生类有一个基类)。
以下程序清单从Fish类派生出了Carp和Tuna类的一种简单的继承层次结构:
#include<iostream> using namespace std; class Fish //定义Fish类 { public: bool FreshWaterFish; void Swim() { if (FreshWaterFish) cout << "Swims in lake" << endl; else cout << "Swims in sea" << endl; } }; class Tuna :public Fish//定义Tuna类,将继承的Fish类的成员或方法作为自己的public成员或方法 { public: Tuna() { FreshWaterFish = false; } }; class Carp :public Fish { public: Carp() { FreshWaterFish = true; } }; int main() { Carp myLunch; Tuna myDinner; cout << "Getting my food to swim" << endl; cout << "Lunch:"; myLunch.Swim(); cout << "Dinner:"; myDinner.Swim(); return 0; }
2.向基类传递参数:
向基类传递参数在初始化派生类对象时有的时候显得很有用。比如上面的Fish类派生出Carp类和Tuna类,可以修改成如下代码:
#include<iostream> using namespace std; class Fish { protected: bool FreshWaterFish; Fish(bool IsFreshWater) { FreshWaterFish = IsFreshWater; } public: void Swim() { if (FreshWaterFish) cout << "Swims in lake" << endl; else cout << "Swims in sea" << endl; return; } }; class Tuna :public Fish { public: Tuna():Fish(false) {}//派生类每次定义对象都指出是淡水鱼还是咸水鱼,采用参数列表将bool值传递给基类的构造函数 }; class Carp :public Fish { public: Carp():Fish(true) {} }; int main() { Carp myLunch; Tuna myDinner; cout << "Getting my food to swim" << endl; cout << "Lunch:"; myLunch.Swim(); cout << "Dinner:"; myDinner.Swim(); return 0; }
3.在派生类中覆盖基类的方法:
如果派生类中有和基类中的函数名相同,返回值和特征标也相同时,就相当于派生类的方法覆盖了基类的方法。我们还拿Fish类,Tuna类和Carp类来举例子。
用Tuna和Carp类的Swim()覆盖Fish类的Swim():
#include<iostream> using namespace std; class Fish { protected: bool FreshWaterFish; Fish(bool IsFreshWater) { FreshWaterFish = IsFreshWater; } public: void Swim() { if (FreshWaterFish) cout << "Swims in lake" << endl; else cout << "Swims in sea" << endl; return; } }; class Tuna :public Fish { public: Tuna():Fish(false) {} void Swim()//此函数与基类函数在调用上效果相同,基类函数被覆盖 { cout << "Tuna swim fast" << endl; } }; class Carp :public Fish { public: Carp():Fish(true) {} void Swim() { cout << "Carp swim low" << endl; } }; int main() { Carp myLunch; Tuna myDinner; cout << "Getting my food to swim" << endl; cout << "Lunch:"; myLunch.Swim(); cout << "Dinner:"; myDinner.Swim(); return 0; }
上面的程序的结果显示了,在主函数中对象调用方法Swim()时调用的并不是Fish类里的方法了,而是Tuna和Carp自己的方法,这样基类的方法就相当于是被覆盖了。覆盖是的派生类的方法可以自定 义,但如果我们还想使用基类的方法,就必须想办法使得机器可以知道我们调用的Swim()的作用域在哪个范围。
4. 调用基类中被覆盖的方法:
上面说到了我们只需要让机器知道我们调用的Swim()的作用域,就可以使得被覆盖的基类方法重新被调用。我们可以在调用基类方法时使用"::"作用域解析运算符。
以上面的调用Swim()的代码为例:
myLunch.Swim();//调用派生类Carp中的Swim()
my.Lunch.Fish::Swim();//调用基类Fish中的Swim()
如果基类的方法没有被覆盖,则可以在派生类中和调用函数一样来调用基类的方法。如果被覆盖的话就要使用"::"作用域解析运算符。在主函数里调用就是类似语句:"my.Lunch.Fish::Swim();"在派 生类调用基类被覆盖方法时类似于语句:"Fish::Swim"。
5. 在派生类中隐藏基类的方法:
覆盖的一种极端情形就是,Tuna::Swim()可能隐藏Fish::Swim()的所有重载版本,使得使用这些重载版本会导致编译错误。
以下面代码为例:
1 #include<iostream> 2 using namespace std; 3 class Fish 4 { 5 public: 6 void Swim() 7 { 8 cout << "Fish swims...!" << endl; 9 } 10 void Swim(bool FreshWaterFish) 11 { 12 if (FreshWaterFish) 13 cout << "Swims in lake" << endl; 14 else 15 cout << "Swims in sea" << endl; 16 return; 17 } 18 }; 19 20 class Tuna :public Fish 21 { 22 public: 23 void Swim() 24 { 25 cout << "Tuna swim fast" << endl; 26 } 27 }; 28 int main() 29 { 30 Tuna myDinner; 31 32 cout << "Getting my food to swim" << endl; 33 cout << "Dinner:"; 34 myDinner.Swim(); 35 //myDinner.Swim(false); 36 37 return 0; 38 }
代码在6-17行实现了基类方法Swim的重载,在23-26行实现了派生类方法Swim对基类方法Swim的覆盖。观察发现派生类里只有一个版本的Swim方法,Fish中的Swim的重载版本Swim(bool)版本好像是没 有被覆盖,但其实Swim(bool)也类似被覆盖的情况是没法在派生类或主函数中像调用其他函数那样被调用的。如果我们去掉35行的注释符号,代码就会有编译错误,这也说明了问题。以上说明了在派生类 中覆盖基类的方法时是与函数的参数无关的,只要函数名称,返回值,特征值相同就会被覆盖。
调用基类中被隐藏的方法:
解决方案1:在main函数中使用作用域解析运算符"::"。
myDinner.Fish::Swim();
解决方案2:在派生类中使用关键字using解除对基类方法的隐藏。
class Tuna:public Fish
{
public:
using Fish Swim;//解除对Fish::Swim()重载版本的隐藏,其中不包括与派生类参数也相同的方法。
void Swim(){}
}
解决方案3:在Tuna类中重新定义所有Fish类的被覆盖的方法的重载版本。