我为最近的学校项目编写了一个使用指针的简单字符串标记程序。但是,我的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;
}




Main.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;
}






编辑:

好的,只要定界符为空格,我现在就可以使程序正常运行。但是,如果我将它作为delim传递给它,则会再次出现访问冲突错误。有任何想法吗?

与空格配合使用的函数:

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;
}

最佳答案

根据已编辑的问题以及其他答案中的各种注释/观察结果提供此答案。

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


pStart为NULL(默认构造函数,否则设置为NULL)
* pStart为'\ 0'(字符串末尾的空字符串)
* pStart为delim(相邻分隔符处为空字符串)
* pStart是其他任何内容(非空字符串令牌)


在这一点上,我们只需要担心第一种选择。因此,我将在此处使用原始的“ if”检查:

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


为什么我们不用担心情况2或3?您可能希望将相邻的定界符视为之间有一个空字符串标记,包括在字符串的开头和结尾。 (如果不是,请调整以适应口味。)while循环将为我们处理该循环,前提是您还添加了“ \ 0”检查(无论是否需要):

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


在while循环之后,您需要小心。现在可能的状态是什么?


* pStart为'\ 0'(令牌在字符串结尾处结束)
* pStart是delim(令牌在下一个定界符处结束)


注意,pStart本身不能为NULL。

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

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

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

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

09-30 14:58