C 程序设计与实践没品笑话合集
图一乐。但或许也真能帮到什么?
未完待续……
格式错误笑话之行末不得有多余空格
你一顿噼里啪啦,开心写下:
#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]);
你的答案正确了,但你的品味没了;这一切,值得吗?
另附一些略有差异的版本:
Output | Code |
---|---|
2,4,6,7,8 | printf(",%d" + !i, a[i]); |
2, 4, 6, 7, 8 | printf(", %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-else
与 switch-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 Pseudocode | Assembly Graph |
---|---|
test1.png | test1_asm.png |
test2.png | test2_asm.png |
test3.png | test3_asm.png |