朋友们好,这篇博客我们进入C++的进阶学习,最近我学习了C++中的继承相关知识,继承是面向对象编程的三大特征之一,十分重要。所以特意整理出来一篇博客供我们一起复习和学习,如果文章中有理解不当的地方,还希望朋友们在评论区指出,我们相互学习,共同进步!
继承是面向对象三大特性之一。
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用。继承是类设计层次的复用。
继承的语法:class 子类 : 继承方式 父类
继承方式:
基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
class person{
protected:
string _name;
int _age;
};
class student :public person
{
public:
int _No;
};
void test01()
{
student sobj;
//1.子类对象可以赋值给父类对象/指针/引用
person pobj = sobj;
person* pp = &sobj;
person& rp = sobj;
//2.基类对象不可以赋值给派生类对象
//sobj = pobj;
//3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
student* ps1 = (student*)pp;//这种情况是可以的
ps1->_No = 10;
pp = &pobj;
student* ps2 = (student*)pp;//这种情况转换时虽然可以,但存在越界访问的问题
ps2->_No = 10;
}
⚠️问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?
class Base {
public:
Base(){
m_A = 100;
}
void func(){
cout << "Base - func()调用" << endl;
}
void func(int a){
cout << "Base - func(int a)调用" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son(){
m_A = 200;
}
//当子类与父类拥有同名的成员函数,子类会隐藏父类中所有版本的同名成员函数
//如果想访问父类中被隐藏的同名成员函数,需要加父类的作用域
void func()
{
cout << "Son - func()调用" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func();
s.Base::func();
s.Base::func(10);
}
⭐️⭐️⭐️总结:
注:子类和父类中有同名成员时构成隐藏关系,也叫重定义。需要注意的是,如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
⚠️:问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致:
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名成员属性
void test01()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通过类名访问
cout << "通过类名访问: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名成员函数
void test02()
{
//通过对象访问
cout << "通过对象访问: " << endl;
Son s;
s.func();
s.Base::func();
cout << "通过类名访问: " << endl;
Son::func();
Son::Base::func();
//出现同名,子类会隐藏掉父类中所有同名成员函数,需要加作作用域访问
Son::Base::func(100);
}
总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式(通过对象 和 通过类名)
友元关系不可以继承,也就是说基类的友元不要可以访问子类的私有成员和保护成员。
(就好比说爸爸的朋友不一定是我的朋友)
基类定义了static静态成员,则整个继承体系只有这一个成员(我们知道静态成员是整个类共享的),无论派生出多少个子类,都只有这么一个static成员。
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 course;
};
void test()
{
student s1;
student s2;
student s3;
graduate s4;
cout << "人数" << person::_count << endl;
student::_count = 0;
cout << "人数" << person::_count << endl;
}
人数4
人数0
请按任意键继续. .
代码解释:因为子类对象构造是会调用基类的构造函数,所以每实例化一个子类对象都会调用一次基类构造,从而_count++,并且静态成员是整个类共享的,所以无论哪个子类都可修改!!!
6个默认成员函数,“默认"的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?
class person
{
public:
person(const char* name = "pxl")
:_name(name)
{}
person(const person& p)
:_name(p._name)
{}
person& operator=(const person& p)
{
if (this != *p){
_name = p._name;
}
return *this;
}
~person()
{}
protected:
string _name;
};
class student :public person
{
public:
student(const char* name, int num)
:person(name)//显示调用基类的构造函数初始化基类成员
, _num(num)
{}
student(const student& s)
:person(s)//注意这里有个隐式的切片操作 person& p = s;
, _num(s._num)
{}
student& operator=(const student& s)
{
if (this != &s){
person::operator=(s);//调用基类的operator=完成基类的赋值
_num = s._num;
}
return *this;
}
~student()
{
cout << "~student()" << endl;
//注意这里会自动调用父类析构
}
protected:
int _num;
};
void test()
{
student s1("ppp", 20);
student s2(s1);
student s3("xxx", 30);
s1 = s3;
}
⚠️留意代码中注释部分!
单继承:一个子类只有一个直接父类时称为单继承
多继承:一个子类有两个或者两个以上直接父类时称这个继承关系为多继承
菱形继承:两个派生类继承同一个基类,又有某个类同时继承者两个派生类。菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。
⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
对于菱形继承的二义性问题,我们可以在访问的时候加上类域,这样是可以解决的,但是数据冗余无法解决。所以下面引入虚拟继承!
为了研究虚拟继承原理,我们给出一个简单的菱形继承体系,再借助内存窗口观察对象成员模型。
class A{
public:
int _a;
};
class B:public A
{
public:
int _b;
};
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;
system("pause");
return 0;
}
这里可以分析出D对象将A放在了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?
这里通过B和C的两个指针,指向一张表。这两个指针叫虚基表指针,这两个表叫虚基表,虚基表中存的是偏移量。通过偏移量可以找到下面的A。
朋友们,看到这里还希望支持一下!
赠人玫瑰,手留余香!!!