单继承是一个类是从另一个基类派生类而来的,多继承则是一个派生类是同两个或多个基类,派生类从两人或多个基类中继承所需的属性。
声明多重继承的方法:
class D: public A, private B, protected C
{
类D新增加的成员
}
一、多重继承派生类的构造函数
多重继承派生类的构造函数形式与单继承时的构造函数形式基本相同,只是在初始表中包含多个基类构造函数。如:
派生类构造函数名 ( 总参数表列 ) : 基类1构造函数 ( 参数表列 ) ,基类2构造函数 (参数表列) ,基类3构造函数 (参数表列)
{
派生类中新增数据成员初始化语句
}
派生类构造函数的执行顺序:同样还是先调用基类的构造函数,再执行派生类构造函数的函数体。
示例代码:
#include <iostream>
#include <string>
using namespace std;
class Teacher{protected:string name;int age;string post; //职称public:// 构造函数Teacher(string name, int age, string post): name(name), age(age), post(post){}
};
class Student{protected:string name;int age;int score;public:// 构造函数Student(string name, int age, int score): name(name), age(age), score(score){}
};
class Graduate: public Teacher, public Student{private:int wage; //工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){cout <<"name:" <<Teacher::name <<endl;cout <<"age:" <<Teacher::age <<endl;cout <<"post:" <<post <<endl;cout <<"score:" <<score <<endl;cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}
运行结果如下:
注意的是,Teacher类和Student类中都有name和age数据成员,这样程序就不知道调用哪一个,没作指明则系统编译时会报错【[Error] reference to 'name' is ambiguous】- 对'name'的引入有歧义。但如何指明其作用域呢,上述代码中已经体现了,通过类名来指定作用域,如:
cout <<"name:" <<Teacher::name <<endl;
二、多重继承引起的二义性问题
多重继承可以反映现实生活中的情况,能够用效地处理一些较复杂的问题,使编写程序具有灵活性,但是多重继承也引起了一些值得注意的问题,它增加了程序的复杂性,最常见的问题则是继承的成员同名而产生的二义性(ambiguous)问题。
(1)两个基类有同名成员。如下图:
示例代码如下:
#include <iostream>
#include <string>
using namespace std;
class Teacher{protected:string name;int age;string post; //职称public:// 构造函数Teacher(string name, int age, string post): name(name), age(age), post(post){}
};
class Student{protected:string name;int age;int score;public:// 构造函数Student(string name, int age, int score): name(name), age(age), score(score){}
};
class Graduate: public Teacher, public Student{private:int wage; //工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);return 0;
}
(2)两个基类和派生类三者都有同名成员。如下图:
示例代码如下:
#include <iostream>
#include <string>
using namespace std;
class Teacher{protected:string name;int age;string post; //职称public:// 构造函数Teacher(string name, int age, string post): name(name), age(age), post(post){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"post:" <<post <<endl;}
};
class Student{protected:string name;int age;int score;public:// 构造函数Student(string name, int age, int score): name(name), age(age), score(score){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
class Graduate: public Teacher, public Student{private:int wage; //工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){Teacher::display();Student::display();cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}
运行结果如下:
(3)两个基类有同一基类派生的,如下图:
示例代码如下:
#include <iostream>
#include <string>
using namespace std;
class Person{protected:string name;int age;public:Person(string name, int age): name(name), age(age){}
};
class Teacher: public Person{protected:string post; //职称public:// 构造函数Teacher(string name, int age, string post): Person(name, age), post(post){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"post:" <<post <<endl;}
};
class Student: public Person{protected:int score;public:// 构造函数Student(string name, int age, int score): Person(name, age), score(score){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
class Graduate: public Teacher, public Student{private:int wage; //工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){cout <<"name:" <<Teacher::name <<endl;cout <<"age:" <<Teacher::age <<endl;cout <<"post:" <<post <<endl;cout <<"score:" <<score <<endl;cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}
运行结果如下:
三、虚基类
3.1 虚基在的作用
如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二叉性,使用类名标识同名成员,如:Person::name。
C++提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员。虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式是声明的。因为一个基类可以生成一个派一类时作为虚基类,而在生成另一个派生类时不作为虚基类。
声明虚基类的一般形式为:
class 派生类名:virtual 继承方式 基类名
为了保证虚基类在派生类中只继承一次,应当在该基类的民有直接派生类中声明为虚基类,否则仍然会出两对基类的多次继承。
3.2 虚基类的初始化
如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。
示例代码如下:
#include <iostream>
#include <string>
using namespace std;
class Person{protected:string name;int age;public:Person(string name, int age): name(name), age(age){}
};
// Person 作为Teacher虚类
class Teacher: virtual public Person{protected:string post; //职称public:// 构造函数Teacher(string name, int age, string post): Person(name, age), post(post){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"post:" <<post <<endl;}
};
// Person作为Student虚类
class Student: virtual public Person{protected:int score;public:// 构造函数Student(string name, int age, int score): Person(name, age), score(score){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
class Graduate: public Teacher, public Student{private:int wage; //工资public:// 多继承构造函数Graduate(string name, int age, string post, int score, int wage):Person(name, age), Teacher(name, age, post), Student(name, age, score), wage(wage){}// 显示信息void display(){cout <<"name:" <<Teacher::name <<endl;cout <<"age:" <<Teacher::age <<endl;cout <<"post:" <<post <<endl;cout <<"score:" <<score <<endl;cout <<"wage:" <<wage <<endl;}
};
int main(){// 实例对象Graduate graduate("Tom", 20, "history", 90.5, 5000);// 显示结果graduate.display();return 0;
}
运行结果如下图:
注意,在定义Graduate类时,与以往使用的方法有所不同。以前,在派生类的构造函数中只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。现在由于虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。
C++中规定,在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。否是系统编译会报错【[Error] no matching function for call to 'Person::Person()'】- 调用Person::Person()没有匹配的函数。
四、基类与派生类的转换
公用继承能较好地保留基类的特征,它保留了除构造函数和析构函数以外基类所有成员,基类的公用或保护成员的访问权限在派生类中全部都按原样保留下来,在派生类外可以调用基类的公用成员函数说基类的私有成员。因此,公用派生类具有基类的全部功能,所有基类能够实现的功能,公用派生类都能实现。而非公用派生类(私有或保护派生类)不能实现基类的全部功能。因此,只有公用派生类才是基类真正的了类型,它完整的继承了基类的功能。
不同类型数据之间的自动转换和赋值,称为赋值兼容。
基类与派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。具体表现有以下几个方面:
(1)派生类对象可以向基类对象赋值,实际上是舍弃派生类自己的数据成员,对基类继承过来数据成员进行赋值。示例如下:
Person p; // 定义基类Person类对象p
Student s; // 定义Person类的公用派生类Student的对象s
p = s; // 用派生类Student对象s对基类对象p赋值
(2)派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化。示例如下:
Person p; // 定义基类Person对象p
Student s; // 定义公用派生类Student对象s
Person& p2 = p; // 定义基类Person对象的引用变量p2,并用p对其实始化
这里也可以用子对象初始化引用p2,不过此时p2不是与对象s共享同一段存储单元,而是与对象s部分共享同一段单元。示例如下:
Person& p2 = s; // 定义基类Person对象的引用变量p2,并用派生类Student对象s对其初始化
(3)如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。示例如下:
// 定义基类Person
class Person{//...
}
// 定义派生类Student
class Student: public Person{//...
}
// 定义一个函数其形参为Person类引用
void display(Person& p){//...
}
int main(){Person p; // 定义基类Person的对象pStudent s; // 定义派生类Student的对象s// 可以用子类对象display(s);return 0;
}
(4)派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。示例代码如下:
#include <iostream>
#include <string>
using namespace std;
// Student类
class Student{protected:string name;int age;public:// 构造函数Student(string name, int age): name(name), age(age){}void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;}
};
class Graduate: public Student{private:int score; //成绩public:// 多继承构造函数Graduate(string name, int age, int score):Student(name, age), score(score){}// 显示信息void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"score:" <<score <<endl;}
};
int main(){// 实例Student对象Student student("Tom", 20);// 实例Graduate对象Graduate graduate("John", 30, 98.5);Student* p = &student; // 定义指向Student类对象的指针并指向studentp->display(); // 调用student对象的成员函数p = &graduate; // 指针指向graduatep->display(); // 调用graduate对象的成员函数return 0;
}
运行结果可以看出,当指针指向对象graduate后调用display(),只输出了派生类继承基类的数据成员,而自身自增的数据成员并未输出。这是因为指向基类对象的指针,只能访问派生类的基类成员,而不能访问派生类增加的成员,所以p->display()调用的是基类 的display()函数,所以没有输出Graduate对象自增部分数据成员。如下图:
五、继承与组合
在一个类中可以用类对象作为数据成员,即子对象。在一个类中以另一个类的对象作为数据成员的,称为类的组合(composition)。
类的组合和继承一样,是软件重用的重要方式。组合和继承都是有效地利用已有类的资源。这里定义一个Birthday类,并将Birthday作为Student数据成员,用存储该学生的生日信息。示例代码如下:
#include <iostream>
#include <string>
using namespace std;
class Person{protected:string name;int age;public:Person(string name, int age): name(name), age(age){}
};
// 定义生日类
class Birthday{private:int year;int month;int day;public:Birthday(int year, int month, int day): year(year), month(month), day(day){}// 显示日期void show(){cout <<year <<'/' <<month <<'/' <<day <<endl;}
};
// Person作为Student基类
class Student: public Person{protected:Birthday birth;public:// 构造函数Student(string name, int age, Birthday birth): Person(name, age), birth(birth){}// 显示结果void display(){cout <<"name:" <<name <<endl;cout <<"age:" <<age <<endl;cout <<"birth:"; birth.show();}
};
int main(){Student s("Tom", 20, Birthday(2000, 1, 15));// 显示信息s.display();return 0;
}
运行结果如下图:
由于C++提供了继承的机制,用户将它们作为基类去建立适合于自己的类(即派生类),并在此基础设计自己的应用程序,类库的出现使得软件的重用更加方便。