前言
继承是面向对象的三大特性之一。
下图为黑马程序员在C++课程中的课件。
利用好继承可以很好的减少重复代码,使程序更加简洁
一、继承的基本语法
语法: class 子类:继承方式(例如本例中的public) 父类
子类: 也被称为——派生类
父类: 也被称为——基类
下面为一个计算机语言类网站的案例,网站有许多公共部分,比如公共的头部信息包括:首页、注册、登录,还有底部、两侧都会有许多相同的地方,只有不同的计算机语言会有各自相应的区,包括视频区、讨论区等。利用继承可以很好将公共部分用一个父类表示,其他不同的地方用子类表示,这样就能减少重复代码。代码如下:
#include<iostream>
using namespace std;
//以做网页举例
//继承实现页面
class Basepage//继承的好处减少重复代码
{
public:
//相同处
void header()
{
cout << "公共头部信息(包括:首页、注册、登录...)" << endl;
}
void footer()
{
cout << "公共底部信息(包括:帮助中心、交流合作、站内地图...)" << endl;
}
void left()
{
cout << "公共分类表(包括:Java、Python、C++...)" << endl;
}
};
//语法: class 子类:继承方式(例如本例中的public) 父类
// 子类 也被称为——派生类
// 父类 也被称为——基类
//Java页面
class Java :public Basepage
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
//Python页面
class Python :public Basepage
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
//C++页面
class CPP :public Basepage
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "-------------------------------" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "-------------------------------" << endl;
cout << "C++下载视频页面如下:" << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
子类中的成员包含了两大部分:一.从父类中继承过来的二.是自己增加的成员 。
从基类继承过来的表现其共性,而新增的成员体现了其个性。
二、继承方式
继承的方式一共有三种:1.公共继承 2.保护继承 3.私有继承
图片来自黑马程序员视频讲义
1.父类中的私有成员子类中都无法访问。
2.在保护继承中,父类中的public、protected中的内容到子类都会变成保护权限。
3.在私有继承中,父类中的public、protected中的内容到子类都会变成私有权限。
三、继承中的对象模型
1.父类中所有非静态成员属性都会被子类继承下去。
2.父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了。
例子代码:
#include<iostream>
using namespace std;
//继承中的对象模型
class base
{
public:
int m_a;
protected:
int m_b;
private:
int m_c;
};
class son :public base
{
public:
int m_d;
};
void test01()
{
cout << "size of son=" << sizeof(son) << endl;
//父类中所有非静态成员属性都会被子类继承下去
//父类中私有成员属性 是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
}
int main()
{
test01();
system("pause");
return 0;
}
通过运行可以发现子类内存为16个字节。可以打开VS开发人员命令提示符工具,找到当前VS文件的路径,并输入下图所示的代码,可见子类中包含16个字节的大小,父类中有3个,自身有1个。
四、继承中的构造和析构顺序
继承中的构造和析构顺序为:先构造父类,再构造子类;析构顺序与构造相反。
代码如下(示例):
#include<iostream>
using namespace std;
//继承中的构造和析构顺序
class base
{
public:
base()
{
cout << "这是base的构造函数" << endl;
}
~base()
{
cout << "这是base的析构函数" << endl;
}
};
class son :public base
{
public:
son()
{
cout << "这是son的构造函数" << endl;
}
~son()
{
cout << "这是son的析构函数" << endl;
}
};
void test01()
{
son s1;
//先构造父类,再构造子类;析构顺序与构造相反。
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果如下图
五、继承同名成员处理方式
1.访问子类同名成员,直接访问即可。
2.访问父类同名成员,需要加作用域。
3.如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,如果想访问到父类中被隐藏的同名成员函数,需要加作用域。
#include<iostream>
using namespace std;
//继承中的同名成员的处理方式
class base
{
public:
base()
{
m_a = 100;
}
void fun()
{
cout << "base-fun()调用" << endl;
}
void fun(int a)
{
cout << "base-fun(int a)调用" << endl;
}
int m_a;
};
class son :public base
{
public:
son()
{
m_a = 200;
}
void fun()
{
cout << "son-fun()调用" << endl;
}
int m_a;
};
//同名的属性的处理方式
void test01()
{
son s1;
cout << "son 下面的m_a=" << s1.m_a << endl;//直接访问s1.m_a是子类中的数据
cout << "base下面的m_a=" << s1.base::m_a << endl;//访问父类下的s1.m_a,需要添加父类的作用域
}
//同名成员属性处理方式
void test02()
{
son s2;
s2.fun();//直接调用,调用的是子类中的成员
s2.base::fun();//需要加父类的作用域
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
s2.base::fun(100);//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
}
//同名成员函数处理方式
int main()
{
test01();
test02();
system("pause");
return 0;
}
运行结果如下:
son 下面的m_a=200
base下面的m_a=100
son-fun()调用
base-fun()调用
base-fun(int a)调用
六、同名静态成员处理
静态成员和非静态成员出现同名,处理的方法一致。
1.访问子类同名成员,直接访问即可。
2.访问父类同名成员,需要加作用域。
下面为静态成员属性的事例代码:
#include<iostream>
using namespace std;
//继承中的同名成员的处理方式
class base
{
public:
static int m_a;
};
int base::m_a=100;//静态成员需要类内声明,类外初始化
class son :public base
{
public:
static int m_a;
};
int son::m_a = 200;
//同名静态成员属性
void test01()
{
//1.通过对象访问
cout << "通过对象访问:" << endl;
son s;
cout << "son 下的m_a= " << s.m_a << endl;
cout << "base下的m_a= " << s.base::m_a << endl;
//2.通过类名访问
cout << "通过类名访问:" << endl;
cout << "son 下的m_a= " << son::m_a << endl;
cout << "base下的m_a= " << son::base::m_a << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
由上述代码可见想访问子类下的父类中的成员函数可用son::base::m_a 来访问。
下面为同名静态成员函数的代码案例。
#include<iostream>
using namespace std;
//继承中的同名成员的处理方式
class base
{
public:
static int m_a;
static void fun()
{
cout << "base-static void fun()" << endl;
}
static void fun(int a)
{
cout << "base-static void fun(int a)" << endl;
}
};
int base::m_a = 100;//静态成员需要类内声明,类外初始化
class son :public base
{
public:
static int m_a;
static void fun()
{
cout << "son-static void fun()" << endl;
}
};
int son::m_a = 200;
//同名静态成员函数
void test02()
{
//1.通过对象访问
cout << "通过对象访问fun()" << endl;
son s;
s.fun();
s.base::fun();
//1.通过类名访问
cout << "通过类名访问fun()" << endl;
son::fun();
son::base::fun();
//同样在静态成员函数中,子类出现与父类同名静态成员函数,也会隐藏父类中所有同名函数,要访问父类,需要加作用域
son::base::fun(100);
}
int main()
{
test02();
system("pause");
return 0;
}
与第五节相似,在静态成员函数中,子类出现与父类同名静态成员函数,也会隐藏父类中所有同名函数,要访问父类,需要加作用域。
七、多继承语法
C++允许一个类继承多个类。
语法:class 子类 :继承方式 父类1,继承方式 父类2.......
多继承可能会引发父类中有同名成员出现,需要加作用域区分。
C++实际开发中不建议用多继承。
代码如下(示例):
#include<iostream>
using namespace std;
//多继承语法
class base1//父类1
{
public:
base1()
{
m_a = 100;
}
int m_a;
};
class base2//父类2
{
public:
base2()
{
m_a= 200;
}
int m_a;
};
//语法:class 子类 :继承方式 父类1,继承方式 父类2......
class son :public base1, public base2//子类继承base1,base2
{
public:
son()
{
m_c=300;
m_d=400;
}
int m_c;
int m_d;
};
void test01()
{
son s;
cout << "sizeof son:" << sizeof(s) << endl;
//当不同父类中有同名成员出现,需要加作用域区分
cout << "base1中m_a= " << s.base1::m_a << endl;
cout << "base2中m_a= " << s.base2::m_a << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
运行结果如下:
sizeof son:16
base1中m_a= 100
base2中m_a= 200
八、菱形继承
图片来自黑马程序员视频课讲义
下面为上图的代码形式
#include<iostream>
using namespace std;
//多继承语法
class animal//动物类
{
public:
int m_age;
};
//可利用虚继承(关键字:virtual)解决菱形继承的问题
//继承前加关键字:virtual,变虚继承。此时的animal类被称为虚基类。
class sheep:virtual public animal//羊类
{};
class camel :virtual public animal//鸵类
{};
class alpaca :public sheep, public camel//羊鸵类
{};
void test01()
{
alpaca al;
al.sheep::m_age = 18;
al.camel::m_age = 20;
//当菱形继承时,有两个父类拥有两个相同的内容,需要加作用域加以区分。
cout << "al.sheep::m_age:" << al.sheep::m_age << endl;
cout << "al.camel::m_age:" << al.camel::m_age << endl;
//只需要有一份数据,菱形继承导致数据有两份,造成资源浪费,可利用虚继承解决
cout << "al.m_age:" << al.m_age << endl;//加了虚继承之后可以直接通过对象访问到这个相同的内容
}
int main()
{
test01();//加了虚继承后,只有一个m_age,后面不管给谁赋初值,都相当于对其赋值,所以m_age=最后赋值的数据
system("pause");
return 0;
}
运行结果如下:
al.sheep::m_age:20
al.camel::m_age:20
al.m_age:20
由此可见,当菱形继承时,有两个父类拥有两个相同的内容,需要加作用域加以区分。但是菱形继承导致数据有两份, 我们只需要有一份数据,会造成资源浪费,可利用虚继承解决。
虚继承关键字为:virtual 。虚继承前加关键字:virtual,变虚继承。此时的上述案例中的animal类被称为虚基类。并且加了虚继承之后可以直接通过对象访问到案例中父类相同的内容。最后加了虚继承后,只有一个m_age,后面不管给谁赋初值,都相当于对其赋值,所以m_age=最后赋值的数据
总结
继承作为C++面向对象的三大特性之一十分的重要,用好继承能让代码更加简洁。