继承与派生
继承
前言
- 为什么会有继承?
- 派生类
- 基类成员在派生类中的访问属性
- 隐藏
- 派生类默认成员函数
- 菱形继承
<一>为什么会有继承
提高代码的可重用性。
(无须修改已有类,只需在已有类的基础上,通过增加少量代码或修改少量代码的方法得到新类,从而较好的解决了代码重用的问题,形成类的层次结构)
<二>派生类
从已有类派生出新类时,可以在派生类内完成以下几种功能:
1.可以增加新的成员(成员函数、成员变量);
2.可以对 基类的成员进行重定义(同名成员隐藏)
3.可以改变基类成员在派生类中的访问属性。
<三>基类成员在派生类中的访问属性
由上表可直观看出:
(1)基类中的私有成员,无论以何种方式继承,基类中的私有成员在派生类中都是不可直接访问的。故,在设计基类时,总要为它的私有数据成员提供公有的成员函数,以便使派生类可以间接访问这些数据成员
(2)基类中的公有成员和保护成员,在派生类中的访问属性与继承方式密切相关。(详情参考上表)
<四>隐藏(重定义)
在继承体系中,子类和父类都有独立的作用域
子类和父类具有同名的成员(同名成员变量/成员函数),子类成员将屏蔽父类对成员的直接访问。当子类对象或者成员函数要访问父类的同名成员时,必须加上作用域
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class Person
{
public:
Person(const char *name="")
:_name(name)
{
cout << "Person()" << endl;
}
void Display()
{
cout << _name << endl;
}
private:
string _name;
};
class Student :public Person
{
public:
Student(const char* name="",const char *number="")
:_name(name),
_number(number)
{
cout << "Student()" << endl;
}
void Display()
{
cout <<_name<<' '<<_number<< endl;
}
private:
string _name;
string _number;
};
int main()
{
Person p("Peter");
Student s("Mary","123456"); //此时,s对象隐藏了父类对象的_name成员变量
s.Display(); //子类Display()与父类Display()成员函数函数名同名,构成隐藏(即:重定义)
s.Person::Display(); //子类对象想访问父类的Display(),必须加上类域。
system("pause");
return 0;
}
<五>派生类默认成员函数
1.派生类的构造函数和析构函数的调用顺序
通常情况下,
当创建派生类对象时,首先调用基类的构造函数,随后再调用派生类的构造函数;
当撤销派生类对象时,首先调用派生类的析构函数,随后再调用基类的析构函数。
2.派生类构造函数和析构函数的构造规则
(1)构造函数
*当基类的构造函数没有参数/没有显示定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数。此时系统自动调用基类的无参构造函数
*当基类构造函数只有含参数的时,派生类必须定义构造函数(以提供把参数传递给基类的构造函数途径),且需显示在初始化成员列表中调用基类的构造函数
即为以下格式:
派生类名(参数总表)
:基类名(参数表)
{
派生类新增数据成员的初始化语句;
}
(2)析构函数
派生类中可以根据需要定义自己的析构函数,用来对派生类中所增加的成员进行清理工作。基类的清理工作仍然由基类的析构函数负责(在执行派生类的析构函数时,系统会自动调用基类的构造函数,对基类的对象进行清理)
派生类的析构函数在其内部对基类的构造函数构成了隐藏
(看似派生类的析构函数与基类的析构函数函数名不同,但实际在底层(即汇编层),编译器将基类和派生类的析构函数名均处理成了相同的函数名_destructor)
注:
派生类的默认的成员函数都是合成的。
<六>菱形继承
1.继承分类(按照派生类所继承的基类是否是单一的)
(1)单继承
(2)多继承
2.菱形继承产生的原因:
由于多继承的存在,就有可能出现菱形继承。
那么,何为菱形继承呢?(解释见下图)
示例如下:
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
private:
int _a;
};
class B:public A //B类是由A类派生下来的
{
{
public:
B()
{
cout << "B()" << endl;
}
private:
int _b;
};
class C:public A //C类也是由A类派生下来的
{
public:
C()
{
cout << "C()" << endl;
}
private:
int _c;
};
class D : public B, public C //D类是由A类和B类派生下来的
{
public:
D()
{
cout << "D()" << endl;
}
private:
int _d;
};
int main()
{
D d; //此时d对象中含有两个_a(其中一个是由A类派生的,另一个是由B类派生的)
system("pause");
return 0;
}
3.如何解决菱形继承导致的问题呢?
(1)二义性 ——————————> 使用时,加类域(假设是B类的_a成员,则 表示为B::_a)
(2)数据冗余——————————> 虚继承 ——————> 生成虚基表
- 未使用虚继承代码如下:
class A
{
public:
A()
{
cout<<"A()"<<endl;
}
public:
int _a;
};
class B:virtual public A
{
public:
B()
{
cout<<"B()"<<endl;
}
public:
int _b;
};
class C:virtual public A
{
public:
C()
{
cout<<"C()"<<endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
D()
{
cout<<"D()"<<endl;
}
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2; //d中有两份_a,分别是B类和C类继承A类的成员,为解决二义性,使用时添加类域
d._b = 3;
d._c = 4;
d._d = 5;
cout << sizeof(D) << endl; //20(=(8)+(8)+4)
system("pause");
return 0;
}
- 使用虚继承代码如下:
class A
{
public:
A()
{
cout<<"A()"<<endl;
}
public:
int _a;
};
class B:virtual public A
{
public:
B()
{
cout<<"B()"<<endl;
}
public:
int _b;
};
class C:virtual public A
{
public:
C()
{
cout<<"C()"<<endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
D()
{
cout<<"D()"<<endl;
}
public:
int _d;
};
int main()
{
D d;
//d.B::_a = 1;
//d.C::_a = 2; //虚继承后,_a只有一个,解决了二义性(即B::_a==C::_a),即:可写如下表达式
d._a=2;
d._b = 3;
d._c = 4;
d._d = 5;
cout << sizeof(D) << endl; //24(=(8)+(8)+4+(4))
system("pause");
return 0;
}
上一篇: 【课堂笔记】《数据库系统概论(第5版)》-第7章:数据库设计
下一篇: c++中继承知识点详解