0

我为最近的一个学校项目使用指针编写了一个简单的字符串标记程序。但是,我的StringTokenizer::Next()方法有问题,该方法在调用时应该返回指向 char 数组中下一个单词的第一个字母的指针。我没有收到编译时错误,但我收到了一个运行时错误,其中指出:

Unhandled exception at 0x012c240f in Project 5.exe: 0xC0000005: Access violation reading location 0x002b0000.

该程序当前标记了 char 数组,但随后停止并弹出此错误。我觉得这与NULL我在Next()方法中所做的检查有关。

那么我该如何解决这个问题呢?

另外,如果您发现我可以更有效地或通过更好的练习来做任何事情,请告诉我。

谢谢!!


StringTokenizer.h:

#pragma once

class StringTokenizer
{
public:
StringTokenizer(void);
StringTokenizer(char* const, char);
char* Next(void);
~StringTokenizer(void);
private:
char* pStart;
char* pNextWord;
char delim;
};

StringTokenizer.cpp:

#include "stringtokenizer.h"
#include <iostream>
using namespace std;

StringTokenizer::StringTokenizer(void)
{
pStart = NULL;
pNextWord = NULL;
delim = 'n';
}

StringTokenizer::StringTokenizer(char* const pArray, char d)
{
pStart = pArray;
delim = d;
}

char* StringTokenizer::Next(void)
{
pNextWord = pStart;
if (pStart == NULL) { return NULL; }

while (*pStart != delim) // access violation error here
{
    pStart++;
}

if (pStart == NULL) { return NULL; }

*pStart = '\0'; // sometimes the access violation error occurs here
pStart++;

return pNextWord;
}

StringTokenizer::~StringTokenizer(void)
{
delete pStart;
delete pNextWord;
}

主要.cpp:

// The PrintHeader function prints out my
// student info in header form
// Parameters - none
// Pre-conditions - none
// Post-conditions - none
// Returns - void
void PrintHeader();

int main ( )
{
const int CHAR_ARRAY_CAPACITY = 128;
const int CHAR_ARRAY_CAPCITY_MINUS_ONE = 127;

// create a place to hold the user's input
// and a char pointer to use with the next( ) function
char words[CHAR_ARRAY_CAPACITY];
char* nextWord;

PrintHeader();

cout << "\nString Tokenizer Project";
cout << "\nyour name\n\n";
cout << "Enter in a short string of words:";
cin.getline ( words, CHAR_ARRAY_CAPCITY_MINUS_ONE );

// create a tokenizer object, pass in the char array
// and a space character for the delimiter
StringTokenizer tk( words, ' ' );

// this loop will display the tokens
while ( ( nextWord = tk.Next ( ) ) != NULL )
{
    cout << nextWord << endl;
}


system("PAUSE");
return 0;
}


编辑:

好的,只要分隔符是空格,我的程序现在就可以正常工作了。但是,如果我将 `/' 作为分隔符传递给它,它会再次出现访问冲突错误。有任何想法吗?

与空格一起使用的功能:

char* StringTokenizer::Next(void)
{
pNextWord = pStart;

if (*pStart == '\0') { return NULL; }

while (*pStart != delim)
{
    pStart++;
}

if (*pStart = '\0') { return NULL; }

*pStart = '\0';
pStart++;

return pNextWord;
}
4

4 回答 4

6

访问冲突(或某些操作系统上的“分段错误”)意味着您试图读取或写入内存中从未分配过的位置。

考虑 Next() 中的 while 循环:

while (*pStart != delim) // access violation error here
{
    pStart++;
}

假设字符串是"blah\0". 请注意,我已经包含了终止空值。现在,问问自己:当循环到达字符串的末尾时,它是如何知道停止的?

更重要的是:*pStart如果循环未能在字符串末尾停止会发生什么?

于 2010-02-08T03:28:30.497 回答
1

在 ::Next 内部,您需要检查 delim 字符,但您还需要检查缓冲区的结尾(我猜它由 \0 表示)。

while (*pStart != '\0' && *pStart != delim) // access violation error here
{
    pStart++;
}

我认为这些测试在 ::Next

if (pStart == NULL) { return NULL; }

应该是这个。

if (*pStart == '\0') { return NULL; }

也就是说,您应该检查 Nul 字符,而不是空指针。不清楚您是否打算让这些测试检测未初始化的 pStart 指针或缓冲区的结尾。

于 2010-02-08T03:30:35.500 回答
1

该答案是根据已编辑的问题和其他答案中的各种评论/观察提供的...

首先,调用 Next() 时 pStart 的可能状态是什么?

  1. pStart 为 NULL(默认构造函数或以其他方式设置为 NULL)
  2. *pStart 是 '\0'(字符串末尾的空字符串)
  3. *pStart 是分隔符(相邻分隔符处的空字符串)
  4. *pStart 是其他任何东西(非空字符串标记)

此时我们只需要担心第一个选项。因此,我会在这里使用原始的“if”检查:

if (pStart == NULL) { return NULL; }

为什么我们还不需要担心案例 2 或 3?您可能希望将相邻的分隔符视为在它们之间有一个空字符串标记,包括在字符串的开头和结尾。(如果没有,请根据口味进行调整。)如果您还添加了 '\0' 检查(无论如何都需要),while 循环将为我们处理这个问题:

while (*pStart != delim && *pStart != '\0')

在while循环之后是你需要小心的地方。现在有哪些可能的状态?

  1. *pStart 是 '\0' (标记在字符串末尾结束)
  2. *pStart 是分隔符(标记在下一个分隔符处结束)

请注意,此处 pStart 本身不能为 NULL。

对于这两种情况,您都需要返回 pNextWord(当前标记),这样您就不会丢弃最后一个标记(即,当 *pStart 为 '\0' 时)。代码正确处理案例 2,但不是案例 1(原始代码危险地递增 pStart 超过 '\0',新代码返回 NULL)。此外,正确重置案例 1 的 pStart 也很重要,这样下一次调用 Next() 会返回 NULL。我将把确切的代码作为练习留给读者,因为它毕竟是家庭作业;)

概述整个函数中数据的可能状态是一个很好的练习,以便确定每个状态的正确操作,类似于正式定义递归函数的基本案例与递归案例。

最后,我注意到您在析构函数中对 pStart 和 pNextWord 都进行了删除调用。首先,要删除数组,需要使用delete [] ptr;(即数组删除)。其次,您不会同时删除 pStart 和 pNextWord,因为 pNextWord 指向 pStart 数组。第三,到最后,pStart 不再指向内存的开始,因此您需要一个单独的成员来存储delete []调用的原始开始。最后,这些数组是在堆栈上而不是在堆上分配的(即 using char var[], not char* var = new char[]),因此它们不应该被删除。因此,您应该简单地使用一个空的析构函数。

另一个有用的技巧是计算newdelete调用的数量;每个应该有相同的数量。在这种情况下,您有 0个new调用和两个delete调用,表明存在严重问题。如果相反,则表明内存泄漏。

于 2010-02-08T05:47:04.183 回答
0

访问冲突通常意味着错误的指针。

在这种情况下,最可能的原因是在找到分隔符之前字符串用完了。

于 2010-02-08T03:21:13.083 回答