C语言中的投射是如何工作的?

How casting works in C?

提问人:tycoon 提问时间:3/14/2022 最后编辑:chqrlietycoon 更新时间:3/14/2022 访问量:130

问:

我正在解决leetcode上的一个问题,这是我的代码。

/*
max int 2147483647 (10^10)
max uint 4294967295 (10^10)
ULONG_MAX 18446744073709551615 (10^20)
LONG_MAX 9223372036854775807 (10^20)
USHRT_MAX 65535, SHRT_MAX 32767
*/

#include <stdio.h>
#include <math.h>

int main(void) {
    int t; 
    scanf("%d", &t);
    while (t--) {
        int length;
        scanf("%d", &length);
        char string[length];
        scanf("%s", string);
        long int answer = 0;
        int ones = 0;
        for (int i = 0; i < length; i++) {
            ones = ones + (string[i] == '1') * (i + 1);
            if (ones & 1) {
                answer = (answer + (long int)pow(2, length - (i + 1))) % 998244353;
            }
        }
        printf("%ld\n", answer);
    }
    
    return 0;
}

它适用于较小的值(可能是 int 可以保存的值)。但是在计算大值时,它给出了意想不到的结果

然后我认为这可能是由于溢出造成的,所以我将我认为可以解决问题的变量更改为变量,但它没有,并且还破坏了更小值的代码int answerlong int answer

然后我注意到我正在使用肯定会超过高长度值限制的函数,因为返回双倍值,我将其转换为之前的 with,我将其更改为powpowint(int)pow(2, length - (i+1))(long int)pow(2, length - (i+1))

我传递这些值来测试代码。

4
16
1111010010111101
2
10
6
101101
4
1111

预期结果是

49359
3
48
12

但我得到了

49359
65535
49152
49152

当我使用时,我得到了预期的结果,但是如果我将答案或pow投射到long,我会得到意想不到的结果。我不确定这是由于投射还是其他原因,但据我所知,只有当我将这些变量投射到长整型时才会发生。int answer(int)pow(...)

C 铸造

评论

4赞 Weather Vane 3/14/2022
不要用于 2 的幂。使用移位。例如。pow()1ull << 40
4赞 mch 3/14/2022
不相关:是一种混淆的写作方式。已计算为 或 。(string[i]=='1'?1:0)(string[i]=='1')==10
1赞 Weather Vane 3/14/2022
您没有更新打印格式。printf("%d\n", answer); ==> printf("%ld\n", answer);
2赞 Weather Vane 3/14/2022
还有缓冲区溢出:char string[length]; ==> char string[length+1];
0赞 Gerhardh 3/14/2022
这些评论是什么意思? 您的值仅为 2*10^9。(10^10)INT_MAX

答:

1赞 chqrlie 3/14/2022 #1

代码中存在多个问题:

  • while (t--)如果 enter 的值为负数,将导致意外行为。用twhile (t-- > 0)

  • char string[length];没有足够的空间容纳字符和 null 终止符。用lengthchar string[length + 1];

  • scanf("%s", string);不提供任何防止缓冲区溢出的保护。没有简单的方法可以告诉 读取 的字节数是可变的。由于长度可以大到 ,因此您可能应该从堆中分配数组并使用 读取 .scanf()%s100000getchar()

  • 循环中的代码似乎没有实现该问题的解决方案:

    给定一个二进制字符串,她将字符串的美定义为 的所有子字符串十进制表示的按位异或。SS

    这些说明具有误导性,因为结果与任何内容的十进制表示无关。但是您的代码不会转换所有子字符串,而只应显示结果为模。对模块应用异或将产生不同的结果。998244353

  • 此外,无需转换二进制表示:您可以乘以 2 并在循环中添加下一个数字的值。powres

要计算生成的位串,请考虑偏移位处的位,从字符串的开头开始,偏移量为:i0

  • 它将与仅删除前缀的子字符串的自身时间进行异或运算。i

  • 然后,索引小于的每个位将是每个子字符串的 XOR 操作时间,并删除最后一位。jiji-j

  • XOR 时间将产生 if 为奇数,因此与使用 的最后一位相反的 进行掩码具有相同的效果。i0ii

    为此,您可以使用 2 个嵌套循环:

          for (int i = 0; i < length; i++) {
              int bit = (string[i] - '0') & ~i;
              for (j = 0; j < i; j++) {
                  bit ^= (string[j] - '0') & ~j;
              }
              answer = (answer * 2 + bit) % 998244353;
          }
    

这是修改后的版本:

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

int main() {
    int t, i, j, c;
    int string_size = 0;
    unsigned char *string = NULL;   /* array for the bits */

    /* read the number of test cases */
    if (scanf("%d", &t) != 1)
        return 1;
    while (t-- > 0) {
        int length;
        /* read the length */
        if (scanf("%d", &length) != 1)
            return 1;
        /* discard the rest of the input line */
        while ((c = getchar()) != EOF && c != '\n')
            continue;
        /* reallocate the string if required */
        if (length > string_size) {
            string_size = length;
            string = realloc(string, string_size);
            if (string == NULL)
                return 1;
        }
        /* read the bits */
        i = 0;
        while (i < length && ((c = getchar()) == '0' || c == '1')) {
            string[i++] = (unsigned char)(c - '0');
        }
        /* discard the rest of the input line */
        while (c != EOF && c != '\n') {
            c = getchar();
        }
        /* compute the answer one bit at a time */
        long int answer = 0;
        for (i = 0; i < length; i++) {
            /* compute the next bit of the result string */
            int bit = string[i] & ~i;
            for (j = 0; j < i; j++) {
                bit ^= string[j] & ~j;
            }
            /* compute the answer one bit at a time, reducing modulo 998244353 */
            answer = (answer * 2 + bit) % 998244353;
        }
        printf("%ld\n", answer);
    }
    free(string);
    return 0;
}

评论

1赞 chqrlie 3/14/2022
@JohnBollinger:你的建议确实很好,但恐怕 C 标准中没有这样的规定: 在 % 之后,按顺序显示以下内容: — 可选的赋值抑制字符 *。 — 大于零的可选十进制整数,用于指定最大字段宽度(以字符为单位)。 — 一个可选的长度修饰符,用于指定接收对象的大小。 — 转换说明符字符,用于指定要的转换类型应用的。
1赞 chqrlie 3/14/2022
@JohnBollinger:没有规定变量限制是一个真正的缺点,这具有不同的含义。附件K没有以可行的方式解决这一缺点,以及它自己的其他问题......scanf()*
1赞 chqrlie 3/15/2022
@tycoon:谢谢你的评论,你当然可以提高你的代码写作:如果你充满激情,磨练你的技能将是愉快和有效的。学习简单需要很长时间。从代码风格和一致性开始,查看开源项目并从示例中学习。
1赞 chqrlie 3/15/2022
@tycoon:关于 ,您可以通过将格式字符串组合成以下命令来使用变量限制: 此解决方案繁琐且容易出错,因为格式字符串不再是字符串常量,从而阻止了对变量参数类型的编译时验证。scanfsnprintf{ char format[20]; snprintf(format, sizeof format, "%%%ds", length); if (scanf(format, string) != 1) return 1; }scanf()
1赞 chqrlie 3/15/2022
@tycoon:可以为不同的错误条件返回特定的状态代码,但更好的解决方案是输出有意义的错误消息以及参数值、不正确的输入值和必要时使用的额外信息。例如:stderrerrnostrerror(errno)FILE *fp = fopen(filename, "r"); if (f == NULL) { fprintf(stderr, "cannot open %s: %s\n", filename, strerror(errno)); return 1; }