c++ 继承
继承
继承是面向对象三大特性之一。
类与类之间存在特殊的关系,定义一些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
利用继承的技术,减少重复的代码。
当我们进入一个网站后,以编程网页为例。其中有着公共的头部,公共的尾部,公共的列表。在网页中间,拥有各个编程语言的视频。不使用继承会让代码多出许多重复的内容。
#include <iostream>
using namespace std;
//普通页面
//C++页面
class CPP
{
public:
void header()//公共头部
{
cout << "公共头部: 首页、公开课、登录、注册..." << endl;
}
void footer()
{
cout << "公共尾部: 帮助中心、 交流合作、 站内信息..." << endl;
}
void left()
{
cout << "公共分类列表:Java、Python、C++..." << endl;
}
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
cout << "C++下载视频页面如下:" << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
在上面的代码中,写了一个C++类。C++类中包含了公共头部、公共尾部、公共列表,还有C++的教学视频。
在网站中不仅仅只包含了C++,还包含了Python、Java等编程语言。因此在代码中添加新的类。可以完全使用复制粘贴的方式,因为他们仅仅只有内部的content模块不同。
class Python
{
public:
void header()//公共头部
{
cout << "公共头部: 首页、公开课、登录、注册..." << endl;
}
void footer()
{
cout << "公共尾部: 帮助中心、 交流合作、 站内信息..." << endl;
}
void left()
{
cout << "公共分类列表:Java、Python、C++..." << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
//JAVA页面
class Java
{
public:
void header()//公共头部
{
cout << "公共头部: 首页、公开课、登录、注册..." << endl;
}
void footer()
{
cout << "公共尾部: 帮助中心、 交流合作、 站内信息..." << endl;
}
void left()
{
cout << "公共分类列表:Java、Python、C++..." << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
写起来很快,因为大部分代码都相同,但是网页中不仅仅只有这三种编程语言,复制粘贴写起来很舒服,但是,如果被你的上司看到,他只会认为你是一个菜鸟,你写的代码太low了。
利用继承实现页面。
#include <iostream>
using namespace std;
//利用继承实现页面
//公共页面类
class basePage
{
public:
void header()//公共头部
{
cout << "公共头部: 首页、公开课、登录、注册..." << endl;
}
void footer()
{
cout << "公共尾部: 帮助中心、 交流合作、 站内信息..." << endl;
}
void left()
{
cout << "公共分类列表:Java、Python、C++..." << endl;
}
};
//C++页面
class CPP :public basePage
{
public:
void content()
{
cout << "C++学习视频" << endl;
}
};
//JAVA页面
class Java :public basePage
{
public:
void content()
{
cout << "Java学习视频" << endl;
}
};
//Python页面
class Python :public basePage
{
public:
void content()
{
cout << "python学习视频" << endl;
}
};
void test01()
{
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
}
int main()
{
test01();
system("pause");
return 0;
}
所以继承的好处:减少重复的代码。
1、继承的基本语法
继承的基本语法其实就一条, class 子类:继承方式 父类。 例如上面代码中的 class CPP : public basePage
子类又称为派生类,父类又称为基类。 子类中的成员包含两部分:一部分是从基类继承过来的,另一部分是自己增加的成员。 从基类继承的表现共性,新增的表现个性。
2、继承方式
继承的方式一共有三种:公共继承、保护继承和私有继承。
在类中同样有这三种类型。父类中的私有属性,无论子类以什么方式继承,都不可访问。
如果子类共有继承父类(class B : public A),成员属性不变,父类中是公有依然公有;保护依然是保护;私有属性不可访问。
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class son1 : public Base1
{
public:
void func()
{
m_A = 10; //父类公共成员依旧是公共权限。
m_B = 10; //保护权限依旧是保护权限。
m_C = 10;//标红,私有权限子类访问不到
}
void test01()
{
son1 s1;
s1.m_A = 100;
s1.m_B = 100; //标红 保护权限类外访问不到。
}
};
如果子类保护继承父类(class B : protected A),子类中不存在公有属性,父类中的共有属性继承后变为保护属性,保护属性依旧是保护属性,私有属性不可访问。
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class son2 : protected Base2
{
public:
void func()
{
m_A = 100;
m_B = 100;
m_C = 100; // 标红,子类访问不到。
}
};
void test02()
{
son2 s2;
s2.m_A = 1000; //变为保护权限,类外访问不到
s2.m_B = 1000;
}
如果子类私有继承父类(class B : private A),所有成员属性都变成私有,父类中的原私有属性不可访问。
3、继承中的对象模型
问题:从父类继承的成员,那些属于子类对象?
父类中,所有的非静态成员属性都会被继承。
#include <iostream>
using namespace std;
//继承中的对象模型
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class son : public Base
{
int m_D;
};
void test01()
{
//父类中,所有的非静态成员属性都会被继承
//私有成员属性 被编译器隐藏了,因此访问不到,但是确实被继承了。
cout << "size of son = " << sizeof(son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
代码运行结果是 16, 所以父类中,所有的非静态成员属性都会被继承。
私有成员属性 被编译器隐藏了,因此访问不到,但是确实被继承了。可以利用开发人员命令提示工具产看对象模型。
4、继承中的构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
问题:父类和子类构造和析构函数顺序谁先谁后?
#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()
{
//Base b;
Son s;
}
int main()
{
test01();
system("pause");
return 0;
}
运行代码:“Base构造函数! Son构造函数! Son析构函数! Base析构函数!
继承中先调用父类构造函数,在调用子类构造函数,析构函数与构造函数相反。
5、继承中同名成员处理方式
问题:当子类与父类中出现相同成员名,如何通过子对象,访问到子类或父类中的同名成员?
访问子类成员,直接访问; 访问父类同名成员,需要加上作用域。
同名成员属性:
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
m_A = 100;
}
int m_A;
};
class Son : public Base
{
public:
Son()
{
m_A = 200;
}
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
s.m_A 访问到子类中的成员m_A.
s.Base::m_A 加了作用域Base:: ,访问到父类Base中的m_A。
同名成员函数:和同名成员变量一样,加上作用域。
s1.func();
s1.Base::func();
那么,当父类中出现了重载函数怎么调用呢?
class Base
{
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base-func()调用 " << endl;
}
void func(int a)
{
cout <<"Base-func(int a)调用 " << endl;
}
int m_A;
};
class Son : public Base
{
public:
Son()
{
m_A = 200;
}
void func()
{
cout << "Son-func()调用 " << endl;
}
int m_A;
};
根据上面的内容,我们可以想到使用
s1.Base::func(100);
来调用。 那我们可不可以直接
s1.func(100);
来调用呢? 答案是不可以的,当我们这么写时,这段代码会被直接标红,原因就是:如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中的所有同名成员函数。也就是说,所有的重载,只要时同名,都被隐藏了。 此时我们只能加作用域。
6、同名静态成员处理
问题:继承同名的静态成员在子类对象上如何访问?
与非静态成员处理方式一致。
举一反三。
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
这是访问同名静态成员的方式。 但是静态成员有两种访问方式,这只是其中的一种:.通过对象来访问。 还可以通过类名来访问。
cout << "Son 下 m_A = " << Son::m_A << endl;
//第一个 :: 代表通过类名的方式来访问, 第二个 :: 代表访问父类作用域下。
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
调用同名静态函数方式相同(重载函数也相同)。
s1.func();
s1.Base::func();
Son::func();
Son::Base::func();
完整代码:
#include <iostream>
using namespace std;
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base-static void func()调用" << endl;
}
};
//初始化
int Base::m_A = 100;
class Son: public Base
{
public:
static int m_A;
static void func()
{
cout << "Son-static void func()调用" << endl;
}
};
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;
}
//静态函数
void test02()
{
Son s1;
//1.通过对象访问
cout << "通过对象访问" << endl;
s1.func();
s1.Base::func();
//2.通过类名访问
cout << "通过类名访问" << endl;
Son::func();
Son::Base::func();
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
7、多继承语法
C++允许一个类继承多个类。 语法:class 子类 :继承方式 父类1,继承方式 父类2... 多继承可能会引发同名成员出现,需要加作用域区分。 C++实际开发不建议多使用。
#include <iostream>
using namespace std;
class Base1
{
public:
Base1()
{
m_A = 100;
}
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 200;
}
int m_A;
};
class Son : public Base1, public Base2
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
int m_C;
int m_D;
};
void test01()
{
Son s;
cout << sizeof(s) << endl; //结果为16。
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;
}
8、菱形继承
概念: 两个派生类继承同一个基类; 某个类同时继承两个派生类; 又叫钻石继承。
#include <iostream>
using namespace std;
//动物类
class Animal
{
public:
int m_Age;
};
//数据只需要一份,菱形继承导致数据有两份,资源浪费。
//利用虚继承 解决菱形继承的问题
//在继承前加关键字 virtual 变为虚继承
//Animal类成为 虚基类
//羊类
class Sheep : virtual public Animal {};
//驼类
class Tuo : virtual public Animal {};
//羊驼类
class SheepTuo : public Sheep, public Tuo {};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 18;
st.Tuo::m_Age = 28;
//当菱形继承,两个父类拥有相同数据,需要加作用域区分。
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age <<endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << st.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}