• Index

纯虚函数和抽象类

Reads: 1

回顾

基础示例

以之前教程的统计字符数量的函数为例:

#include <iostream> // std::cout std::endl
#include <string> // std::u32string

class basic_count_condition
{
public:
    // 由于该类需要被继承, 所以需要将析构函数定义成虚函数
    // 由于该类不需要进行释放操作, 所以使用默认析构函数即可
    virtual ~basic_count_condition(void) = default;

    // 由派生类继承, 用于统计函数的判断函数
    virtual bool is_true(char32_t ch);
};

// 以下是统计函数的使用说明
// 功能: 统计函数
// 参数: 需要被统计的字符串
// 参数: 统计函数需要的判断函数, 需要继承basic_count_condition并且重写
//       虚函数is_true. 统计函数将会向判断函数传入每一个字符,
//       然后统计函数会根据判断函数的返回值进行统计, 如果判断函数返回
//       true, 统计函数则会处理; 如果判断函数返回false, 统计函数则忽略
// 返回值: 统计判断函数返回true的数量
unsigned int count_if(std::u32string text, basic_count_condition *obj);

// 以上是统计的统计函数声明和类声明, 以下是统计的函数定义和类成员函数定义

bool basic_count_condition::is_true(char32_t ch)
{
    return false;
}

unsigned int count_if(std::u32string text, basic_count_condition *obj)
{
    unsigned int count = 0; // 用来保存统计数量

    // 遍历字符串中所有字符, 如果符合条件则统计
    for (auto ch : text)
    {
        if (obj->is_true(ch))
        {
            ++count;
        }
    }

    // 返回统计数量
    return count;
}

// 以上是统计函数提供的功能和接口,下面是使用统计函数需要写的代码

class gu_condition : public basic_count_condition
{
public:
    virtual ~gu_condition(void) = default;
    virtual bool is_true(char32_t ch) override; // 如果字符是'古'则返回true
};

int main(void)
{
    // 统计字符串中一共有多少个古字
    gu_condition gu;
    std::cout << count_if(U"小古银小古银我是美美哒小古银", &gu) << std::endl;
    return 0;
}

bool gu_condition::is_true(char32_t ch)
{
    return ch == U'古';
}

输出结果:

3

基础讲解

可以看出无论从设计还是使用都是函数式编程比较简单,不过现在不是讲函数式编程,所以不要在意这些细节。这篇教程重点讲的是如何设计一个类,但是现在先简单看一下使用方法。

现在假设basic_count_conditioncount_if都是别人设计给我们用的,要使用统计函数就必须先继承类basic_count_condition,然后实现重写成员函数is_true。然后还需要创建派生类gu_condition的对象,在调用函数count_if的时候作为第二个参数传入它的地址。然后就可以求出在字符串中的数量。

关键还是在于如何设计一个类。由于我们的count_if需要一个参数去接收判断条件,这里就需要一个给count_if用的基类,将它命名为basic_count_condition。由于这个基类是专门用来继承的,所以必须将析构函数定义为虚函数,而基类不需要释放资源,所以采用默认析构函数即可。接着是关键,这个基类必须要统一出一个虚函数作为接口,一是给count_if调用,二是给调用者继承,这样,count_if使用基类指针或引用时就可以使用多态了。因为count_if需要的判断函数是可以接收一个字符并且返回布尔,所以这个虚函数应该声明为virtual bool is_true(char32_t ch);,而这个函数一般情况是不会改变成员变量,但也不排除少数情况派生类需要改变,所以这个函数不用const修饰。那么这个虚函数就确定下来了,后面只需要调用者继承这个类并且重写这个虚函数就可以了。由于作为接口的虚函数已经确定下来,所以接下来就可以实现函数count_if了。由于前面已经说过count_if的实现,所以现在就不重复了,只是判断函数改变了。

纯虚函数

从上面示例代码可以看出,基类basic_count_condition的虚函数is_true只返回了false,没有其他作用。再从基类虚函数is_true的功能来看,它只是用来给派生类重写的,自身不应该有功能,也就是不应该被定义实现。

当虚函数的功能只是给派生类重写而没有其他更具体的作用时,那么就不应该为该函数写定义并且应该将它设置为纯虚函数。这样纯虚函数就彻底成为一个接口专门供派生类重写出功能。

纯虚函数只需要在虚函数声明后面加上= 0;即可,以上面的基类虚函数is_true为例子,函数声明应该改成:virtual bool is_true(char32_t ch) = 0;。将虚函数定义成纯虚函数后,就不能为纯虚函数写定义了,否则将会编译报错。

基础示例

接下来看一下上面代码修改后的完整代码:

#include <iostream> // std::cout std::endl
#include <string> // std::u32string

class basic_count_condition
{
public:
    virtual ~basic_count_condition(void) = default;
    virtual bool is_true(char32_t ch) = 0; // 纯虚函数
};

unsigned int count_if(std::u32string text, basic_count_condition *obj);
unsigned int count_if(std::u32string text, basic_count_condition *obj)
{
    unsigned int count = 0;
    for (auto ch : text)
    {
        if (obj->is_true(ch))
        {
            ++count;
        }
    }
    return count;
}

class gu_condition : public basic_count_condition
{
public:
    virtual ~gu_condition(void) = default;
    virtual bool is_true(char32_t ch) override;
};

int main(void)
{
    // 统计字符串中一共有多少个古字
    gu_condition gu;
    std::cout << count_if(U"小古银小古银我是美美哒小古银", &gu) << std::endl;
    return 0;
}

bool gu_condition::is_true(char32_t ch)
{
    return ch == U'古';
}

输出结果:

3

基础讲解

我们把基类的is_true声明为纯虚函数:

virtual bool is_true(char32_t ch) = 0;

那么就不需要为它写函数定义,写了也会报错。当有派生类继承basic_count_condition时,就必须重写is_true或者将is_true再设为纯虚函数,否则将会编译报错提醒你:你设计的派生类必须实现基类的纯虚函数。而且有纯虚函数的类是不能用来创建对象的,否则将会编译报错。这样就可以保证,设计出的派生类必须重写纯虚函数才能创建出对象,而使用多态必须先有对象,这样就可以正常使用多态。即上面代码count_if永远能够正常通过指针obj使用多态,而派生类必须重写is_true才能创建对象,从而才能为函数count_if的第二个参数传入地址。

抽象类

有纯虚函数的类就叫做抽象类。由于有纯虚函数,那么类和成员函数都应该理解为:只供继承而不能使用。根据这个规定,所以抽象类是不能创建出对象的。


Comments

Make a comment

  • Index

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.