如何避免冗长的switch语句?C++

8
我正在为我的班级制作一个“字典”。我有一个名为NumOfWordsInFile[]的int数组,其中NumOfWordsInFile[0]对应于A.txt中的单词数,NumOfWordsInFile[25]对应于Z.txt中的单词数。
目前,我有一个巨大的switch语句处理26个不同字母的情况。我有一个名为AddWord(string word)的函数。AddWord获取传递给它的单词的第一个字母,并将其插入到相应的.txt文件中。现在问题来了,每次添加单词到A.txt时,我必须将NumOfWordsInFile[0]增加1。我能想到的唯一方法是使用这些巨大的switch语句。如果要删除单词,则相反地递减NumOfWordsInFile[]。现在,我不想有两个26个case的switch语句,但问题是我不知道还有其他方法。现在我可以为删除功能做同样的事情,但我真的不想再增加数百行代码。有更好的方法吗? AddWord函数中的switch示例:
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指出的那样),这是最好的解决方案。 - Mike Bailey
8个回答

7

很快看一下,似乎您正在使用字母在字母表中的位置来执行操作。

您可以将所有的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以及其他人发布的解决方案更适合您的具体示例。


1
在某些情况下,您决定添加额外的字符(如数字)时,您只需将第一行替换为一个接受字符并返回索引号的函数。 - swestrup
@swetstrup:没错。这是一个完美的不要重复自己(DRY)的例子。 - Mike Bailey

6
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.
1
@Matthieu M. 我希望如此。但是在任何程序员职业的前几年中,对“效率”和“聪明才智”的追求似乎都超越了一切。 - Erik

6

在使用C或C++时,你可能会遇到大多数实用的字符编码,'a''z'是连续的,所以你可以通过简单地执行(c - 'a')来获取要使用的数组索引,其中c是你正在查看的char


1
有很大的可能性,你的银行账户和保险记录是以非连续的英文字母编码(EBCDIC)存储的。然而,这些系统很可能是用COBOL编程的... - 6502
@6502:这不是问题,只要你的数组大小为('z'-'a'+1)。你只会在中间有一些未使用的条目。所有合理的字符编码都具有 z > a - MSalters
@MSalters:没错。我的评论只是关于“所有实际的”这个词……忽略了当今商业计算中严重比例使用的编码方式,我认为是不正确的。解决方案还可以(我没有发布一个因为已经有了),但是对字符编码的评论不正确。可能加上“在C编程的系统上”会更好……据我所知,通常EBCDIC机器是用其他语言编程的(例如,它们在“{”上遇到麻烦)。 - 6502

3
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);
}

只有在您的用例中仅使用英文字母时,此方法才有效。


3

字符基本上就是数字。'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);
    }
   }

如果您需要更一般的解决方案,请了解哈希映射和函数指针(例如,对于每个字符,您可能想要分配不同的函数)。


2

C++中的单个字符实际上只是对应其ASCII值的数字。您可以将字母相减以获得数值。因此,如果word[0]包含字母A,则word[0] - 'A'将为0

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

请注意,'A'和'a'具有不同的数字值,因此如果您混合使用大写和小写字母,则需要进行一些额外的工作。


1

这取决于您希望有多可移植或多国际化。如果您可以忽略第一个字母可能是带重音符号的字符的情况,并且假设您永远不会在主机或使用EBCDIC的任何其他地方上运行,则可以将第一个字母转换为特定大小写,并从中减去'a'或'A'(取决于大小写)以获得索引。但是,C++标准不能保证字母是连续的,它们也不在EBCDIC中,也不在任何支持带重音符号的字符的编码中。当然,您最起码需要测试第一个字符是否为字母。

处理国际化问题很困难,因为没有一个通用的编码方式,而且一些编码方式是多字节的。对于单字节编码,使用映射表相对简单;一个包含256个条目的表,由第一个字母(转换为无符号字符)索引,返回到您的表中的索引。对于像UTF-8这样的多字节编码,问题更加复杂:您可以将UTF-8序列中的初始字符转换为int,但可能会得到一百万或更多的值,您不希望有一个包含数百万条目的表(其中大部分都是完全无关的)。一个简单的解决方案可能是添加第27个条目以表示“其他”。(这也可以捕获像“2nd”这样的“单词”)。

一个非常便携的方法是:

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

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


1
如果你的文件是 A.txt,让你的数组索引为 'A' - 'A'(= 0),如果文件是 B.txt,则让数组索引为 'B' - 'A'(= 1),以此类推。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接