在C++中查找(奇怪的)字符串中的单词

Finding words in a (weird) string in C++

本文关键字:单词 字符串 C++ 查找      更新时间:2023-10-16

这个程序在技术上有什么错误?预期的结果是6,因为这是字符串中出现的单词总数。

#include <iostream>
using namespace std; 
int main()
{
string str = "  Let's   count     the      number     of    words  ";
int word = 0;
for (int i = 0; str[i] != '';)
{
if ((str[i] == 32 && str[i + 1] == 32) || (str[i] == 32 && str[i - 1] == 32))
{
++i;
}
else if ((str[i] == 32 && str[i - 1] != 32) || (str[i] == 32 && str[i + 1] != 32))
{
word++;
}
++i;
}
cout << "No. of words: " << word << endl;
return 0;
}

我的错误结果:

No. of words: 0

此外,如果我尝试将字符串中的空格,甚至字符串本身更改为一组全新的间隔单词,比如:

string str = "   Hello world   ";
string str = "Hello    world! How   are you?   ";

我仍然得到不正确的结果,但与0不同。我是C++编程的新手,这些奇怪的行为让我做噩梦。这很常见吗?我能做些什么来纠正这个问题?

如果你能按照我写的方式突出显示或更正我的程序,这将对我很有帮助,也会让我很快理解错误,而不必在这一点上知道一些新命令。因为,正如我所说,我完全是C/C++的初学者。

谢谢你抽出时间!

我是C++编程的新手,这些奇怪的行为让我做噩梦。这很常见吗?

是的,这很常见。您已经编写了大量堆积在堆中的逻辑,但您没有工具来理解它的行为。

我能做些什么来纠正这个问题?

您可以从两个方向进行操作:

  1. 调试它以提高您对其操作方式的理解:

    • 在每一行预先确定您希望它对一些输入做什么
    • 在调试器中执行一步,看看它实际做了什么
    • 想想为什么它没有达到你的预期

    有时问题是你的代码没有正确实现你的算法,有时算法本身也被破坏了,通常两者兼而有之。两者兼而有之会让你有所领悟。

  2. 首先编写更容易理解的代码(同样,编写易于推理的算法)。

    这取决于你对是否容易推理有一些直觉,这是你从迭代步骤1中开发出来的。

。。。而不必在此时知道一些新命令。

好吧,无论如何,你都需要学会使用调试器,所以现在是开始的好时机。

我们当然可以改进现有的代码,尽管我更愿意修复逻辑。一般来说,我鼓励您将现有的if条件抽象为一些小函数,但问题是它们目前似乎没有任何意义。

那么,我们如何定义一个词呢?

你的代码说它至少是一个非空格字符,前面是或后面是一个空格。(顺便说一句,你肯定更喜欢' '而不是32std::isspace比任何一个都好。)

然而,您的代码的隐含定义是有问题的,因为:

  • 每一个超过一个字符的单词都有第一个和最后一个字符,您将计算每个字符
  • 在不越界的情况下,无法检查第一个字符前面是否有任何内容
  • 最后一个字符后面跟着null终止符,但不能将其视为空白

让我们选择一个不同的定义,它不需要读取str[i-1],也不需要当前代码出错的复杂遍历。

我声称一个单词是非空白字符的连续子串,单词由空白字符的相邻子串分隔。因此,我们可以编写伪代码来工作,而不是查看每对连续的字符:

for (current = str.begin(); current != str.end(); ) {
// skip any leading whitespace
current = find_next_non_whitespace(str, current);
if (current != str.end()) {
// we found a word
++words;
current = find_next_whitespace(str, current);
}
}

注:。当我谈到将代码抽象成小函数时,我指的是像find_next_non_whitespace这样的东西——它们应该实现起来很简单,易于测试,并且有一个告诉你一些东西的名称。

当我说你现有的条件似乎没有意义时,这是因为更换

if ((str[i] == 32 && str[i + 1] == 32) || (str[i] == 32 && str[i - 1] == 32))

比如说

if (two_consecutive_spaces(str, i))

提示的问题多于回答的问题。为什么恰好有两个连续空间的特殊情况?只是一个空间不同吗?如果我们有两个单词之间只有一个空格,实际会发生什么?为什么在这种情况下我们前进两个字符,而在单词分支上只有一个字符?

代码不能很容易地映射回可解释的逻辑,这是一个坏迹象——即使它有效(我们知道它不起作用),我们也不太了解它,无法对其进行更改、扩展或重构

我认为您有一些方法可以做到这一点。与您的非常相似:

string s = "  Let's   count     the      number     of    words  ";
int word = 0;
for (auto i = 0; s[i] != ''; i++) {
if (i == 0) {
if (s[i] != ' ') {
++word;
}
continue;
}
if (s[i - 1] == ' ' && s[i] != ' ') {
++word;
}
}
cout << "No of Words: " << word << endl;

其思想是对逐字符读取的字符串进行迭代。所以我们做了一些逻辑:

  • 如果我们在第一个字符串中,并且它等于",则转到下一个循环迭代
  • 如果我们在第一个字符串中,并且它与"不同,则意味着我们正在开始一个单词,因此计算它并跳到下一个循环迭代
  • 如果我们到达第二个if,则意味着我们不在第一个位置,因此尝试访问i - 1应该是有效的。然后我们只检查前一个字符是否是空白,而当前字符是否不是。这意味着我们要开始一个新词。所以计算它并跳到下一个循环迭代

另一种更简单的方法是使用字符串流:

string s = "  Let's   count     the      number     of    words  ";
stringstream ss(s);
string sub;
int word = 0;
while (ss >> sub) {
++word;
}
cout << "No of Words: " << word << endl;

通过这种方式,你基本上是从字符串中逐字逐句地提取。