C++ 使用指针的访问冲突?-C++;

C++ 使用指针的访问冲突?-C++;,c++,pointers,tokenize,runtime-error,C++,Pointers,Tokenize,Runtime Error,我为最近的一个学校项目编写了一个使用指针的简单字符串标记化程序。但是,我的StringTokenizer::Next()方法遇到了问题,调用该方法时,应该返回指向char数组中下一个单词的第一个字母的指针。我没有得到编译时错误,但我得到一个运行时错误,该错误声明: Unhandled exception at 0x012c240f in Project 5.exe: 0xC0000005: Access violation reading location 0x002b0000. 程序当前标记

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

Unhandled exception at 0x012c240f in Project 5.exe: 0xC0000005: Access violation reading location 0x002b0000.
程序当前标记字符数组,但随后停止并弹出此错误。我觉得这与我在
Next()
方法中执行的
NULL
检查有关

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

此外,如果您注意到我可以做得更有效或更好的实践,请让我知道

谢谢


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

StringTokenizer.cpp:

#pragma once

class StringTokenizer
{
public:
StringTokenizer(void);
StringTokenizer(char* const, char);
char* Next(void);
~StringTokenizer(void);
private:
char* pStart;
char* pNextWord;
char delim;
};
#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;
}
// 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;
}

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


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

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

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

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

考虑Next()中的while循环:

假设字符串是
“blah\0”
。注意,我已经包含了终止null。现在,扪心自问:当循环到达字符串末尾时,它如何知道停止

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

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

考虑Next()中的while循环:

假设字符串是
“blah\0”
。注意,我已经包含了终止null。现在,扪心自问:当循环到达字符串末尾时,它如何知道停止


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

Inside::接下来,您需要检查delim字符,但还需要检查缓冲区的结尾(我猜它由\0表示)

我认为接下来的测试

if (pStart == NULL) { return NULL; }
应该是这个

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

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

Inside::接下来,您需要检查delim字符,但还需要检查缓冲区的结尾(我猜是由\0指示的)

我认为接下来的测试

if (pStart == NULL) { return NULL; }
应该是这个

if (*pStart == '\0') { return NULL; }
也就是说,您应该检查Nul字符,而不是空指针。不清楚这些测试是要检测未初始化的pStart指针还是缓冲区的结尾。

此答案基于编辑的问题和其他答案中的各种注释/观察结果提供

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

  • pStart为NULL(默认构造函数或以其他方式设置为NULL)
  • *pStart为“\0”(字符串末尾的空字符串)
  • *pStart是delim(相邻分隔符处的空字符串)
  • *pStart是其他任何东西(非空字符串标记)
  • 在这一点上,我们只需要担心第一种选择。因此,我将在此处使用原始的“如果”复选框:

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

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

  • *pStart为“\0”(标记在字符串末尾结束)
  • *pStart是delim(标记在下一个分隔符处结束)
  • 请注意,此处pStart本身不能为NULL

    您需要为这两个条件返回pNextWord(当前令牌),以便不删除最后一个令牌(即,*pStart为'\0')。代码正确地处理了案例2,但没有处理案例1(原始代码危险地增加pStart超过'\0',新代码返回NULL)。此外,正确重置案例1的pStart非常重要,这样对next()的下一次调用将返回NULL。我将把确切的代码作为练习留给读者,因为它毕竟是家庭作业;)

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

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

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

    此答案基于