c++求助:这题应该怎么做?

今天和几位同仁一起探讨了一下C++的一些基础知识,在座的同仁都是行家了,有的多次当过C++技术面试官。不过我出的题过于刁钻: 不是看起来太难,而是看起来极其容易,但是其实非常难! 结果一圈下来,4道题,平均答对半题。于是只能安慰大家,这几道题,答不对是正常的。
    "你真的清楚构造函数,拷贝构造函数,operator=,析构函数都做了什么吗? 它们什么时候被调用?",这些问题可不是面向初菜的问题,对于老鸟而言,甚至对于许多自诩为老手的人而言,倒在这上面也是很正常的。因为这个问题的答案不但考察我们对于C++语言的理解,而且答案是和编译器的实现有关的!
【第一题】以下代码,main函数中G.i的打印结果是什么? 写在一张纸上,再看答案。我不是在挑战大家的知识,我是在挑战很多人的常识。

点击(此处)折叠或打开

    "3,2,1,公布答案"。G.i是多少? 回答4及其以上的统统枪毙。回答3及其以下的留下继续讨论。注意,这里根本就没有调用到operator=,因为operator=被调用的前提是一个对象已经存在,我们再次给它赋值,调用的才是operator=。
   为什么? 因为G g1=Create();这句话,可能会触发C++编译器的一个实现特性,叫做NRVO,命名返回值优化。也就是G函数中的obj并没有被创建在G的调用栈中,而是调用Create()函数的main的栈当中,因此obj不再是一个函数的返回变量,而是用g1给Create()返回的变量命名。
   VC的Debug版没有触发NRVO,因此会多调用一个拷贝构造函数,结果和Release版不一样----能说出这个的C++一定是中级以上水平了。
   这就带了一个问题,如果用VC编程的话,HouseKeep/计数的信息如果在ctor/copy ctor里面,那么不能保证调试版和发布版的行为一致。这个坑太大了。但是GCC没有这个问题!瞬间对理查德-斯托曼无比敬仰。

【第二题】以下程序的运行结果是什么:

点击(此处)折叠或打开

    第一轮没有被枪毙的同学注意了: 这道题目的答案仍然是和编译器有关的,而且和版本还有关系。

点击(此处)折叠或打开

先调用一个默认构造函数构造Noisy临时对象,然后把临时对象拷贝给vector的两个程序,再把临时对象析构掉。太傻了吧!Release版的结果稍微好一点,返回的vector不再被拷贝了,就如同第一题所说的:

点击(此处)折叠或打开

点击(此处)折叠或打开

点击(此处)折叠或打开

点击(此处)折叠或打开

点击(此处)折叠或打开

【第三题】以下程序的运行结果是什么?

点击(此处)折叠或打开

点击(此处)折叠或打开

【第四题】下面这个指针的声明,const的意义是(A)指针指向的内容不能变,还是(B)指针本身不能变

实际上所谓"离谁近就修饰谁"这个说法不准确,只有const直接跟一个变量名,中间没有其他任何符号(除了空格)的时候,const才是修饰变量名本身的。

点击(此处)折叠或打开

    好了,有了前面4道题的讨论基础,做个小测验:构造函数,用初始化列表和不用初始化列表有什么区别? 写出以下代码的输出:

点击(此处)折叠或打开

 上回出了几道有挑战的题,当然那些不会真的做面试题的,让一大半人都挂的题目是没有出的必要的。C++是一个语言标准,不是一个实现标准,语言标准只规定了源代码长什么样合法,没有规定看到想到的和编译出来的东西就一定一样。例如,一个类有virtual关键字修饰的函数,那么就会有这个类就会有虚函数表吗? 不一定啊,因为C++标准压根就没有规定要如何实现虚函数!所谓的虚函数表只是一种流行的,实现虚函数的方式而已。
    C++是马,而某个具体的C++编译器实现是"白马"。白马非马也!有了上一篇文章的基础,我们继续讨论和构造/析构/赋值相关的话题。

点击(此处)折叠或打开


    这题的关键是Derived d2(d)这句话,继承类的拷贝构造函数,会调用基类的哪个构造函数呢? 没有显示指定初始化列表,那就是调用基类的默认构造函数,因此本题的答案是2。

点击(此处)折叠或打开

C++的"类"和"对象"只是语言级的概念,C++标准根本就没有规定编译的结果里面也存在对象,这样就能给编译器和优化器以无穷的空间----反过来说,我们不能假设对象真的有物理存在,因为构造函数有可能被内联,甚至release版连对象都优化得没有了,"多态"这个概念也是可以被编译器优化掉的。因此ctor/dtor要调用类内部的虚函数而根本把所谓多态置之脑后。
    所以,C++在ctor/dtor当中遇到虚函数调用的时候,直接当成非虚函数调用类内部的版本。这道题调用的是My::f(),输出是1。如果允许在基类构造期间调用继承类的函数,那么该函数需要访问继承类的成员例如指针,可此时继承类还没有构造,指针错误,崩溃了。

点击(此处)折叠或打开

点击(此处)折叠或打开

    不要着急说,调用一个haha()调用一个不存在的虚函数导致空指针错误。因为上面的代码根本编译不过。因为编译器让析构函数~My()调用本累的f(),而本类的f()是纯虚的,没有实现体,因此提示undefined reference to 'My::f()'。把上面的代码改一改,就能编过了:

点击(此处)折叠或打开

You()会让这个错误错误消失吗? 不会,因为析构函数先~You析构继承类的部分,然后进入~My。这个~My调用的时候,继承类的部分已经不存在了,因此此时虚函数的调用路径回到了基类,程序还是崩溃了。
    虚拟机语言如C#/Java对象生命周期是GC全局管理,因此不存在这样的陷阱,多态的基类引用在ctor里面调用虚函数,是调进继承类。一下两段代码都是输出两个"Derived"。

点击(此处)折叠或打开

点击(此处)折叠或打开

}

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

我在这想看到几件事情:

1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少 秒而不是计算出实际的值,是更清晰而没有代价的。

3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

2 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。

这个测试是为下面的目的而设的:

1) 标识#define在宏中应用的基本知识。这是很重要的。因为在 嵌入(inline)操作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

2)三重条件 操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。

3) 懂得在宏中小心地把参数用括号括起来

4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

3. 预处理器标识#error的目的是什么?

如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只 有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。

4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

这个问题用几个解决方案。我首选的方案是:

一些程序员更喜欢如下方案:

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原 理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。

第三个方案是用 goto

应试者如给出上面的方案,这说明或者他是一个汇编 语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

5. 用变量a给出下面的定义

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当 我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少 大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:

1) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数 据和代码范围的好处和重要性。

我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可 以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)

如果应试者能正 确回答这个问题,我将问他一个附加的问题:

下面的声明都是什么意思?

前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可 以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常 指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句, 也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:

1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理 其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)

2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

}

我要回帖

更多关于 怎么做语文阅读理解题 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信