继承
派生类继承了基类的所有成员函数和数据成员(构造函数、析构函数和操作符重载函数除外)。派生类除了继承基类所有成员函数和数据成员外,还可以拥有基类没有的成员函数和是数据方法
多态的实现依赖于继承,多态是指:究竟哪个类的成员函数的调用取决于对象属于哪个类,而对象的归属只有当程序运行以后才能决定(无法在编译时期确定)
继承除了表达is a关系外,还可以描述派生类与其基类的特别关系
C++支持多继承,即一个派生类可以拥有多个基类
类直接支持抽象数据类型的创建,而继承通过从已有的抽象数据类型派生出新的类型,进一步扩展了这种机制。因此,面向对象语言实际上向编程人员提供了一套设计抽象数据类型的有力工具。
不指明继承方式关键字public时,编译器会默认继承方式为private或protected
基类的所有私有成员仅在基类中可见,而在派生类中是不可见的。基类的私有成员可以由派生类继承,但是派生类不可见
使用using声明可以改变成员在派生类中的访问限制
例如,基类中的共有成员一般情况下被继承为公有成员,但使用using声明可将其改成私有数据;
代码1:
class BC
{
public:
void set_x(float a) {x = a;}
private:
float x;
};
class DC : public BC
{
public:
void set_y(float b) {y = b;}
private:
float y;
};
int main()
{
DC d;
d.set_y(4.31);
d.set_x(-8.03);
return 0;
}
此程序完全正确,但是看看下面代码2:
class BC
{
public:
void set_x(float a) {x = a;}
private:
float x;
};
class DC : public BC
{
public:
void set_y(float b) {y = b;}
private:
float y;
using BC::set_x ;
};
int main()
{
DC d;
d.set_y(4.31);
d.set_x(-8.03);
return 0;
}
下面的代码编译通不过
如上所示,如果派生类将基类的公有成员函数转换成私有成员函数,则不能通过派生类的对象直接调用这些函数。
如果派生类添加了一个数据成员,而该成员与基类中的某个数据成员同名,新的数据类型就 隐藏 了继承来的同名成员;同理,函数也存在隐藏
例子:
#include <iostream>
#include <string>
using namespace std;
class Film
{
public:
void store_title(const string &t) {title = t;}
void store_title(const char *t) {title = t;}
void store_director(const string &d) { director = d;}
void store_director(const char * d) { director = d;}
void store_time(int t) {time = t;}
void store_quality(int q) {quality = q;}
void output() const;
private:
string title;
string director;
int time;
int quality;
};
void Film::output() const
{
cout << "Title: " << title << endl;
cout << "Director: " << director << endl;
cout << "Time: " << time << " mins" << endl;
cout << "Quality: ";
for(int i = 0 ;i < quality ; i++)
cout << '*';
cout << '\n';
}
class DirectorCut : public Film
{
public:
void store_rev_time(int t ) {rev_time = t;}
void store_changes(const string &s) {changes = s;}
void store_changes(const char *s) {changes = s;}
void output() const;
private:
int rev_time;
string changes;
};
void DirectorCut::output() const
{
Film::output();
cout << "Received time: " << rev_time << " mins\n";
cout << "Changes: " << changes << endl;
}
class ForeignFilm : public Film
{
public:
void store_language(const string &l) {language = l;}
void store_language(const char *l ) {language = l;}
void output() const;
private:
string language;
};
void ForeignFilm::output() const
{
Film::output();
cout << "Language: " << language << endl;
}
int main()
{
Film f;
f.store_title("Rear Windows");
f.store_director("Alfred Hitchcock");
f.store_time(112);
f.store_quality(4);
cout << "Film--\n";
f.output();
cout << endl;
DirectorCut d;
d.store_title("Jail Bait");
d.store_director("Ed Wood");
d.store_time(70);
d.store_quality(2);
d.store_rev_time(72);
d.store_changes("Extra footage not in original included");
cout << "DirectorCut--\n";
d.output();
cout <<endl;
ForeignFilm ff;
ff.store_title("Jules and Jim");
ff.store_director("Francois Truffaut");
ff.store_time(104);
ff.store_quality(4);
ff.store_language("French");
cout << "ForeignFilm--\n";
ff.output();
return 0;
}
结果:
一般来说,采用类层次结构进行设计的主要原因是为了提供一个通用的接口
在设计类层次结构时,首先必须确定类层次结构中应该包含哪些类,以及每个类应该有什么成员函数。对类层次结构进行编码实现时,应该首先编写基类的代码,并对其进行调试和测试以确认基类正确无误,然后再进行派生类的编码、调试和测试。
除了私有和公有成员,C++还提供了保护成员。在没有继承的情况下,保护成员和私有成员类似,只在该类中可见。在共有继承下,保护成员和私有成员具有不同的性质,基类的保护成员在派生类中得到了保护。因此当一个派生类从基类继承了一个保护成员时,该保护成员在派生类中是可见的
派生类可对从基类继承来的保护成员进行访问,也就是说保护成员在派生类中是可见的 【不过派生类不能访问一个基类对象的保护成员,这是因为基类对象属于基类,不属于派生类】这句话有疑问
例子:
#include <iostream>
using namespace std;
class BC
{
public:
void set_x(int a){x=a;}
protected:
int get_x() const {return x;}
private:
int x;
};
class DC:public BC
{
public:
void add2() {int c = get_x(); set_x(c+2);}
};
int main()
{
DC d;
d.set_x(3);
//cout << d.get_x() << endl; //错误,不能访问
// d.x = 77; //错误不能访问
d.add2();
return 0;
}
main函数中能够访问DC的公有成员set_x和add2,但不能访问保护成员get_x,get_x仅在类层次结构中可见 (不能以对象的方式访问)
看下面的代码和结果:
虽然私有成员也被派生类继承,但私有成员在派生类中是不可见的。除了friend函数,类的私有成员只能被这个类的其他成员访问。派生类继承了基类的保护成员,而且派生类可以直接访问这些成员,这样保护成员在整个类层次结构中都是可见的。
除了friend函数,只有处于类层次结构中的成员函数才能访问保护成员。如果一个成员是公有的,那么只要该成员的所属类可见,这个成员可见
一般来说,应避免将数据成员设计为保护类型,即使某个数据成员可以成为保护成员,但更好的解决方案是:首先将这个数据成员定义为私有成员,然后为它设计一个用来进行存取访问的保护成员函数,通常将这种类型的成员函数称为访问函数(accessor)。
当然不能一概而论,如果这个数据成员比较复杂,例如一个数组,与其为它设计大量的访问函数,还不如直接将它定义成一个保护成员。将数据成员定义为私有的理由之一是实现数据的隐藏,同时还有其他优点。例如,采用上述私有数据成员和相应的保护型访问函数结合的设计模式,可以在不修改类的访问接口(保护的和公有的成员函数)的前提下,任意修改这个类的实现代码(即接口与实现分离)
派生类可以看作是基类的特殊版本
当创建一个派生类时,基类的构造函数被自动调用,用来对派生类对象中的基类部分初始化,并完成其他一些相关事务
派生类的构造函数负责对象中派生类添加部分的初始化工作 ---------原理
例子:
有时候基类构造函数的功能对派生类而言已经足够,这时候派生类不必自行设计构造函数,否则派生类必须定义自己的构造函数。可以在派生类的构造函数中调用基类的构造函数(前提是基类拥有构造函数)
在一个层次很深的类层次结构中,创建一个派生类对象将导致派生链中的所有类的构造函数被逐一调用,这是一个多米诺骨牌效应
如果基类拥有构造函数但 没有默认构造函数,那么派生类的构造函数必须显示的调用基类的某个构造函数
一般来说,最好为基类提供一个默认构造函数,这样就可以避免出现没有基类默认构造函数而出错的问题,而且并不妨碍派生类构造函数去调用基类的非默认构造函数。总而言之,建议为每一个基类都设计一个默认构造函数!!
假设基类拥有默认构造函数,而其派生类也定义了一些构造函数,不过派生类的任何构造函数都没有显示地调用基类的某个构造函数。在这种情况下,当创建一个派生类对象时,基类的默认构造函数将被自动地调用
以”DC类从BC类派生“为例总结如下:
1,若DC有构造函数而BC没有,当创建DC类的对象时,DC的相应构造函数被自动调用
2,若DC没有构造函数而BC有,则BC必须拥有默认构造函数。只有这样,当创建DC类的对象时,才能自动执行BC默认构造函数
3,若DC有构造函数,且BC有默认构造函数,则创建DC类的对象时,BC的默认构造函数会自动执行,除非当前被调用的派生类构造函数在其初始化中 显式 调用了BC的非默认构造函数
4,若DC和BC都有构造函数,但BC没有默认构造函数,则DC的每个构造函数必须在其初始化段中显式地调用BC的某个构造函数。只有这样,当创建DC对象时,BC的构造函数才能获得指向机会
再次强调,在创建派生类对象时,必须显式的或隐式的执行其基类的某个构造函数,这一点非常重要!!
有时候派生类的构造函数可能会依赖于基类的构造函数来完成一些必要的操作。例如,依赖基类构造函数来完成部分数据成员的初始化。而且,一般可以认为派生类对象是对基类对象的特化,这就进一步说明了为什么基类的构造函数(如果该类有构造函数)必须在派生类对象创建时 首先执行。在此之后,特化的构造函数(即派生类的构造函数)再负责处理派生类的特化信息(派生类的新增数据成员)
例子:这个例子很有代表性:
class Team
{
public:
Team(int len=100)
{
names = new striing[maxno = len];
}
protected:
string * names;
int maxno;
};
class BaseballTeam : public Team
{
public:
BaseballTeam(const string s[],int si) : Team(si)
{
for(int i = 0 ;i < si ; i ++)
names[i] = s[i];
}
};
在类的层次结构中,构造函数按基类到派生类的次序执行,析构函数则按派生类到基类的次序执行,因此,析构函数的执行次序和构造函数的执行次序是相反的
由于每个类至多只有一个析构函数,因此对析构函数的调用不会产生二义性,这样在析构函数中不必显式地调用其他析构函数,这一点和析构函数的调用规则是不同的
派生类、基类的构造函数都不是必须的,派生类、基类的析构函数也不是必须的
例子:
头文件sequence.h
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class Sequence
{
public:
bool addS(int,const string &);
bool del(int);
void output() const;
Sequence():last(-1) {}
Sequence(const char *);
~Sequence();
protected:
enum {MaxStr = 50};
string s[MaxStr];
int last;
private:
string filename;
ifstream in;
ofstream out;
};
bool Sequence::addS(int pos,const string &entry)
{
if(last == MaxStr - 1 || pos < 0 || pos > last + 1)
return false;
for(int i = last; i >= pos ; i --)
s[i+1] = s[i];
s[pos] = entry;
last ++;
return true;
}
bool Sequence::del(int pos)
{
if(pos < 0 || pos > last)
return false;
for(int i = pos ;i < last ; i++)
s[i] = s[i+1];
last --;
return true;
}
void Sequence::output() const
{
for(int i = 0 ;i <= last ;i ++)
cout << i << " " << s[i] << endl;
}
Sequence::Sequence(const char *fname)
{
last = -1 ;
filename = fname;
in.open(fname);
if(!in)
return ;
while(last < MaxStr -1 && getline(in,s[last+1]))
last ++;
in.close();
}
Sequence::~Sequence()
{
if(filename == "")
return;
out.open(filename.c_str());
for(int i = 0 ; i<= last ;i ++)
out << s[i] << endl;
out.close();
}
class SortedSeq : public Sequence
{
public:
bool addSS(const string &);
SortedSeq() {}
SortedSeq(const char *);
protected:
void sort();
private:
using Sequence::addS;
};
void SortedSeq::sort()
{
string temp;
int i,j;
for(i = 0 ;i <= last-1; i ++)
{
temp = s[i + 1];
for(j = i ; j >= 0 ; j --)
if(temp < s[j])
s[j+1] = s[j];
else
break;
s[j+1] = temp;
}
}
bool SortedSeq::addSS(const string &entry)
{
int i;
for(i = 0 ;i <= last ;i ++)
if(entry <= s[i])
break;
return addS(i,entry);
}
SortedSeq::SortedSeq(const char *fname) : Sequence(fname)
{
sort();
}
SorteSeq.cpp函数:
#include "sequence.h"
#include <cstdlib>
int main()
{
string inbuff,where;
int wh;
SortedSeq sortitems("test.dat");
while(true)
{
cout << "\nSortedSeq output: \n";
sortitems.output();
cout << "\n1 -- add\n"
<< "2 -- delete\n"
<< "3 -- quit\n";
getline(cin,inbuff);
if(inbuff == "1")
{
cout << "\nitem to add: ";
getline(cin,inbuff);
if(sortitems.addSS(inbuff))
cout << "item added\n";
else
cout << "item not added\n";
}
else if(inbuff == "2")
{
cout << "\nwhere to delete: ";
getline(cin,where);
wh = atoi(where.c_str());
if(sortitems.del(wh))
cout << "item not deleted\n";
else
cout << "item not deleted\n";
}
else if(inbuff == "3")
break;
}
return 0;
}
结果为:
单继承构造的层次是树,而多继承构造的层次是图
在单继承层次结构中,派生类通常是对其基类的特化,这是因为C++中类就是用户定义数据类型,而基类代表通用的数据类型,派生类则是该数据类型的特化或提炼。
在多继承层次结构中,派生类是其所有基类的组合体
多继承和访问规则与单继承的相同
使用多继承机制,将增加命名冲突出现的可能性,表现形式有两种:
1,派生类和某个基类之间发生命名冲突
2,基类与基类之间发生命名冲突
看例子怎么解决的:
注意一点:当多继承中,多个基类每个基类都要加上访问修饰符,缺省则默认为私有的
看下面例子:
下面对比看一个:
虚基类:
class BC
{
int x;
//...
};
class DC1 : public BC
{
//...
};
class DC2 : public BC
{
//...
};
class Z : public DC1,public DC2
{
//...
};
本例中类z通过两次继承BC,从而获得两份相同的数据成员x,一份经由DC1,另一份经由DC2.这会造成浪费和混淆,通过将DC1和DC2改为Z的Virtual基类,就可以解决问题:
class BC
{
int x;
//...
};
class DC1 : virtual public BC
{
// ...
};
class DC2 : virtual public BC
{
// ...
};
class Z : public DC1,public DC2
{
//...
};
经过这样的处理,在Z中只有x的一份拷贝了,将DC1和DC2说明为Z的虚基类,就是要求DC1和DC2仅将同名数据成员的一份拷贝发放的z当中,而不管DC1和DC2从共同祖先那获得多少个同名数据成员
继承的两种不同的应用:
1,用继承对一个类或对数据类型进行特化或提炼
2,用继承实现对多个类或数据类型的组合
保护继承的性质如下:
1,基类中的保护成员和公有成员在派生类中是保护成员
2,基类中的私有成员仅在基类中可见 即基类的私有成员不能继承)
私有继承的性质如下:
1,基类中所有公有或保护成员在派生类中是私有的
2,基类中所有私有成员仅在基类中可见 (即基类的私有成员不能继承)
常见编程错误:
1,使用派生机制时,默认的继承是私有继承,因此要实现公有继承,必须指明关键字public。而且多继承还必须为每一个基类指定一个
2,除非是friend函数,否则不能在类层次之外访问类的保护成员和私有成员