CS50 Vigenere,代码快完成了,但我不知道还缺少什么?

CS50 Vigenere, code is almost done but I don't know what's missing?

#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

int main(int argc, string argv[])
{
    // two arguments
    if (argc != 2)
    {
        printf("Give two arguments\n");
        return 1;
    }
    printf("plaintext: ");
    string plaintext = get_string();
    printf("ciphertext: ");

    string key = argv[1];

    for (int i = 0, t = 0, n = strlen(plaintext); i < n; i++, t++)
    {
        // if it's no letter, then:

        if (!isalpha(plaintext[i]) && plaintext[i] != ' ')
        {
            printf("False");

            return 1;

        }

        int number = 0;

        if (isalpha(plaintext[i]))
        {
            number += 1;
        }

        if (strlen(key) > number)
        {
            number = 0;
        }


        if (isupper(plaintext[i]))
        {
            printf("%c", (((plaintext[i] - 65) + key[number]) % 26) + 65);
        }

        //if it is lowercase
        else if (islower(plaintext[i]))
        {
            printf("%c", (((plaintext[i] - 97) + key[number]) % 26) + 97);
        }

        else
        {
            printf("%c", plaintext[i]);
        }

    }
    printf("\n");
}

所以我的代码中缺少一些东西。当我执行 ./vigenere baz 然后键入明文时:Hello, world!,我得到密文:ByffiFalse。我应该得到 iekmo, vprke! 另外,当我输入 ./vigenere hello,然后输入 bye 作为明文时,我也会得到密文 bye,而它应该是 icp .有人可以找出我的代码缺少什么或有什么问题吗?

您的代码最大的两个问题是计算正确的密钥差异值(您不是)和密钥推进​​。我会倒着讲。

按键推进应从第一个按键字符开始,然后随着每个正在处理的纯文本逐一推进。当键位置到达字符串末尾时,它会重新启动。最基本的伪代码是

char *keyp = argv[1];

for (loop through plainttext)
{
    if (*keyp == 0) // reached the terminator ?
        keyp = argv[1]; // then reset to beginning.

   //... process the current plain text character, using *keyp
   //...  as the next key character to use.

   // advance key to next position (possibly conditionally)
   ++keyp;
}

但是您的代码没有这样做。相反,它会立即推进密钥,这意味着您从 second 字符开始。

int number = 0;

if (isalpha(plaintext[i]))
{
    number += 1; // HERE. first pass will use key[1]. it should be key[0]
}

if (strlen(key) > number) // this is backward
{
    number = 0;
}

其次,也许更重要的是,如果 Vigenere 密码有效地使用正方形阴影,则整个要点 table。 See this link for a picture of that。您正在编码的算法的要点是 act 就像 table 使用数学存在一样。偏移量很重要part.When 你做这个计算:

(((plaintext[i] - 65) + key[number]) % 26) + 65

实际上应该是这样的:

(((plaintext[i] - 'A') + key[number]) % 26) + 'A'

考虑添加关键字符的作用。举个例子:

key: baz
plaintext: Hello, World!

您计算出的第一个密文字符为:

((('H' - 'A') + 'a') % 26) + 'A'

注意:'a' 在那里是因为你的第一关被一个打破了,记得吗? 压缩如下

(((7) + 97) % 26) + 'A'
((105) % 26) + 'A'
(1 % 26) + 'A'
1 + 'A'
'B'

这正是您得到的。但这是错误的。这是错误的,因为这是错误的:

(((plaintext[i] - 'A') + key[number]) % 26) + 'A'
                         ^^^^^^^^^^^

这是输入字符的原始 ascii 值。它应该是 1..26 之间的计算值。简而言之,您没有正确调整按键输入。

假设解

以下假定密钥始终为小写。它还修复了您的 first-skip 逻辑,并使用 cs50.h 解耦(坦率地说,我认为弊大于利)。最后,它使用 `char* 来跟踪下一个要使用的关键字符。我把支持混合大小写输入键的任务留给你:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main(int argc, char *argv[])
{
    // two arguments
    if (argc != 2)
    {
        printf("Give two arguments\n");
        return 1;
    }

    printf("plaintext: ");
    char pt[256] = { 0 };
    if (fgets(pt, sizeof pt, stdin))
    {
        // get the plaintext length
        size_t ptlen = strlen(pt);

        // remove trailing newline if present, and adjust ptlen
        if (ptlen > 0 && pt[ptlen - 1] == '\n')
            pt[--ptlen] = 0;

        // the key we're using. intially at the start
        char *key = argv[1];

        for (size_t i = 0; i < ptlen; ++i)
        {
            // reset key if prior iteration landed on terminator
            if (!*key)
                key = argv[1];

            if (isalpha((unsigned char)pt[i]))
            {
                if (isupper((unsigned char)pt[i]))
                {
                    printf("%c", (((pt[i] - 'A') + (*key-'a')) % 26) + 'A');
                    ++key;
                }

                //if it is lowercase
                else if (islower((unsigned char)pt[i]))
                {
                    printf("%c", (((pt[i] - 'a') + (*key-'a')) % 26) + 'a');
                    ++key;
                }
                else
                {
                    fputc(pt[i], stdout);
                }
            }
            else
            {
                fputc(pt[i], stdout);
            }
        }

        fputc('\n', stdout);
    }
    else
    {
        perror("Failed to read string");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;

}

来自./progname baz

的输出
plaintext: Hello, World!
Iekmo, Vprke!
  1. 所有非字母字符(不仅仅是空格)都应该在不编码的情况下跳过。不要打印 "False" 和 return,例如 "Hello, world!" 字符串中的 ',' 符号。此外,您可以就地编码字符串。因此,主循环可能看起来像
printf("plaintext: ");
string s = GetString();
if (s == NULL)
    return 1;

for (int i = 0, len = strlen(s); i < len; ++i) {
    if (isalpha(s[i])) {
        /* encode s[i] in-place,
         * all non-alpha characters left as is
         */
    }
}

printf("ciphertext: %s\n", s);
  1. 关键字符也应该是"shifted"。例如,对于大写字母
s[i] = ((s[i] - 'A') + (key[n] - 'A') % 26) + 'A';
if (++n >= keylen)
    n = 0;

我建议在主循环之前规范化键,这样您就可以对输入字符串中的小写和大写字符使用(key[n] - 'A')

string key = argv[1];
strupper(k);
int keylen = strlen(key);
int n = 0;

虽然我不想提供完整的代码,因为这是你的课程,但我认为你自己做会更好。但是……一些作品:

strupper函数:

void strupper(string s)
{
    for (int i = 0, n = strlen(s); i < n; ++i)
        s[i] = toupper(s[i]);    
}

紧凑主循环:

for (int i = 0, n = strlen(s); i < n; ++i) {
    if (isalpha(s[i])) {
        char ref = isupper(s[i]) ? 'A' : 'a';
        int shift = k[j] - 'A';
        s[i] = ref + (s[i] - ref + shift) % 26;
        if (++j >= klen) j = 0;
    }
}

p.s。由于 int number = 0;for 循环中定义和归零,您对所有输入字符使用相同的键字符。