C++程序设计——多重继承详解
实验目的
熟悉并掌握C++中有关多重继承的内容
实验内容
注!:部分内容参考:C++中的继承,原作者不喜可删
1、广义的继承
“继承”存在于生活中的方方面面。
举个最简单的例子,如下图:
生物作为基类,它拥有的特性是最基本、最广泛的。
而动物、植物、微生物作为它的派生类,都继承了它的特性:都是具有动能的生命体,能够繁殖后代。
但是各自又都有自己的特性,不完全与基类相同:
- 动物:一般可以自主运动,以有机物为食
- 植物:一般有叶绿素和细胞壁,能够通过光合作用进行自养
- 微生物:一般繁殖很快
另一个例子:
肉类、蔬菜和水果都属于食物,都是能吃到的东西,但他们的属性又有细微的差别,肉类含高蛋白质、脂肪和能量更多一些,蔬菜提供多种维生素和矿物质,水果也含有丰富的维生素且能够促进消化。
2、计算机科学中的继承
在计算机科学中,继承是复用代码提高效率的可靠手段,和广义继承相似,它允许在原有的基类基础上,继承基类的特性和功能,并对其进行扩展和延伸,得到派生类。
当通过继承创建一个类时,不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。
这个已有的类称为基类,新建的类称为派生类。
一个类作为基类,可以派生多个类,而一个派生类也可以同时继承多个类。
一个例子
#include <iostream>
using namespace std;
class food
{
public:
void eat()
{
cout<<"该物品是食物,可吃"<<endl;
cout<<"该食物的能量:"<<energy<<endl;
cout<<"该食物的重量:"<<weight<<endl;
}
int weight=0,energy=0;
};
class meat:public food
{
public:
void eat()
{
cout<<"该食物是肉类,很美味"<<endl;
cout<<"该肉类的能量:"<<energy<<endl;
cout<<"该肉类的重量:"<<weight<<endl;
}
};
class fruits:public food
{
public:
void eat()
{
cout<<"该食物是水果,酸甜可口"<<endl;
cout<<"该水果的能量:"<<energy<<endl;
cout<<"该水果的重量:"<<weight<<endl;
}
};
class vegetables:public food
{
public:
void eat()
{
cout<<"该食物是蔬菜,利于排便"<<endl;
cout<<"该蔬菜的能量:"<<energy<<endl;
cout<<"该蔬菜的重量:"<<weight<<endl;
}
};
int main()
{
food afood;
afood.energy=200;
afood.weight=500;
afood.eat();
cout<<"————————————————————"<<endl;
meat beef;
beef.energy=500;
beef.weight=400;
beef.eat();
cout<<"————————————————————"<<endl;
fruits apple;
apple.energy=100;
apple.weight=50;
apple.eat();
cout<<"————————————————————"<<endl;
vegetables cucumber;
cucumber.energy=70;
cucumber.weight=30;
cucumber.eat();
}
结果
该物品是食物,可吃
该食物的能量:200
该食物的重量:500
————————————————————
该食物是肉类,很美味
该肉类的能量:500
该肉类的重量:400
————————————————————
该食物是水果,酸甜可口
该水果的能量:100
该水果的重量:50
————————————————————
该食物是蔬菜,利于排便
该蔬菜的能量:70
该蔬菜的重量:30
在这个例子中,我们可以看到,food作为基类,派生出了三个子类(派生类):meat,fruits,vegetables,继承方式是public;它们都继承了food类的属性:weight和energy;还有方法eat(),我们在子类中再次定义从而隐藏父类中的该方法,以便识别本身是food的一种,更加具体了。
下面是三种继承方式:
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型,下面会谈到他们的区别。
3、访问控制和继承类型
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:
访问 | public | protected | private |
---|---|---|---|
访问当前类 | yes | yes | yes |
派生类访问基类 | yes | yes | no |
外部的类访问当前类 | yes | no | no |
一般来说,一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数
- 基类的重载运算符
- 基类的友元函数
因为虽然派生类继承自基类,但它们不可能完全一样,所以派生类要有自己的最基本的一些函数
继承公式:
class derived-class: access-specifier base-class
继承类型是通过上面的访问修饰符 access-specifier 来指定的。
事实上,protected继承和private继承很少会用到,通常使用public继承。
例:
class meat:public food{};
或者
class meat:protected food{};
或者
class meat:private food{};
当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
4、基类和派生类指针的相互赋值和转换
因为基类对象和派生类对象相比,基类对象有的属性和方法派生类对象一定有,而派生类对象有的属性和方法基类对象却不一定有。
所以
- 派生类对象可以赋值给基类对象/基类的指针/基类的引用。
这里有个形象的说法叫“切片”或者“切割”。寓意把派生类中基类的那部分属性切下来赋给基类对象。 - 基类对象不能赋值给派生类对象。
这个很好理解,因为可能有些属性基类对象没有。 - 基类的指针不能直接赋值给派生类的指针。但是通过强制类型转换,也可以将基类指针强制转换成派生类指针后再赋值给派生类指针。只是在这种情况下,程序员需要保证被转换的基类指针本来就指向一个派生类的对象,这样才是安全的,否则很容易出错。
下面为代码示例
#include <iostream>
using namespace std;
class Person
{
protected:
string _name; //姓名
string _sex; //性别
int _age; //年龄
};
class Student:public Person
{
public:
int _No; //学号
};
void Test()
{
Student sobj;
//1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
sboj = pobj; //error
//3.基类的指针可以通过强制类型转换赋值给派生类指针
pp = &sobj;
Student* ps1 = (Student*)pp;
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; //这种情况虽然可以,但会发生访问越界。
ps2->_No = 10;
}
int main()
{
Test();
return 0;
}
5、继承中的作用域
1、在继承体系中,作为独立的类,基类和派生类当然也有自己独立的作用域
2、在子类和父类中,如果有相同名称的方法或者属性,那么子类会屏蔽对父类中同名成员的访问,这就是重定义
辨析
1、成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
2、覆盖是指派生类函数覆盖基类函数。
(1)不同的范围(分别位于基类和派生类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
3、“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类函数同名,但是参数不同。此时,不论有无virtual, 基类的函数将被隐藏;
(2)如果派生类的函数与基类的函数同名,并且形参也相同,但是基类函数没有virtual,此时,基类的函数被隐藏。
6、派生类的默认成员函数
1、派生类的构造函数必须调用基类的构造函数初始化基类的那一部分初始化成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用;
2、派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝初始化;
3、派生类的operator=必须要调用基类的operator=完成基类的赋值;
4、派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理,基类对象再清理的顺序;
5、派生类对象初始化先调用基类构造函数再调用派生类构造函数;
6、派生类对象析构清理先调用派生类的析构函数再调用基类的析构函数。
#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;
class Person
{
public:
Person(const char* name = "Romeo")
:_name(name){
cout << "Person()" << endl;
}
Person(const Person& p)
:_name(p._name){
cout << "Person(const Person& p)" << endl;
}
Person& operator=(const Person& p){
cout << "Person& operator=(const Person& p)" << endl;
if (this != &p){
_name = p._name;
}
return *this;
}
~Person(){
cout << "~Person()" << endl;
}
protected:
string _name; //姓名
};
class Student :public Person
{
public:
Student(const char* name, int num)
:Person(name) //调用父类的构造函数完成从父类继承来的成员的初始化
, _num(num)
{
cout << "Student()" << endl;
}
Student(const Student& s)
:Person(s) //调用父类的拷贝构造函数,其中也为“切片”操作,因为父类的拷贝构造函数的参数为引用
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
Student& operator=(const Student& s){
cout << "Student& operator=(const Student& s)" << endl;
if (this != &s){
Person::operator=(s); //调用基类的
_num = s._num;
}
return *this;
}
~Student(){
//此处不需要显示调用父类的析构函数,编译器会自动调用
cout << "~Student()" << endl;
}
protected:
int _num; //学号
};
void Test()
{
Student s1("jack", 18);
Student s2(s1);
Student s3("rose", 17);
s1 = s3;
}
int main()
{
Test();
system("pause");
return 0;
}
结果如下:
Person()
Student()
Person(const Person& p)
Student(const Student& s)
Person()
Student()
Student& operator=(const Student& s)
Person& operator=(const Person& p)
~Student()
~Person()
~Student()
~Person()
~Student()
~Person()
7、继承与友元
友元关系不能继承,也就是说,基类的友元函数不能访问子类的私有和保护成员。如下:
#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; //姓名
};
class Student :public Person
{
protected:
int _stuNum; //学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl; //error!
cout << s._stuNum << endl; //error!
}
int main()
{
Person p;
Student s;
Display(p, s);
return 0;
}
8、继承与静态成员
基类定义了static静态成员,则整个继承体系里只有一个这样的成员。无论派生出多少个子类,都有一个static成员实例。
#include <iostream>
#include <string>
#include <stdlib.h>
using namespace std;
class Person
{
public:
Person(){
++_count;
}
protected:
string _name; //姓名
public:
static int _count; //统计人数
};
int Person::_count = 0;
class Student :public Person
{
protected:
int _stuNum; //学号
};
class Graduate : public Student
{
protected:
string _seminarCourse; //研究科目
};
void TestPerson()
{
Student s1;
Student s2;
Student s3;
Graduate s4;
cout << "人数: " << Person::_count << endl;
Student::_count = 0;
cout << "人数: " << Person::_count << endl;
}
int main()
{
TestPerson();
system("pause");
return 0;
}
结果表明只有一个静态成员,改变了子类的,基类的该成员也变化,实际上就是同一个
人数: 4
人数: 0
实验结果
通过这次实验,了解到C++中的继承绝不是看起来那么简单,从基类派生出子类,他牵扯到很多其他知识,比如友元与继承、静态成员和继承。