从空队列中删除元素

Deleting an element from an empty queue?

本文关键字:删除 元素 队列      更新时间:2023-10-16

我正在编写抽象数据类型的优先级队列,作为大学的任务,其他人将使用它。我的类dequeue中有一个函数,它删除队列中的第一个元素并返回该元素的数据。但是,当我试图从一个空队列中删除一个元素时,程序会崩溃。我在这里该怎么办?

如果有帮助的话,下面是代码:

#ifndef PRIORITYQUEUE_H
#define PRIORITYQUEUE_H
#include <iostream>
using namespace std;
const int max_queue_items = 1000;
template<class T>
struct node{
    T data;
    int priority;
    node *next;
};
template<class T>
class PriorityQueue
{
    public:
        /*
            Constructor that creates an empty queue.
        */
        PriorityQueue(){
            head = NULL;
            size = 0;
        }
        /*
            Adds an element to the queue.
            Params:
            data - data of the element
            priority - priority of the element
        */
        bool is_empty(){
            if (size == 0){
                return true;
            }
            return false;
        }
        bool is_full(){
            if (size == max_queue_items){
                return true;
            }
            return false;
        }
        /*
            Adds an element to thq queue.
            It gets inserted before the first element with
            lower priority.
        */
        void enqueue(T data, int priority){
            node<T> * previous = NULL;
            node<T> * now = head;
            while (now != NULL && now->priority >= priority){
                previous = now;
                now = now->next;
            }
            node<T> * new_element = new node<T>;
            new_element->data = data;
            new_element->priority = priority;
            new_element->next = now;
            if (previous == NULL){
                head = new_element;
            } else {
                previous->next = new_element;
            }
            size++;
        }
        /*
            Removes the first element in the queue
        */
        T dequeue(){
            T data;
            if (!is_empty()){
                node<T> * now = head;
                data = now->data;
                head = head->next;
                delete now;
                size--;
            }
            return data;
        }
        /*
            Returns the priority of the first element.
            It's always the highest priority in the queue.
        */
        int get_first_priority(){
            return head->priority;
        }
        /*
            Returns the data of the first element in the queue.
        */
        T get_first_value(){
            if (is_empty())
                throw 0;
            return head->data;
        }
        /*
            Returns the number of elements in the queue.
        */
        int get_size(){
            return size;
        }
        /*
            Deletes the whole queue from the memory.
        */
        void flush(){
            node<T> * now;
            while (head != NULL){
                now = head;
                head = head->next;
                delete now;
                size--;
            }
        }
        /*
            Prints the whole queue following this format:
            data(priority)
        */
        void print(){
            node<T> * now = head;
            while (now != NULL){
                cout << now->data << "(" << now->priority << ")" << endl;
                now = now->next;
            }
        }
    private:
        node<T> * head; // Pointer to the head of the queue
        int size; // Number of elements in the queue
};
#endif // PRIORITYQUEUE_H

这可能是问题的根源,也可能不是,但我肯定会认为这是一个问题。在函数dequeue()中,当is_empty()返回true:时,您可能会返回一个未初始化的变量(如果T不是类类型)

    T dequeue()
    {
        T data; // Uninitialized if T is not a class type
        if (!is_empty())
        {
            node<T> * now = head;
            //--------------------------------------------------------------
            // This initialization is skipped when `is_empty()` returns true
            data = now->data;
            //--------------------------------------------------------------
            head = head->next;
            delete now;
            size--;
        }
        return data;
    }

根据您对该函数返回的值所做的操作以及T的类型,您的程序可能具有Undefined Behavior(我可以想象T是您稍后取消引用的指针类型)。

您可能想将函数的第一行更改为:

T data = T();

它强制执行data对象的值初始化。如果T是类类型,则将调用默认构造函数。否则,data将被零初始化。

调用dequeue()的函数在使用返回值之前应该检查它(或者更好的方法是,在尝试从中弹出值之前,在队列上调用is_empty()以检查它是否为空)。

当调用dequeue()并且队列为空时,您甚至可以考虑抛出异常:

T dequeue()
{
    if (is_empty())
    {
        // Requires including the <stdexcept> standard header
        throw std::logic_error("Queue is empty"); 
    }
    node<T> * now = head;
    T data = now->data;
    head = head->next;
    delete now;
    size--;
    return data;
}

客户端现在负责确保dequeue()永远不会在空队列上被调用(或者他们将把对dequeue()的调用封装到try/catch块中,以处理可能引发的异常

另一种可能性是向客户端返回一个bool,指示该值是否成功弹出,可能会将弹出的元素分配给通过引用传递的参数:

bool dequeue(T& data)
{
    if (is_empty())
    {
        return false;
    }
    node<T> * now = head;
    data = now->data;
    head = head->next;
    delete now;
    size--;
    return true;
}

通过这种方式,客户端负责检查函数的结果。如果函数返回falsedata变量将被初始化为客户端将其初始化为的任何值。处理错误情况的责任再次转移到客户端。

我认为存在一些问题。

首先也是最重要的一点是,类没有析构函数。如果不是所有元素都在程序中出列,那么就会出现内存泄漏。编写析构函数或使用智能指针而不是原始指针。

其次,正如@Andy Prowl(顺便说一句,谁知道如何在推特这样的帖子中@人们?)所说,应该考虑未初始化的局部变量。T data = T()对于内置类型和自定义类型都能很好地工作。

第三,我认为队列有容量限制max_queue_items,但入队部分没有相应的代码。

尽管如此,我不认为所有这些缺陷在正常情况下都会导致严重的崩溃。也许问题发生在您的代码调用类中,对未初始化的返回值的错误处理会导致崩溃。

我在您的出队列中看到的唯一潜在问题是您正在创建未知类型T的临时变量。如果您在优先级队列中存储没有默认构造函数的类型的数据,那么当您的出队调用并尝试默认构造该变量时,您将遇到问题。

如果是这种情况,我建议您重新处理优先级队列,以保存指向模板类型的指针,而不是数据本身。