在C中打高尔夫球的技巧


137

您在C高尔夫方面有哪些一般提示?我正在寻找可用于编码一般至少在某些程度上针对C的高尔夫问题的想法(例如,“删除注释”不是答案)。请为每个答案发布一个提示。另外,如果您的技巧适用于C89和/或C99,并且仅适用于某些编译器,请提供。


8
我认为最大的单句提示是:阅读提交给IOCCC的获奖代码。
vsz 2012年

Answers:


107

使用按位XOR检查整数之间的不等式:

if(a^b)而不是if(a!=b)保存1个字符。


73
a-b给你同样的效果。
ugoren

22
同样,您可以使用a*b代替a&&b(具有不同的优先级,可能不好也可能不会不好)。如果您知道/ = -b(例如,它们是未签名的),则a||b==a+b
walpen 2012年

3
最好将其与猫王运算符结合使用?:(而不是if):例如,如果有区别,则只做某事: a^b?_diff_:;
Olivier Dulac

1
@OlivierDulac是否有编译器接受虚假的三元组?
乔纳森·弗雷希

1
@OlivierDulac您可以检查。据我所知,海湾合作委员会的?:运营商相当于a ? a : b
Chromium

75
  • 滥用main的参数列表来声明一个或多个整数变量:

    main(a){for(;++a<28;)putchar(95+a);}
    

    (以编程语言回答字母

    该解决方案还滥用了a(aka argc)以开头的事实1,前提是该程序不带任何参数调用。

  • 使用全局变量将事物初始化为零:

    t[52],i;main(c){for(;i<52;)(c=getchar())<11?i+=26:t[i+c-97]++;
    for(i=27;--i&&t[i-1]==t[i+25];);puts(i?"false":"true");}
    

    (回答Anagram Code Golf!


62

逗号运算符可用于在单个块中执行多个表达式,同时避免大括号:

main(){                                                                                     

int i = 0;                                                                                  
int j = 1;                                                                                  
if(1)                                                                                       
    i=j,j+=1,printf("%d %d\n",i,j); // multiple statements are all executed                                                  
else                                                                                        
    printf("failed\n");                                                                     

}

输出: 1 2


如果其中一个语句为,则无效break
Maxim Mikhaylov

9
@MaxLawnboy因为break是一条语句,而这个答案是关于表达式的。
NieDzejkob

59

避免灾难性的函数参数类型声明

如果您要声明一个函数,其中所有五个参数均为ints,那么生活就很好。你可以简单地写

f(a,b,c,d,e){

但是假设d需要是char,甚至是int*。然后你就被搞砸了!如果一个参数前面带有类型,则所有参数必须为:

f(int a,int b,int c,int*d,int e){

可是等等!可以解决这种无用字符的灾难性爆炸。它是这样的:

f(a,b,c,d,e) int *d; {

main如果您需要使用命令行参数,这甚至可以节省标准声明:

main(c,v)char**v;{

比两个字节短

main(int c,char**v){

我很惊讶地发现了这一点,因为到目前为止我还没有在PPCG上遇到它。


6
为什么在地球上起作用?
纳撒尼尔(Nathaniel)2014年

30
显然,这称为K&R样式,它比ANSI C早十年。
丹尼斯

请注意,不能同时使用K&R功能和较新的功能(例如'99),否则可能不会。取决于您的编译器。
dmckee,2015年

5
@dmckee是正确的。C99不允许隐式int,因此您必须使用-std=gnu99,现在无法移植。用clc语言来说,您本身甚至不是在编写“ C”代码,而是在编写“ Gnu99-C”。“在这里,我们大多忽略了这一点,但是如果您发布特定于编译器的代码,则值得一提。有时人们实际上确实下载并执行了我们的这些程序。:)
luser droog

@luserdroog:您可以-std=c89用来告诉gcc或clang根据该旧标准编译代码,该标准仅允许带警告的隐式int。
彼得·科德斯

37

当比较值大于零时,可以简单地使用整数除法(/)来代替> =和<=,这样可以节省一个字符。例如:

putchar(c/32&&126/c?c:46); //Prints the character, but if it is unprintable print "."

当然,它仍然可以缩小,例如仅使用>和^(在某些情况下避免编写&&或||的聪明方法)。

putchar(c>31^c>126?c:46);

例如,整数除法将有助于确定数字是否小于100,因为这样可以节省字符:

a<100 vs 99/a

这在需要更高优先级的情况下也很好。


您可以写putchar(c>31&c<127?c:46);
Jin X

37

某些编译器(例如GCC)允许您省略的基本#include,参数和返回类型main

以下是使用GCC编译(带有警告)的有效C89和C99程序:

main(i) { printf("%d", i); }

请注意,#include缺少for stdio.h,缺少for的返回类型,main并且缺少for 的类型声明i


17
从技术上讲,根据标准,它无效,因为main接受零个或两个参数,而不是一个。并不是说有人在乎代码高尔夫。
Konrad Borowski14年

printf()没有原型的调用(或任何可变参数函数)会导致未定义的行为。默认情况下,GCC不会编译标准C。如果以C89模式(gcc -ansi -pedantic)或C99模式(gcc -std=c99 -pedantic)调用gcc ,至少在后一种情况下,您会收到很多抱怨。
NisseEngström2014年

@NisseEngström:主流C实现的调用约定使无需原型就可以调用可变参数函数。因此,大多数C实现确实定义了行为。
彼得·科德斯

29

三元有条件的经营者?:往往可以用作简单的一个立场if- else语句在相当大的节约。

C ++等效项不同该运算符不会正式产生lvalue,但是某些编译器(尤其是gcc)会让您无法使用它,这是一个不错的收获。


加法:如果只需要一个if而不是一个else,那么三元数仍然有用。
Casey

9
&&||也可以使用:if(x==3)f()随您的建议而定x==3?f():0,可以进一步改进为x==3&&f()。但是请注意运算符的优先级-如果f()将替换为y=1,则&&解决方案需要使用一组额外的括号。
ugoren

1
我从未意识到gcc会?:产生一个左值。我可以在生产代码中使用它吗?大声笑
Jeff Burdges 2012年

4
@ugoren:x==3&&f()可以进一步打高尔夫球x^3||f()
fgrieu 2012年

@fgrieu,是的,尽管它与此处的主题不完全相同(此答案表明了这一点)。
ugoren

27

http://graphics.stanford.edu/~seander/bithacks.html

有点不错。

~-x = x - 1
-~x = x + 1

但是具有不同的优先级,并且不要像++和-那样更改x。您也可以在特定情况下使用此功能:〜9比-10短。

if(!(x&y)) x | y == x ^ y == x + y
if(!(~x&y)) x ^ y == x - y

这比较神秘,但是我偶尔会使用它。如果您不关心短路

x*y == x && y
if(x!=-y) x+y == x || y

也:

if(x>0 && y>0) x/y == x>=y   

5
最后一个技巧((x/y) == (x>=y))非常有用。
ugoren 2012年

24

使用lambdas(不可移植)

代替

f(int*a,int*b){return*a>*b?1:-1;}
...
qsort(a,b,4,f);

或(仅限gcc)

qsort(a,b,4,({int L(int*a,int*b){a=*a>*b?1:-1;}L;}));

或(支持块的llvm)

qsort_b(a,b,4,^(const void*a,const void*b){return*(int*)a>*(int*)b?1:-1;});

尝试类似

qsort(a,b,4,"\x8b\7+\6\xc3");

...其中带引号的字符串包含“ lambda”函数的机器语言指令(符合所有平台ABI要求)。

这在字符串常量被标记为可执行的环境中起作用。默认情况下,在Linux和OSX中为true,但在Windows中为true。

学习编写自己的“ lambda”函数的一种愚蠢的方法是用C编写该函数,对其进行编译,使用类似的方法进行检查objdump -D并将相应的十六进制代码复制到字符串中。例如,

int f(int*a, int*b){return *a-*b;}

... gcc -Os -c针对Linux x86_64目标进行编译时会生成类似

0:   8b 07                   mov    (%rdi),%eax
2:   2b 06                   sub    (%rsi),%eax
4:   c3                      retq

GNU CC goto

您可以直接调用这些“ lambda函数”,但是如果您所调用的代码不带参数且不会返回,则可以goto节省一些字节。所以代替

((int(*)())L"ﻫ")();

或者(如果您的环境没有阿拉伯字形)

((int(*)())L"\xfeeb")();

尝试

goto*&L"ﻫ";

要么

goto*&L"\xfeeb";

在此示例中,eb fex86机器语言用于类似for(;;);的东西,并且是不带参数并且不会返回的简单例子:-)

事实证明,您可以goto编写返回到调用方父级的代码。

#include<stdio.h>
int f(int a){
 if(!a)return 1;
 goto*&L"\xc3c031"; // return 0;
 return 2; // never gets here
}
int main(){
 printf("f(0)=%d f(1)=%d\n",f(0),f(1));
}

上面的示例(可能使用编译并在Linux上运行gcc -O)对堆栈布局很敏感。

编辑:根据您的工具链,您可能必须使用-zexecstack编译标志。

如果不是立即可见,则此答案主要是针对lol编写的。通过阅读本文,我对打高尔夫球的好坏不承担任何心理责任。


2
我刚刚编写了一个脚本,以从standard中读取C函数的一部分并打印C lambda。在您的回答中可能值得一提,因为您首先教过我这样做,所以也许对您来说很高兴。
MD XF

23

使用游标而不是指针。brk()在开始时抓取,并将其用作基本指针

char*m=brk();

然后为内存访问创建一个#define。

#define M [m]

M成为*应用于整数的后缀。(旧的a [x] == x [a]技巧。)

但是,还有更多!然后,您可以拥有指针args并在比宏短的函数中返回(特别是如果您缩写“ return”):

f(x){return x M;} //implicit ints, but they work like pointers
#define f(x) (x M)

要从指针中创建光标,您需要减去基指针,得到一个ptrdiff_t,它会被截断为一个int,损失是多少。

char *p = sbrk(sizeof(whatever)) - m;
strcpy(m+p, "hello world");

我在为无类型的lambda演算编写解释器的答案中使用了此技术。


21

定义参数而不是变量。

f(x){int y=x+1;...}

f(x,y){y=x+1;...}

您实际上不需要传递第二个参数。

另外,您可以使用运算符优先级来保存括号。
例如,(x+y)*2可以成为x+y<<1


或者,只是x+y*2保存另一个字符。
Braden Best

4
@ B1KMusic,x+y*2由于运算符的优先级而不同。
ugoren

是的,哈哈 那将是x +(y * 2)。x+y<<1假设示例被评估为x+(y<<1),我对示例固执己见,并建议使用该示例*2。我不知道对移位操作的评估是例如(x+y)<<2
Braden Best 2014年

20

通常EOF == -1,使用按位NOT运算符检查EOF:while(~(c=getchar()))while(c=getchar()+1)在每个位置修改c的值


1
我不太了解C,但是while(1+c=getchar())行不通吗?
ɐɔıʇǝɥʇuʎs

6
@ɐɔıʇǝɥʇuʎsNo。加法运算符的+优先级高于赋值运算符=,因此1+c=getchar()等价于(1+c)=getchar(),由于(1+c)不是左值,因此不会编译。
ace_HongKong独立

19

三元运算符?:的不同之处在于它具有两个独立的部分。因此,它为标准运算符优先级规则提供了一些漏洞。这对于避免括号很有用。

请看以下示例:

if (t()) a = b, b = 0;  /* 15 chars */

通常的高尔夫方法是更换if&&,但因为逗号操作符的优先级比较低,你需要一个额外的对括号:

t() && (a = b, b = 0);  /* still 15 chars */

但是,三元运算符的中间部分不需要括号:

t() ? a = b, b = 0 : 0;  /* 14 chars */

类似的注释适用于数组下标。


7
在这个例子中,b-=a=b甚至更短。这个?:技巧仍然有用,-=因为它的优先级也很低。
ugoren

好点子; 我的例子很复杂。
面包箱

另一点是,有时您想翻转条件:for x>0||(y=3)x>0?0:(y=3)没有用,但x<1?y=3:0可以完成工作。
ugoren

clang和gcc都允许在三进制中为空的true大小写。如果省略,则其值为条件的值。例如,x>5?:y=1
Chris Uzdavinis

19

您的代码中重复多次的任何部分都可以替换为预处理器。

#define R return

如果您的代码涉及多个功能,这是一个非常常见的用例。其他稍长的关键字,例如whiledoubleswitch,和case也是候选人; 以及您代码中所有idomatic的内容。

我通常为此保留大写字符。


1
较短的替换将是-DR=return。请注意,如果包含某些字符,则有必要在define周围使用单引号或双引号-DP='puts("hello")'

15

如果您的程序是在每一步中进行读取或写入,请始终尝试使用读取写入功能,而不要使用getchar()putchar()

示例反向stdin并放在stdout上

main(_){write(read(0,&_,1)&&main());}

练习:使用此技术可以在此处获得良好的成绩。


在每个步骤中您指的是什么?
Casey

凯西:我想他们的意思是程序正在读取内容,对其进行操作并写入输出。可以这么说。与必须立即读取和处理所有输入的方法相反。
乔伊,

乔伊是正确的,对不起,我的意思是直到今天我才检查我的收件箱。
Quixotic

8
该堆栈操作非常出色。
安德里亚·比昂多

14

反向循环

如果可以,请尝试更换

for(int i=0;i<n;i++){...}

for(int i=n;i--;){...}

13

如果您只需要输出一个换行符(\n),请不要使用putchar(10),请使用puts("")


12

利用返回值归零。如果调用某个函数,并且该函数在正常情况下返回零,则可以将其放置在期望为零的位置。同样,如果您知道函数将返回非零并加上一个bang。毕竟,无论如何您都不会在代码高尔夫中进行适当的错误处理,对吗?

例子:

close(fd);foo=0;   →  foo=close(fd);    /* saves two bytes */
putchar(c);bar=0;  →  bar=!putchar(c);  /* saves one byte  */

12

分配而不是返回。

这不是真正的标准C语言,但是可以与我所知道的每个编译器和CPU一起使用:

int sqr(int a){return a*a;}

具有与以下相同的效果:

int sqr(int a){a*=a;}

因为第一个参数与返回值存储在同一CPU寄存器中。

注意:如一条评论所述,这是未定义的行为,不能保证对每个操作都有效。而且任何编译器优化都将跳过它。

X-宏

另一个有用的功能:X-Macros在您拥有变量列表并且需要执行一些涉及所有变量的操作时可以为您提供帮助:

https://zh.wikipedia.org/wiki/X_Macro


3
我引用了这个,然后我得到了纠正,这根本不是事实。它仅适用于乘法和除法以及优化被关闭时。这是因为两个操作都将其结果放在eax中,而eax是返回的通用寄存器。参数存储在堆栈或ecx或edx中。自己尝试。
加斯帕79年

3
没错,这是不确定的行为,它还取决于编译器和体系结构,在使用此技巧发布任何答案之前,我通常会在x86和armv7上使用gcc进行检查。当然,如果启用优化,任何智能编译器都将删除不必要的乘法。
GB

3
我见过与GCC合作的这项工作,但没有其他人见过
艾伯特·伦肖

1
@ Gaspa79:gcc -O0始终选择在返回值寄存器中求值表达式。我至少已经在xcc,ARM和MIPS上(在gcc.godbolt.org上)进行了研究,而gcc似乎已经尽力了-O0。但要记住,如果你利用这一点,你在编程是语言gcc -O0而不是C,你应该作出相应的说明你的答案,而不是为C。它在除-O0调试模式以外的任何优化级别上均失败,并且不适用于clang IIRC。
彼得·科德斯

11
  1. 用于*a代替a[0]访问数组的第一个元素。

  2. 关系运算符(!=>等)给出01。使用此用算术运算符给取决于条件是否为真或假的不同的偏移:a[1+2*(i<3)]将访问a[1],如果i >= 3a[3]其他。


11
a[i<3?3:1]比短两个字符a[1+2*(i<3)]
Reto Koradi

10

您可以查看IOCCC档案(国际混淆C代码竞赛)。

一个值得注意的技巧是#define扩展带有不平衡括号/括号的宏,例如

#define P printf(

16
不匹配的括号本身没有任何价值。关键是要定义尽可能多的重复模式。您可能想使用进一步说明#define P;printf(
ugoren

如何缩短字节数?也许提供一个例子?
Cyoce

2
@Cyoce例如,参见此答案
乔纳森·弗雷奇

8

for(int i=0;i<n;i++){a(i);b(i);} 可以通过以下几种方式缩短:

for(int i=0;i<n;){a(i);b(i++);} -1用于将循环移动++到最后i

for(int i=0;i<n;b(i++))a(i); -3更多的用于将除一条语句之外的所有语句移入主循环的顶部和底部,并删除括号


在某些情况下,使用逗号运算符是避免使用括号的另一种方法。
彼得·科德斯

8

发挥作用!

如果您可以将问题简化为具有相同签名并定义为单个表达式的简单函数,那么与定义#define r return所有函数的几乎所有样板相比,您可以做得更好。

#define D(f,...)f(x){return __VA_ARGS__;}
D(f,x+2)
D(g,4*x-4)
D(main,g(4))

程序结果是其状态值返回给OS或控制Shell或IDE。

使用__VA_ARGS__允许您使用逗号运算符在这些函数表达式中引入序列点。如果不需要,宏可以更短。

#define D(f,b)f(x){return b;}

7
  1. 用于scanf("%*d ");读取虚拟输入。(如果输入在其他程序中无意义),它比scanf("%d",&t);您还需要声明变量t的地方短。

  2. 将字符存储在int数组中要比字符数组好得多。例。

    s[],t;main(c){for(scanf("%*d ");~(c=getchar());s[t++]=c)putchar(s[t]);}


2
实际上,我%*d不仅在Golf中使用,因为它在scanf("%[^\n]%*c",str);
某些

6

打印一个字符然后回车,而不是:

printf("%c\n",c);

要么

putchar(c);putchar('\n'); // or its ascii value, whatever!

简单地,将c声明为int并:

puts(&c);

9
值得指出的是,这取决于小端架构。如果c是big-endian int,那么您将得到回车符。(另一方面,如果c是一个字符,则可能会在返回而不是回车后得到随机垃圾。)
面包箱

@breadbox是的,您完全正确;我刚刚编辑过:最后一个摘录应将c用作int(通常很容易这样声明)。
moala 2013年

是否puts(&c)真的有用吗?那并不一定是空终止的。
硕果累累'18

1
@EsolangingFruit在具有32位int的little-endian上,将int 0≤c <256存储为字节序列c 0 0 0。当解释器的地址Ç作为char *,我们看到了一个单字符串:字符Ç,后跟一个空字节。
丹尼斯

6

使用asprintf()可以节省显式分配,也可以测量字符串的长度char*!对于代码编程,这可能不太有用,但是使用char数组可以简化日常工作。在21世纪C中还有更多不错的建议。

用法示例:

#define _GNU_SOURCE
#include <stdio.h>

int main(int argc, char** argv) {
  char* foo;
  asprintf(&foo, "%s", argv[1]);
  printf("%s",foo);
}

6

import 如果你必须

第一个答案所述,某些编译器(尤其是GCC和clang)使您无需#include为标准库函数省略s。

即使您不能仅删除#include,也可能有其他方法来避免它,但这并不总是实用的,尤其是打高尔夫球。

在其余情况下,您可以使用#import<header file>而不是#include<header file>保存一个字节。这是一个GNU扩展,被认为已弃用,但至少在gcc 4.8,gcc 5.1和clang 3.7中有效。


6

尝试cpow()代替cos()

代替

double y=cos(M_PI*2*x);

尝试类似

double y=cpow(-1,x*2);

这使用了Euler公式,一些复杂的分析以及观察到的发现,即将一个复杂的对象赋给double会得到实数部分(注意可变参数函数调用和其他细微之处)。

\ cos 2 \ pi x + j \ sin 2 \ pi x = e ^ {j2 \ pi x} = e ^ {j \ pi 2x} =(-1)^ {2x}

这种招数可以用来减少

double y=cpow(-1,x/2);

进入

double y=cpow(1i,x);

因为 (-1)^ \ frac {x} {2} = j ^ {\ frac {2x} {2}} = j ^ x


5

这是我习惯的一些技巧。我无耻地从别人那里偷了他们,所以要相信我以外的任何人:

结合赋值和函数调用

代替这个:

r = /* Some random expression */
printf("%d", r);

做这个:

printf("%d", r = /* Some random expression */);

一起初始化多个变量(如果可能)

代替这个:

for(i=0,j=0;...;...){ /* ... */ }

做这个:

for(i=j=0;...;...){ /* ... */ }

折叠零/非零值

这是我从这里的某个人那里拿到的一个绝妙的把戏(不记得是谁,对不起)。当您具有整数值并且需要将其折叠为1或0时,可以!!轻松地做到这一点。有时对于其他替代方案(如)有利?:

采取这种情况:

n=2*n+isupper(s[j])?1:0; /* 24 */

您可以改为:

n=n*2+!!isupper(s[j]); /* 22 */

另一个例子:

r=R+(memcmp(b+6,"---",3)?R:0); /* 30 */

可以改写为:

r=R+R*!!memcmp(b+6,"---",3)); /* 29 */

1
可能R*-~!!mxxxx
l4m2

5

知道基本的逻辑相等性也许可以节省几个字节。例如,代替if (!(a&&b)){}尝试使用DeMorgan定律if (!a||!b){}。按位函数也是如此:代替~(a|b)do ~a&~b


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.