You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++自定义结构体栈的operator[]重载异常问题排查

自定义栈[]运算符重载异常:stack[i]访问返回随机值

问题描述

我正在学习内存管理,用结构体在堆上实现了一个自定义栈,栈的push/pop功能正常。之后尝试重载[]运算符,希望以O(n)时间访问栈内元素,但发现stack->operator[]调用正常,而stack[i](i≠0)会返回随机数值。

代码示例

#include <iostream>

struct stackElem
{
    int v;
    stackElem *p;
    stackElem(){
        v = 0;
        p = nullptr;
    }
    ~stackElem(){
        delete p;
        p = nullptr;
    }
    int& operator[](size_t index) {
        if(index == 0) return this->v;
        stackElem *temp = this->p;
        for (size_t i = 0; i < index; i++){
            temp = temp->p;
        }
        return temp->v;
    }   //stack[0] returns top, then goes down
    const int operator[](size_t index) const {
        if(index == 0) return this->v;
        stackElem *temp = this->p;
        for (size_t i = 0; i < index; i++){
            temp = temp->p;
        }
        return temp->v;
    }
};

void stackPush(stackElem *&top, int val){
    stackElem *temp = top;
    top = new stackElem;
    top->v = val;
    top->p = temp;

}   //First in, last out

int stackPop(stackElem *&top){
    int ret = top->v;
    stackElem *temp = top->p;
    top->p = nullptr;
    delete top;
    top = temp;
    return ret;
}

int main(){
    stackElem *list = new stackElem;
    stackPush(list, 13);
    stackPush(list, 14);
    stackPush(list, 15);
    stackPush(list, 16);
    stackPush(list, 17);
    stackPush(list, 18);
    
    std::cout<<list->p->v<<std::endl;
    std::cout<<list[0].v<<"	"<<list[1].v<<std::endl;//<<list[2].v<<list[3].v<<list[4].v<<list[5].v;
    list->operator[](1) = 100;   //This works correctly
    int test = list->operator[](1);   //This works correctly
    int test2 = list[1];   //This breaks
    std::cout<<test<<std::endl;
    std::cout<<list[1];   //This breaks
    return 0;
}

补充说明:我重载了两个版本的[]运算符(分别用于int = stack[]和stack[] = int场景),const版本中无法用stackElem *temp = this,因为“不能用const stackElem *初始化stackElem *”,因此使用了特殊的if(index == 0)逻辑并将temp赋值为this->p。


问题根源

核心问题在于指针的原生下标行为和你重载的成员函数完全无关:

  • list是stackElem*类型,当你写list[i]时,C++会将其解析为*(list + i)——也就是从list指向的内存地址向后偏移i * sizeof(stackElem)字节,直接访问该内存区域的stackElem对象。这和你遍历链表的重载逻辑毫无关系,访问的是堆上不属于你链表结构的内存,因此返回随机值。
  • 而list->operator[](i)是正确调用了你在stackElem中重载的成员函数,会遍历链表找到对应元素,所以行为正常。

解决方案

将栈的逻辑封装到一个独立的Stack类中,让类本身重载operator[],而非让底层节点结构体或指针承担这个功能。这样Stack对象的[]调用会触发你自定义的逻辑,而非指针的原生偏移行为。

修改后的代码示例:

#include <iostream>
#include <stdexcept> // 用于std::out_of_range

struct stackElem
{
    int v;
    stackElem *p;
    stackElem() : v(0), p(nullptr) {}
    ~stackElem() { delete p; } // delete nullptr是安全的,无需额外赋值
};

class Stack
{
private:
    stackElem* top;
public:
    Stack() : top(new stackElem) {}
    ~Stack() { delete top; }

    void push(int val)
    {
        stackElem* temp = top;
        top = new stackElem;
        top->v = val;
        top->p = temp;
    }

    int pop()
    {
        if (!top)
            throw std::out_of_range("Pop from empty stack");
            
        int ret = top->v;
        stackElem* temp = top->p;
        top->p = nullptr; // 避免析构时递归删除后续元素
        delete top;
        top = temp;
        return ret;
    }

    // 非const版本,支持赋值操作
    int& operator[](size_t index)
    {
        stackElem* current = top;
        for (size_t i = 0; i < index; ++i)
        {
            if (!current)
                throw std::out_of_range("Stack index out of range");
            current = current->p;
        }
        if (!current)
            throw std::out_of_range("Stack index out of range");
        return current->v;
    }

    // const版本,仅支持只读访问
    int operator[](size_t index) const
    {
        const stackElem* current = top;
        for (size_t i = 0; i < index; ++i)
        {
            if (!current)
                throw std::out_of_range("Stack index out of range");
            current = current->p;
        }
        if (!current)
            throw std::out_of_range("Stack index out of range");
        return current->v;
    }
};

int main()
{
    Stack list;
    list.push(13);
    list.push(14);
    list.push(15);
    list.push(16);
    list.push(17);
    list.push(18);

    std::cout << list[1] << std::endl; // 正常输出17
    list[1] = 100;
    int test = list[1];
    std::cout << test << std::endl; // 输出100
    std::cout << list[1] << std::endl; // 输出100
    return 0;
}

额外优化点

  • 增加越界检查,抛出std::out_of_range异常,避免空指针访问导致的未定义行为;
  • 封装栈的操作到Stack类中,符合面向对象的封装原则,隐藏底层实现细节;
  • 使用构造函数初始化列表替代赋值,更符合C++编码规范;
  • 简化析构函数逻辑,delete nullptr是安全操作,无需额外赋值为nullptr。

内容的提问来源于stack exchange,提问作者n0rmi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 01:45:20