如何在C ++中的main()内部什么都没有声明,而在编译后却具有正常工作的应用程序呢?


86

在一次采访中,我遇到了这样的问题:

您的朋友给了您一个源代码文件,该文件在控制台上打印了斐波那契数字。请注意,main()块为空,并且内部没有任何语句。

说明这是怎么可能的(提示:全局实例!)

我真的很想知道,这样的事情怎么可能!


26
看提示!
R. Martinho Fernandes

14
因为这是1)我从未听说过的东西,2)有用的琐事,因为人们在面试中要求它,3)一种有趣的语言应用来知道,以便4)我可以认出它并用脸刺任何人如果我看到他们在生产代码中实际使用了它,那将是一个生锈的窗口。
OmnipotentEntity

4
有能力的专业C ++程序员将知道此问题的答案。如果此面试问题的目的是确定所面试的人是否是有能力的专业C ++程序员,则该问题不应给他们答案。
John Dibling 2013年

1
在采访设置中,一种选择是将逻辑包含在代码中的任何函数中,然后使用assert#pragma message等记录输出。这将在编译过程中将输出重定向到控制台。该程序甚至可能永远不会完全编译,但这肯定是在面试中展示“开箱即用”思想的一种有趣方式。这满足了引用的问题,因为它没有提及有关生成二进制文件的任何内容;而是只讨论可以在控制台上显示“内容”的C文件。;-)
TheCodeArtist 2013年

1
这是IOCC的采访吗?:-)好吧,我承认我经常这样做是为了初始化我的工厂或执行一些测试代码。顺便说一句,“单个源代码文件”也是一个提示,入口链接(默认为main)不会被链接程序代替。
Valentin Heinitz

Answers:


127

它很可能实现为(或其变体):

 void print_fibs() 
 {
       //implementation
 }

 int ignore = (print_fibs(), 0);

 int main() {}

在此代码中,全局变量ignore必须在进入main()函数之前进行初始化。现在,为了初始化全局变量,print_fibs()需要在可以执行任何操作的位置执行该命令-在这种情况下,请计算斐波纳契数并打印出来!我在以下问题(很久以前问过)中显示了类似的内容:

请注意,这样的代码并不安全,通常最好避免使用。例如,std::cout对象可能在print_fibs()执行时未初始化,如果是的话std::cout,该函数会做什么呢?但是,如果在其他情况下它不依赖于此类初始化顺序,则可以安全地调用初始化函数(这是C和C ++中的常见做法)。


3
@Nawaz可能值得引用确切的保证。保证翻译单元内的对象按顺序初始化。保证标准流对象在对象的第一次初始化之前或期间被初始化std::ios_base::Init。并且<iostream>保证行为“好像”它包含std::ios_base_Init名称空间范围内的对象的实例。
James Kanze 2013年

3
@ Steve314:它不返回任何内容,这就是为什么我使用逗号运算符来确保整个表达式的类型为的(print_fibs(), 0)原因int。这是在线演示
纳瓦兹

1
@Nawaz void函数和逗号运算符的替代方法是返回abool和变量bool fibsPrinted如果该功能仅在此处使用,那可能会稍微清洁一点。(但是差异可能不足以担心。)
James Kanze 2013年

1
+1,谈论精彩。不得不加入stackoverflow只是为了支持这个问题和这个答案。
定点

1
@Nawaz我不确定您的意思是。的定义std::cout在库中的某个位置。但是,正如我已经指出的那样,该标准要求std::ios_base::Init对象的第一个构造函数完成之前对其进行初始化,并且它要求包括的<iostream>行为就像std::ios_base::Init对象是在名称空间范围内定义的。如果翻译单元<iostream>在要初始化的对象的定义之前包括在内,std::cout则可以保证构造该单元。
James Kanze

18

希望这可以帮助

class cls
{
  public:
    cls()
    {
      // Your code for fibonacci series
    }
} objCls;

int main()
{
}

因此,一旦声明了该类的全局变量,便会调用构造函数,并在其中添加逻辑以打印出Fibonacci系列。


9

是的,有可能。您需要在对象构造函数中声明一个用于计算斐波纳契数的对象的全局实例。


6
您需要声明一个对象的全局实例,该对象的初始化程序将计算斐波那契数。
James Kanze 2013年

4

我知道你讲的一些例子。一种获取方法是使用模板元编程。使用它可以将一些计算过程移至编译。

在这里您可以得到斐波那契数的例子

如果在静态类构造函数中使用它,则可以编写数字而无需在主函数中编写任何代码。

希望对您有帮助。


3

在初始化全局/静态变量期间可能会发生事情。该代码将在应用程序启动时触发。


3

文件作用域对象的all [*]构造函数在到达之前就被调用main,非对象文件作用域变量的所有初始化表达式也是如此。

编辑:而且,所有文件作用域对象的all [*]析构函数在main退出后都会以相反的构造顺序被调用。从理论上讲,您可以将fibonacci程序放在对象的析构函数中。

[*]请注意,“全部”将忽略动态加载和卸载未与程序直接链接的库的行为。但是,从技术上讲,这些都不在C ++基本语言之外。


全部吗?甚至那些在dll中显式加载的文件main
James Kanze 2013年

嗯,C ++在技术上没有定义动态加载的库,因此在纯C ++中,我的说法是正确的。因此,将其着色为“全部,保存为到达main之后加载的DLL / DSO中包含的初始化程序和文件作用域对象”。在这种情况下,它main为空,因此那些DLL / DSO必须由析构函数加载,这是不明智的。但是,这是计算机科学,我想我们应该小心使用“ all”之类的词。
Joe Z

我在上面的回答中对“全部”添加了警告,还添加了有关dtor的注释。
乔Z

是的,但希望那会到来。C ++ 11之前版本包含一些狡猾的措辞,意在允许DLL,但实际上,这仅意味着技术上的保证并不总是存在的,即使它在所有实际实现中都存在,并且很多代码都依赖于此。C ++ 11至少解决了该问题。
James Kanze 2013年
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.