0

我正在解决有关 USACO 的问题。在这个问题中,我必须将两个字符串作为输入并计算模数 47 的数值。如果值相同,则要打印 GO,否则必须打印 STAY。初始数值将通过取字母表的数值的乘积来计算(A 为 1,Z 为 26),然后使用模数计算最终数字。

我的程序正在编译,没有任何错误,第一种情况也是成功的。但问题在于第二种情况以及我的文件被附加的方式。程序如下:-

#include<stdio.h>
#include<malloc.h>
#include<string.h>
#define MAX 6
main()
{
    int cal(char *ptr);
    int a,b;
    char *comet,*group;
    FILE *fptr;
    comet=malloc(6*sizeof(char));
    group=malloc(6*sizeof(char));
    scanf("%s",comet);
    a=cal(comet);
    scanf("%s",group);
    b=cal(group);
    fptr=fopen("ride.out","a+");                (1)
    //fptr=fopen("ride.txt","a+");              (2)
    if(a==b)
        fprintf(fptr,"GO\n");               (3)
        //printf("GO\n");                    (4)
    else
        fprintf(fptr,"STAY\n");              (5)
        //printf("STAY\n");                   (6)   
    fclose(fptr);
    return 0;
}
int cal(char *ptr)
{
    int c,prod=1,mod;
    while(*ptr)
        {
                c=(*ptr++)-'A'+1;
                prod=prod*c;
        }
    mod=prod%47;
    return mod;
}

输出:-

输出图像

第一种情况是设置两个字符串:-

    COMETQ
    HVNGAT

第二种情况在错误通知本身中给出。

如果我从 (2) 中删除注释符号并将其放在 (1) 上,那么程序运行正常,因为我可以看到文件的内容,并且它们看起来就像评分系统想要的那样。(1) 的实际陈述没有发生。第 (4) 行和 (6) 行的注释也很好,但第 (1) 行不行。我无法弄清楚这一点。有什么帮助吗?

4

1 回答 1

2

首先要注意几点:

  • main():一个体面的主要是:

    int main(void)
    or
    int main(int argc, char *argv[])
    
  • 使用malloc()你应该总是检查它是否返回NULL,也就是失败。

  • 总是free()malloc 的对象。
  • 每个人都有他/她/自己的编码风格。当涉及到 C 编码时,我发现这是非常宝贵的。使用它作为许多其他人的基础。点是结构化代码更容易阅读、调试、解码等。

有关您的代码的更多详细信息:

cal() 的签名

您声明的第一行main的签名cal()。尽管这可行,但您可能会将其放在上面main,或者将cal()函数全部放在上面main

最长长度

您有一个#define MAX 6从未使用过的定义。如果它最多为六个字符并且您读取一个字符串,您还必须考虑尾随零。

例如来自cplusplus.com scanf

说明符 's':任意数量的非空白字符,在找到的第一个空白字符处停止。终止的空字符会自动添加到存储序列的末尾。

因此:

#define MAX_LEN_NAME    7
...
comet = malloc(sizeof(char) * MAX_LEN_NAME);

因为学习使用是件好事,所以在这里这样做并malloc()没有错。由于它很简单,您可能希望使用:

char comet[MAX_LEN_NAME] = {0};
char group[MAX_LEN_NAME] = {0};

反而。至少:如果使用mallocthen 检查成功并在完成后释放,否则使用静态数组。

更安全的 scanf()

scanf()given"%s"不会停止读取目标缓冲区的大小 - 它会继续读取数据并将其写入内存中的连续地址,直到它读取一个空白。例如:

/* data stream = "USACOeRRORbLAHbLAH NOP" */
comet = malloc(szieof(char) * 7);
scanf("%s", buf);

在内存中,我们会:

Address (example)
0x00000f           comet[0]
0x000010           comet[1]
0x000011           comet[2]
0x000012           comet[3]
0x000013           comet[4]
0x000014           comet[5]
0x000015           comet[6]
0x000016           comet[7]
0x000017           /* Anything; Here e.g. group starts, or perhaps fptr */
0x000018           /* Anything; */
0x000019           /* Anything; */
...

并且在阅读上面建议的流/字符串时,我们不会读USACOe入,但comet我们会继续阅读超出. comet换句话说(可能)覆盖其他变量等。这听起来可能很愚蠢,但由于 C 是一种低级语言,这是你必须知道的事情之一。随着你了解更多,你很可能也会学会爱它的力量:)

maximum length为了防止这种情况,您可以通过例如使用+来限制读取[what to read]。例如:

 scanf("%6[A-Z]", comet);
         | |      |
         | |      +------- Write to `comet`
         | +-------------- Read only A to Z
         +---------------- Read maximum 6 entities

输入数据

阅读您的预期结果、您的错误、您的(N)评论等。听起来您应该有一个输入文件和一个输出文件。

由于您的代码现在依赖于从标准输入读取数据,即stdin. 因此,您也使用scanf(). 我怀疑您应该改为从文件中读取 fscanf()

所以:类似:

FILE *fptr_in;
char *file_data = "ride.in";
int res;
...
if ((fptr_in = fopen(file_data, "r")) == NULL) {
    fprintf(stderr, "Unable to open %s for reading.\n", file_data);
    return 1; /* error code returned by program */
}
if ((res = fscanf(fptr_in, "%6[A-Z]%*[ \n]", comet)) != 1) {
    fprintf(stderr, "Read comet failed. Got %d.\n", res);
    return 2;
}

b = cal(comet);

if ((res = fscanf(fptr_in, "%6[A-Z]%*[ \n]", group)) != 1) {
    fprintf(stderr, "Read group failed. Got %d.\n", res);
    return 2;
}

...

cal() 函数

首先,命名。假设这是一个项目的开始,最终会产生多个文件和数千行代码。您可能没有名为cal(). 学会给函数起个好名字。上面关于编码风格的链接给出了一些观点。恕我直言,在小型项目中也这样做。这是一个很好的练习,可以让你在写大到大的时候更容易。将其命名为cprod_mod_47().

那么mod变量(也可能是 c)是多余的。一种替代方法可能是:

int cprod_mod_47(char *str)
{
    int prod = 1;

    while (*str)
        prod *= *(str++) - 'A' + 1;

    return prod % 47;
}

一些更一般的建议

编译时使用许多警告和错误选项。例如,如果使用 gcc 说:

$ gcc -Wall -Wextra -pedantic -std=c89 -o my_prog my_prog.c

这是巨大的帮助。进一步是使用类似valgrindgdb无价的工具。

于 2013-02-02T16:28:15.883 回答