第十六章 继承
16.1 继承的概念:
面向对象程序设计可以让你声明一个新类作为另一个类的派生。派生类(又称子类)继承父亲的属性和操作。子类也声明了新的属性和新的操作,剔除了那些不适合于其用途的继承下来的操作。即,继承可以让你重用父类的代码,专注于为子类编写新代码,继承可以使已经存在的类不需改动的适应新应用。
16.2 继承的工作方式:
例如,现在有一个学生类student,要新建一个研究生类graduatestudent。研究生除了具有研究生的性质还具有学生的所有性质,所以我们用继承的方式重用学生类。
class student
{
//...
};
class graduatestudent:public student
{
//...
};
graduatestudent类继承了student类的所有成员。继承的方式如上。而graduatestudent类也有自己特定的成员。
举一个例子:继承student类
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
class Advisor//导师资料
{
int noofmeeting;
};
class student
{
public:
student(char* pname = "no name")
{
strcpy_s(name, pname);
average = semesterhours = 0;
}
void addcourse(int hour, float grade)
{
average = (semesterhours * average + grade);//总分
semesterhours += hour;//总修学时
average /= semesterhours;//平均分
}
int gethours()
{
return semesterhours;
}
float getaverage()
{
return average;
}
void display()
{
cout << "name=\"" << name << "\""
<< ",hour=" << semesterhours
<< ",average=" << average << endl;
}
protected:
char name[60];
int semesterhours;
float average;
};
class graduatestudent :public student
{
public:
int getqualifier()
{
return qualifiergrade;
}
protected:
Advisor advisor;
int qualifiergrade;
};
int main()
{
student ds("lo lee undergrade");
graduatestudent gs;
ds.addcourse(3, 2.5);
ds.display();
gs.addcourse(3, 3.0);
gs.display();
return 0;
}
ds是student类对象,gs是graduatestudent类对象。作为student的子类,对象gs可以做ds能做的任何事,它有name,semesterhour average数据成员,以及addcourse()成员函数,此外gs比ds还多一点东西,它有导师Advisor和资格考试分qualifiergrade。
gs是一个学生,所以对student中的addcourse()成员函数的调用等于是在调用自己的成员函数。
void fn(student*s)
{
//任何学生想要干的事
}
int main() {
graduatestudent gs;
fn(gs);
}
虽然fn()函数期望接受的是student类对象,但是来自main()的调用传给他一个graduatestudent对象,fn()把它视同为student对象并接受。
16.3 派生类的构造:
在上述代码中,没有声明派生类graduatestudent的构造函数,导致派生类对象创建时,执行默认构造函数,该默认构造函数会首先调用基类的默认构造函数,而基类没有默认构造函数,但正好匹配默认参数的构造函数。导致gs对象的name为"no name"。
16.4 继承与组合:
类以另一个类对象作数据成员,称为组合。在程序中,graduatestudent类组合了Advisor类。这种场合,称graduatestudent有一个Advisor;而在继承的组合,称graduatestudent是一个student。
继承和组合的区别:
#include<iostream>
using namespace std;
class vehicle
{
//...
};
class Motor//马达
{
//...
};
class car :public vehicle
{
public:
Motor motor;
};
void vehiclefn(vehicle &v);
void motorfn(Motor &m);
int main()
{
car c;
vehiclefn(c);
motorfn(c);//error.参数要求是马达,而汽车不是马达,所以参数c不匹配该函数。
motorfn(c.motor);
return 0;
}
16.5 多态性:
在16.2代码中,graduatestudent类对象gs调用student类的成员函数display(),该函数在输出时,没办法输出graduatestudent自己的数据成员qualifierfrade。因此继承时希望重载display()。
在下面代码中,基类和派生类中都定义了计算学费的函数:
#include<iostream>
using namespace std;
class student
{
public:
//...
float calctuition()//计算学费
{
//...
}
};
class graduatestudent:public student
{
public:
//...
float calctuition()
{
//...
}
};
int main()
{
student s;
graduatestudent gs;
s.calctuition;
gs.calctuition;
return 0;
}
//
class student
{
public:
//...
float calctuition()//计算学费
{
//...
}
};
class graduatestudent:public student
{
public:
//...
float calctuition()
{
//...
}
};
void fn(student&x)
{
x.calctuition();
}
int main()
{
student s;
graduatestudent gs;
fn(s);
fn(gs);
return 0;
}
17.多重继承
17.1 多重继承如何工作:
#include<iostream>
using namespace std;
class Bed
{
public:
Bed():weight(0){}
void sleep()
{
cout<<"sleeping...\n";
}
void setweight(int i)
{
weight=i;
}
protected:
int weight;
};
class Sofa
{
public:
Sofa():weight(0){}
void watchtv()
{
cout<<"watchtv...\n";
}
void setweight(int i){weight=i;}
protected:
int weight;
};
class sleepersofa:public Bed,public Sofa
{
public:
sleepersofa(){}
void foldout()//折叠与打开
{
cout<<"fold out the sofa.\n";
}
};
int main()
{
sleepersofa ss;//只需定义一个对象便可调用三个类
ss.watchtv();
ss.foldout();
ss.sleep();
return 0;
}
17.2 继承的模糊性:
在上节中,Sofa 和 Bed 都有一个成员weight。但是sleepersofa继承哪个weight呢?既然两者都继承,因为名字一样,使得对weight引用变得模糊不清。如何引用呢?
int main()
{
sleepersofa ss;
ss.setweight(20);//Bed的setweight还是Sofa的setweight?
return 0;
}
//导致名字冲突,编译时错误
int main()
{
sleepersofa ss;
ss.sofa.setweight(20);//说明sofa的重量是20
return 0;
}
17.3 虚拟继承
public 前面加上 virtual
我们可以通过分解具体考察他们的关系:
#include<iostream>
using namespace std;
class Furniture//家具
{
public:
Furniture(){}
void setweight(int i){weight=i;}
int getweight(){return weight;}
protected:
int weight;
};
class Bed:public Furniture
{
public:
Bed(){}
void sleep()
{
cout<<"sleeping...\n";
}
};
class Sofa:public Furniture
{
public:
Sofa(){}
void watchtv()
{
cout<<"watching TV.\n";
}
};
class sleepersofa:public Bed,public Sofa
{
public:
sleepersofa():Sofa(),Bed(){}
void foldout()
{
cout<<"fold out the sofa.\n";
}
};
int main()
{
sleepersofa ss;
ss.setweight(20);//error. 模糊的setweight成员
Furniture *pf;
pf=(Furniture*)&ss;//error.模糊的Furniture成员
cout<<pf->getweight()<<endl;
}
这里的sleepersofa不是直接继承Furniture,而是Bed和Sofa各自继承Furniture。这里sleepersofa包括一个完整的Bed和完整的Sofa,后面还有一个sleepersofa特有的东西,而sleepersofa中的每一个子对象都有它自己的Furniture部分,导致了继承层次的错误。不知道setweight()属于哪一个Furniture成员,指向Furniture的指针也不知道是指向哪一个Furniture导致编译错误。
sleepersofa只需要一个Furniture,所以我们希望它只含一个Furniture拷贝,同时又要共享Bed和Sofa的成员函数与数据成员。这时候就需要虚拟继承:
#include<iostream>
using namespace std;
class Furniture//家具
{
public:
Furniture(){}
void setweight(int i){weight=i;}
int getweight(){return weight;}
protected:
int weight;
};
class Bed:virtual public Furniture
{
public:
Bed(){}
void sleep()
{
cout<<"sleeping...\n";
}
};
class Sofa:virtual public Furniture
{
public:
Sofa(){}
void watchtv()
{
cout<<"watching TV.\n";
}
};
class sleepersofa:public Bed,public Sofa
{
public:
sleepersofa():Sofa(),Bed(){}
void foldout()
{
cout<<"fold out the sofa.\n";
}
};
int main()
{
sleepersofa ss;
ss.setweight(20);//error.
cout<<ss.getweight()<<endl;
}
虚拟继承的虚拟和虚拟函数的虚拟没有关系。
17.4 多继承的构造顺序:
(1). 任何虚拟基类的构造函数按照它们被继承的顺序构造。
(2). 任何非虚拟基类的构造函数按照它们被继承的顺序构造。
(3). 任何成员对象的构造函数按照它们声明的顺序被调用。
(4). 类自己的构造函数。
17.5 继承的访问控制:
Ps:本文是学习笔记,内容来自清华大学出版社出版的C++程序设计教程(修订版)——设计思想与实现。钱能著。