纯虚函数与抽象类
- 虚析构函数
- 状态转换的引入
- C中的状态转换
- C++中的状态转换
- 职责链模式
- 纯虚函数和虚基类
- 使用规则
- 实例
- 接口继承和实现继承
虚析构函数
在上一次博客中写到了这么一段代码:
class object {
private: int value;
public:
object(int x = 0) : value(x) {}
~object() {}
virtual void print(int x) //2
{ cout << "object::print:" <<x<<endl; }
};
class Base :public object {
int num;
public:
Base(int x = 0) :object(x + 10), num(x) { }
~Base() {}
void print(int x) { cout << "Base::print:" << x << endl; }
};
int main() {
object* op = new Base(10);
op->print(1);
delete op;
return 0;
}
我们发现这段代码的问题出在析构的时候只会析构obj对象,不会析构Base对象,这是什么原因呢?因为在编译过程中op是obj类型的指针,所以就将op和obj类型进行了绑定,析构的时候同样也就只析构了obj类型,造成了内存泄漏。但是怎么解决这个问题呢,我们可以给obj类的析构函数加上virtual关键字,将其变成一个虚函数,这样析构的时候就是动态联编,析构时查看虚表看其指向的是哪个对象的析构函数,然后进行析构。当你的类的成员函数中存在虚函数,就需要将其析构函数设置成虚函数。
我们依旧是上面一段代码,执行下面主函数
int main() {
// object* op[10] = { new Base,new Base, new Base, new Base, new Base, new Base, new Base, new Base, new Base, new Base };
object* op[10];
for (int i = 0; i < 10; i++) {
op[i] = new Base;
}
op[4]->print(1);//动态联编
(*(op[4])).print(1);//动态联编
for (int i = 0; i < 10; i++) {
delete op[i];
}
//delete []op;
return 0;
}
本来我的思路是这样的,直接创建10个obj类型的指针,将其指向10个动态创建的Base对象,然后使用指针偏移量来查找虚函数并调用,并且析构。如果两个派生类没有属性,只有重新修改的虚函数,那么两个类型的大小相同,也就可以使用指针偏移量来解决这个问题,释放的时候也可以直接使用delete []op来析构。为什么可以这样析构呢,原因是在动态创建这个数组的时候会多申请四个字节来存放创建对象的个数,所以在析构的时候我们不需要写入创建对象的个数。但是呢我在用偏移量查找虚函数的时候出现了错误。因为两个类型的大小不一样,而偏移量是根据指针的类型进行偏移的,+1一次会按照其类型大小进行+1,所以呢当指针+1之后,偏移量+8,而偏移量+8针对于派生类没有偏移够一个对象的大小,也就是没有到下一个对象的首地址,为此呢我使用了指针数组,使得每一个指针指向一个Base对象,但是析构的时候便要循环析构指针。
状态转换的引入
C中的状态转换
我在做题的时候出现了这么一道题,给你一个字符串判断其中单词的个数。
我们写单词的时候会有这种情况(I’am)(hello-world)这样也算一个单词,同时解决这种问题。
我们便可以使用状态转换来解决这个问题,如下
我们举例一个字符串“I am a 45 student ”这样一个字符串,我们刚开始进入字符串,第一个I是字母,便进入了第一个单词,当遇到空格便在单词外,进入a便在单词里,依次遍历下去,进入单词里一次单词数+1,这就是状态转换,状态转换图:
我们这里直接给出代码,代码过于简单,大家可以自行理解
#define BEGIN 0
#define IN_WORD 1
#define OUT_WORD 2
int StrToWord(const char* str)
{
int sum = 0;
int tag = BEGIN;
for (const char* p = str; *p != '\0'; ++p)
{
switch (tag)
{
case BEGIN:
if (isalpha(*p)) { tag = IN_WORD; }
else { tag = OUT_WORD; }
break;
case IN_WORD:
if (isalpha(*p)||(*p) == '\'' || (*p) == '-') { }
else {
tag = OUT_WORD;
sum += 1;
}
break;
case OUT_WORD:
if (isalpha(*p))
{
tag = IN_WORD;
}
break;
}
}
if (IN_WORD == tag) sum++;
return sum;
}
C++中的状态转换
在c语言中存在状态转换,是使用一个变量的不同值来设置不同的状态,而C++中自然也存在状态转换,这就使用到了抽象类和多态。
举一个例子,在童话故事中,有青蛙王子这么一说,有一只青蛙,叫声是Ribbet,其被公主亲吻了一下便有概率变成王子,王子说话是Darling,但是也有概率变成一只狗,其叫声是wangwang,这个实例中存在三种状态,青蛙,王子和狗,其表现形式便是不一样的,我们可以写一个程序,一只青蛙,被公主亲了一下,变成了王子或者狗。代码如下:
class Creature
{
class State
{
public:
virtual string response() = 0; // 纯虚函数 //
virtual ~State() {}
};
class Forg : public State
{
public:
virtual string response() { return "Ribbet ! "; }
};
class Prince : public State
{
public:
virtual string response() { return "Darling ! "; }
};
class Dog : public State
{
public:
virtual string response() { return "wang wang ! "; }
};
private:
State* pstate;
public:
Creature() :pstate(new Forg()) {}
~Creature() { delete pstate; }
void greet()
{
cout << pstate->response() << endl;
}
void kiss()
{
delete pstate;
srand(time(nullptr));
if (rand() % 2 == 0)
{
pstate = new Prince();
}
else
{
pstate = new Dog();
}
}
};
int main()
{
Creature ca;
ca.greet();
ca.kiss();
ca.greet();
return 0;
}
职责链模式
职责链模式就是一个请求可以有多个对象进行处理,且处理条件权限不一样。我们举一个例子?有一个小孩,他想吃糖,首先找了他的妈妈,他妈妈没有糖,再一次找了爸爸,爸爸也没有糖,紧接着找了奶奶,奶奶同样的也没有,最后找了爷爷,爷爷最终给了一颗糖。这就很明确的反应了职责链模式。我们同样也可以用代码把这个实例展示出来。
enum Answer { NO, YES };
class GimmeStrategy
{
public:
virtual Answer canIHave() = 0;
virtual ~GimmeStrategy() {}
};
class AskMom : public GimmeStrategy
{
public:
virtual Answer canIHave()
{
cout << " Mooom ? Can I have this ? " << endl;
return NO;
}
};
class AskDad : public GimmeStrategy
{
public:
virtual Answer canIHave()
{
cout << " Mad? Can I have this ? " << endl;
return NO;
}
};
class AskCrandpa : public GimmeStrategy
{
public:
virtual Answer canIHave()
{
cout << " Grandpa ? Can I have this ? " << endl;
return NO;
}
};
class AskGrandma : public GimmeStrategy
{
public:
virtual Answer canIHave()
{
cout << " Grandma ? Can I have this ? " << endl;
return YES;
}
};
class Gimme : public GimmeStrategy
{
private:
std::vector<GimmeStrategy*> chian;
public:
Gimme()
{
chian.push_back(new AskMom());
chian.push_back(new AskDad());
chian.push_back(new AskCrandpa());
chian.push_back(new AskGrandma());
}
Answer canIHave()
{
for (auto p : chian)
{
if (p->canIHave() == YES)
{
return YES;
}
cout << "whiiiiine !" << endl;
}
return NO;
}
~Gimme() {
for (auto &p : chian)
{
delete p;
p = nullptr;
}
chian.clear();
}
};
int main()
{
Gimme chain;
chain.canIHave();
return 0;
}
其流程图如下:
纯虚函数和虚基类
纯虚函数是指没有具体实现的虚成员函数,其实现依赖于不同的派生类。表现形式->virtual 返回类型 函数名 (参数表)=0
"=0"表示程序员将不定义该虚函数实现,没有函数体,只有函数的声明;函数的声明是为了在虚函数表中保留一个位置,本质上是将指向函数体的指针定义为nullptr。
抽象类:含有纯虚函数的类是抽象类。抽象类是一种特殊的类,他是为了抽象的目的而建立的,它处于继承层次结构的较上层。其不能实例化对象,因为纯虚函数没有实现部分。
抽象类作用:将相关的类型组织在一个继承层次结构中,抽象类为派生类提供一个公共的根,相关的派生类型是从这个根派生而来的。
通过以下代码理解以下抽象类和虚基类的概念
class shape {
public:
virtual ~shape() {}
virtual float area() = 0;
virtual void draw()const {
cout << "shape draw" << endl;
}
};
class Circle :public shape {
private:
static const float pi;
float radius;
public:
Circle(float r = 0.0) :radius(r) {}
float area() { return pi * radius * radius; }
void draw()const {
cout << "Draw==>Circle" << endl;
}
};
const float Circle::pi = 3.14f;
class Square :public shape {
float length;
public:
Square(float l = 0.0) :length(l) {}
float area() { return length*length; }
void draw()const {
cout << "Draw==>Square" << endl;
}
};
int main() {
shape* p = nullptr;
Circle c1(10);
Square c2(20);
p = &c1;
p->draw();
cout << p->area() << endl;
p = &c2;
p->draw();
cout << p->area() << endl;
return 0;
}
使用规则
- 抽象类只能用作其他类的基类,奴能创建抽象类的对象。
- 抽象类不能用作参数类型,函数返回类型或显式类型的转换。
- 可以定义抽象类的指针和引用,此指针可以指向他的派生类对象,从而实现运行时多态。
注意:抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承了纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类,可以船舰对象的具体类型。
实例
//应用类型 既不提供派生,也不提供继承
class CDTime {};
//节点类型 提供了继承和多态的基础,但没有纯虚函数
class Shape {
string name;
public:
virtual float area() { return 0.0f;}
string getname()const { return name;}
}
//抽象类型 抽象类型只能作为基类来使用,其纯虚函数的实现由派生类给出。
class Shape {
string name;
public:
virtual float area()=0;
string getname()const { return name;}
}
//接口类,只有纯虚函数,没有属性
class Shape {
public:
virtual float area()=0;
virtual string getname()=0;
}
//实现类型 继承了接口或抽象类型 定义了纯虚函数的实现
class Shape {
string name;
int radio;
public:
virtual float area() {return radio*radio;}
string getname()const { return name;}
}
接口继承和实现继承
公有继承可以分为函数接口的继承和函数实现的继承。
类的设计者:
- 有时希望派生类只继承成员函数的接口(声明),纯虚函数。
- 有时则希望可以同时继承函数的接口和实现,但允许派生类改写实现,虚函数。
- 有时希望同时继承接口和实现,并不允许派生类修改任何东西,非虚函数。