0

我需要在列表末尾添加新项目,删除最后一个并显示整个列表。显示整个列表时,由于某种原因,仅显示堆栈的最后一个元素,按列表中的元素数量计算。为什么?

#include <iostream>
#include <stdio.h>
#include <stack>

using namespace std;

struct Node
{
    char* name = new char[6];
    float sumary;
    int amount;
    Node(char* name, float sumary, int amount) :name(name), sumary(sumary), amount(amount)
    {
    }
    Node() {}
};

int main()
{
    int command;
    stack<Node> node;
    for (;;)
    {
        printf("Input command:\n 1 - add,\n 2 - delete last,\n 3 - show all,\n 4 - exit\n");
        scanf("%d", &command);
        switch (command)
        {
        case 1:
            char name[6];
            float sumary;
            int amount;
            printf("Enter name: ");
            scanf("%s", &name);
            printf("Enter sumary: ");
            scanf("%f", &sumary);
            printf("Enter amount: ");
            scanf("%d", &amount);
            node.push(Node(name, sumary, amount));
            break;
        case 2:
            node.pop();
            printf("The last have been deleted");
            break;
        case 3:
            while (!node.empty())
            {
                Node temp = node.top();
                node.pop();
                cout << temp.name << " " << temp.sumary << " " << temp.amount << endl;
            }
            break;
        case 4:
            return 0;
        default:
            printf("Wrong command...");
            break;
        }
    }
}
4

2 回答 2

1

在构造函数中,当name是 a时,您:name(name)不会做您所期望的事情,您不会深度复制名称,而只是保存因未定义行为而变得无效的指针(并丢失了产生内存泄漏的分配数组)。char*switchchar

使用 astd::string而不是 a char*,您将获得预期的行为。这也大大简化了复制、分配、删除节点时的管理,因为您无事可做,而使用指针则不是这种情况。

char name[6];如果有超过 5 个字符的单词可用,那么它会以未定义的行为从scanf("%s", &name);数组中写入。

您也不会检查scanf成功检查它们返回 1 的调用,如果输入无效,您将不受保护。

请注意,您可以使用iostream功能(也检查读数是否成功),而不是使用 C 函数来读/写。

选择 astd::stack而不是例如 astd::vector很奇怪,因为编写它的内容不太实用。

一种方法是在编写其 contains 时不清空堆栈:

#include <iostream>
#include <string.h>
#include <stack>
#include <limits>

struct Node
{
  std::string name;
  float sumary;
  int amount;

  Node(std::string name, float sumary, int amount) 
    :name(name), sumary(sumary), amount(amount) {
 }
 Node() {}
};

void inputError(const char * msg)
{
  if (std::cin.rdstate() & std::istream::eofbit)
  {
    // EOF
    std::cerr << "EOF, abort" << std::endl;
    exit(-1);
  }

  std::cerr << msg << std::endl;

  std::cin.clear();
  // flush all the line, you can just read a word
  // with "string s; cin >> s;" if you prefer
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

int main()
{
    int command;
    std::stack<Node> node;

    for (;;)
    {
        std::cout << "Input command:\n 1 - add,\n 2 - delete last,\n 3 - show all,\n 4 - exit" << std::endl;

        if (!(std::cin >> command))
          inputError("invalid command, not an integer");
        else
        {
          switch (command)
          {
          case 1:
            {
              std::string name;
              float sumary;
              int amount;

              if (!(std::cout << "Enter name: ", std::cin >> name) ||
                  !(std::cout << "Enter sumary: ", std::cin >> sumary) ||
                  !(std::cout << "Enter amount: ", std::cin >> amount))
                inputError("invalid value");
              else
                node.push(Node(name, sumary, amount));
            }
            break;
          case 2:
            if (node.empty())
              std::cerr << "no node to delete" << std::endl;
            else 
            {
              node.pop();
              std::cout << "The last have been deleted" << std::endl;
            }
            break;
          case 3:
            {
              std::stack<Node> temp = node;

              while (!temp.empty())
              {
                const Node & n = temp.top();

                temp.pop();
                std::cout << n.name << " " << n.sumary << " " << n.amount << std::endl;
              }
            }
            break;
          case 4:
            return 0;
          default:
            std::cerr << "Wrong command number" << std::endl;
            break;
          }
        }
    }
}

编译和执行:

pi@raspberrypi:/tmp $ g++ -Wall c.cc
pi@raspberrypi:/tmp $ ./a.out
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
7
Wrong command number
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
z
invalid command, not an integer
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
1
Enter name: aze
Enter sumary: a
invalid value
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
1
Enter name: aze
Enter sumary: 1.1
Enter amount: 2
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
1
Enter name: qsd
Enter sumary: 2.2
Enter amount: 3
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
3
qsd 2.2 3
aze 1.1 2
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
3
qsd 2.2 3
aze 1.1 2
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
2
The last have been deleted
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
3
aze 1.1 2
Input command:
 1 - add,
 2 - delete last,
 3 - show all,
 4 - exit
4
pi@raspberrypi:/tmp $ 

operator<<Node中定义而不是将其成员公开以便能够在main中访问它们以编写它们也是一个好主意

于 2020-06-07T22:11:33.220 回答
0

这个构造函数

Node(char* name, float sumary, int amount) :name(name), sumary(sumary), amount(amount)
{
}

没有意义,因为在类定义中初始化数据成员名称

char* name = new char[6];

被忽略。来自 C++ 17 标准(12.6.2 初始化基和成员)

10 如果给定的非静态数据成员同时具有大括号或等式初始化器和内存初始化器,则执行内存初始化器指定的初始化,并且非静态数据成员的大括号或等式初始化器被忽略。

所以数据成员总是由具有自动存储持续时间name的本地数组的第一个元素的地址初始化name

    switch (command)
    {
    case 1:
        char name[6];
        //...
        node.push(Node(name, sumary, amount));
        //...

所以程序有未定义的行为。结果,正如您自己指出的那样,您会发现所有节点都输出相同的字符串。

注意scanf的这个调用

scanf("%s", &name);

是不正确的。你必须写

scanf("%s", name);

但无论如何,使用 C++ 流函数要好得多。而不是动态分配的数组,您应该使用标准类std::string

此外,如果您使用动态内存分配,您的类没有显式析构函数来释放分配的内存。

所以用指针声明代替类型的数据成员的声明std::string

于 2020-06-07T22:42:03.200 回答