1.概念
继承可以理解为一个类从另一个类中获取成员变量和成员方法的过程
目的
实现代码的复用。
理解
继承后子类自动拥有了父类的属性和方法,但特别注意的是,父类的私有属性和构造方法并不能被继承。
另外子类可以写自己特有的属性和方法,目的是实现功能的扩展,子类也可以复写父类的方法即方法的从写。
2.继承的权限
<1> 公有继承:基类中的所有属性原来具有什么样的权限,在派生类中保持不变,基类中的私有属性在派生类中不能访问。
<2> 保护继承:基类中的public在派生类中具有保护属性,其它不变,基类中的私有属性在派生类中不能访问。
<3> 私有继承:基类中public和protected都变成私有属性,基类中的私有属性在派生类中不能访问。
继承时,存在同名变量,子类依旧会实例化父类同名变量,但在子类不可见。
什么时候可以调用父类同名变量呢?
1.域解析符::,c1->Parent::m_a;调用父类变量
2.调用父类函数访问同名变量。
#include <iostream>
using namespace std;
class Parent
{
public:
int m_a;
public:
void test(int a)
{
m_a = a;
cout << m_a << endl;
}
};
class Child:public Parent
{
public:
int m_a;
public:
void print(int a)
{
m_a = a;
cout << m_a << endl;
}
};
int main(int argc, char *argv[])
{
Child c1;
c1.print(1);
c1.test(2);
cout << c1.m_a << endl;
cout << c1.Parent::m_a << endl;
}
继承中的构造:
当父类需要有参构造时,需要用初始化列表,进行初始化。
父类构造函数,子类无法继承,但能调用(在初始化列表中)
初始化列表:
1.引用,常引用,const修饰成员变量(其值在栈中的符号表中,与键值对一一对应)
常成员函数
当const修饰成员函数时
1.相当于修饰成员函数隐含的this指针。
2.其形参或者函数内定义的变量,可自由更改。
3.this指针指向实例化对象的成员属性(除成员引用以外,引用其本质是一个指针,const修饰引用 = 不能修改指针的指向但能改变指针指向的内存的值)不能通过this指针修改(作用域在const修饰的成员函数中)
4.可以通过,别的途径修改值。
静态成员函数
当static修饰成员函数时
1.静态成员函数仅能访问静态的数据成员,不能访问非静态的数据成员,也不能访问非静态的成员函数
2.静态的成员函数没有this指针。
#include <iostream>
using namespace std;
class Parent
{
public:
int &m_b;
const int m_a;
int m_c;
public:
// void test(int a)
// {
// m_a = a;
// cout << m_a << endl;
// }
Parent(int a):m_a(a),m_b(a)
{
}
void cs(int b) const
{
int a = 3;
b = 1;
b++;
a++;
this->m_b = 5;
// m_c = 5;
cout << a << " " << b << " "<< m_b << endl;
}
};
class Child:public Parent
{
public:
int m_a;
public:
Child(int a):Parent(a)
{
m_a = a;
}
void print(int a)
{
m_a = a;
cout << m_a << endl;
}
};
int main(int argc, char *argv[])
{
Child c1(3);
c1.print(1);
// c1.test(2);
c1.cs(2);
cout << c1.m_a << endl;
cout << c1.Parent::m_a << endl;
}
1
4 2 32765
1
3
static:静态变量在继承中
在class类定义还未实例化对象时,static修饰的成员函数就被编译器在静态储存区分配空间,在实例化对象时也不会再分配空间,而是所有对象共享同一静态变量。
1.必须初始化,且必须在类外初始化(int Child::m_p)
2.初始化时使用作用域运算符标明所属类,是类的成员,而不是对象的成员
在继承中也是所有父类与子类实例化对象都公用一个静态变量。
继承中兼容性原则
概念:在需要基类的地方都可以用子类来代替。
1.子类对象可以当作父类来使用。
2.父类指针可以指向子类
3.父类引用可以直接引用派生类
多继承
概念
每一个派生类可以继承多个基类。
多继承的二义性:解决:虚继承
虚继承
目的:为了让某类做出声明,承诺愿意共享其基类,被共享的基类称为虚基类。
虚继承:构造顺序,分配内存位置?
虚基类优先级最高,先构造虚基类,且只构造一遍,其与不变。分配内存位置时,虚基类却在最后。
因为构造虚基类时,通过虚基类指针得到与子类成员函数的地址偏移量,提前在最后构造虚基类。
无论虚基类在继承体系中出现多少次,只为其开辟一次内存空间。
class Child:virtual public Parent
{
}本质:有一个虚基类指针指向Child,通过虚基类指针可以找到Child成员与共享的基类成员,虚基类指针保存了共享地址与成员头部的偏移量。
充当父类时,其子类也继承该虚基类指针。
向上转型
可以让父类赋值给子类。
多态
概念:多种形态,当基类指针指向基类对象时,期待按照基类的方法,当基类指针指向派生类对象时,就按照派生类的方法,同一接口多种方法,多种形态,称为多态。
多态条件:
1.要有继承
2.要有虚函数重写(发生在不同的作用域)
3.基类指针指向派生类对象
原理
实例化子类与实例化父类有且只一个虚函数表指针,指向虚函数表,表内放的是虚函数和重写的同名函数(不同与函数重载,只需同名即可,对传参与返回值并无要求,但虚函数重写作用域不同)。
父类指针指向实例化的不同子类调用不同虚函数表指针,指向不同的虚函数表,调用虚函数名相同但实现不同的函数,实现多态。
虚析构函数
根据析构规则:从当前基类开始向上析构,其子类及以下不会被调用析构函数,出现内存泄漏问题
为什么设为虚析构函数就能调用子类析构?
本质:是编译器将父类虚析构写入子类,且父类是虚析构函数的子类也是虚析构函数,所以能一直向下。得到最底部的子类析构函数并且向上析构。
#include <iostream>
using namespace std;
class Parent
{
public:
int a;
virtual void show()
{
cout << "this Parent" << endl;
}
Parent()
{
cout << "Parent" << endl;
}
virtual ~Parent()
{
cout << "~Parent" << endl;
}
};
class Child:public Parent
{
public:
void show()
{
cout << "this is child" << endl;
}
Child()
{
cout << "Child" << endl;
}
~Child()
{
cout << "~Child" << endl;
}
};
int main(int argc, char* argv[])
{
Parent *p = new Child;
delete p;
return 0;
}
动态类型识别
dynamic_cast
1.c++新型关键词
2.基类与派生类之间的转换
3.要求目标是多态(1.有继承2.有虚函数重写3.有父类指针指向子类)
作用时:
要求所在类至少有一个虚函数
用于指针转换时,转换失败会返回NULL指针。
用于引用转换时,转换失败会引发bad_cast异常。
优势:
1.不需要显示的声明和定义虚函数
2.不用为每个类分配类型ID
缺点:
1.只能用于有虚函数的类
Child *c = dynamic_cast<Child *>(p);//如果p指向的是基类对象,则转换失败,返回NULL;
typeid
作用
用于获取一个表达式的类型信息
操作对象既可以是表达式,也可以是数据类型
typeid(dataType);
纯虚函数
virtual 返回值类型 函数名(函数参数) = 0;
特点:
无法实例化,无法创建对象。
有纯虚函数的类又叫抽象类。
作用:
充当接口,抽象类做基类,不同的继承的子类重写纯虚函数,实现多种形态。
且子类若不重写纯虚函数,子类继承纯虚函数依旧是抽象类。