继承
一、继承的基础介绍
继承是面向对象三大特征之一
有些类和类之间存在特殊关系,如:
我们可以发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的个性。这个时候我们就可用继承的技术减少重复代码。 继承的基本语法 如很多网站中都有公共的头部,公共的底部,公共的左侧列表,只有中心内容不同,接下来用普通写法和继承写法来实现网页(我以CSDN的网页为例)中的内容,看一下继承存在的意义以及好处
普通版网页和继承版网页的区别
普通版网页
//普通版网页
class ZhuYe
{
public:
void head()
{
cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;
}
void foot()
{
cout << "用户名 关注 收藏" << endl;
}
void left()
{
cout << "原创 周排名 总排名 访问" << endl;
}
};
class Czhidao
{
public:
void head()
{
cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;
}
void foot()
{
cout << "用户名 关注 收藏" << endl;
}
void context()
{
cout << "请输入你的问题" << endl;
}
void left()
{
cout << "原创 周排名 总排名 访问" << endl;
}
};
class SheQu
{
public:
void head()
{
cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;
}
void foot()
{
cout << "用户名 关注 收藏" << endl;
}
void context()
{
cout << "与我相关 最新发布 最新回复 最热 有活动 有问题" << endl;
}
void left()
{
cout << "原创 周排名 总排名 访问" << endl;
}
};
void test01()
{
ZhuYe a;
a.head();
a.foot();
a.left();
cout << "-------------------------" << endl;
Czhidao b;
b.head();
b.context();
b.foot();
b.left();
cout << "-------------------------" << endl;
SheQu c;
c.head();
c.context();
c.foot();
c.left();
}
int main()
{
test01();
return 0;
}
其实会发现这个代码中有大量重复代码,这样子的代码是很Low的,也不符合C++是面向对象的语言的标准,而且会使得代码量加大,这个在企业开发中是一定要杜绝的
继承版网页
//继承版网页
class ZhuYe
{
public:
void head()
{
cout << "博客 下载 学习 社区 C知道 GitCode InsCode" << endl;
}
void foot()
{
cout << "用户名 关注 收藏" << endl;
}
void left()
{
cout << "原创 周排名 总排名 访问" << endl;
}
};
class Czhidao : public ZhuYe
{
public:
void context()
{
cout << "请输入你的问题" << endl;
}
};
class SheQu : public ZhuYe
{
public:
void context()
{
cout << "与我相关 最新发布 最新回复 最热 有活动 有问题" << endl;
}
};
void test01()
{
ZhuYe a;
a.head();
a.foot();
a.left();
cout << "-------------------------" << endl;
Czhidao b;
b.head();
b.context();
b.foot();
b.left();
cout << "-------------------------" << endl;
SheQu c;
c.head();
c.context();
c.foot();
c.left();
}
int main()
{
test01();
return 0;
}
以上是继承版代码,可以看出来他将重复的部分给删掉了,这里用了继承这个语法,现在我来讲讲继承的语法
语法
语法:class 子类 : 继承方式 父类
子类:又称派生类
父类:又称基类
派生类中的成员包括两大部分 一类是从基类继承过来的,一类是自己增加的成员 从基类继承过来的表现其共性,而新增的成员则体现了个性
二、继承方式
三种继承方式
公共继承
保护继承
**私有继承
从图可知:
父类中的私有成员,不管子类以哪种方式继承,都不可访问
就像父亲的银行卡密码,就算你是他儿子,他也不会告诉你,因为那是他的私有财产
三、继承中的对象模型
问题,从父类继承过来的成员,哪些属于子类中?
答案:
1.父类所有非静态成员属性都会被子类继承下去 2.父类中的成员属性是被编译器给隐藏了,因此是访问不到的,但是确实被继承下去了,大家可以用以下代码检测一下
class A
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class B : public A
{
public:
int m_D;
};
int main()
{
B test;
cout << sizeof(test) << endl;
return 0;
}
以上代码的答案是:16,虽然B只能访问A的m_A,m_B,加上自己的m_D,只有12个字节数,但是父类的m_C只是不可访问,不等于其不存在,因此,可以看出来子类继承了父类中所有非静态成员
四、继承中构造和析构函数
子类继承父类后,当创建子类对象,也会调用父类的构造函数
问题:父类和子类的构造和析构函数顺序谁先谁后?
大家可以通过以下代码来看看:
class A
{
public:
A()
{
cout << "父类构造函数执行" << endl;
}
~A()
{
cout << "父类析构函数函数执行" << endl;
}
};
class B : public A
{
public:
B()
{
cout << "子类构造函数执行" << endl;
}
~B()
{
cout << "子类析构函数函数执行" << endl;
}
};
int main()
{
B test;
return 0;
}
会发现继承中的构造和析构顺序如下: 先构造父类,再构造子类,析构的顺序与构造的顺序相反
五、继承同名成员的处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象访问父类中同名的数据呢?
访问同名成员:
访问子类同名成员 直接访问即可
访问父类类同名成员 需要加作用域
作用域写法:
对象名.父类::成员名
大家可以看看以下代码来理解
class A
{
public:
int m_A = 10;
};
class B : public A
{
public:
int m_A = 20;
};
int main()
{
B test;
cout << test.m_A << endl;
cout << test.A::m_A << endl;
return 0;
}
如果子类中出现与父类同名成员函数;要访问就要加作用域
六、继承同名静态成员的处理方式
继承同名静态成员在子类对象上如何访问? 静态成员和非静态成员出现同名,处理方式一致
访问同名成员:
访问子类同名成员 直接访问即可
访问父类类同名成员 需要加作用域
不过,静态成员有两种方式访问
通过对象
通过类名
第一种方法与非静态成员一样的方式,我就不过多赘述,我来讲讲第二种方式
为什么能用类名访问静态成员?
因为静态成员与静态成员函数在内存中都只有一份,所以所有对象都能直接访问他,因此只需要类名就能知道它具体的值
类名访问语法(以以下代码的访问为例)
class A
{
public:
static int m_A;
};
int A::m_A = 10;
class B : public A
{
public:
static int m_A;
};
int B::m_A = 20;
int main()
{
cout << B::m_A << endl;
cout << B::A::m_A << endl;
return 0;
}
插入静态成员知识点:
静态成员:
类型前加static
类内声明,类外初始化,一定要初始(因为静态变量放在全局区,全局区在编译阶段就分配内存)
静态成员函数:
返回类型前加static
只可访问静态变量
总结:
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(对象,类名)
七、多继承语法
C++中允许一个类继承多个类
语法:
class 子类 : 继承方式 父类1,继承方式 父类2, 继承方式 父类3……
多继承可能会引发父类有同名成员出现,要加作用域区分,因为容易出错,所以C++实际开发中不建议用多继承,因此不作过多介绍
八、菱形继承
概念:
两个派生类继承同一个基类 又有某个类同时继承两个派生类
以下例子虽然不符合事实动物的来源,但是有利于理解,大家就理解概念就好
菱形继承问题:
1.羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性
class Animal
{
public:
int m_Age;
};
class Sheep:public Animal
{
public:
int m_Age;
};
class Tuo :public Animal
{
public:
int m_Age;
};
class SheepTuo :public Sheep, public Tuo
{
public:
int m_Age;
};
void test()
{
SheepTuo st;
st.Sheep::m_Age = 20;
st.Tuo::m_Age = 10;
}
int main()
{
test();
return 0;
}
以上代码就有二义性:羊驼的年龄应该是和羊一样为20岁,还是应该和驼一样为10岁呢?
2.羊驼继承的动物的数据继承了两份,这份数据我们只需要一份就行
解决办法:
利用虚继承,解决菱形继承的问题
虚继承语法:
在继承之前加上关键字virtual变成虚继承
class Animal
{
public:
int m_Age;
};
class Sheep:virtual public Animal
{
public:
int m_Age;
};
class Tuo :virtual public Animal
{
public:
int m_Age;
};
class SheepTuo :public Sheep, public Tuo
{
public:
int m_Age;
};
void test()
{
SheepTuo st;
st.Sheep::m_Age = 20;
st.Tuo::m_Age = 10;
}
int main()
{
test();
return 0;
}
这时你
cout << st.m_Age << endl;
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;
都是等于10,因为三者在实际上时共用了一个数据,这里涉及了虚拟基类指针和虚拟基类表,这里涉及开发命令页的操作来展现,大家感兴趣的可以自行查找相关资料
总结:
菱形继承带来的主要问题是:子类继承两份相同的数据导致资源浪费以及毫无意义 可以用虚拟继承方式解决