【问题标题】:Read a file with only char pointers in c++在 C++ 中读取只有 char 指针的文件
【发布时间】:2017-10-26 04:01:27
【问题描述】:

我需要将文件内容读入某些对象,不幸的是我不能随意使用 std::string ,因此必须使用 char 指针。 但是,当我这样做时,我会直接从内存中得到奇怪的迹象,但 solution 却不起作用。 所以我通过直接从istream而不是std库使用getline来重建,但同样的情况发生了。 如何在不使用 std::string 的情况下正确读取文件。

PortsContainer game::ParsePort(std::istream& stream)
{
    PortsContainer ports;

    bool passFirstRow = false;

    char* portLine = new char[1000000];
    int i = 0;
    while (!stream.eof())
    {
        if (!stream)
            throw std::system_error(Error::STREAM_ERROR);

        if (portLine[0] == '\0' || portLine == nullptr || portLine[0] == '#')
            continue;

        std::stringstream ss(portLine);

        if (!passFirstRow) {
            char* name = new char[100];
            while (!ss.eof()) {
                ss.getline(name, sizeof name, ';');
                Port* port = new Port();
                //port->name = const_cast<char*>(name);
                port->name = const_cast<char*>(name);
                ports.addItem(port);
            }

            passFirstRow = true;
        } else {
            i++;
        }

        if (!stream)
            throw std::system_error(Error::STREAM_ERROR);
    }

    return ports;
}

PortsContainer game::ParsePort(std::istream& stream, std::error_code& errorBuffer)
{
    try
    {
        return ParsePort(stream);
    }
    catch (std::system_error exception)
    {
        errorBuffer = exception.code();
    }
}

PortsContainer game::GetAvailablePorts()
{
    PortsContainer ports;

    std::ifstream stream("./ports.csv");

    std::error_code errorBuffer;
    ports = ParsePort(stream, errorBuffer);
    if (errorBuffer)
        return PortsContainer();

    return ports;
}

【问题讨论】:

  • 首先我建议你阅读Why is iostream::eof inside a loop condition considered wrong?。那么你应该记住,C++ 中的char 字符串实际上称为null-terminate 字节字符串。空终止符(不要与空指针混淆)很重要。还要记住,您分配的内存将不会被初始化,即使读取它也会导致未定义的行为。最后,您可能到处都有一些内存泄漏。
  • sizeof name 你期望什么价值?
  • 我不能随意使用 std::string YAIT(又一个不称职的老师)
  • 不允许您使用std::string 但您可以使用基于std:: stringstd::stringstream 是没有意义的
  • 你从来没有读过 stream 的文章,这怎么能正常阅读?

标签: c++ pointers char getline


【解决方案1】:

您没有使用任何数据填充portLine。实际上,您根本没有从stream 读取任何数据。

您在滥用eof()eofbit 标志在第一次尝试读取操作之后才会更新。所以在发eof()之前你必须阅读。

您正在泄漏您的 portLinename 缓冲区。更糟糕的是,由于不允许使用std::string,这意味着Port::name 成员是char* 指针,这意味着您(可能)有多个Port 对象指向内存中的同一物理缓冲区。如果Port 稍后尝试释放该缓冲区,例如在其析构函数中,您将遇到内存错误。

试试类似的方法:

PortsContainer game::ParsePort(std::istream& stream)
{
    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    PortsContainer ports;

    bool passFirstRow = false;

    // better would be to use std::unique_ptr<char[]>, std::vector<char>,
    // or std::string instead so the memory is freed automatically ...
    char *portLine = new char[1000000];

    int i = 0;

    do
    {
        if (!stream.getline(portLine, 1000000))
        {
            delete[] portLine; // <-- free the buffer for line data...
            throw std::system_error(Error::STREAM_ERROR);
        }

        if ((stream.gcount() == 0) || (portLine[0] == '#'))
            continue;

        if (!passFirstRow)
        {
            std::istringstream iss(portLine);

            // better would be to use std::unique_ptr<char[]>, std::vector<char>,
            // or std::string instead so the memory is freed automatically ...
            char* name = new char[100];

            while (iss.getline(name, 100, ';'))
            {
                if (iss.gcount() == 0) continue;

                Port *port = new Port();
                port->name = name; // <-- assumes ownership is transferred!
                ports.addItem(port);
                name = new char[100]; // <-- have to reallocate a new buffer each time!
            }
            delete[] name; // <-- free the last buffer not used...
            passFirstRow = true;
        } else {
            ++i;
        }
    }
    while (!stream.eof());

    delete[] portLine; // <-- free the buffer for line data...

    return ports;
}

PortsContainer game::ParsePort(std::istream& stream, std::error_code& errorBuffer)
{
    try
    {
        return ParsePort(stream);
    }
    catch (const std::system_error &exception)
    {
        errorBuffer = exception.code();
        return PortsContainer(); // <-- don't forget to return something!
    }
}

PortsContainer game::GetAvailablePorts()
{
    std::ifstream stream("./ports.csv");
    std::error_code errorBuffer;
    return ParsePort(stream, errorBuffer); // <-- no need to check errorBuffer before returning!
}

但是,我强烈建议您使用 STL 智能指针来帮助确保更安全的内存管理:

PortsContainer game::ParsePort(std::istream& stream)
{
    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    PortsContainer ports;

    bool passFirstRow = false;

    // since you are using std::error_code, that means you are
    // using C++11 or later, so use std::unique_ptr to ensure
    // safe memory management...
    std::unique_ptr<char[]> portLine(new char[1000000]);

    int i = 0;

    do
    {
        if (!stream.getline(portLine.get(), 1000000))
            throw std::system_error(Error::STREAM_ERROR);

        if ((stream.gcount() == 0) || (portLine[0] == '#'))
            continue;

        if (!passFirstRow)
        {
            std::istringstream iss(portLine.get());

            // use std::unique_ptr here, too...
            std::unique_ptr<char[]> name(new char[100]);

            while (iss.getline(name.get(), 100, ';'))
            {
                if (iss.gcount() == 0) continue;

                // use std::unique_ptr here, too...
                std::unique_ptr<Port> port(new Port);
                port->name = name.release(); // <-- assumes ownership is transferred!
                                             // better to make Port::name use std::unique_ptr<char[]> and then std::move() ownership of name to it...
                ports.addItem(port.get());
                port.release();

                name.reset(new char[100]); // <-- have to reallocate a new buffer each time!
            }
            passFirstRow = true;
        } else {
            ++i;
        }
    }
    while (!stream.eof());

    return ports;
}

不过,使用std::string 是最好的选择:

PortsContainer game::ParsePort(std::istream& stream)
{
    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    PortsContainer ports;

    bool passFirstRow = false;
    std::string portLine;
    int i = 0;

    while (std::getline(stream, portLine))
    {
        if (portLine.empty() || (portLine[0] == '#'))
            continue;

        if (!passFirstRow)
        {
            std::istringstream iss(portLine);
            std::string name;

            while (std::getline(iss, name, ';'))
            {
                if (name.empty()) continue;

                std::unique_ptr<Port> port(new Port);
                port->name = name; // <-- make Port::name be std::string as well!
                ports.addItem(port.get());
                port.release();
            }
            passFirstRow = true;
        } else {
            ++i;
        }
    }

    if (!stream)
        throw std::system_error(Error::STREAM_ERROR);

    return ports;
}

【讨论】:

  • 感谢您的支持和解释,我得到了它的大部分工作,现在最后它崩溃了,但这是在不同的部分,我必须修复。关于字符串,我也觉得这很荒谬,但不幸的是我无能为力。我得到的容器是因为它确保学生理解指针和堆栈和堆背后的逻辑以及诸如此类的东西,但 std::string 的东西只是“那不是容器吗?哦,是的,你是对的,你也不能使用它”。所以它背后没有逻辑,除了愤怒管理之外根本没有教任何东西。
  • 原始答案更相关。正如我之前的评论所暗示的,这部分课程想让学生/我学习内存管理,所以不允许使用智能指针。出于某种原因,我遇到了内存泄漏,但我将能够解决这些问题。再次感谢您提供的解决方案。
  • 我将代码恢复为使用原始 char* 指针,但我保留了其他代码作为额外建议。
  • ++ 努力。我想说,在 C++ 课程中强调手动内存管理与语言的观点相悖……对我来说,这是以后应该教的东西,也许只有当学生表示有兴趣使用这些工具时,因为由于某种原因,标准库的 RAII 类不能满足他们的需求。对于大多数人来说,stdlib 可以做需要做的事情,应该教他们更多有用的东西,IMO。
  • @underscore_d: 是的,很多人都同意应该首先教授 STL,因为它是 C++ 标准的核心,然后应该在稍后教授更高级的技术,如算法设计,优化等
猜你喜欢
  • 2021-11-26
  • 1970-01-01
  • 2011-12-05
  • 2020-11-24
  • 2014-07-23
  • 1970-01-01
  • 2021-07-03
  • 2011-04-24
  • 1970-01-01
相关资源
最近更新 更多