不确定为什么 toupper() 会切断 C 中的最后一个字母

Unsure as to why toupper() is cutting off last letter in C

所以这个程序的目标是基本上在终端中获取一个 26 字母 'key'(通过 argv[])并使用其索引作为替换指南。因此,您在终端中输入了 2 个输入,一个在 argv[] 中,一个只是普通的 get_string() 输入。 argv[] 输入将如下所示: ./s YTNSHKVEFXRBAUQZCLWDMIPGJO 其中 s 是文件名。然后 get_string() 输入将如下所示:plaintext: HELLO。 (输入为 HELLO)。程序接下来要做的是遍历明文输入中的所有字母,并根据 argv[] 键的索引替换其字母索引。例如,H 的字母索引为 7(其中 a = 0 且 z = 25),因此我们查看键 [=28] 中的第 7 个索引=] 在这种情况下是 E。它对输入中的每个字母都这样做,我们将得到输出 ciphertext: EHBBQ。这是它在终端中的样子:

./s YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext:  HELLO
ciphertext: EHBBQ

但是我的输出是 EHBB,因为当我使用 toupper().

时由于某种原因它切断了最后一个字母

另外,大小写取决于明文输入,如果明文输入为hello, worldargv[]键为YTNSHKVEFXRBAUQZCLWDMIPGJO,则输出为jrssb, ybwsp,如果输入是 HellO, world 并使用相同的键,则输出将是 JrssB, ybwsp.

我基本上解决了这个问题,我的程序根据通过命令行输入的密钥将给出的明文替换为正确的密文。现在,假设明文输入是 HELLO,密钥是 vchprzgjntlskfbdqwaxeuymoi(全部小写),那么它应该是 return HELLO 而不是 hello。这是因为我的程序将命令行键中的所有字母放入一个长度为 26 的数组中,然后循环遍历所有明文字母并将其 ascii 值(减去某个数字使其进入 0-25 索引范围)与键中的索引。所以 E 的字母索引为 4,所以在这种情况下我的程序会变成小写 p,但我需要它是 P,所以这就是我使用 toupper() 的原因].

当我使用 tolower() 时,一切正常,一旦我开始使用 toupper()ciphertext 的最后一个字母由于某种原因被截断了。这是我在使用 toupper():

之前的输出
ciphertext: EHBBQ

这是我使用 toupper() 后的输出:

ciphertext: EHBB

这是我的代码:

int main(int argc, string argv[]) {
    string plaintext = get_string("plaintext: ");
    
    // Putting all the argv letters into an array called key
    char key[26]; // change 4 to 26
    for (int i = 0; i < 26; i++) // change 4 to 26
    {
        key[i] = argv[1][i];
    }
    
    // Assigning array called ciphertext, the length of the inputted text, to hold cipertext chars
    char ciphertext[strlen(plaintext)];
    
    // Looping through the inputted text, checking for upper and lower case letters
    for (int i = 0; i < strlen(plaintext); i++)
    {
        // The letter is lower case
        if (islower(plaintext[i]) != 0)
        {
            int asciiVal = plaintext[i] - 97; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            char l = tolower(key[asciiVal]); // tolower() works properly
            //printf("%c", l);
            strncat(ciphertext, &l, 1); // Using strncat() to append the converted plaintext char to ciphertext
        }
        // The letter is uppercase
        else if (isupper(plaintext[i]) != 0)
        {
            int asciiVal = plaintext[i] - 65; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            char u = toupper(key[asciiVal]);  // For some reason having this cuts off the last letter 
            strncat(ciphertext, &u, 1); // Using strncat() to append the converted plaintext char to ciphertext
        }
        // If its a space, comma, apostrophe, etc...
        else
        {
            strncat(ciphertext, &plaintext[i], 1);
        }
    }
    
    // prints out ciphertext output
    printf("ciphertext: ");
    for (int i = 0; i < strlen(plaintext); i++)
    {
        printf("%c", ciphertext[i]);
    }
    printf("\n");
    printf("%c\n", ciphertext[1]);
    printf("%c\n", ciphertext[4]);
    //printf("%s\n", ciphertext);
    return 0;
}

strncat 函数期望它的第一个参数是它追加的以空字符结尾的字符串。您在未初始化时用 ciphertext 调用它。这意味着您正在读取未初始化的内存,可能读取到数组末尾,触发 undefined behavior.

在调用 strncat 之前,您需要将 ciphertext 设为空字符串。此外,您需要将此数组的大小加 1 以说明已完成字符串上的终止空字节,以防止注销它的末尾。

char ciphertext[strlen(plaintext)+1];
ciphertext[0] = 0;

代码中存在多个问题:

  • 你没有测试命令行参数的存在和长度
  • 应该为数组分配 1 个额外字节作为空终止符,并初始化为空字符串,以便 strncat() 正常工作。
  • 不要硬编码 ASCII 值,例如 9765,而是使用字符常量,例如 'a''A'
  • strncat() 对您的目的来说太过分了。你可以只写 ciphertext[i] = l; 而不是 strncat(ciphertext, &l, 1)
  • islower()isupper() 仅针对 unsigned char 类型的正值和特殊的负值 EOF 定义。您应该将 char 参数转换为 (unsigned char)c 以避免在 char 恰好是签名类型的平台上非 ASCII 字节出现未定义的行为。
  • 避免冗余测试,例如islower(xxx) != 0。只写 if (islower(xxx))
  • 更为惯用

这是修改后的版本:

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

int main(int argc, string argv[]) {
    // Testing the argument
    if (argc < 2 || strlen(argv[1]) != 26) {
        printf("invalid or missing argument\n");
        return 1;
    }
    // Putting all the argv letters into an array called key
    char key[26];
    memcpy(key, argv[1], 26);
    
    string plaintext = get_string("plaintext: ");
    int len = strlen(plaintext);
    
    // Define an array called ciphertext, the length of the inputted text, to hold ciphertext chars and a null terminator
    char ciphertext[len + 1];
    
    // Looping through the inputted text, checking for upper and lower case letters
    for (int i = 0; i < len; i++) {
        unsigned char c = plaintext[i];

        if (islower(c)) {        // The letter is lower case
            int index = c - 'a'; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            ciphertext[i] = tolower((unsigned char)key[index]);
        } else
        if (isupper(c)) {
            // The letter is uppercase
            int index = c - 'A'; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            ciphertext[i] = toupper((unsigned char)key[index]);
        } else {
            // other characters are unchanged
            ciphertext[i] = c;
        }
    }
    ciphertext[len] = '[=10=]';  // set the null terminator

    printf("ciphertext: %s\n", ciphertext);
    return 0;
}