我见过一个C程序打印1到1000而不使用任何循环结构或条件语句,但我不明白它是如何工作的。任何人都可以通过代码解释每一行吗? 查看实时演示here。为什么会导致运行时错误?
#include <stdio.h>
#include <stdlib.h>
int main(int i)
{
printf("%d\n",i);
((void(*[])()){main, exit})[i / 1000](i + 1);
return 0;
}
答案 0 :(得分:4)
var commentedOutHTml = [];
var iterator = document.createNodeIterator(document.body, NodeFilter.SHOW_COMMENT, NodeFilter.FILTER_ACCEPT, false);
var currentNode;
while (currentNode = iterator.nextNode()) {
commentedOutHTml.push(currentNode.nodeValue);
}
alert(commentedOutHTml.toString());
这一行创建一个两元素的函数指针数组,第一个元素包含<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
<TextView
android:id="@+id/dateLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_alignTop="@+id/date"
android:layout_alignBottom="@+id/date"
android:gravity="center"
android:layout_marginEnd="@dimen/labelMargin"
android:layout_marginStart="@dimen/labelMargin"
android:text="Date*"
android:textSize="16sp"
android:textColor="#000000"/>
<DatePicker
android:id="@+id/date"
android:layout_width="match_parent"
android:layout_height="130dp"
android:layout_alignLeft="@+id/name"
android:layout_toEndOf="@+id/dateLabel"
android:layout_toRightOf="@+id/dateLabel"
android:calendarViewShown="false"
android:datePickerMode="spinner">
</DatePicker>
...
</RelativeLayout>
函数,另一个元素包含((void(*[])()){main, exit})[i / 1000](i + 1);
函数。
然后它按main
索引此数组。如果exit
获得i / 1000
功能,main
时获得i < 1000
功能。
然后调用刚刚用exit
作为参数索引的函数指针。
这是一个递归函数,使用数组索引而不是条件确定停止条件。我不认为这是有效的C; i == 1000
的签名是错误的,并且对函数指针的强制转换会从i+1
函数中删除返回类型。
答案 1 :(得分:4)
打破你提出的关于
的界限((void(*[])()){main, exit})
这是一个数组文字,它创建一个包含两个函数指针的未命名临时数组,并将这些指针初始化为指向main
和exit
[i / 1000]
这是对temp数组的索引。整数除法向0截断,因此当0 <= i <0时1000,这得到元素0(指向main
的指针)并且当1000&lt; = i&lt; 1999年,它获得了元素1(指向exit
)
(i + 1);
这用i + 1作为参数调用指向的函数。
这里有很多东西都是未定义的行为。根据标准将main
声明为具有单个int参数是非法的,但通常会起作用,将命令行参数的数量作为单个参数。获取指向main
的指针同样未定义,因为main
是一个可能具有非标准调用约定的特殊函数。
答案 2 :(得分:3)
这段代码是一个很好的例子,说明你可以弯曲C而不会破坏它。在您可以编写可理解的 C。
之前,您不应该尝试理解这一点以下是主要特征和假设:
首先,假设程序是在没有参数的情况下调用的。参数i
位于通常称为argc
的位置,因此它将具有初始值1(参数数组中的元素数,通常称为argv
)。
接下来,假设main
的单参数形式不会导致任何问题。官方支持的main
形式没有参数,2个参数(argc, argv
),有时3个参数(argc, argv, envp
)。
所以第一个printf
打印1。
接下来我们有一个复合文字。如果你先看一个更简单的一个,可能会更容易理解:
(int[]){10,20}
这是一个2 int
s的匿名数组,值为10和20. int[]
是类型。它在括号列表中放在括号中。那是什么:
(void(*[])()){main, exit}
void(*[])()
是一种类型。它表示带有签名void (*foo)()
的函数指针数组。括号中的类型名称后跟一个支撑列表是复合文字,就像(int[]){10,20}
示例一样。在这种情况下,它会创建一个包含2个函数指针的数组,其元素为main
和exit
。
假设函数指针类型(返回void
)和main
(返回int
)之间的不匹配不会导致问题。
此:
((void(*[])()){main, exit})[i / 1000]
是我们的2个元素的匿名数组,在一些冗余括号内,后跟[i / 1000]
。这只是普通的数组索引语法。如果i/1000
为0,则获得数组的第一个元素(thearray[0]
),即main
。对于所有i
而言,这种情况发生在0到999之间。如果i/1000
为1,这发生在i==1000
时,我们会查看thearray[1]
,这会让我们获得第二个元素:exit
。
到目前为止,我们在main
时的表达式等于i<1000
,在exit
时等于i==1000
。
现在看看整个声明:
...that_big_thing_that_is_either_main_or_exit...(i + 1)
一个函数指针,后跟一个带括号的参数列表。这是一个函数调用。无论我们从数组中选择哪个函数,现在我们将调用它,提供一个等于传入参数(i
)加1的参数。
因此,当i
为1时,第一次调用为函数选择main
,为参数选择i+1
= 1+1
= 2。它会调用main(2)
。
main(2)
执行打印2的printf
,然后调用main(3)
。
main(3)
执行打印3的printf
,然后调用main(4)
。
...依此类推......
main(999)
执行打印999的printf
,然后调用main(1000)
。
main(1000)
执行打印1000的printf
,然后调用exit(1001)
。
main
的所有调用都没有返回,return 0
从未发生,因为exit
终止了该过程。事件返回退出代码1001而不是0的事实似乎是ideone的“运行时错误”消息的原因。
答案 3 :(得分:0)
递归调用main function
。
void print_nb(int n)
{
if (n <= 1000)
{
printf("%d\n", n);
print_nb(n + 1);
}
}
像Sourav Ghosh所说的那样,禁止以递归方式使用main()
,更好地使用其他功能。