为什么编程语言(尤其是C)使用大括号而不是方括号?


96

实际上,“ C样式语言”的定义可以简化为“使用花括号({})”。我们为什么要使用该特定字符(为什么不使用更合理的字符(如[],至少在美国键盘上不需要Shift键)?

这些花括号会给程序员的生产力带来实际的好处,还是新的语言设计师应该寻找替代品(例如,Python背后的人)?

Wikipedia告诉我们C 使用大括号,但不是为什么。Wikipedia上有关基于C的编程语言列表的一篇文章表明,此语法元素有些特殊:

广义上讲,C系列语言是使用类似C的块语法的语言(包括大括号来开始和结束块)。


35
唯一可以回答这个问题的人是丹尼斯·里奇(Dennis Ritchie),他已经死了。一个合理的猜测是[]已经用于数组。
德克·霍尔索普

2
@DirkHolsopple所以他没有留下任何理由吗?德拉特 另外:对我真正好奇的事情有两票否决?谢谢
大家...。– SomeKittens


2
我已经解锁了这个帖子。请保留有关该问题的任何评论以及有关元问题的适当性的讨论
Thomas Owens

5
这可能与以下事实有关:在数学的集合符号中使用了花括号,使得花括号在使用数组元素时显得有些尴尬,而不是像声明“ set”之类的东西(如结构,数组等)。甚至像Python这样的现代语言也使用花括号来声明集合和字典。那么问题是,为什么C还使用花括号来声明作用域?可能是因为设计人员只是不喜欢BEGIN / END这样的已知替代方法,并且认为重载数组访问符号([])在美学上比集合符号更不合理。
Charles Salvia

Answers:


102

对C的两个主要影响是Algol语言族(Algol 60和Algol 68)和BCPL(C以此命名)。

BCPL是第一种大括号编程语言,大括号在语法更改中幸免了下来,并成为表示程序源代码语句的常用方式。实际上,在当今有限的键盘上,源程序经常使用序列$(和$)代替符号{和}。BCPL的单行'//'注释在C中未使用,在C ++中以及随后在C99中重新出现。

来自http://www.princeton.edu/~achaney/tmve/wiki100k/docs/BCPL.html

BCPL引入并实施了一些创新,这些创新在后来的语言设计中已成为非常普遍的元素。因此,它是第一种花括号编程语言(使用{}作为块定界符),也是第一种使用//标记内联注释的语言。

来自http://progopedia.com/language/bcpl/

在BCPL中,人们经常看到花括号,但并非总是如此。这是当时键盘的局限性。在字符上,$(和和$)在字典上等同于{}字母和字母组合在C中保持不变(尽管花括号替换使用- ??<??>)。

花括号的使用在B(在C之前)中得到了进一步完善。

从Ken Thompson的用户参考到B

/* The following function will print a non-negative number, n, to
  the base b, where 2<=b<=10,  This routine uses the fact that
  in the ASCII character set, the digits 0 to 9 have sequential
  code values.  */

printn(n,b) {
        extern putchar;
        auto a;

        if(a=n/b) /* assignment, not test for equality */
                printn(a, b); /* recursive */
        putchar(n%b + '0');
}

有迹象表明,大括号被用作短手适应症beginend内陵。

我记得您也将它们包含在您在CACM中发布的256个字符的卡代码中,因为我发现您提出可以代替Algol的“ begin”和“ end”关键字来使用它们很有趣,这恰好是后来如何在C语言中使用它们。

来自http://www.bobbemer.com/BRACES.HTM


方括号(作为问题中的建议替代品)的使用甚至可以追溯到更早。如前所述,Algol家族影响了C。在Algol 60和68中(C于1972年编写,BCPL于1966年编写),方括号用于将索引指定为数组或矩阵。

BEGIN
  FILE F(KIND=REMOTE);
  EBCDIC ARRAY E[0:11];
  REPLACE E BY "HELLO WORLD!";
  WRITE(F, *, E);
END.

由于程序员已经熟悉Algol和BCPL中的数组的方括号,以及BCPL中的块的花括号,因此在制作另一种语言时几乎不需要更改。


更新后的问题包括使用花括号的生产率附录,并提到了python。尽管答案可以归结为“它的轶事,而您习惯的是最有生产力的东西”,但是还有其他一些资源可以进行这项研究。由于编程技能和对不同语言的熟悉程度差异很大,因此很难解释这些问题。

另请参阅:堆栈溢出是否有统计研究表明Python效率更高?

许多收益将取决于所使用的IDE(或缺少)。在基于vi的编辑器中,将光标放在一个匹配的打开/关闭上,%然后按将光标移动到另一个匹配的字符。过去使用基于C的语言时,这非常有效-如今已不再如此。

更好的比较是/ {}和这是今天的选择(水平空间非常宝贵)。许多Wirth语言都基于和风格(Algol(如上所述),pascal(很多人熟悉)和Modula家族)。beginendbeginend

我很难找到任何可以隔离这种特定语言功能的东西-最多我能做的就是证明花括号语言比开始结束语言更受欢迎,这是一种常见的构造。如上面的Bob Bemer链接中所述,花括号用于简化速记程序的编写。

为什么Pascal不是我最喜欢的编程语言

与{和}相比,C和Ratfor程序员发现“开始”和“结束”庞大。

可以说的全部-它的熟悉性和偏好。


14
现在这里的每个人都在学习BCPL,而不是在工作:)
DenysSéguret13年

{}??<和的三元组(在1989 ISO C标准中引入)??>。有向图(由1995年修订版引入)为<%%>。在很早的翻译阶段,三部曲在所有情况下都得到了扩展。图是标记,不会在字符串文字,字符常量或注释中扩展。
基思·汤普森

在1989年以前,C语言中就存在这种情况(我必须掏出第一版的书才能得到日期)。并非所有EBCDIC代码页中都带有花括号(或方括号),并且最早的C编译器中对此有规定。

@NevilleDNZ BCPL于1966年使用花括号。Algol68的概念源自何处,但BCPL并非Algo68的。三元运算符是我一直感兴趣的东西,并且可以追溯到CPL(1963)(BCPL的前身),后者从Lisp(1958)借用了这个概念。

1968:Algol68的允许支架(〜)作为的速记开始结束 粗体符号块。这些称为简短符号,请参阅wp:Algol68粗体符号,这使代码块可以像表达式一样对待。A68也有一些简短的简写,例如C的?:三元运算符, 例如x:=(c|s1|s2)代替C的x=c?s1|s2。同样,这适用于ifcase语句。¢顺便说一句:A68是从外壳获得esacfi的地方 ¢
NevilleDNZ

24

[]自从IBM 2741终端(在Multics上广泛使用)操作系统以来,方括号的键入更加容易,而OS 2 终端则由 C语言创建者之一Dennis Ritchie 担任开发团队成员

http://upload.wikimedia.org/wikipedia/commons/thumb/9/9f/APL-keybd2.svg/600px-APL-keybd2.svg.png

请注意,IBM 2741布局中没有花括号!

在C语言中,“大括号”被“采用”,因为它们用于数组和指针。如果语言设计师期望数组和指针比代码块更重要/更频繁地使用(这听起来像是一个合理的假设,更多地是在下面的编码样式的历史背景下),则这意味着花括号将变得“不太重要”。 “ 句法。

数组的重要性在Ritchie 的文章《 C语言的发展》中很明显。甚至有一个明确声明的假设:“ C程序中的指针盛行”

...新语言保留了对数组语义的连贯且可行的解释(如果不寻常的话)... C语言在其同类语言中最有两个特征:数组与指针之间的关系... C的另一个特征C,对数组的处理…… 具有真正的优点。尽管指针和数组之间的关系不寻常,但可以了解。此外,该语言还具有描述重要概念的强大能力,例如,向量的长度在运行时会有所变化,只有一些基本规则和约定...


为了进一步了解创建C语言时的历史背景和编码风格,需要考虑到“ C的起源与Unix的开发紧密相关”,尤其是将OS移植到PDP- 11 “导致开发C的早期版本”引文来源)。根据Wikipedia的说法,“在1972年,Unix用C编程语言重写了”

各种旧版本的Unix的源代码都可以在线获得,例如在Unix Tree网站上。在这里介绍的各种版本中,最相关的似乎是1972-06年的第二版Unix

肯恩·汤普森,丹尼斯·里奇和其他人在贝尔实验室为PDP-11开发了Unix的第二版。它通过更多的系统调用和更多的命令扩展了第一版。该版本还看到了C语言的开始,该语言用于编写一些命令...

您可以从第二版Unix(V2)页面浏览和研究C源代码, 以了解当时的典型编码风格。

V2 / c / ncc.c源代码中可以找到一个著名的示例,该示例支持当时程序员很容易键入方括号的想法,这一点很重要:

/* C command */

main(argc, argv)
char argv[][]; {
    extern callsys, printf, unlink, link, nodup;
    extern getsuf, setsuf, copy;
    extern tsp;
    extern tmp0, tmp1, tmp2, tmp3;
    char tmp0[], tmp1[], tmp2[], tmp3[];
    char glotch[100][], clist[50][], llist[50][], ts[500];
    char tsp[], av[50][], t[];
    auto nc, nl, cflag, i, j, c;

    tmp0 = tmp1 = tmp2 = tmp3 = "//";
    tsp = ts;
    i = nc = nl = cflag = 0;
    while(++i < argc) {
        if(*argv[i] == '-' & argv[i][1]=='c')
            cflag++;
        else {
            t = copy(argv[i]);
            if((c=getsuf(t))=='c') {
                clist[nc++] = t;
                llist[nl++] = setsuf(copy(t));
            } else {
            if (nodup(llist, t))
                llist[nl++] = t;
            }
        }
    }
    if(nc==0)
        goto nocom;
    tmp0 = copy("/tmp/ctm0a");
    while((c=open(tmp0, 0))>=0) {
        close(c);
        tmp0[9]++;
    }
    while((creat(tmp0, 012))<0)
        tmp0[9]++;
    intr(delfil);
    (tmp1 = copy(tmp0))[8] = '1';
    (tmp2 = copy(tmp0))[8] = '2';
    (tmp3 = copy(tmp0))[8] = '3';
    i = 0;
    while(i<nc) {
        if (nc>1)
            printf("%s:\n", clist[i]);
        av[0] = "c0";
        av[1] = clist[i];
        av[2] = tmp1;
        av[3] = tmp2;
        av[4] = 0;
        if (callsys("/usr/lib/c0", av)) {
            cflag++;
            goto loop;
        }
        av[0] = "c1";
        av[1] = tmp1;
        av[2] = tmp2;
        av[3] = tmp3;
        av[4] = 0;
        if(callsys("/usr/lib/c1", av)) {
            cflag++;
            goto loop;
        }
        av[0] = "as";
        av[1] = "-";
        av[2] = tmp3;
        av[3] = 0;
        callsys("/bin/as", av);
        t = setsuf(clist[i]);
        unlink(t);
        if(link("a.out", t) | unlink("a.out")) {
            printf("move failed: %s\n", t);
            cflag++;
        }
loop:;
        i++;
    }
nocom:
    if (cflag==0 & nl!=0) {
        i = 0;
        av[0] = "ld";
        av[1] = "/usr/lib/crt0.o";
        j = 2;
        while(i<nl)
            av[j++] = llist[i++];
        av[j++] = "-lc";
        av[j++] = "-l";
        av[j++] = 0;
        callsys("/bin/ld", av);
    }
delfil:
    dexit();
}
dexit()
{
    extern tmp0, tmp1, tmp2, tmp3;

    unlink(tmp1);
    unlink(tmp2);
    unlink(tmp3);
    unlink(tmp0);
    exit();
}

getsuf(s)
char s[];
{
    extern exit, printf;
    auto c;
    char t, os[];

    c = 0;
    os = s;
    while(t = *s++)
        if (t=='/')
            c = 0;
        else
            c++;
    s =- 3;
    if (c<=8 & c>2 & *s++=='.' & *s=='c')
        return('c');
    return(0);
}

setsuf(s)
char s[];
{
    char os[];

    os = s;
    while(*s++);
    s[-2] = 'o';
    return(os);
}

callsys(f, v)
char f[], v[][]; {

    extern fork, execv, wait, printf;
    auto t, status;

    if ((t=fork())==0) {
        execv(f, v);
        printf("Can't find %s\n", f);
        exit(1);
    } else
        if (t == -1) {
            printf("Try again\n");
            return(1);
        }
    while(t!=wait(&status));
    if ((t=(status&0377)) != 0) {
        if (t!=9)       /* interrupt */
            printf("Fatal error in %s\n", f);
        dexit();
    }
    return((status>>8) & 0377);
}

copy(s)
char s[]; {
    extern tsp;
    char tsp[], otsp[];

    otsp = tsp;
    while(*tsp++ = *s++);
    return(otsp);
}

nodup(l, s)
char l[][], s[]; {

    char t[], os[], c;

    os = s;
    while(t = *l++) {
        s = os;
        while(c = *s++)
            if (c != *t++) goto ll;
        if (*t++ == '\0') return (0);
ll:;
    }
    return(1);
}

tsp;
tmp0;
tmp1;
tmp2;
tmp3;

有意思的是,有趣的是,根据在有针对性的实际应用中的使用情况,挑选字符来表示语言语法元素的务实动机如何类似于齐普夫定律,正如在这个绝妙的答案中所解释的 ...

观察到的频率和长度之间的关系称为齐普夫定律

...唯一的区别是上述语句中的长度被/概括为打字速度。


5
是否有任何支持语言设计师的“明显”期望的东西?在C中不需要花太多编程就可以注意到花括号比数组声明更常见。自从过去以来,这并没有真正改变-看看K&R。

1
我以某种方式怀疑这种解释。我们不知道期望值是什么,因为他们也是决定数组表示法的人,所以他们可以很容易地选择它。我们甚至不知道他们是否认为花括号是“不太重要”的选择,也许他们更喜欢花括号。
thorstenmüller2013年

3
@gnat:方括号在现代键盘上更容易键入,这是否适用于首次实现unix和c时使用的键盘?我没有理由怀疑他们使用的是相同的键盘,或者他们会假设其他键盘会像他们的键盘一样,或者他们认为打字速度值得一个字符来优化。
迈克尔·肖

1
同样,齐普夫定律是对自然语言中最终结果的概括。C是人为构造的,因此除非C的设计者有意识地决定故意使用它,否则没有理由认为它会在这里应用。如果确实适用,则没有理由假设它会简化已经短于单个字符的内容。
Michael Shaw

1
@gnat FWIW grep -Fo告诉我*.cCPython源代码的文件(修订版4b42d7f288c5,因为这就是我手头的文件),其中包括libffi,包含39511 {(39508 {,不知道为什么两个大括号没有关闭),但只有13718 [(13702)[)。这是在字符串中以及与此问题无关的上下文中计数的发生次数,因此,即使我们忽略代码库可能不具有代表性(请注意,这种偏向可能会朝任一方向发展),这也不是很准确。仍然是2.8的倍数?

1

C(以及后来的C ++和C#)继承了其前身B的支撑风格,该风格由Ken Thompson(由Dennis Ritchie贡献)在1969年编写。

此示例摘自Ken Thompson的《用户对B的引用》(通过Wikipedia):

/* The following function will print a non-negative number, n, to
   the base b, where 2<=b<=10,  This routine uses the fact that
   in the ASCII character set, the digits 0 to 9 have sequential
   code values.  */

printn(n,b) {
        extern putchar;
        auto a;

        if(a=n/b) /* assignment, not test for equality */
                printn(a, b); /* recursive */
        putchar(n%b + '0');
}

B本身又基于BCPL,这是Martin Richards在1966年为Multics操作系统编写的一种语言。B的支撑系统仅使用圆括号,并由其他字符修改(Martin Richards的打印阶乘示例,通过Wikipedia):

GET "LIBHDR"

LET START() = VALOF $(
        FOR I = 1 TO 5 DO
                WRITEF("%N! = %I4*N", I, FACT(I))
        RESULTIS 0
)$

AND FACT(N) = N = 0 -> 1, N * FACT(N - 1)

B和后续语言“ {...}”中使用的花括号是Ken Thompson对BCPL“ $(...)$”中原始复合花括号样式的改进。


1
否。似乎鲍勃·贝默(en.wikipedia.org/wiki/Bob_Bemer)对此负有责任-“ ...您提议可以使用它们代替Algol的'begin'和'end'关键字,这恰恰是后来如何在C语言中使用它们。” (从bobbemer.com/BRACES.HTM
SChepurin

1
$( ... $)格式相当于{ ... }在BCPL词法分析器,就像??< ... ??>是等同于{ ... }C中的两种风格之间的改进是在键盘的硬件-不是语言。
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.