Очень всё странно! У вас же метка C++
стоит, зачем вы пишете сишный код?
Ну в принципе ладно. У вас ошибка с использованием strtok()
. Почитайте как она работает. strtok()
void On_the_words(char* text, char**& words, int& k)
{ // для примера входная строка "one, two! three, four?\0"
char* sentence = strtok(text, ".!?");
// sentence = "one, two!" причем это указатель на первоначальную строку, а не копия!
// строка стала такой "one, two\0 three, four?\0"
while (sentence)
{
sentences[count - 1] = sentence;
// на вход подается "one, two\0"
char* word = strtok(sentences[count - 1], " ,");
// word = "one" sentences[count - 1] = "one\0 two\0"
while (word)
{
last_word = word; // запоминаем последнее слово перед переходом к следующему
word = strtok(NULL, " ,");
// word = "two"
// word = "\0"
}
// а вот здесь strtok() работает с указателем на "\0", оставшимся после последнего вызова
sentence = strtok(NULL, ".!?");
// результат sentence = nullptr
}
}
После внутреннего цикла, внутренний указатель функции strtok()
указывает на ноль завершающий строку "one, two\0". Соответственно поиск следующего токена ничего не находит и возвращается nullptr
. Просто нельзя пересекать вызовы strtok()
- сначала нужно получить указатели на все sentence = strtok(text, ".!?");
, а только потом пройти по каждому из них и разбить на слова word = strtok(NULL, " ,");
.
Причем использование realloc()
под массив указателей - не самая удачная идея. Воспользуйтесь простым массивом указателей. Или сделайте vector<char*>
- гораздо эффективнее по быстродействию будет, особенно на больших текстах.
С realloc()
у вас ещё одна неочевидная ошибка - если она не сможет выделить память, то старый блок не удалит, а просто вернет nullptr
. В результате будет сбой программы. И вообще выделять/перевыделять память вручную в C++
- плохой стиль. Почитайте про RAII.
sentences = (char**)realloc(sentences, ++count * sizeof(char*)); // sentences == nullptr !!!
// надо так
char** tmp = (char**)realloc( .... );
if(tmp)
sentences = tmp;
else
// что-то делать с ошибкой.
Но это всё здесь вообще лишнее - sentences = (char**)realloc(sentences, ++count * sizeof(char*));
Зачем вы запоминаете строки, если их дальше нигде не используете? Вам нужна одна текущая строка, достаточно одного указателя, а не массива. Также last_word
это указатель на одну строку. Зачем на него перевыделять память, тем более она утекает. И вы ещё получаете UB когда попытаетесь перевыделить память, которую не выделяли.
while (word)
{ last_word = (char*)realloc(last_word, count * sizeof(char));
last_word = word; // утечка памяти
// а на следующей итерации будет UB
}
Внутренний цикл разбиения на токены можно заменить на поиск с конца строки. На больших строках даст хорошую экономию по производительности!
// while (sentence) // вместо цикла - поиск с конца
{
// sentences = (char**)realloc(sentences, ++count * sizeof(char*)); // не нужно
// sentences[count - 1] = sentence; // не нужно
char* last_word = strrchr(sentence, ' '); // сделать поиск с конца
words = (char**)realloc(words, ++k * sizeof(char*)); // лучше заменить на vector<char*>
words[k - 1] = last_word; // добавляем последнее слово в массив words
sentence = strtok(NULL, ".!?");
}
Вообще всё то же самое можно написать на std::string_view
- никаких копирований, просто работа с диапазонами исходной строки. А в векторе есть размер, поэтому отдельно его передавать не надо.
void
On_the_words(string_view &text, vector<string_view> &words)
{
while ( !text.empty() )
{
size_t pos = text.find_first_of(".!?"sv);
string_view sentence = text.substr( 0, pos);
text = text.substr(pos+1);
pos = sentence.find_last_of(" ,"sv); // сделать поиск с конца
string_view last_word = sentence.substr( pos+1 );
words.push_back(last_word);
}
}
int main()
{
string_view str("one, two! three, four?");
vector<string_view> words;
On_the_words( str, words);
cout << str << "\n";
for(auto i : words)
cout << i << "\n";
return 0;
}