去除C语言中的标点符号和大写字母

7
我正在为学校编写一个程序,要求从文件中读取文本,将所有内容大写,并删除标点和空格。文件“Congress.txt”包含以下内容:
(Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the government for a redress of grievances.)
尽管我已经成功读取了文件,但是我现在遇到了一些问题,即如何去除标点、空格并使所有字母大写,因为这可能会导致垃圾字符出现。我目前的代码如下:
void processFile(char line[]) {
    FILE *fp;
    int i = 0;
    char c;

    if (!(fp = fopen("congress.txt", "r"))) {
        printf("File could not be opened for input.\n");
        exit(1);
    }

    line[i] = '\0';
    fseek(fp, 0, SEEK_END);
    fseek(fp, 0, SEEK_SET);
    for (i = 0; i < MAX; ++i) {
        fscanf(fp, "%c", &line[i]);
        if (line[i] == ' ')
            i++;
        else if (ispunct((unsigned char)line[i]))
            i++;
        else if (islower((unsigned char)line[i])) {
            line[i] = toupper((unsigned char)line[i]);
            i++;
        }
        printf("%c", line[i]);
        fprintf(csis, "%c", line[i]);
    }

    fclose(fp);
}

我不知道这是否是一个问题,但我定义了MAX为272,因为文本文件包含标点符号和空格。

我得到的输出是:

    C╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠
    ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠Press any key to continue . . .

6
问题在于当你读取标点符号时,你会对 i 进行两次递增操作。你只需要在将内容复制到数组中时递增 i。退出循环前(无论如何)应该对字符串进行空终止处理。 - Jonathan Leffler
@JonathanLeffler,我认为OP应该让for循环自增i,不需要在其他地方修改它。 - Zoltán
4
@Zoltán: 实际上不是这样。循环可能需要重写为while ((c = getc(fp)) != EOF)的循环形式,并且只有在执行赋值操作时才会对i进行递增。 - Jonathan Leffler
除了将我的for循环更改为上述while循环外,现在打印出的确切输出相同,只不过在“C”的位置多了一个奇怪的字符。 - Zach Greene
通过以下方式获取文件大小:'fseek(fp, 0L, SEEK_END); max = ftell(fp); fseek(fp, 0L, SEEK_SET);' 此外,不能保证调用者传入的“line”足够长以包含整个文件。 - user3629249
显示剩余2条评论
3个回答

6
基本算法需要遵循以下步骤:
while next character is not EOF
    if it is alphabetic
        save the upper case version of it in the string
null terminate the string

这将被翻译成C语言:

int c;
int i = 0;

while ((c = getc(fp)) != EOF)
{
    if (isalpha(c))
        line[i++] = toupper(c);
}
line[i] = '\0';

这段代码无需使用 (unsigned char)<ctype.h> 库中获取函数,因为 c 变量保证只会包含 EOF (此时它不会进入循环体)或转换为 unsigned char 的字符值。但是在使用 char c(与问题中的代码一致)并尝试写入 toupper(c) 或者 isalpha(c) 时,您需要注意类型转换。问题是普通的 char 可能是有符号型,因此一些字符(比如ÿ(y-umlaut,U+00FF,LATIN SMALL LETTER Y WITH DIAERESIS))将显示为负值,这破坏了对 <ctype.h> 函数输入的要求。此代码将尝试转换已经是大写的字符,但这可能比进行第二次测试更便宜。

关于打印等其他内容,请根据您的需要进行操作。变量 csis 是一个全局范围的变量;这有点棘手。您应该在输出末尾加上一个换行符。

该代码存在缓冲区溢出漏洞。如果 line 的长度为 MAX,则可以将循环条件修改为:

while (i < MAX - 1 && (c = getc(fp)) != EOF)

如果按照更好的设计,您将函数签名更改为:

如果按更好的设计,你把函数签名改成:

void processFile(int size, char line[]) {

并断言大小严格为正:

    assert(size > 0);

然后循环条件改变为:

while (i < size - 1 && (c = getc(fp)) != EOF)

显然,您也需要更改调用方式:
char line[4096];

processFile(sizeof(line), line);

1
关于何时需要使用(unsigned char)的解释很好。 - chux - Reinstate Monica

1
在发布的代码中,没有中间处理,因此以下代码忽略了“line []”输入参数。
void processFile()
{
    FILE *fp = NULL;

    if (!(fp = fopen("congress.txt", "r")))
    {
        printf("File could not be opened for input.\n");
        exit(1);
    }

    // implied else, fopen successful

    unsigned int c; // must be integer so EOF (-1) can be recognized
    while( EOF != (c =(unsigned)fgetc(fp) ) )
    {
        if( (isalpha(c) || isblank(c) ) && !ispunct(c) ) // a...z or A...Z or space
        {
            // note toupper has no effect on upper case characters
            // note toupper has no effect on a space
            printf("%c", toupper(c));
            fprintf(csis, "%c", toupper(c));
        }
    }
    printf( "\n" );

    fclose(fp);
} // end function: processFile

0

好的,我所做的是创建了第二个字符数组。我的第一个数组读入了整个文件。我创建了第二个数组,它只会从第一个数组中获取字母字符,然后将它们转换为大写。我完成并正确的作业部分函数如下:

void processFile(char line[], char newline[]) {
    FILE *fp;
    int i = 0;
    int j = 0;

    if (!(fp = fopen("congress.txt", "r"))) {                 //checks file open
        printf("File could not be opened for input.\n");
        exit(1);
    }
    line[i] = '\0';
    fseek(fp, 0, SEEK_END);               //idk what they do but they make it not crash
    fseek(fp, 0, SEEK_SET);

    for (i = 0; i < MAX; ++i) {           //reads the file into the first array
        fscanf(fp, "%c", &line[i]);
    }

    for (i = 0; i < MAX; ++i) {    
        if (isalpha(line[i])){                     //if it's an alphabetical character
            newline[j] = line[i];                  //read into new array
            newline[j] = toupper(newline[j]);      //makes that letter capitalized
            j++;
        }
    }

    fclose(fp);
}

确保创建新数组后,它的大小小于您定义的MAX。为了简化起见,我只计算了现在缺失的标点符号和空格(总共50个),所以对于未来的“for”循环,它是这样的:

for (i = 0; i < MAX - 50; ++i)

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