(持续更新ing...)
1. 类和对象--静态成员和静态成员函数
先看代码案例:
class People
{
public:
static int m_Age;
static void speak()
{
std::cout<<"this is a static function"<<endl;
}
};
int People::m_Age = 100;
void test()
{
People p;
cout<<p.m_Age<<endl;
p.speak();
cout<<People.m_Age<<endl;
People.speak();
}
知识点:
静态成员变量需要在类内声明类外初始化,编译过程就分配内存,所以对象共享一份数据即可以通过类名直接调用该静态变量和函数而不属于某一具体对象。
2. 类和对象--菱形继承
先看代码案例:
class Animal
{
public:
Base()
{
m_Age = 30;
}
int m_Age;
};
class Sheep: virtual public Animal{};
class Tuo: virtual public Animal{};
class SheepTuo: public Sheep, public Tuo{};
void Test()
{
SheepTuo st;
st.Sheep::m_Age = 200;
st.Tuo::m_Age = 100;
cout<<st.Sheep::m_Age<<endl;
cout<<st.Tuo::m_Age<<endl;
cout<<st.m_Age<<endl;
}
知识点:
先说上面代码的运行结果:三个输出都是同样的,等于100,因为此时SheepTuo只有一份m_Age数据。
菱形继承类似于“爷爷--两个父亲--孙,子”这样一个形式,该继承类型出现一个问题:孙子类会继承两份相同的数据,导致资源浪费;
引入虚继承和虚基类概念:在继承前加入virtual关键字属于虚继承,此时Animal属于虚基类。
原理剖析:Sheep和Tuo通过虚继承操作,不会直接继承m_Age数据,而是分别继承了一个vbptr数据类型,即虚基类指针,而vbptr又分别指向一个vstable,即虚基类表,此时各自的vbtable中会记录一个数据,即地址偏移量,用于指向此时Animal中的m_Age。即最终SheepTuo中只继承了一份m_Age数据,解决了菱形继承的问题。
3. 类和对象--多态
先看代码案例:
class Animal
{
public:
virtual void speak()
{
cout<<"Animal is talking"<<endl;
}
};
class Cat: public Animal
{
void speak()
{
cout<<"Cat is talking"<<endl;
}
};
class Dog: public Animal
{
void speak()
{
cout<<"Dog is talking"<<endl;
}
};
void doSpeak(Animal& animal) //C++允许父子之间的类型转换,无须强制类型转换
{
animal.speak();
}
void test()
{
Animal animal;
Cat cat;
Dog dog;
doSpeak(animal);
doSpeak(cat);
doSpeak(dog);
}
知识点:
先说上面代码的运行结果:三个输出都是不同的,等于各自成员函数的输出值。如果Animal类中的speak()函数无关键词virtural,则三个输出都是相同的,因为此时属于静态多态中的地址早绑定范畴。
多态,顾名思义就是多种形态,引入黑马程序员的解释:
上图重点:虚函数,即附带关键字virtual;动态多态的函数地址晚绑定,即函数地址在运行阶段才确定。
多态的使用需满足条件:有继承关系;子类重写父类中的虚函数;父类指针或引用指向子类对象。
原理剖析:当Animal类中通过virtural来创建一个虚函数时,会产生一个虚函数指针vfptr,该指针指向一个vftable虚函数表,该表记录虚函数的地址,即&Animal::speak();当创建一个Cat类时,对Animal类进行一个继承,即把父类的全部内容拿过来一份,也就包括vfptr和vftable,此时若Cat类不重写父类的虚函数的话,vftable中记录的仍然是&Animal::speak()这个虚函数地址,而当Cat类重写了父亲的虚函数之后,此时Cat继承过来的这个vfptr指向的vftable中记录的数据就会被替换为&Cat::speak()这个函数地址,即,此时通过doSpeak()函数运行animal.speak()函数的时候,就会开始去确定speak()函数的地址,此时传入的是cat对象,就会读取到其vftable中的地址进行调用了。
2024.10.12补充
使用多态的时候需要注意虚析构函数和纯虚析构函数的使用。此情况适用于使用new关键词在栈区开辟数据需要手动释放的情况。由于虚函数的使用需要由父类指针接收子类对象进行使用,导致在delete父类在栈区的指针对象的时候,只会调用父类的析构函数而不会调用子类的析构函数,所以需要在父类中使用虚析构函数来解决这个问题。示例代码可以参考下面这个。
#include<iostream>
#include<string>
#include<cstddef> //nullptr空指针需要用
using namespace std;
class waterBase
{
public:
virtual void madeFunc() = 0;
virtual ~waterBase() = 0; //纯虚析