目录
1、继承的引出
- 网页中有很多公共部分
- 实现时出现许多重复代码
- 引出继承,基类 (父类) 公共网页
- 具体子类实现不同的方法(内容)
- 语法 class 子类 : 继承方式 父类
c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//class News
//{
//public:
// void header()
// {
// cout << "公共头部" << endl;
// }
// void footer()
// {
// cout << "公共底部" << endl;
// }
// void left()
// {
// cout << "左侧列表" << endl;
// }
// void content()
// {
// cout << "新闻播报" << endl;
// }
//};
//
//class YULE
//{
//public:
// void header()
// {
// cout << "公共头部" << endl;
// }
// void footer()
// {
// cout << "公共底部" << endl;
// }
// void left()
// {
// cout << "左侧列表" << endl;
// }
// void content()
// {
// cout << "娱乐播报" << endl;
// }
//};
//
//void test01()
//{
// //新闻页
// News news;
// news.header();
// news.footer();
// news.left();
// news.content();
//
// //娱乐业
// YULE yl;
// yl.header();
// yl.footer();
// yl.left();
// yl.content();
//}
//继承 重复代码抽象出来
//抽象一个基类的网页 重复的代码都写到这个网页上
class BasePage
{
public:
void header()
{
cout << "公共头部" << endl;
}
void footer()
{
cout << "公共底部" << endl;
}
void left()
{
cout << "左侧列表" << endl;
}
};
class News : public BasePage //继承 News类 继承于 BasePage类
{
public:
void content()
{
cout << "新闻播报" << endl;
}
};
class YULE : public BasePage //继承 News类 继承于 BasePage类
{
public:
void content()
{
cout << "娱乐播报" << endl;
}
};
class Game : public BasePage
{
public:
void content()
{
cout << "KPL直播" << endl;
}
};
void test02()
{
cout << "新闻内容" << endl;
News news;
news.header();
news.footer();
news.left();
news.content();
cout << "娱乐网页内容" << endl;
YULE yl;
yl.header();
yl.footer();
yl.left();
yl.content();
}
//继承减少代码的重复内容
//BasePage 基类(父类) News 派生类(子类)
int main()
{
//test01();
test02();
system("pause");
return 0;
}
2、继承方式
派生类定义格式
Class 派生类名 : 继承方式 基类名{
//派生类新增的数据成员和成员函数
}
三种继承方式:
- public : 公有继承
- private : 私有继承
- protected : 保护继承
从继承源上分:
- 单继承:指每个派生类只直接继承了一个基类的特征
- 多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征
#include<iostream>
using namespace std;
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公有继承
class Son1 :public Base1
{
public:
void func()
{
//cout << m_C << endl; //基类中私有属性不可继承
cout << m_A << endl; //基类中的公有属性可以继承,还是public
cout << m_B << endl; //基类中的保护属性 可继承,还是protected,类外访问不到
}
};
//保护继承
class Son2 :protected Base1
{
public:
void func()
{
//cout << m_C << endl; //基类中私有属性不可继承
cout << m_A << endl; //基类中的公有属性 可继承,是protected
cout << m_B << endl; //基类中的保护属性 可继承,是protected
}
};
void myFunc()
{
Son1 s1;
s1.m_A;
//s1.m_B;
}
void myFunc2()
{
Son2 s;
//s.m_A; 不能访问
}
//私有继承
class Son3 :private Base1
{
public:
void func()
{
//cout << m_C << endl; //基类中私有属性不可继承
cout << m_A << endl; //基类中的公有属性 可继承,是private
cout << m_B << endl; //基类中的保护属性 可继承,是private
}
};
class GrandSon3 :public Son3
{
public:
void myFunc()
{
//cout << m_A << endl; //孙子类中 访问不到m_A,因为在Son3中m_A已经是私有属性了
}
};
int main()
{
myFunc2();
system("pause");
return 0;
}
注意:不管公有继承 保护 还是私有 基类中的私有属性 ,都不可以继承下去
继承方式
3、继承中的对象模型
- 子类会继承父类中所有的内容 ,包括了 私有属性
- 只是我们访问不到,编译器给隐藏了
- cl /d1 reportSingleClassLayout类名 文件名
#define _CRT_SECURE_NO_WARNINGS
#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 << sizeof(Son) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
利用VS2013开发人员命令提示 位置在 vs2013\Common7\Tools\Shortcuts下
4、继承中的构造和析构顺序
- 子类创建对象时,先调用父类的构造,然后调用自身构造
- 析构顺序与构造顺序相反
- 子类是不会继承父类的构造函数和析构函数
- 如果父类中没有合适默认构造,那么子类可以利用初始化列表的方式显示的调用父类的其他构造
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
m_A = 10;
cout << "Base默认构造函数调用" << endl;
}
~Base()
{
cout << "Base析构函数调用" << endl;
}
int m_A;
};
//子类中 会继承父类的成员属性,成员函数
//但是 子类不会继承 父类构造函数 和析构函数
//只有父类自己知道如何构造和析构自己的属性,而子类不知道
class Son :public Base
{
public:
Son()
{
cout << "Son默认构造函数调用" << endl;
}
~Son()
{
cout << "Son析构函数调用" << endl;
}
};
void test01()
{
//Base b1;
Son s1;
}
class Base2
{
public:
Base2(int a)
{
this->m_A = a;
cout << "有参默认构造调用" << endl;
}
int m_A;
};
class Son2 :public Base2
{
public:
Son2(int a):Base2(10) //利用初始化列表方式 显示调用有参构造
{
}
};
void test02()
{
Son2 s2(1000);
}
int main()
{
test01();
system("pause");
return 0;
}
5、继承中的同名处理
- 成员属性 直接调用先调用子类,如果想调用父类 需要作用域
- 成员函数 直接调用先调用子类,父类的所有版本都会被隐藏,除非显示用作用域运算符去调用
- 当子类成员和父类成员同名时,子类依然从父类继承同名成员
- 如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)
- 在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
m_A = 100;
cout << "Base默认构造函数调用" << endl;
}
void func()
{
cout << "Base func调用" << endl;
}
void func(int a)
{
cout << "Base func调用" << endl;
}
int m_A;
};
class Son :public Base
{
public:
Son()
{
m_A = 200;
cout << "Son默认构造函数调用" << endl;
}
/*void func()
{
cout << "Son func调用" << endl;
}*/
int m_A;
};
void test01()
{
Son s1;
cout << s1.m_A << endl;
s1.func();
//想调用父类中的m_A 通过子类调用父类中的东西
cout << s1.Base::m_A << endl;
s1.Base::func();
//如果子类和父类拥有同名的函数 属性 子类不会覆盖子类的成员,如果子类没有提供,那么直接调用父类中的
//如果子类 父类成员函数相同,子类会把父类的所有同名版本全部隐藏
//想调用的话 加作用域
s1.func(10);
}
int main()
{
test01();
system("pause");
return 0;
}
6、继承中静态成员的处理
- 类似非静态成员函数处理
- 如果想访问父类中的成员,加作用域即可
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base
{
public:
static void func()
{
cout << "base func()" << endl;
}
static void func(int a)
{
cout << "base func(int)" << endl;
}
static int m_A;
};
int Base::m_A = 10;
class Son :public Base
{
public:
static void func()
{
cout << "son func()" << endl;
}
static int m_A;
};
int Son::m_A = 20;
//静态成员属性 子类可以继承下来
void test01()
{
cout << Son::m_A << endl;
//访问父类的m_A
cout << Base::m_A << endl;
Son::func();
//访问父类同名的函数
Son::Base::func(10);
}
int main()
{
test01();
system("pause");
return 0;
}
7、多继承的概念以及问题
多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类1继承的版本还是从基类2继承的版本?
解决方法:显示指定调用那个基类的版本。
- class A : public B1, public B2,….
- 引发二义性问题
- 想解决二义性问题,就需要通过作用域来进行区分
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Base1
{
public:
Base1()
{
m_A = 10;
}
public:
int m_A;
};
class Base2
{
public:
Base2()
{
m_A = 20;
}
public:
int m_A;
};
class Son :public Base1, public Base2
{
public:
int m_C;
int m_D;
};
//多继承中很容易引发二义性
void test01()
{
cout << sizeof(Son) << endl;
Son s1;
//cout << s1.m_A << endl; 出现二义性
cout << s1.Base1::m_A << endl;
cout << s1.Base2::m_A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
8、菱形继承问题以及解决
两个派生类继承同一个基类而又有某个类同时继承这两个派生类,这种继承被称为菱形继承,或者钻石型继承。
这种继承所带来的问题:
- 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。
- 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
- 解决问题利用虚基类
- sheepTuo内部结构
- vbptr 虚基类指针
- 指向一张 虚基类表
- 通过表找到偏移量
- 找到共有的数据
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
int m_Age;
};
//虚基类Sheep
class Sheep:virtual public Animal
{
};
//虚基类Tuo
class Tuo :virtual public Animal
{
};
class SheepTuo :public Sheep, public Tuo
{
};
//菱形继承的解决方案 利用虚继承
//操作的是共享的一份数据
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 10;
st.Tuo::m_Age = 20;
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;
cout << st.m_Age << endl; //可以直接访问,原因已经没有二义性的可能了
cout << *(int*)((int *)*(int*)&st + 1) << endl; //找到Sheep的偏移量
cout << *((int*)((int *)*((int*)&st + 1) + 1)) << endl;//找到Tuo的偏移量
//输出Age
cout << ((Animal*)((char*)&st + *(int*)((int*)*(int*)&st + 1)))->m_Age << endl; //m_Age=20
}
int main()
{
test01();
system("pause");
return 0;
}