Вопрос

Я написал простую программу токенизации строк с использованием указателей для недавнего школьного проекта.Однако у меня проблемы с моим StringTokenizer::Next() метод, который при вызове должен возвращать указатель на первую букву следующего слова в массиве символов.Я не получаю ошибок времени компиляции, но получаю ошибку времени выполнения, которая гласит:

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

В настоящее время программа токенизирует массив символов, но затем останавливается, и появляется эта ошибка.У меня такое ощущение, что это связано с 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;
};

Стрингтокенайзер.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;
}
Это было полезно?

Решение

Этот ответ предоставляется на основе отредактированного вопроса и различных комментариев/наблюдений в других ответах...

Во-первых, каковы возможные состояния pStart при вызове Next()?

  1. pStart имеет значение NULL (конструктор по умолчанию или иным образом установлен в NULL)
  2. *pStart равен '\0' (пустая строка в конце строки)
  3. *pStart имеет разделитель (пустая строка в соседнем разделителе)
  4. *pStart — это что-нибудь еще (токен с непустой строкой)

На данный момент нам нужно беспокоиться только о первом варианте.Поэтому я бы использовал здесь оригинальную проверку «if»:

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

Почему нам пока не стоит беспокоиться о случаях 2 или 3?Вероятно, вы захотите рассматривать соседние разделители как имеющие между собой токен пустой строки, в том числе в начале и конце строки.(Если нет, отрегулируйте по вкусу.) Цикл while сделает это за нас, при условии, что вы также добавите проверку '\0' (необходима в любом случае):

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

После цикла while нужно быть осторожным.Каковы возможные состояния сейчас?

  1. *pStart равен '\0' (токен заканчивается в конце строки)
  2. *pStart имеет разделитель (токен заканчивается на следующем разделителе)

Обратите внимание, что здесь pStart не может иметь значение NULL.

Вам нужно вернуть pNextWord (текущий токен) для оба этих условий, чтобы не удалить последний токен (т. е., когда *pStart равен '\0').Код правильно обрабатывает случай 2, но не случай 1 (исходный код опасно увеличивал pStart после '\0', новый код возвращал NULL).Кроме того, важно правильно сбросить pStart для случая 1, чтобы следующий вызов Next() возвращал NULL.Точный код я оставлю читателю в качестве упражнения, так как в конце концов это домашнее задание ;)

Хорошим упражнением будет обрисовать возможные состояния данных в функции, чтобы определить правильное действие для каждого состояния, аналогично формальному определению базовых случаев и состояний.рекурсивные случаи для рекурсивных функций.

Наконец, я заметил, что в вашем деструкторе есть вызовы удаления как для pStart, так и для pNextWord.Во-первых, чтобы удалить массивы, вам нужно использовать delete [] ptr; (т. е. удаление массива).Во-вторых, вы не будете удалять и pStart, и pNextWord, потому что pNextWord указывает на массив pStart.В-третьих, к концу pStart больше не указывает на начало памяти, поэтому вам понадобится отдельный элемент для хранения исходного начала для delete [] вызов.Наконец, эти массивы размещаются в стеке, а не в куче (т. е. с использованием char var[], нет char* var = new char[]), поэтому их не следует удалять.Поэтому вам следует просто использовать пустой деструктор.

Еще один полезный совет — подсчитайте количество new и delete звонки;их должно быть одинаковое количество.В этом случае у вас ноль new звонки и два delete звонки, указывающие на серьезную проблему.Если бы было наоборот, это указывало бы на утечку памяти.

Другие советы

Нарушение прав доступа (или «ошибка сегментации» в некоторых ОС) означает, что вы попытались прочитать или записать позицию в памяти, которую вы никогда не выделяли.

Рассмотрим цикл while в Next():

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

Допустим, строка "blah\0".Обратите внимание, что я включил завершающий ноль.Теперь спросите себя:как этот цикл узнает, что нужно остановиться, когда достигнет конца строки?

Важнее:что происходит с *pStart если цикл терпит неудачу остановиться в конце строки?

Внутри ::Next вам нужно проверить символ-разделитель, но вам также нужно проверить конец буфера (который, как я предполагаю, обозначается \0).

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

И я думаю, что эти тесты в ::Next

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

Вместо этого должно быть это.

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

То есть вам следует проверять наличие символа Nul, а не нулевого указателя.Неясно, хотите ли вы, чтобы эти тесты обнаруживали неинициализированный указатель pStart или конец буфера.

Нарушение прав доступа обычно означает неправильный указатель.

В этом случае наиболее вероятной причиной является исчерпание строки до того, как вы найдете разделитель.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top