【问题标题】:Program don't encrypt the way I want (C language). From CS-50 lesson Vegenere程序不会按照我想要的方式加密(C 语言)。来自 CS-50 课 Vegenere
【发布时间】:2022-07-14 23:27:55
【问题描述】:

程序必须通过命令行中的密钥加密明文。

如果 p 是一些明文,k 是关键字(即字母字符串,其中 A(或 a)表示 0,B(或 b)表示 1,C(或 c)表示 2,……,Z(或 z) 表示 25),则密文 c 中的每个字母 ci 计算如下:

ci = (pi + kj) % 26

注意这个密码使用 kj 而不是 k。如果 k 比 p 短,那么 k 中的字母必须循环重复使用尽可能多的次数来加密 p。

换句话说,如果 Vigenère 自己想秘密地向某人打招呼,使用 ABC 等关键字,他会用 0 的密钥(即 A)加密 H,用 1 的密钥加密 E (即 B)和第一个 L 的密钥为 2(即 C),此时他将在关键字中没有字母,因此他将重用(部分)它来加密第二个L 再次带有 0 键(即 A),O 再次带有 1 键(即 B)。因此,他将 HELLO 写为 HFNLP,如下所示: 这样:

plaintext       H   E   L   L   O

+key            A   B   C   A   B

(shift value)   0   1   2   0   1

= ciphertext    H   F   N   L   P

例如:

$ ./vigenere bacon

plaintext:  Meet me at the park at eleven am

ciphertext: Negh zf av huf pcfx bt gzrwep oz

我的情况:

键: baz

明文: barfoo

预期: caqgon

我的结果: caqfgv

我的代码:

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

// Functions:
bool check_arguments(int argc);
bool is_key_alpha(string key);
int shift(char c);

int main(int argc, string argv[])
{

    if (check_arguments(argc) == false)
    {
        return 1;
    }

    // Declaring key string variable:
    string key = argv[1];

    // Check containing any character that is not an alphabetic character
    if (is_key_alpha(key) == false)
    {
        return 1;
    }

    // Prompting user for plaintext:
    string plaintext = get_string("plaintext: ");
    
    // Ecipher:
    printf("ciphertext: ");
    for (int i = 0; i < strlen(plaintext); i++)
    {
        if (islower(plaintext[i]))
        {
            printf("%c", ((plaintext[i]) - 97 + shift(key[i])) % 26 + 97);
        }
        else if (isupper(plaintext[i]))
        {
            printf("%c", ((plaintext[i]) - 65 + shift(key[i])) % 26 + 65);
        }
        else
        {
            printf("%c", plaintext[i]);
        }
    }
    printf("\n");

    return 0;
}

// FUNCTIONS :

// Checking if there's more than one command-line argument
// Checking if the command-line argument exists:
bool check_arguments(int argc)
{
    // Checking if there's more than one command-line argument
    if (argc > 2)
    {
        printf("Usage: ./vigenere keyword\n\n");
        return false;
    }
    // Checking if the command-line argument exists:
    else if (argc < 2)
    {
        printf("Usage: ./vigenere keyword\n");
        return false;
    }
    // If okey:
    else
    {
        return true;
    }
}

// containing any character that is not an alphabetic character
bool is_key_alpha(string key)
{
    for (int i = 0; i < strlen(key); i++)
    {
        if (isalpha(key[i]) == false)
        {
            printf("Key contains non-alphabetical chars");
            return false;
        }
    }
    return true;
}

// convert character into the correct shift value
int shift(char c)
{ // for ex. char = a == 97 ascii
    if (isalpha(c))
    {
        if (isupper(c))
        // The ASCII value of A is 65
        {
            c = c - 65;
        }
        else if (islower(c))
        // The ASCII value of a is 97
        {
            c = c - 97;
        }
    }
    else
    {
        return c;
    }
    return c;
}

【问题讨论】:

  • 请注意,key[i] 在您的示例中 i &gt;= 4 时未定义。你的意思可能是i%strlen(plaintext) 或什么的
  • 巴里走在正确的轨道上。它 UB。在我的系统上,我得到了:caqflr 将:shift(key[i]) 更改为 shift(key[i % strlen(key)])
  • 旁注: for (int i = 0; i &lt; strlen(plaintext); i++) 需要二次时间 (O(n^2)) 来执行。将其替换为:for (int i = 0; plaintext[i] != 0; i++),这只是 O(n)

标签: c for-loop encryption cs50


【解决方案1】:

巴里走在正确的轨道上。是UB。在我的系统上,我得到了:caqflr

如果i 达到key 的长度,则key[i] 超出key 的末尾

变化:

shift(key[i])

进入:

shift(key[i % strlen(key)])

这修复了错误。

但是,代码比它需要的要复杂。这可能是问题难以发现的原因之一。

另外,代码运行缓慢。


在循环字符串时使用strlen 会将运行时间从 O(n) 更改为 O(n^2)。那是因为strlen 在每次循环迭代时都会重新扫描字符串。

因此,要解决此问题,请更改(例如):

for (int i = 0;  i < strlen(plaintext);  ++i)

进入:

for (int i = 0;  plaintext[i] != 0;  ++i)

此外,在这样的循环中,有很多使用plaintext[i] 的重复代码。尽管优化器会理解这一点并生成快速代码,但如果我们使用额外的变量来包含当前值,则可以简化它:

for (int i = 0, chr = plaintext[i];  chr != 0;  chr = plaintext[++i])

现在,我们可以使用chr,而不是在循环中使用plaintext[i]


key 的长度是不变的。因此,我们可以预先计算该值并直接使用它,而不是在 plaintext 循环的每次迭代中重新计算它。


避免使用“幻数”:

  1. 当你想要 'A' 时使用它而不是 65
  2. 当你想要 'a' 时使用它而不是 97

另外,shift 可以改进/消除:

  1. 因为is_key_alphaisalpha 进行检查,所以key 保证 只是字母字符。所以,不需要shift 使用isalpha
  2. 我们不需要保留原来的key 值,只需要修改后的值。也就是说,我们可以将key 中的每个值替换为shift 的结果
  3. 换句话说,我们只需要在初始化期间对每个键执行一次移位操作。我们可以让is_key_alpha 这样做。

这是重构后的代码:

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

// Functions:
bool check_arguments(int argc);
bool is_key_alpha(string key);
int shift(char c);

int
main(int argc, string argv[])
{

    if (check_arguments(argc) == false) {
        return 1;
    }

    // Declaring key string variable:
    string key = argv[1];

    // get the length of the key
    // (1) cache it so we don't need to call strlen(key) in the loop (it's
    //     invariant)
    // (2) because is_key_alpha transforms the key, calling strlen on key
    //     will no longer be valid
    int klen = strlen(key);

    // Check containing any character that is not an alphabetic character
    if (is_key_alpha(key) == false)
        return 1;

    // Prompting user for plaintext:
    string plaintext = get_string("plaintext: ");

    // Ecipher:
    printf("ciphertext: ");
    for (int i = 0, chr = plaintext[i];  chr != 0; chr = plaintext[++i]) {
        int k = key[i % klen];

        if (islower(chr)) {
            chr = ((chr - 'a') + k) % 26 + 'a';
        }
        else if (isupper(chr)) {
            chr = ((chr - 'A') + k) % 26 + 'A';
        }

        printf("%c", chr);
    }
    printf("\n");

    return 0;
}

// FUNCTIONS :

// Checking if there's more than one command-line argument
// Checking if the command-line argument exists:
bool
check_arguments(int argc)
{
    // Checking if there's more than one command-line argument
    if (argc > 2) {
        printf("Usage: ./vigenere keyword\n\n");
        return false;
    }
    // Checking if the command-line argument exists:
    else if (argc < 2) {
        printf("Usage: ./vigenere keyword\n");
        return false;
    }
    // If okey:
    else {
        return true;
    }
}

// containing any character that is not an alphabetic character
bool
is_key_alpha(string key)
{

    // do what shift does and convert the key into offsets
    for (int i = 0, chr = key[i];  chr != 0; chr = key[++i]) {
        if (isupper(chr)) {
            key[i] = chr - 'A';
            continue;
        }

        if (islower(chr)) {
            key[i] = chr - 'a';
            continue;
        }

        printf("Key contains non-alphabetical chars");
        return false;
    }

    return true;
}

// convert character into the correct shift value
int
shift(char c)
{                                       // for ex. char = a == 97 ascii

    // The ASCII value of A is 65
    // The ASCII value of a is 97

    // NOTES:
    // (1) is_key_alpha guarantees that the key value is an alpha
    // (2) now that is_key_alpha computes the offset, this function is obsolete

    if (isupper(c))
        c -= 'A';
    else if (islower(c))
        c -= 'a';

    return c;
}

我们可以进一步加强这一点。 shift 不再需要。而且,我们可以让is_key_alpha 返回密钥长度(-1 是错误)。现在,它改变了键,我们可以重命名它以更描述它的功能

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

// Functions:
bool check_arguments(int argc);
int key_transform(string key);

int
main(int argc, string argv[])
{

    if (check_arguments(argc) == false) {
        return 1;
    }

    // Declaring key string variable:
    string key = argv[1];

    // check and transform the key
    int klen = key_transform(key);
    if (klen < 0)
        return 1;

    // Prompting user for plaintext:
    string plaintext = get_string("plaintext: ");

    // Ecipher:
    printf("ciphertext: ");
    for (int i = 0, chr = plaintext[i];  chr != 0; chr = plaintext[++i]) {
        int k = key[i % klen];

        if (islower(chr)) {
            chr = ((chr - 'a') + k) % 26 + 'a';
        }
        else if (isupper(chr)) {
            chr = ((chr - 'A') + k) % 26 + 'A';
        }

        printf("%c", chr);
    }
    printf("\n");

    return 0;
}

// FUNCTIONS :

// Checking if there's more than one command-line argument
// Checking if the command-line argument exists:
bool
check_arguments(int argc)
{
    // Checking if there's more than one command-line argument
    if (argc > 2) {
        printf("Usage: ./vigenere keyword\n\n");
        return false;
    }
    // Checking if the command-line argument exists:
    else if (argc < 2) {
        printf("Usage: ./vigenere keyword\n");
        return false;
    }
    // If okey:
    else {
        return true;
    }
}

// containing any character that is not an alphabetic character
// RETURNS: key length (-1=error)
int
key_transform(string key)
{

    // do what shift does and convert the key into offsets
    int i = 0;
    for (int chr = key[i];  chr != 0;  chr = key[++i]) {
        if (isupper(chr)) {
            key[i] = chr - 'A';
            continue;
        }

        if (islower(chr)) {
            key[i] = chr - 'a';
            continue;
        }

        printf("Key contains non-alphabetical chars");
        return -1;
    }

    return i;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-19
    • 2020-12-13
    • 1970-01-01
    • 1970-01-01
    • 2011-12-09
    • 1970-01-01
    • 2021-02-03
    • 2010-10-27
    相关资源
    最近更新 更多