c++的继承
继承是面向对象三大特性之一
作用:减少重复代码
语法
class 子类:继承方式 父类
子类也称为派生类
父类也称为基类
派生类中的成员,包含两部分:一类从基类继承过来,一类是自己增加的成员。
从基类继承过来的表现其共性,而新增的成员体现了其个性
#include<iostream>
using namespace std;
class Father
{
public:
void header()
{
cout << "我是头部" << endl;
}
void left()
{
cout << "我是left" << endl;
}
void footer()
{
cout << "我是footer" << endl;
}
};
class Son :public Father
{
public:
void content()
{
cout << "我是个性的content" << endl;
}
};
class Son1 :public Father
{
public:
void content()
{
cout << "我是个性的content1" << endl;
}
};
void test()
{
Son s;
s.header();
s.content();
s.left();
s.footer();
Son1 s1;
s1.header();
s1.content();
s1.left();
s1.footer();
}
int main()
{
test();
system("pause");
return 0;
}
继承方式
继承方式一共有三种
公共继承
保护继承
私有继承
#include<iostream>
using namespace std;
class Father
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//第一种保护继承方式
class Son :public Father
{
void fn()
{
m_A = 100;//访问权限依然是公共权限
m_B = 100;//访问权限依然是保护权限
//m_C = 100;//不能继承Father类中私有成员
}
};
class Son1 :protected Father//保护继承
{
void fn()
{
m_A = 100;//访问权限变成了保护权限
m_B = 100;//访问权限依然是保护权限
//m_C = 100;//不能继承Father类中私有成员
}
};
class Son2 :private Father
{
void fn()
{
m_A = 100;//访问权限变成了私有权限
m_B = 100;//访问权限依然是私有权限
//m_C = 100;//不能继承Father类中私有成员
}
};
class Son3 :private Son2
{
void fn()
{
//m_A = 100;//继承了Son2类,因为他的类都是私有,不能继承过来他的私有成员
//m_B = 100;//继承了Son2类,因为他的类都是私有,不能继承过来他的私有成员
//m_C = 100;//Son2不能继承Father类中私有成员,目前都没有这个成员
}
};
void test01()
{
Son s;
s.m_A = 100;//访问权限是公共权限能访问到
//s.m_B = 110;//访问权限是保护,类外访问不到
Son1 s1;
//s1.m_A = 100;//访问权限都为保护权限,类外访问不到
//s1.m_B = 120;//访问权限都为保护权限,类外访问不到
Son2 s3;
//s3.m_A = 100;//访问权限都为私有权限,类外访问不到
//s3.m_B = 100;//访问权限都为私有权限,类外访问不到
}
int main()
{
test01();
system("pause");
return 0;
}
继承中的对象模型
父类中所有非静态成员属性都会被子类继承下去
父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但确实被继承下去了
#include<iostream>
using namespace std;
class Father
{
public:
static int m_S;
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son :public Father
{
public:
int m_D;
};
int Son::m_S = 100;
int main()
{
cout << sizeof(Son) << endl;//16,在继承的时候,父类的非静态成员属性包括私有,都继承给子类,只是继承过来的私有属性被编译器隐藏了。
Son s;
cout << s.m_S << endl;
system("pause");
return 0;
}
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
#include<iostream>
using namespace std;
class Father
{
public:
static int m_S;
int m_A;
Father()
{
cout << "father构造函数调用" << endl;
}
~Father()
{
cout << "father析构函数调用" << endl;
}
protected:
int m_B;
private:
int m_C;
};
class Son :public Father
{
public:
int m_D;
Son()
{
cout << "Son构造函数调用" << endl;
}
~Son()
{
cout << "Son析构函数调用" << endl;
}
};
int Son::m_S = 100;
int main()
{
Son s;
system("pause");
return 0;
}
继承同名成员处理方式
访问子类同名成员 直接访问即可
访问父类同名成员 需要加作用域
#include<iostream>
using namespace std;
class Father
{
public:
int m_A;
Father()
{
m_A = 100;
}
void print1()
{
cout << "我是Father类中的成员函数" << endl;
}
void print1(int a)
{
cout << "我是Father类中的成员函数int a" << endl;
}
};
class Son:public Father
{
public:
int m_A;
Son()
{
m_A = 200;
}
void print1()
{
cout << "我是Son类中的成员函数" << endl;
}
};
void test07()
{
Son s;
cout << "m_A= " << s.m_A << endl;//输出是两百,出现父子之间同名情况,在子类,会替换掉父类或者隐藏掉父类的成员
//想访问到父类的同名成员,必须要加上作用域
cout << "m_A= " << s.Father::m_A << endl;//100
}
void test08()
{
Son s1;
s1.print1();//我是Son类中的成员函数,输出为子类的成员函数
//s1.print1(100);
//要访问到父类的同名函数,要加作用域
s1.Father::print1();//我是Father类中的成员函数
s1.Father::print1(100);
}
int main()
{
//test07();
test08();
system("pause");
return 0;
}
子类对象可以直接访问到子类中同名成员
子类对象想访问父类同名成员,要加作用域
3.当子类与父类拥有同名的成员函数,子类会隐藏或者替换父类中的同名成员函数,加作用域可以访问到父类中同名函数
继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
访问子类同名成员,直接点处理就行。
访问父类静态成员,要加作用域
#include<iostream>
using namespace std;
class Father
{
public:
static int m_A;
};
int Father::m_A = 100;
class Son:public Father
{
public:
static int m_A;
};
int Son::m_A = 200;
void test14()
{
Son s;
//子类和父类静态成员同名访问和非静态成员访问基本一致
//但是静态成员可以通过类名和对象方式访问
cout << "m_A =" << s.m_A << endl;//子类中m_A
cout << "m_A =" << s.Father::m_A << endl;//父类中m_A
//通过类名访问
cout << "m_A =" << Son::m_A << endl;//通过类名访问到了Son类中的m_A
cout << "m_A =" << Son::Father::m_A << endl;//第一个::表示在Son类下,第二个::表示在Father作用域下
}
int main()
{
test14();
system("pause");
return 0;
}
多继承语法
c++运行一个类继承多个类
语法:
class 子类 : 继承方式 父类1,继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
c++实际开发中不建议用多继承
#include<iostream>
using namespace std;
class Base1
{
public:
int m_B;
};
class Base
{
public:
int m_B;
};
class Son:public Base,public Base1 {
public:
int m_C;
int m_D;
};
int main()
{
Son s;
s.Base::m_B = 100;
s.Base1::m_B = 200;
cout << "m_A =" << s.Base1::m_B << endl;//多继承容易造成同名成员的问题,解决这个问题需要加作用域
cout << "m_A =" << s.Base::m_B << endl;
system("pause");
return 0;
}
菱形继承
菱形继承概念
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
利用虚继承可以解决,菱形继承的问题
在继承最大基类的子类中加上virtual关键字
这时最大基类我们就称它为虚基类
多态
多态的基本概念
多态是c++面向对象三大特性之一
多态分为两类
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别
静态多态的函数地址早绑定,编译阶段确定函数地址
动态多态的函数地址晚绑定.运行阶段确定函数地址
动态多态满足条件
1.有继承关系
2.子类重写父类的虚函数
动态多态使用
父类的指针或者引用 指向子类对象 重写:函数名 函数返回值 参数列表 完全一致称为重写
#include<iostream>
using namespace std;
class Father
//多态分为静态多态和动态多态
//静态多态:列如函数重载和运算符重载等
//动态多态:虚函数和派生类实现运行时多态
//区别:静态,函数地址早绑定,在编译时函数地址就绑定完毕,动态多态,函数地址晚绑定,运行阶段确定函数地址
{
public:
virtual void speak()
{
cout << "动物叫" << endl;
}
};
class Son:public Father
{
//多态条件
//有继承
//重写父类的虚函数
//多态的使用
//父类引用或者指针指向子类对象
public:
virtual void speak()
{
cout << "小猫叫" << endl;
}
};
void speak(Father &f)
{
f.speak();
}
void test01()
{
Son s;
speak(s);
}
int main()
{
test01();
system("pause");
return 0;
}
多态的原理剖析
- 当类中存在虚函数时,编译器会在类中自动生成一个虚函数表
- 虚函数表是一个存储类成员函数指针的数据结构
- 虚函数表由编译器自动生成和维护
- virtual 修饰的成员函数会被编译器放入虚函数表中
- 存在虚函数时,编译器会为对象自动生成一个指向虚函数表的指针(通常称之为 vptr 指针)
#include <iostream>
using namespace std;
class Parent
{
public:
// 父类虚函数必须要有 virtual 关键字
virtual void fun()
{
cout << "父类" << endl;
}
};
class Child : public Parent
{
public:
// 子类有没有 virtual 关键字都可以
void fun()
{
cout << "子类" << endl;
}
};
int main()
{
Parent *p = NULL; // 创建一个父类的指针
Parent parent;
Child child;
p = &parent; // 指向父类的对象
p->fun(); // 执行的是父类的 fun() 函数
p = &child; // 指向子类的对象
p->fun(); // 执行的是子类的 fun() 函数
return 0;
}
如上例代码所示,当我们传入父类对象时,将调用和执行父类的函数,当我们传入子类对象时,将调用和执行子类的函数。而 C++ 编译器的执行过程其实是这样的:
- 父类的 fun() 是个虚函数,所以编译器给父类对象自动添加了一个 vptr 指针,指向父类的虚函数表,这个虚函数表里存放了父类的 fun() 函数的函数指针
- 子类的 fun() 函数是重写了父类的,即写不写 virtual 编译器都会为其自动添加一个 virtual,然后编译器给子类对象自动添加了一个 vptr 指针,指向子类的虚函数表,这个虚函数表里存放了子类的 fun() 函数的函数指针
- 执行 p->fun() 时,编译器检测到 fun() 是一个虚函数,所以不会静态的将 Parent 类的 fun() 方法直接编译过来,而是是运行的时候,动态的根据 base 指向的对象,找到这个对象的 vptr 指针,然后找到这个对象的虚函数表,最后调用虚函数表里对应的函数,实现多态
多态案例-计算器类
多态优点:
代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
开闭原则:对扩展进行开放,对修改进行关闭
#include<iostream>
#include<string>
using namespace std;
//普通写法
//不利于前期和后期的维护,
//代码结构不清晰
//可读性差
class Category
{
public:
int m_A;
int m_B;
int getRuslt(string a)
{
if (a == "+")
{
return this->m_A + this->m_B;
}
else if (a == "-")
{
return this->m_A - this->m_B;
}
else if (a == "*")
{
return this->m_A * this->m_B;
}
}
};
//利用多态写法
class Acategory
{
public:
int m_A;
int m_B;
virtual int getRuslt()
{
return 0;
}
};
class AddAcategory:public Acategory
{
virtual int getRuslt()
{
return m_A + m_B;
}
};
class SubAcategory :public Acategory
{
virtual int getRuslt()
{
return m_A - m_B;
}
};
class CAcategory :public Acategory
{
virtual int getRuslt()
{
return m_A * m_B;
}
};
void test03()
{
Category c;
c.m_A = 100;
c.m_B = 100;
cout << "m_A" << "+" << "m_B" << "=" << c.getRuslt("+") << endl;
cout << "m_A" << "-" << "m_B" << "=" << c.getRuslt("-") << endl;
cout << "m_A" << "*" << "m_B" << "=" << c.getRuslt("*") << endl;
}
void test04()
{
Acategory* a = new AddAcategory();
a->m_A = 100;
a->m_B = 100;
cout << "m_A" << "+" << "m_B" << "=" << a->getRuslt() << endl;
delete a;
a = new SubAcategory();
a->m_A = 100;
a->m_B = 100;
cout << "m_A" << "-" << "m_B" << "=" << a->getRuslt() << endl;
delete a;
a = new CAcategory();
a->m_A = 100;
a->m_B = 100;
cout << "m_A" << "*" << "m_B" << "=" << a->getRuslt() << endl;
}
void test05(Acategory &a)
{
//引用
a.m_A = 100;
a.m_B = 100;
cout << "m_A" << "+" << "m_B" << "=" << a.getRuslt() << endl;
}
void test06()
{
AddAcategory a;
test05(a);
SubAcategory c;
test05(c);
CAcategory ca;
test05(ca);
}
int main()
{
//test03();
//test04();
test06();
system("pause");
return 0;
}
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类的特点:
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include<iostream>
using namespace std;
class Base
{
public:
virtual void fn() = 0;//只要类中拥有纯虚函数,那么这个类就是抽象类
};
class Son:public Base
{
public:
virtual void fn() {
cout << "111" << endl;
}
};
void test08()
{
//Base b;//抽象类的特点:无法实例化对象,子类继承不重写纯虚函数,那么也变成了抽象类,不能实例化对象
//Son s;//子类继承不重写纯虚函数,那么也变成了抽象类,不能实例化对象
//new Son;//也不能在堆区创造
Son s;
s.fn();
}
int main()
{
test08();
system("pause");
return 0;
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
纯虚析构 需要声明也需要实现
虚析构语法: virtual ~类名(){}
纯虚析构语法: virtual ~类名() = 0
类名::~类名(){}
#include<iostream>
using namespace std;
class Base12
{
public:
Base12() {
cout << "我是Base构造函数" << endl;
}
virtual void speak1() = 0;
//virtual ~Base12()//解决子类析构函数的调用
//{
// cout << "Base虚析构函数" << endl;
//}
virtual ~Base12() = 0;
};
Base12::~Base12() {
cout << "Base虚析构函数" << endl;//纯虚析构函数
}
class Son1:public Base12
{
public:
Son1(int a)
{
m_A = new int(a);
cout << "Son1构造函数调用" << endl;
}
virtual void speak1()
{
cout << "m_A=" << *m_A << endl;
}
~Son1()
{
cout << "Son1虚析构函数" << endl;
if (m_A != NULL) {
delete m_A;
m_A = NULL;
}
}
int *m_A;
};
void test10()
{
Base12* base = new Son1(10);
base->speak1();
delete base;
//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄露
//解决方法
//给父类添加一个虚析构函数,虚析构函数就是用于解决父类指针释放子类指针
}
int main()
{
test10();
system("pause");
return 0;
}
总结:
1.虚析构或者纯虚析构就是用于解决父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写虚析构或者纯虚析构
3.拥有纯虚析构函数的类也属于抽象类