看过(问!)之后,有很多类似的问题
int (*f)(int (*a)[5])
在C 中是什么意思?
甚至看到他们制作了一个程序来帮助人们理解C语法,我不禁想知道:
为什么用这种方式设计C的语法?
例如,如果我正在设计指针,我会将“指向10个元素的指针数组的指针”转换为
int*[10]* p;
而不是
int* (*p)[10];
我觉得大多数人都同意,这要简单得多。
所以我想知道,为什么这种不直观的语法?语法是否解决了我未意识到的特定问题(也许有歧义?)?
看过(问!)之后,有很多类似的问题
int (*f)(int (*a)[5])
在C 中是什么意思?
甚至看到他们制作了一个程序来帮助人们理解C语法,我不禁想知道:
为什么用这种方式设计C的语法?
例如,如果我正在设计指针,我会将“指向10个元素的指针数组的指针”转换为
int*[10]* p;
而不是
int* (*p)[10];
我觉得大多数人都同意,这要简单得多。
所以我想知道,为什么这种不直观的语法?语法是否解决了我未意识到的特定问题(也许有歧义?)?
Answers:
我对它的历史的理解是,它基于两个要点...
首先,语言作者倾向于使语法以变量为中心,而不是以类型为中心。也就是说,他们在声明中需要一个程序员的外观,并认为“如果我写的表情*func(arg)
,那将导致int
;如果我写*arg[N]
我有一个浮动”,而不是“ func
必须是一个指向函数服用此并返回那个 ”。
Ritchie的想法是在类似于标识符使用的上下文中声明标识符:“声明反映了使用”。
...引用K&R2的p122,a,我不必亲自为您找到扩展报价。
其次,在处理任意级别的间接访问时,要想出一个一致的声明语法实际上非常非常困难。您的示例可能很好地表达了您当即想到的类型,但是它是否可以扩展为使用指向这些类型的数组的指针并返回一些其他麻烦的函数呢?(也许是,但是您检查了吗?可以证明吗?)。
记住,C的成功部分是由于编译器是为许多不同的平台编写的,因此为了使编译器更易于编写,最好忽略某种程度的可读性。
话虽如此,我不是语言语法或编译器编写方面的专家。但是我知道很多,所以还有很多要知道的;)
在设计C语言时,可以用计算机的工作方式来解释C语言的许多奇怪之处。存储空间非常有限,因此,最小化源代码文件本身的大小非常重要。上世纪70年代和80年代的编程实践是确保源代码包含尽可能少的字符,最好不要包含过多的源代码注释。
今天,这当然很荒谬,硬盘驱动器上几乎没有无限的存储空间。但这是C一般具有这种奇怪语法的部分原因。
具体而言,关于数组指针,您的第二个示例应该是int (*p)[10];
(是的,语法非常混乱)。我可能会将其读为“指向十个数组的整数指针”……这在一定程度上是有意义的。如果没有括号,则编译器会将其解释为由十个指针组成的数组,这将使声明具有完全不同的含义。
由于数组指针和函数指针在C语言中都具有相当晦涩的语法,因此明智的做法是将怪异类型化。也许像这样:
晦涩的例子:
int func (int (*arr_ptr)[10])
{
return 0;
}
int main()
{
int array[10];
int (*arr_ptr)[10] = &array;
int (*func_ptr)(int(*)[10]) = &func;
func_ptr(arr_ptr);
}
非模糊的等效示例:
typedef int array_t[10];
typedef int (*funcptr_t)(array_t*);
int func (array_t* arr_ptr)
{
return 0;
}
int main()
{
int array[10];
array_t* arr_ptr = &array; /* non-obscure array pointer */
funcptr_t func_ptr = &func; /* non-obscure function pointer */
func_ptr(arr_ptr);
}
如果您要处理函数指针数组,事情会变得更加晦涩难懂。或其中最晦涩的是:函数返回函数指针(轻度有用)。如果您不使用typedef进行此类操作,您将很快发疯。
这很简单:int *p
意味着这*p
是一个整数;int a[5]
表示这a[i]
是一个整数。
int (*f)(int (*a)[5])
表示这*f
是一个函数,*a
是一个由五个整数组成的数组,因此f
一个函数也将获取一个指向五个整数的数组的指针,并返回int。但是,在C语言中,将指针传递给数组没有用。
C声明很少会变得如此复杂。
另外,您可以使用typedef进行说明:
typedef int vec5[5];
int (*f)(vec5 *a);
typedef
,const
,volatile
,并声明中初始化事物的能力。在最初设计的语言中,不会出现声明语法的许多烦人的歧义(例如,是否int const *p, *q;
应绑定const
到类型或声明符)。我希望该语言在类型和声明符之间添加一个冒号,但允许在使用不带限定符的内置“保留字”类型时省略该语言。的意义int: const *p,*q;
和int const *: p,*q;
本来清晰。
我认为您必须将* []视为附加到变量的运算符。*写在变量之前,[]之后。
让我们阅读类型表达式
int* (*p)[10];
最里面的元素是p,是一个变量,因此
p
表示:p是一个变量。
在变量之前有*,*运算符始终放在它所引用的表达式之前,因此,
(*p)
表示:变量p是一个指针。如果没有(),则右边的[]运算符将具有更高的优先级,即
**p[]
将被解析为
*(*(p[]))
下一步是[]:由于没有其他(),[]的优先级高于外部*,因此
(*p)[]
表示:(变量p是指针)到数组。然后有第二个*:
* (*p)[]
意思是:((变量p是一个指针)指向数组的指针)
最后,您拥有int运算符(类型名称),其优先级最低:
int* (*p)[]
意思是:((((变量p是一个指针)指向数组的指针)指向数组的指针)整数。
因此,整个系统基于带有运算符的类型表达式,并且每个运算符都有自己的优先级规则。这允许定义非常复杂的类型。
开始思考时并不难,C从来都不是很容易的语言。而且int*[10]* p
真的是不容易比int* (*p)[10]
什么k的类型将是int*[10]* p, k;
pointer to int
和int
甚至没有同类型的,所以他们应该另行申报。期。听那个男人。他有18k代表,这是有原因的。