【问题标题】:How to avoid long switch statements? C++如何避免长开关语句? C++
【发布时间】:2011-04-03 23:13:07
【问题描述】:

我正在为我的班级编写“词典”。我有一个名为 NumOfWordsInFile[] 的 int 数组,其中 NumOfWordsInFile[0] 对应于 A.txt 中有多少个单词,NumOfWordsInFile[25] 对应于 Z.txt

现在我对 26 种不同的字母条件进行了巨大的切换。我有一个名为AddWord(string word) 的函数。 AddWord 获取传递给它的单词的第一个字母并将其插入到适当的 .txt 文件中。现在问题来了。每次在 A.txt 中添加一个单词时,我都必须将 NumOfWordsInFile[0] 增加 1。我能想到的唯一方法是使用这些巨大的开关。我还有一个 deleteWord 函数,如果这个词被删除,它会减少 NumOfWordsInFile[]。现在我不想有两个 26 箱开关,但问题是我不知道该怎么做。现在我可以对删除函数做同样的事情,但我真的不想有数百行代码要经过。有没有更好的方法来做到这一点?

AddWord 函数中的开关示例:

case 'w':
    if (numOfWordsInFile[22] < maxWordsPerFile) {
        fout.open(fileName.data(), ios::app);
        fout << word << " " << endl;
        numOfWordsInFile[22]++;
        if (totalWordsInDict < maxWordsInDict) {
            totalWordsInDict++;
        }
        return(Dictionary::success);
    } else {
        return(Dictionary::failure);
    }

case 'x':
    if (numOfWordsInFile[23] < maxWordsPerFile) {
        fout.open(fileName.data(),ios::app);
        fout << word << " " << endl;
        numOfWordsInFile[23]++;
        if (totalWordsInDict < maxWordsInDict) {
            totalWordsInDict++;
        }
        return(Dictionary::success);
    } else {
        return(Dictionary::failure);
    }

删除函数。

bool Dictionary::DeleteAWord(string word)
{
    ofstream fout;
    ifstream fin;
    string x;
    string fileName="#.txt";
    int count=0;
    vector <string> words;
    bool deleted=false;

    fileName[0]=toupper(word[0]);
    fin.open(fileName.data()); //makes the file depending on the first letter of the argument "word"

    while (fin >> x)
    {
        words.push_back(x);
        count++;//number of elements in vector
    }
    if (SearchForWord(x))
    {
        for ( ;count > 0; count--)
        {
            if (words[count-1] == word)
            {
                // cout << "Found word " << word << " during search, now deleting" << endl;
                words.erase(words.begin()+(count-1));
                deleted = true;

                /*
                    This clearly doesn't work and is what I need help with, I know why it
                    doesn't work but I don't know how to make it better than having another
                    huge switch.
                */
                numOfWordsInFile[toupper(word[0])]--;
                /*

                */

                totalWordsInDict--;
                fin.close();
            }
        }

        if (deleted)
        {
            fout.open(fileName.data());
            for (int i = 0; i < words.size(); i++)
                fout << words[i] << endl;
            return(Dictionary::success);
        }
        return(Dictionary::failure);
    }
    return(Dictionary::failure);
}

【问题讨论】:

  • 你有你的答案。几乎每个人都同意(有充分的理由)使用字母 a-z 的连续布局来完成你想要的。只要您只使用英文字符(正如 RedX 指出的那样),这是最好的解决方案。

标签: c++ performance switch-statement


【解决方案1】:

快速浏览一下,您似乎正在使用字母在字母表中的位置来做事。

您可以将所有 switch 语句替换为如下语句:

int letter = (int)(ActualLetter - 'a');

if(numOfWordsInFile[letter]<maxWordsPerFile){
 fout.open(fileName.data(),ios::app);
 fout<<word<<" "<<endl;
 numOfWordsInFile[letter]++;
 if(totalWordsInDict<maxWordsInDict){
   totalWordsInDict++;
 }
 return(Dictionary::success);
}else{
 return(Dictionary::failure);
}

例如,ActualLetter 类似于“a”。

在相关说明中,如果您将来确实有大型 switch 语句,请考虑将代码封装在函数中:

switch (letter)
{
    case 'a':
      LetterA();
      break;

    case 'b':
      LetterB();
      break;

    ...
}

或者更好的是,您可以使用多态性让 C++ 根据特定的派生类分派到您想要的方法:

class BaseLetter
{
   ...
public:
   virtual void DoStuff() = 0;
};

class LetterA : public BaseLetter
{
public:
   void DoStuff();
};

class LetterB : public BaseLetter
{
public:
    void DoStuff();
};

void Foo(BaseLetter *letter)
{
    // Use dynamic dispatch to figure out what to do
    letter->DoStuff();
}

请注意,动态调度确实对性能有(轻微的)影响,而上面的实际使用它是一个非常糟糕的地方。我、RedX 和其他人发布的解决方案更适合您的具体示例。

【讨论】:

  • 如果您决定添加额外的字符(例如数字),那么您只需将第一行替换为一个接收字符并返回索引号的函数。
  • @svetstrup:没错。这是不要重复自己的完美例子。
【解决方案2】:
struct FileInfo {
  int NumWords;
  std::string Filename;
};

std::map<char, FileInfo> TheFiles; 

FileInfo & FI = TheFiles[letter];
// Work with FI.NumWords and FI.Filename

或者:

std::vector<FileInfo> TheFiles;
FileInfo & FI = TheFiles[std::tolower(Letter) - 'a'];

【讨论】:

  • 最后一个理智的回应:最简单的任务数据结构不是由数据结构的简单程度来定义,而是由交互的简单程度来定义(对于这个任务)是。避开表述,专注于可用的方法。
  • @Matthieu M. 我希望。但在任何程序员职业生涯的头几年,对“效率”和“聪明”的追求似乎压倒了一切。
【解决方案3】:

在使用 C 或 C++ 时可能会遇到的大多数实际字符编码中,'a''z' 是连续的,因此您只需执行 (c - 'a') 即可获得要使用的数组索引,其中 @987654324 @ 是您正在查看的 char

【讨论】:

  • 您的银行账户和保险记录很可能使用英文字母不连续的编码 (EBCDIC) 存储。然而,这些系统很可能是用 COBOL 编程的......
  • @6502:这不是问题,只要你的数组有大小('z'-'a'+1)。您只会在中间有一些未使用的条目。所有健全的字符编码都有z > a
  • @MSalters:是的。我的评论只是关于“所有实用”这个词......忽略今天完成大部分商业计算的编码在我看来是不正确的。解决方案是好的(我没有发布一个,因为它存在),但是关于字符编码的评论不是。可能添加“在用 C 编程的系统上”会更好...... AFAIK 通常 EBCDIC 机器是用其他语言编程的(例如,他们在使用“{”时遇到了麻烦)。
【解决方案4】:

字符基本上是数字。 'a' 是 97,'b' 是 98 等等。 最简单的方法是简单地将每个numOfWordsInFile[n] 替换为numOfWordsInFile[current_char - 'a'],并且为每种情况重复的整个代码可能驻留在一个函数中,如下所示:

   int AddWord(char current_char) {
    if(numOfWordsInFile[current_char - 'a']<maxWordsPerFile){
     fout.open(fileName.data(),ios::app);
     fout<<word<<" "<<endl;
     numOfWordsInFile[current_char - 'a']++;
      if(totalWordsInDict<maxWordsInDict){
       totalWordsInDict++;
     }
     return(Dictionary::success);
    }else{
     return(Dictionary::failure);
    }
   }

有关更通用的解决方案,请阅读散列映射和函数指针(例如,当您可能希望为每个 char 分配不同的函数时。

【讨论】:

    【解决方案5】:
    if(numOfWordsInFile[letter - 'A']<maxWordsPerFile){
     fout.open(fileName.data(),ios::app);
     fout<<word<<" "<<endl;
     numOfWordsInFile[letter - 'A']++;
     if(totalWordsInDict<maxWordsInDict){
       totalWordsInDict++;
     }
     return(Dictionary::success);
    }else{
     return(Dictionary::failure);
    }
    

    这只有在你的用例中只有英文字母时才有效。

    【讨论】:

      【解决方案6】:

      C++ 中的单个字符实际上只是对应于它们的 ASCII 值的数字。您可以将字母彼此相减以获得数值。所以如果word[0] 包含字母A,那么word[0] - 'A' 将是0

      因此您可以直接索引您的 numOfWordsInFile 数组,而您根本不需要开关:numOfWordsInFiled[word[0] - 'A']

      请注意,'A' and 'a' 具有不同的数值,因此如果要混合大小写,则必须做一些额外的工作。

      【讨论】:

        【解决方案7】:

        如果你的文件是A.txt,让你的数组索引是'A' - 'A'(= 0),如果文件是B.txt,让数组索引是'B' - 'A'(= 1),等等

        【讨论】:

          【解决方案8】:

          这取决于您想要的便携性,或者如何 国际化。如果你有能力忽略这种可能性 第一个字母可能是重音字符,并假设 你永远不会在大型机或任何地方运行 否则使用 E​​BCDIC,那么您可以将第一个字母转换为 特定情况,并减去“a”或“A”(视情况而定) 从中获取索引。 C++ 标准不保证 但是这些字母是连续的,并且它们不在 EBCDIC,也不在任何支持重音的编码中 人物。至少,您必须测试 当然,第一个字符是字母。

          处理国际化问题很困难,因为 没有一种普遍使用的编码,而且有些 编码是多字节的。对于单字节编码,它是 相当直接地使用映射表;一张桌子 256 个条目,由第一个字母索引(强制转换为无符号 char),它将索引返回到您的表中。对于多字节 编码,如 UTF-8,问题更复杂:你可以 将 UTF-8 序列中的初始字符转换为 int, 但你最终可以得到大约一百万或更多的价值,你 不想要一个包含一百万个条目的表(其中大部分是 完全无关。一种简单的解决方案可能是添加 “其他”的第 27 个条目。 (这也会捕捉到“单词”,如 “第二”。)

          一种非常便携的方法是:

          int mappingTable[256];
          
          std::fill_n(mappingTable, 256, 26);
          static char const upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ;
          static char const lower[] = "abcdefghijklmnopqrstuvwxyz;
          for (int i = 0; i < 26; ++ i) {
              mappingTable[upper[i]] = i;
              mappingTable[lower[i]] = i;
          }
          

          只是不要忘记将初始字符转换为无符号字符 索引之前。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-10-01
            • 1970-01-01
            • 1970-01-01
            • 2017-03-11
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多