C 中的嵌套 for 和 if 循环

Nested for and if loops in C

我一直在学习哈佛的 CS50 课程,我有一个关于嵌套 for 和 if 循环的设计和使用的问题。我已经提交了一个问题集,现在我只是在进行“post-mortem”,看看我是否可以更有效地编写它。特别是,我有一个接受 2 个参数的函数:用户的一段文本和一个 26 字母的密码密钥。然后通过用相应的密码密钥值替换每个字符,将明文转换为密文。

我想知道我在这里写的方式是否会被认为是糟糕的设计?多层的 for 和 if 循环对我来说似乎很笨重。 (特别是为每个字符遍历我的字母字符串,然后将该字符重新分配给相应的密码密钥,看起来很复杂?)

谢谢,

// function for converting plain text to cipher text
string substitution(string text, string cipher)
{
    string alphabet = ("abcdefghijklmnopqrstuvwxyz");
    
    
    // for each character:
    for (int i = 0, n = strlen(text); i < n; i++)
    {
        // check if character is in the alphabet:
        if ((text[i] >= 'a' && text[i] <= 'z') || (text[i] >= 'A' && text[i] <= 'Z'))
        {
            // find position in alphabet by index, then convert to same index in the cipher string
            for (int j = 0; j < 26; j++)
            {
                if (text[i] == alphabet[j])
                {
                    text[i] = cipher[j];
                    break;
                }
                if (text[i] + 32 == alphabet[j])
                {
                    text[i] = cipher[j] - 32;
                    break;
                }
            }
        }
    }
    return text;
}

你用它来检查 text[i] 是否是字母表而不是 isalpha(),所以我假设你将程序的目标环境限制为字母表的字符代码是连续的(如 ASCII) .

        if ((text[i] >= 'a' && text[i] <= 'z') || (text[i] >= 'A' && text[i] <= 'Z'))

在此限制下,您可以简单地减去'a''A'来获得字符的索引,而无需使用循环来查找字符。

这表示内循环部分

            for (int j = 0; j < 26; j++)
            {
                if (text[i] == alphabet[j])
                {
                    text[i] = cipher[j];
                    break;
                }
                if (text[i] + 32 == alphabet[j])
                {
                    text[i] = cipher[j] - 32;
                    break;
                }
            }

可以写成

            if (text[i] >= 'a' && text[i] <= 'z')
            {
                text[i] = cipher[text[i] - 'a'];
            }
            else
            {
                text[i] = cipher[text[i] - 'A'] - 32;
            }

您可以使用 strchr 函数压缩字符搜索,它...与您做的一样。那里的效率提高不多。

要计算索引,您可以使用直接减法:

// check if character is in the alphabet:
if ((text[i] >= 'a' && text[i] <= 'z') {
    j = text[i]-'a';
    text[i] = cipher[j];
} else if ((text[i] >= 'A' && text[i] <= 'Z') {
    j = text[i]-'A';
    text[i] = cipher[j] + 32;
}

您还可以预先计算所有 255 个可能的 char 值的 table(排除零),如果不能对字符进行加密,则将值设置为零,否则设置为密码值。

然后:

for (i = 0; text[i]; i++) {
    if (table[text[i]]) {
        text[i] = table[text[i]];
    }
}

如果您需要对长文本进行加密,这当然是有意义的,否则您在计算 table 上花费的时间比使用它要多。

此外,由于在许多体系结构上分支比简单分配更昂贵,请考虑 MikeCAT 的出色建议,即“非密码”字符被自己替换,因此 text[i] = table[text[i]] 实际上什么都不做:

for (i = 0; text[i]; i++) {
    text[i] = table[text[i]];
}

这样做应该更容易一些

string substitution(string text, string cipher)
{    
    // for each character:
    for (int i = 0, n = strlen(text); i < n; i++)
    {
        // check if character is in the alphabet:
        if (text[i] >= 'a' && text[i] <= 'z') 
        {
            text[i] = cipher[text[i] - 97]; // index of 'a' in alphabet is 97
        }
        else if (text[i] >= 'A' && text[i] <= 'Z'))
        {
            text[i] = cipher[text[i] - 65] - 32; // index of 'A' in alphabet is 65
        }
    }
    return text;
}

如果您要对字符代码顺序和关系(例如 upper/lowercase 转换)做出假设,那么 alphabet 数组就没有什么用处。它的唯一目的可能是将代码与平台定义的字符集排序分离。

在实践中,您的假设对于您可能遇到的任何平台都有效可能是一个公平的赌注,但如果您要定义 alphabet 那么您也许应该保持一致并使其余的独立于平台的代码 character-set。为此,您应该使用 ctype.h header。下面的代码做到了这一点,还消除了一些其他问题,例如“幻数”。

#include <string.h>
#include <ctype.h>
#include <stdbool.h>

// function for converting plain text to cipher text
string substitution(string text, string cipher)
{
    static const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
    static const size_t ALPHABET_LEN = sizeof(alphabet) / sizeof(*alphabet) ;
    
    // for each character:
    size_t text_len = strlen( text ) ;
    for( int i = 0; i < text_len; i++ )
    {
        // check if character is in the alphabet:
        if( isalpha( text[i] ) )
        {
            // find position in alphabet by index, then convert to same index in the cipher string
            for (int j = 0; j < ALPHABET_LEN; j++)
            {
                bool is_upper = isupper( text[i] ) ;
                if( !is_upper && text[i] == alphabet[j])
                {
                    text[i] = cipher[j];
                    break;
                }
                else if( is_upper && tolower(text[i]) == alphabet[j])
                {
                    text[i] = toupper( cipher[j] ) ;
                    break;
                }
            }
        }
    }
    
    return text;
}

这也许是一个见仁见智的问题,但许多编码标准只允许 break 作为 switch-case 分隔符。 breakcontinue 用于中止循环会产生糟糕且混乱的结构和控制流,并且会使调试变得更加复杂,因为控制流可以“跳过”break-points。避免使用 break:

        bool match = false ;
        for (int j = 0; !match && j < ALPHABET_LEN; j++)
        {
            bool is_upper = isupper( text[i] ) ;
            if( !is_upper && text[i] == alphabet[j])
            {
                text[i] = cipher[j];
                match = true ;
            }
            else if( is_upper && tolower(text[i]) == alphabet[j] )
            {
                text[i] = toupper( cipher[j] ) ;
                match = true ;
            }
        }