图一乐。但或许也真能帮到什么?
未完待续……

格式错误笑话之行末不得有多余空格

你一顿噼里啪啦,开心写下:

#include<stdio.h>
int main() {
    int n, a[100] = {0};
    scanf("%d", &n);
    for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
    
    for(int i = 0; i < n; i ++) {
        // Business logic
    }
    
    for(int i = 0; i < n; i ++) printf("%d ", a[i]);
    return 0;
}

反手一个 gcc test.c 再一个 ./a,程序输出了!没问题!提交!——格式错误!

——我翻开 PTA 一查,这题没有样例,歪歪斜斜的每一页上都写着「格式错误」几个字。我横竖睡不着,仔细看了半夜,才从字缝里看出字来,满本都写着——「行末不得有多余空格」!

哀 C 无 " ".join(),怒题有病。一气之下挥就:

for(int i = 0; i < n; i ++) printf(" %d" + !i, a[i]);

你的答案正确了,但你的品味没了;这一切,值得吗?

另附一些略有差异的版本:

OutputCode
2,4,6,7,8printf(",%d" + !i, a[i]);
2, 4, 6, 7, 8printf(", %d" + !i * 2, a[i]);

以及一个不恶臭的写法或许可以是 printf(i ? ",%d" : "%d", a[i]);

要读一行字符串,scanf 遇见空格不往下读,烦都烦死了!

人生苦短,我用 gets()

char in[1000] = {0};
gets(in);
for(int i = 0; in[i] != 0; i ++) {
    // Business logic
}
puts(in);

什么,你说这不安全!那就 read() 好了!栈溢出退退退!

等号笑话之 if(a = 5)

[Warning] suggest parentheses around assignment used as truth value [-Wparentheses]

写成 if(5 == a)
再敢少等号,编译都不给你过!

该问题分流转专业面试常考,这就是满昏标答。
但老实说,只要等号两边都是变量就然并卵了。

所以其实我是开 -Wall 解君愁派的……(跑。)

if-elseswitch-case 的区别?

该问题也是分流转专业面试真题。但没区别,开了 O2 都一样。(此处应有罐头笑声)
好吧,认真地说。

首先,在语法与功能上,不知道有什么区别的,先自己去看书(半恼)

假设你看书回来了,并且知道,在语义上 switch-case 可以写成等价的 if-else。那 switch-case 有个毛用!难道就是个语法糖吗?

其实,它有编译优化!

下面来对比一下(不开 -On 的)它们俩:

对于 if-else

编译为一组比较和跳转指令。依次判断每个条件,直到找到匹配的分支,就像你做理论题人肉跑代码的时候干的一样。换句话说,时间复杂度 $O(n)$。

什么?难道还有不 $O(n)$ 的?

对于 switch-case

编译器会见风使舵,有好多不同的编译优化:

  • 跳转表
    case 值连续或接近连续时,会编译出一个跳转表。时间复杂度 $O(1)$。
  • 二分
    case 值分布稀疏时,编译成一个类似二分的逻辑。时间复杂度 $O(\log n)$。
  • 顺序
    case 值很少时,退化成顺序比较,就和 if-else 一样。时间复杂度 $O(n)$。

好了,看到这里大概够你面试直接薄纱了(

ちょっと待って,$O(1)$??跳转表是咩啊?

我们先不去看汇编。就用 C 讲!

回到我们刚学 C 时。习题让我们实现一个函数,它是这样的:

$$ f(x) = \begin{cases} 1, & \text{if } x = 0; \\ 2, & \text{if } x = 1; \\ 3, & \text{if } x = 2; \\ 7, & \text{if } x = 3; \\ 6, & \text{if } x = 4; \\ 4, & \text{if } x = 5; \\ 5, & \text{if } x = 6; \\ 0, & \text{if } x = 7; \\ 8, & \text{if } x = 8; \end{cases} $$

于是你写出了这样的一个代码:

#include<stdio.h>

int f(int x) {
    if(!(0 <= x && x <= 8)) return -1;
    if (x == 0) return 1;
    else if (x == 1) return 2;
    else if (x == 2) return 3;
    else if (x == 3) return 7;
    else if (x == 4) return 6;
    else if (x == 5) return 4;
    else if (x == 6) return 5;
    else if (x == 7) return 0;
    else if (x == 8) return 8;
}
int main() {
   int x;
   scanf("%d", &x);
   printf("%d", f(x));
   return 0;
}

非常好 if,使我 $O(n)$ 旋转!

后来,我们学习了数组,于是你想到,这个函数也可以是这样的:

int f(int x) {
    if(!(0 <= x && x <= 8)) return -1;
    int a[9] = {1, 2, 3, 7, 6, 4, 5, 0, 8};
    return a[x];
}

哇,居然是 $O(1)$ 耶!

这和刚刚说的又有什么关系?难道我写一个这样的:

#include<stdio.h>

void func1() { printf("This is func1\n"); }
void func2() { printf("This is func2\n"); }
void func3() { printf("This is func3\n"); }
void func4() { printf("This is func4\n"); }
void func5() { printf("This is func5\n"); }
int main() {
    int index;
    scanf("%d", &index);
    switch (index) {
        case 0:
            func1(); break;
        case 2:
            func2(); break;
        case 3:
            func3(); break;
        case 4:
            func4(); break;
        case 6:
            func5(); break;
    }
    return 0;
}

你还能给我玩出花来?

还真能:

int main() {
    int index;
    void (*func_array[7])() = {func1, NULL, func2, func3, func4, NULL, func5}; 
    scanf("%d", &index);
    func_array[index]();
    return 0;
}

跳转表的原理其实差不多就是这么个事儿。
(其实这俩编译出来不太一样。不过 $O(1)$ 的道理大概是一样的。(跑))

二分又是咩啊?

你或许已经想到:你这 case 几乎是连续的,我但凡散一点呢?

switch (index) {
    case 1:
        func1(); break;
    case 5:
        func2(); break;
    case 10:
        func3(); break;
    case 20:
        func4(); break;
    case 100:
        func5(); break;
}

那岂不是要建一个长度 101 的数组了?于是这个数组 95% 的长度都浪费掉了?

虽然浪费一点也不是不行,不过这个时候它更可能会被编译成这样的逻辑:

if (index == 100) func5();
else if (index <= 100) {
    if (index == 20) func4();
    else if (index <= 20) {
        if (index == 10) func3();
        else if (index <= 10) {
            if (index == 1) func1();
            else if (index == 5) func2();
        }
    }
}

看了就知道为什么说这是二分了。

为什么会退化呢?

如果再散一点,散成这样呢?

switch (index) {
    case 1:
        func1(); break;
    case 2147483647:
        func2(); break;
}

思来想去,是不是还是干脆一点最好:

if (index == 1) func1();
else if (index == 2147483647) func2();

我倒要看看究竟怎么个事儿

好吧。IDA,启动!

Code & Decompiled PseudocodeAssembly Graph
test1.pngtest1_asm.png
test2.pngtest2_asm.png
test3.pngtest3_asm.png

添加新评论