目录
一、继承的概念
1、概念
继承 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在 保持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用, 继承是类设计层次的复用。
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
class Student : public Person
{
protected:
int _stuid; // 学号
};
class Teacher : public Person
{
protected:
int _jobid; // 工号
};
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和Teacher复用了Person的成员。
2、继承的定义
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。
3、继承关系和访问限定符
我们知道访问限定符有 public、protected 和 private三种,而继承方式也是有三种。他们之间重要的关系如下:
注:不可见表示子类无法访问父类的私有成员。因此如果父类里有不想让子类访问的成员,就可以设为私有权限。
基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承。
二、基类和派生类对象赋值转换
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};
int main()
{
Student sobj ;
// 子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
}
三、继承下的子类的成员函数
父类
class Person
{
public:
Person(const char* name = "ZDL")
: _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);
Student(const Student& s);
Student& operator=(const Student& s);
~Student();
protected:
int _num;
};
1、构造函数
子类自己的成员,跟类和对象一样。从父类继承过来的成员需要去调用父类的构造函数。
Student(const char* name,int num)
:Person(name) //_name是由父类继承而来,需要去调用父类的构造函数
,_num(num) //子类自己的成员自己初始化
{}
2、拷贝构造函数
子类自己的成员,跟类和对象一样(内置类型完成值拷贝,自定义类型调用它自己的拷贝构造)。从父类继承过来的成员需要去调用父类的拷贝构造函数。
Student(const Student& s)
:Person::Person(s)//调用父类的拷贝构造
,_num(s._num)
{}
3、赋值运算符
Student& operator=(const Student& s)
{
if (this != &s)
{
Person::operator=(s);
_num = s._num;
}
return *this;
}
4、析构函数
在继承的情况中,不需要显示调用父类的析构函数,编译器自动调用,先析构子类,再析构父类。只需要处理子类中的成员。
//子类的析构函数跟父类的析构函数构成隐藏
~Student()
{
//处理子类自己的
}
四、继承与友元
五、继承与静态成员
六、菱形继承问题
1、单继承
一个子类只有一个直接父类时称这个继承关系为单继承。
2、多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承。
3、菱形继承
菱形继承是多继承的一种特殊情况。 如下图:
class A
{
public:
int _a;
};
// class B : virtual public A
class B : public A
{
public:
int _b;
};
// class C : virtual public A
class C : public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
从上面的图中我们就可以看出在D中A的成员_a有两份,有数据冗余的问题。接下来我们了看看虚拟继承下的D中的情况。
这下我们发现 A 的 _a 成员只有一份了(红色框起来的),而 B 类和 C类中也分别多出了一个指针。那么这指针是个什么的呢?下面我们来讲一讲。
从内存的结构看去,我们发现指针指向的内容为它到取到 _a 的位置的偏移量,这样我们就可以找到共用的_a。这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A(虚基类) 。