C&C ++声明的沉思
#c #cpp

我仍然不确定语言声明语法,在该语句中使用的语法使用模仿声明的变量的使用。这是引起强烈批评的事情之一,但它具有一定的逻辑。

- dennis M. Ritchie,C

的创建者

我认为C声明语法是一个失败的实验。

c ++的创建者

序幕

经常,我遇到了C和C ++声明的解释,这些声明试图简化初学者。例如,声明经常被解释(错误地),例如:

type name ;

但是,如果您查看C的正式语法,则不会出现类似的语法。相反,您找到了这个:

声明规格 init-declarator-list-list ;

a 声明规范式包括基本类型(例如intchar等),可选的预选赛(例如const)和一个可选的存储类(例如staticextern等)。具体来说,它确实 包括[] for Arrays,() for functions for functions或for pointers for Pointers - 这些东西是 everarator

的一部分

这无疑更为复杂,因此我了解试图简化初学者的动机。但是,从长远来看,简化是一种损害,因为它最终使初学者更难理解复杂的声明,因为他们对声明的心理模型错误。最好解释c声明。

介绍

作为设计编程语言的一部分,您通常需要设计一个单独的语法来声明事物(变量,常数,函数等)。单独的语法的优点是通常很清楚。 (轻微的)缺点是单独的语法不是告诉您如何使用所声明的事物。例如,将api声明为Pascal中整数的一系列指针:

api: array[0..4] of ^integer;

crystal清晰,但要使用变量,您可以写出类似的内容:

api[0]^ := 42;

注意:

  1. 在声明中,api[是相邻的不是
  2. 在声明中,^是前缀(而在使用后缀)中。

pascal是为此示例选择的,因为它是1970年代用于计算机科学教育的主要语言,当时C仅是最近发明的,加上Kernighan famously doesn’t like Pascal

正如题词所建议的那样,里奇采取了另一种方法。C将api声明为c中整数的一系列指针,你将名称写入表达式中的名字(主要语法的一部分)对于语言),然后将整个内容的基本类型预先添加 - 表达式的类型:

int *api[4];    // array of pointer to integer

那就是*api[...]是您使用它来产生int的方式。虽然有些奇怪,但正如Ritchie所指出的那样,它确实具有一定的逻辑。但是,一旦声明变得更加复杂,一旦将const和函数原型等内容添加到c(c的原始版本中都不存在),声明臭名昭著地很难阅读。

>

ANSI C&C ++并发症

实际上,通过任何措施,ANSI C都对原始C进行了改进(通常称为 c编程语言的第一版)。对于声明,添加constvoid和功能原型是改进的 - 但在某些情况下,它们使声明略微复杂,并且违反了Ritchie的精神。

const

添加const使指针声明更加复杂,因为有两件事可以是恒定的:值指向(指向const),指针本身(const pointer)或两者兼而有之:

const char *pcc;         // pointer to const char
char *const cpc;         // const pointer to char
const char *const cpcc;  // const pointer to const char

这样的声明也不一致,因为对于基本类型,const通常写在类型的左侧(const char),但对于指针,const 必须必须写在*的右侧。 。为了使事情变得更加一致,有些人(包括我本人)更喜欢(或eastâ)const,以便const总是在正确的(East)中出现在持续不断的情况下:< br>

char const *pcc;         // equals: char const *pcc
char const *const cpcc;  // equals: const char *const cpcc

从左右读取,第二个声明是:cpcc是恒定字符的指针。

void

添加void允许指针到原始的,不遵守的记忆,但指针to-void声明违反了里奇(Ritchie)制作宣言的设计精神。考虑:

void *p;

问题是*p can can 从未出现在使用中,因为它是非法的,因为void对象不存在void的指针。

不存在。

功能原型

从C ++中添加功能原型肯定是整体上的改进,但其语法与非专业型声明不一致。非概况声明允许在同一声明中声明多个事项:

int i, j;
int *p, *q;
int k, a[4], *r, f(), *g();

在此类声明中,逗号用于分离具有相同基本类型的声明。但是,具有多个参数的函数的原型声明类似:

int lcd( int i, int j );

即使基本类型相同,也要使用逗号分开声明。这意味着您可以声明具有相同基本类型的多个参数,仅指定基本类型一次。尝试这样做可能是C:
的错误

double f( double x, y );         // means: double x, int y

y是一个int,因为缺少基本类型,而c默认为int中的基本类型缺失。幸运的是,C编译器警告说。 (在C ++中,这是一个错误。)

就个人而言,我认为STROUSTRUP应该已经制作了原型声明,使用与非概况声明相同的语法。例如:

double f( double x, y; int r );  // alternate syntax

将允许具有相同基本类型的多个参数重复使用。仅当基本类型更改时,半洛龙才能用于分离参数。这样的语法也将更接近Ritchie的原始函数定义参数语法:

double f( x, y, r )              // K&R C function definition
    double x, y;
    int r;

唯一的区别是将声明在()中移动。

C ++参考

在C ++中添加引用使能够有效地传递大型对象作为函数参数透明地传递,尤其是对于操作员的过载。但是,虽然参考声明类似:

int i;
int &r = i;

是一致的,因为您将*替换为用&的指针声明以进行参考声明,因此它们违反了Ritchie设计的精神,因为Expressions中的&确实是不是 的意思 - 解雇,但表示地址。

这是龙

您可能会认为像int *api[4]这样的声明是不好的。但是,如果您想声明指向整数数组的指针,则必须写下:

int (*pai)[4];  // pointer to array of integer

具体来说,您需要添加()才能正确获得优先级。问题源于以下事实:*是A prefix 运算符,而[] Postfix 运算符。 (如果*是pascal中的^,那么这个问题就不存在。)

声明可能会变得更加复杂。例如:

char *(*strtab[4])();

strtab指针的数组,用于将指针返回char ;甚至更糟:

void (*signal(int sig, void (*f)(int)))(int);

其中signal函数(sig as intf作为函数指针的指针(int)返回void)返回指针返回function(int)返回void

幸运的是,Ritchie还发明了可用于杀死此类龙的typedef

typedef char (*PF_C)(); // pointer to function returning char
PF_C strtab[4];

typedef void (*sig_t)(int);
sig_t signal( int sig, sig_t f );

因此,在实践中,声明通常是

此外,您可以使用cdecl既破译和构成声明。

西方指针

尽管C声明为不是

type name ;

有些善意的人试图通过将*放在左侧(西部)的指针声明中,以使事情看起来如此:

char* s;     // as opposed to: char *s

虽然此类声明自从C编译器不关心空格以来就起作用,但它也不关心:

char* s, t;  // t is just char

您可能会在哪里也要t成为char*。然后,同一个人倾向于说您不应该在同一声明中声明多个事情,而是这样做:

char* s;
char* t;     // verbose

就我个人而言,我发现这是毫无必要的冗长,因为

为了类比:学习西班牙语时,您会发现形容词追随名词。您是否希望形容词在以英语为中心的视图之前进行匹配是无关紧要的。您必须以这样的方式说西班牙语,而不是您更喜欢它的方式。 c。

也是如此

结语

c是古怪,有缺陷且成功的巨大成功。

修订

在教c时,从长远来看,最好的疣和实际上是所有的。