.NET程序员面试宝典(txt+pdf+epub+mobi电子书下载)


发布时间:2020-06-09 11:29:54

点击下载

作者:欧立奇,赵娟

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

.NET程序员面试宝典

.NET程序员面试宝典试读:

前言

第1部分 求职过程

第1章 应聘求职

1.1 应聘渠道

1.2 应聘流程

第2章 求职五步曲

2.1 笔试

2.2 电话面试

2.3 面试

2.4 签约

2.5 违约

第3章 简历书写

3.1 简历注意事项

3.2 简历模板

第4章 职业生涯发展规划

4.1 缺乏工作经验的应届毕业生

4.2 更换工作的程序员们

4.3 快乐地工作

第2部分 C#程序设计

第5章 C#程序设计基本概念

5.1 常见概念

5.2 C++和C#的区别

5.3 数据类型

5.4 extern

5.5 其他

第6章 const、异常和反射

6.1 const

6.2 异常

6.3 反射

第7章 传递与引用

7.1 传值

7.2 静态变量与私有变量

7.3 输入/输出流

7.4 程序集

7.5 序列化

第8章 循环、条件和概率

8.1 foreach

8.2 递归与回朔

8.3 条件语言

8.4 随机数

第9章 关于面向对象的面试问题

9.1 面向对象的基本概念

9.2 访问修饰符

9.3 类和结构

9.4 Static

9.5 密封类

9.6 构造函数和析构函数

9.7 虚函数

9.8 静态构造函数与私有构造函数

9.9 多态的概念

9.10 索引器

第10章 继承与接口

10.1 继承基础问题

10.2 关于new

10.3 this问题

10.4 base问题

10.5 抽象类

10.6 接口

第11章 委托、事件和泛型

11.1 委托

11.2 事件

11.3 泛型

第3部分 数据结构和软件工程

第12章 数据结构

12.1 链表

12.2 栈和堆

12.3 树

第13章 字符串

13.1 字符串基础

13.2 StringBuilder

13.3 正则表达式

13.4 数组

13.5 字符串其他问题

第14章 排序

14.1 排序基础知识

14.2 冒泡排序

14.3 鸡尾酒排序

14.4 选择排序

14.5 插入排序

14.6 希尔排序

14.7 快速排序

14.8 归并排序

14.9 堆排序

第15章 设计模式

15.1 基础模式

15.2 结构模式

15.3 Clone

15.4 观察者模式

15.5 UML

15.6 软件工程

第16章 软件测试

16.1 白盒测试

16.2 性能测试

16.3 关于游戏

第4部分 UNIX、Oracle和网络

第17章 操作系统面试例题

17.1 进程

17.2 线程

17.3 UNIX

17.4 Windows

第18章 数据库和SQL语言

18.1 数据库理论

18.2 ORACLE基础

18.3 SQL语言客观题

第19章 计算机网络及分布式系统

19.1 网络结构

19.2 TCP/IP

19.3 网络安全

19.4 网络其他

第5部分 .NET扩展项目

第20章 Winform窗体与控件

20.1 窗体类

20.2 控件类

20.3 图形

第21章 ADO数据库相关知识

21.1 ASP.NET基础

21.2 ADO.NET体系结构

21.3 数据绑定

21.4 Datagrid

21.5 Dataset

第22章 .NET中Web设计

22.1 Session

22.2 Cookie

22.3 XML

22.4 数据传递

第23章 ASP.NET应用技术

23.1 .NET局域网

23.2 .NET Remoting体系

23.3 Web Service

第24章 .NET性能与安全

24.1 内存管理

24.2 垃圾管理

24.3 .NET安全设置

第6部分 综合面试题

第25章 英语面试

25.1 面试过程和技巧

25.2 关于工作(About Job)

25.3 关于个人(About Person)

25.4 关于未来(About Future)

第26章 电话面试

26.1 电话面试之前的准备工作

26.2 电话面试交流常见问题

第27章 智力测试

27.1 关于数字的智力问题

27.2 关于推理的智力问题

27.3 关于综合的智力问题

27.4 关于群体面试

第28章 逻辑测试

28.1 文字逻辑

28.2 图形逻辑

28.3 找规律

附录A内容简介

本书取材于各大IT公司历年面试真题(笔试、口试、电话面试、英语面试,以及逻辑测试和智商测试),详细分析了应聘程序员职位的常见考点。本书分为6部分,主要内容包括:求职过程,C#程序设计,数据结构和软件工程,UNIX、Oracle和网络,.NET扩展项目,综合面试题等。最后本书着力讲述了如何进行英语面试和电话面试,并给出了大量实际英语面试中的问题、参考答案及常用词汇,尝试解决程序员应聘外企时语言问题造成的瓶颈。本书的面试题除了有详细解析和回答外,对相关知识点还有扩展说明。希望真正做到由点成线,举一反三,对读者从求职就业到提升计算机专业知识都有显著帮助。

本书适合计算机相关专业应届毕业生阅读,也适合作为正在应聘软件行业的相关就业人员和计算机爱好者的参考书。前言

本书是程序员面试宝典系列中的一本。

对于刚毕业的学生和正在找工作或将要找工作的程序员来说,当你应聘一份程序设计、软件开发或者技术咨询方面的工作时,招聘方几乎总会安排一次面试以考查你的程序设计能力。我们写作这本书的目的就是希望能帮助大家顺利地通过这类面试。

通过.NET, Microsoft为我们提供了一种全新的开发平台,这个平台推动了以新体系为基础的协同Web应用开发。当前,第2版Visual Studio.NET已经出现,市场前景广阔,并将在多个方面与Java体系分庭抗礼。可以说,无论是在技术上还是在战略上,.NET作为一种全新的技术,融合许多根本性的、深层次的创意。而随着Microsoft.NET战略影响日趋扩大,拥有丰富的市场能量。用户、网络管理员和开发者,以及软件公司大量转投.NET阵营,从而也影响了IT市场人才分布及相应面试题目。

在2003年前,几乎没有任何公司会以.NET、C#作为面试资料来考查面试者。可如今,.NET程序员面试已成为继C++、Java后第三大面试主题,并有后来居上的趋势。而市场上却几乎没有一本专门阐述关于.NET程序员面试体系的书籍,所以.NET程序员及潜在的其他编程人群迫切需要一本能介绍.NET程序员面试的相关书籍。

作为本书的作者,在过去的几年里,由于工作和项目的缘故经常接触面试和笔试,进入公司后,也由一个被面试者转而成为一个去考别人的主考官。其中感触良多。笔者在上一本书中,是作为一个在校学生,对求职面试进行研究和理解。步入职场后,随着经验和阅历的加深,眼界不再拘泥于语言的局限,而是发掘语言后面的数据结构、算法思想等。希望在本书中,能将我的心得呈现给大家。

作为市场上一本专门介绍.NET程序员面试的著作,本书会详述.NET体系的诸多特点和面试注意事项,但更加有趣的是体系背后的东西:设计模式、数据结构、软件测试、算法策略、信息化……也就是说,在新书中追求的是程序员求职背后的一些东西——对于技术的本质理解。

本书结构是一种问询式的结构。这样不仅言简意赅,平易近人,而且可以容纳更多的题目,真正达到宝典之效用。但本书又不简单作为一个题库出现,对一个类型的问题不简单加以重复。本书采用循序渐进的办法:(1)将重要概念加以复习;(2)完善解题思路,而不是仅仅给出答案;(3)给出完整可靠的答案,如果是可以验证的,要给出验证的结果;(4)综合几种解题方案,给出最优解;(5)触类旁通,给出语言背后的算法本质性解释。本书的解题思路不仅能够让大家知道什么是正确的解决方案,而且让大家明白怎样能获得最佳方案。《.NET程序员面试宝典》不同于其他.NET书籍的主要特点如下。

♦细致

国外的面试书籍与中国国情不是很相符,因为中国的软件企业比较小,涉及的方面比较细、比较基础,比如常会考到一些编程基础性的面试问题及编程语言特点,如static与const、类和结构的区别、类型转化等,而原有的面试书籍对此方面鲜有触及。一些国内公司多半是浅显基础的问询,很少有体系结构观点和管理领悟观点。换句话说,他们考得很细。本书把面试中应对国内公司所最易考到的基础考点,放在第1部分C#程序设计里面,希望能切切实实解决实际问题。

♦专业

面试题是通过一道题考一个专类的能力,比如说关键字Singleton的面试题是考验你对密封类、单键模式的理解;Dataset、ADO.NET是招聘.NET Web程序员过程中必需而且有效的方法;链表和二叉树考验你的数据结构功底。从被面试者的角度来讲,你能了解许多关于出题者或公司的情况。如果上述任何问题的答案是“是”的话,那么这标志着答题者确实是花时间在相关系统上。从面试者的角度来讲,一个测试也许能从多方面揭示应试者的基本素质及知识水平。

♦广度

求职者应聘的职位,一般有网络工程师、测试工程师、软件开发人员。实际上市面上流行的面试书籍仅对第三类软件开发人员比较侧重,而忽略网络工程师和测试工程师,而现实情况是诸如趋势科技、华为3Com、思科等公司对网络方面的考题日趋增加,本书就这一方面给出详细论断并结合大量考题分析题目特点给出应试方案。

此外,随着全球五百强企业的进入,外企对逻辑测试、设计模式、软件度量、群体面试等面试题的喜爱有增无减,而市面上的书籍却鲜有综述,使求职者面对这方面面试时无处下手。本书将在这些方面加以改进,以适应市场需求。

♦扩展

由于C#是一种相对年轻的语言,继承了C++的语言特点并类似Java体系。所以.NET的面试题很多与这两种语言有共通性,所以本书在描述面试真题时不会就事论事,而是谋求对于技术本质的扩展与深入。所以本书虽命名为“.NET程序员面试宝典”,但不仅限于对.NET技术的单纯讲解。因为只有这样,求职者才能不被语言所羁绊,而对于一个大公司而言,除了看中求职者对语言的熟练程度,更看重大局观、整体架构等超脱语言的东西。

♦真实

本书的所有面试题都是2007—2008年各大公司面试题的汇总,内容非常新,可以算做面试者求职前的一份全真模拟。我们希望营造一种真实的面试氛围,同时作者希望把如何写好简历,如何在面试过程中应答的实际感悟融汇在书中,指引读者走上理想的工作岗位。

本书不是一本万能书籍,但却肯定是您工作求职的好助手和好伙伴!编著者序

首先,我要感谢本书的作者能够选择这样一个备受大家关注的话题作为题材,同时也要感谢电子工业出版社能够将此书大力推广。要知道,程序员和面试可能是现在因特网上大家最为关心的字眼之一了——不,应该是之二。正好,本书详尽地描述了程序员应该学些什么、做些什么,然后应该如何面对烦人的但又必不可少的面试过程。当然,如果您不是程序员,我依然认为本书会对您的职业生涯有所帮助,相信我吧。

哦,忘了介绍我自己了。我是孔文达,毕业于北京某某大学材料系,现任微软(中国)有限公司顾问。咦?怎么读材料的从事上IT工作了?这说来可话长了。一句概括的话,就是:努力加机遇。当然,我并不想长篇大论应该如何努力及如何把握机遇,我想说的是和本书密切相关的话题——面试。

其实,无论是程序员还是其他任何行业的任何职位,面试过程都大同小异,无非就是提交简历、电话面试、面谈、得到offer等这一系列过程。当然,这其中每一步都很重要!简历要写得得体、漂亮,尽量突出自己的优势,屏蔽自己的劣势。电话面试还好一些,因为只是电话交谈,所以您也许会更好地把握自己的语言。面谈是最关键的一步,而且如果您准备不充分的话,一定会紧张。紧张,就有可能出现错误。不过还好,大多数面试官都可以接受面试者的紧张,只要不是太过分,问题就不大了。一般来说,中型或大型企业的面试都不止一轮,有些甚至有十几轮。就拿微软来说吧,官方渠道需要12轮面试,内部推荐也需要4轮,而且是一票否决式。就是说,有一个面试官说你不行,你就没戏了。怎么搞定所有的面试官呢?当然有很多技巧,但最重要的一条就是:面试官是个活生生的人,他/她一定有个人偏好,在你见到面试官时,尽可能在最短的时间内——最好是在他/她了解你之前——了解他/她,因地制宜地与他/她展开对话。最后一点,最好不要极其地、非常地、十分地想得到某个职位,这有可能会使你失态,抱一平常心有时会得到意想不到的效果。

这本书写得非常好,它非常详尽地描述了作为一名程序员应该为面试准备些什么和注意些什么。也许您现在还用不到它,先看看吧,指不定什么时候就用上了呢!这不是杞人忧天,而是未雨绸缪!Microsoft技术顾问微软全国TOP3讲师[在正式加入微软(中国)有限公司前,曾任微软外聘顾问及特约讲师7年,并在北京中达金桥科技开发有限公司(微软在国内最大的技术及培训合作伙伴)任人力资源部总监及副总裁。]第二届微软十佳金牌讲师首届微软十佳金牌讲师MLC认证讲师微软护航专家CIW认证讲师(CIW CI)CIW网络安全分析大师(CIW)华为网络工程师(HCNE)HP-UNIX系统及网络管理员(HP-UX Administrator)Cisco认证网络专家(CCNA)微软认证讲师(MCT)微软认证数据库管理员(MCDBA)微软认证系统工程师(MCSE)微软认证专家(MCP)微软销售专员(MSS)……第1部分 求职过程The procedure of applying for a job

本部分将详述作为一个计算机专业的应届毕业生或程序员,在求职面试中应该注意的一些问题。

古人云:凡事预则立,不预则废。机会都是垂青有准备的人的。为了得到一份满意的工作,大家一定要对整个求职过程有清醒的了解。把能够预见的、必须开始做的事情早一些做完,这样在大规模招聘开始的时候就可以专心地为面试做准备。求职过程中会发生很多预料不到的事情,当你的计划被这些事情打乱之后,要做的事会越堆越多,一步落后,步步落后。如果能够早把能做的事情做完,即便有计划外事件发生,也不会产生太严重的影响。努力地使事态的发展处在自己能控制的范围之内,这样无论发生任何事都能有应对之策。第1章 应聘求职

每年的9月到次年的1月,都是应届生求职、在职人员跳槽的高峰期。对于即将成为程序员的应届毕业生们,在求职过程中要怎样确定目标公司和目标职位;对于已经是程序员的跳槽大军来说,是按照技术路线发展自己的职业生涯,还是走向管理岗位来继续自己的职业道路,或者是改变自己的发展轨迹;大家在求职过程中都要注意哪些细节?这些都是大家所关心的话题。

国内的IT业比国外兴起得晚,而且目前还没有权威的适合中国本土程序员的职业生涯发展规划。因此,国内流行的“35岁退休说”其实是一种误解,只要我们好好规划自己的职业生涯,提高自己的技术水平、沟通技巧和管理能力,就能够获得更高更好的职位,完全可以像国外程序员一样工作到60岁再退休。

让我们先从应聘流程中的注意事项,这个轻松却又容易被人忽略的话题开始吧。1.1 应聘渠道

对于应届生而言,可以选择参加校园宣讲会的形式投递简历。如下图所示,这是EMC公司2006年校园宣讲会日程表。我们可以选择就近的城市参加它的宣讲会并投递简历。

招聘会投递的简历是“纸”的简历,尽管现在网上投递电子简历的方式大行其道。但是“纸”的简历方式仍然有其无可比拟的优势。HR拿到“纸”的简历,相比一份电子简历更有一种亲切感,重视程度也较电子简历高一些。

第二种方式是投递电子简历,可以通过公司电子信箱和公司网站招聘信息栏(数据库),以及各大招聘门户网站,如ChinaHR或者智联招聘等来投递自己的电子简历。1.2 应聘流程

应聘时的一个完整流程如下图所示。

通常一个外企的应聘流程是一个很长的过程,甚至可以达到一两个月。还是以EMC公司为例,如下图所示,让我们看一下他们的应聘流程。第2章 求职五步曲

笔试、电话面试、面试是顺利求职的三个过程。三关全过才能顺利签约,只要有一关没通过,就会被“刷”掉。除此之外,签约本身又何尝不是一个重要的考试?涉及到你的未来,人生,行业甚至家庭。当然,有签约就有可能会有违约,真希望你们不必走第五步,但是这个世界毕竟不是童话。2.1 笔试

笔试是程序员面试三个过程中最重要的一个环节,也是最难以提升的一个环节。本书中主要叙述也是程序员的笔试经历。不论你有多么大的才干,多么广博的知识。如果未能通过笔试,则无缘下面的进程。下面是一个表,描述各种IT公司笔试所考的类型。

根据上表,我们可以对公司的笔试题目和所考的内容初窥门径,并得出以下几个结论。

1.语言的偏向性

综合上表所示,IT公司笔试在编程语言上有一定偏向性,以C、C++为主,或者是以Java为主。语言的本身并没有什么高低贵贱之分,但相对来说,考到Delphi或者Visual Basic的可能性很小,所以作为应届毕业生,如果只是学过Visual Basic或Visual FoxPro,却从来没有接触过C系语言,则在笔试中是比较吃亏的。

2.英语的重要性

外企的笔试卷子基本上都是英语试卷,无论从出题到解答,都是让你用英文去回答,所以必须有很好的英文阅读能力,这也是外企招人对英语非常看重的原因。其实也不需一定通过英语六级,但一定要有相对多的单词量,能够看懂考题的意思就行。然后按自己的想法组织语言描述就可以了。

国内企业一般对外语要求不是很看重,题目也是中文。如果不想进外企的话,也不用特别地准备英语。

3.淡看智力测试

之所以要强调这一点是和市面上过度强调外企智力测试有关,实际上笔者参加过的微软等外企笔试,智力测试只占很小的比例,约3%~5%左右。而华为、神州数码等国内IT企业就基本上没有智力测试,完全是技术考试。所以奉劝大家不要把精力都投在所谓的外企智力测试上面,还是应该准备技术方面的笔试为主。

4.有的放矢准备简历

不同的公司会考不同的内容,这就像高中时准备不同的科目考试差别一样。比如说,神州数码就不会去考嵌入式编程,而VIA考设计模式的可能性是很小的。一般有点偏“硬”的IT公司会对C++中指针的用法、数据结构考得比较多。偏“软”的企业会对设计模式、模板注重一些。所以本书分得很细,力求对各种IT公司的笔试题目进行详尽阐述。

作为求职者,笔试前首先搞清这个公司的基本情况,它是做什么的,它有什么产品,你是学什么方面的。做到有的放矢才能折桂。

5.纸上写程序

搞计算机的肯定不习惯在纸上写程序,然而技术面试的时候这是面试官最常用的一招。让写的常见程序有:数据结构书上的程序,经典C程序(strcmp, strcpy, atoi……),C++程序(表现C++经典特性的)。第一次在面试官面前写程序,思路容易紊乱。建议大家事先多练习,找个同学坐在边上,在他面前写程序,把该同学当成面试官。经过多次考验,在纸上写程序基本不慌了。

每次面试总会有些问题回答得不好,回来之后一定要总结,把不懂的问题搞明白,一个求职者就碰到两家公司问了同样的问题,第一次答不出,回去又没查,第二次又被问倒,当然这是很郁闷的事情。

下面举一个笔试的实际例子,本题是美国某因特网公司2007年上海某校的校园招聘试题。

面试例题1:两个二进制数的异或结果是多少?

面试例题2:递归函数最终会结束,那么这个函数一定_______。(不定项选择)

A.使用了局部变量 B.有一个分支不调用自身

C.使用了全局变量或者使用了一个或多个参数

面试例题3:以下函数的结果是______。

面试例题4:以下程序的结果是______。

面试例题5:下面哪项不是链表优于数组的特点?

A.方便删除B.方便插入C.长度可变D.存储空间小

面试例题6:T(n)=25T(n/5)+n^2的时间复杂度?

面试例题7:n个顶点,m条边的全连通图,至少去掉几条边才能构成一棵树?

面试例题8:正则表达式(01¦10¦1001¦0110)*与下列哪个表达式一样?

A.(0¦1)*

B.(01¦01)*

C.(01¦10)*

D.(11¦01)*

E.(01¦1)*

面试例题9:如何减少换页错误?

A.进程倾向于占用CPU

B.访问局部性(locality of reference)满足进程要求

C.进程倾向于占用I/O

D.使用基于最短剩余时间(shortest remaining time)的调度机制

E.减少页大小

面试例题10:实现两个N*N矩阵的乘法,矩阵由一维数组表示。

面试例题11:找到单向链表中间那个元素,如果有两个则取前面一个。

面试例题12:长度为n的整数数组,找出其中任意(n-1)个乘积最大的那一组,只能用乘法,不可以用除法。要求对算法的时间复杂度和空间复杂度进行分析,不要求写程序。

从这套试题分析来看,题目本身都不是太难的,不管是微软还是谷歌,招聘者是HR而不是神仙,不要把题目想得太神秘了。但是也不要看这个简单,据说这套题目的标准是最多只能错两道题才能进入下一轮面试。况且这只是基本的题目,一般还会有编程或者测试的大题,那些才是难点。

从题型来看,涉及面比较广,包括:数据结构(链表、树、图)、C++编程基础、数组、递归、矩阵、内存管理,以及时间复杂度(实际上是考数学)。所以必须对计算机的基础知识有一个全面的了解,并在此基础上,加强对一些关键知识点的认知。2.2 电话面试

电话面试主要是对简历上一些模糊信息的确认,之前经历的验证,针对应聘职位简单技术问题的提问,以及英文方面的考查。

由于模式的限制,电话面试时间不会很长。在这个环节中,一定要表现得自信、礼貌、认真严肃。这样会在声音上给对方一个良好的印象。如果声音慵懒、语气生硬,除非是技术题目及英文方面表现得足够好,否则很难予以平衡。

在回答电话面试的问题时,不要过于紧张,要留心对方的问题,这些问题也许在当面的面试中还会再出现。如果对方在电话面试中要求你做英文的自我介绍,或者干脆用英文和你对话,那在电话面试结束后一定要好好准备英文面试的内容。

笔者曾经参加过thoughtworks、sybase、sap、麒麟原创等公司的电话面试。像外企一般都会要求你做一个英文自我介绍和一些小问题,总的来说,不会太多地涉及技术方面的问题。因为用英语来描述技术对国人而言还是有一定困难的。国企会问到技术问题,我就曾被问到如何在C++中调用C程序,索引的分类等技术问题,这个基本上要靠平时的积累和对知识的掌控能力。2.3 面试

面试能够问出求职者对哪些方面擅长,对哪些方面不足。如果面试官针对求职者不足之处穷追猛打,或是炫耀自己的才能,这是不足取的。

对于求职者而言,面试是重点环节,要守时是理所当然的。如果不能按时参加面试,最好提前通知对方。着装上不需要过分准备,舒服、干净就好了。一般的IT公司对技术人员都不会有很高的着装要求。虽然着装不要求,但精神状态一定要好。饱满的精神状态会显得你很自信。

有笔试的话(有时笔试和面试是同时进行的,即面试官会在提问后请你回答并写下详细描述),也无非是与应聘职位相关的技术考查或者是英文考查,如英汉互译等。视你应聘职位的等级进行准备。

初级职位会针对你的编程能力和以往的项目经验进行一个重点的考查。如果面试官针对你做的某个项目反复提问,那么就需要注意了,要么面试官在这个方面特别精通,要么就是未来的职位需要用到这方面的技术。我们应该抱着一种诚恳的态度来回答,对熟悉的技术点可以详细阐述,对于不熟悉的部分可以诚实地告诉面试官,千万不要不懂装懂。不过,可以引导与面试官的谈话,把谈话内容尽量引导到我们所擅长的领域。在美国著名数据分析公司S面试时,在回答完面试官单链表逆置和复制构造函数问题之后,笔者把话题引入了所擅长的设计模式话题,这是一种谈话的艺术。

中级职位,不但会考查代码编写,而且会对软件架构或相关行业知识方面进行考查。代码编写方面,主要以考查某种编程技巧来判断你对代码的驾驭能力。比如,某国际知名软件公司经常会让面试者编写malloc或atoi函数,越是简单的函数就越考验应聘者的编码能力,不但要实现功能,而且还要对可能出现的错误编写防御性代码,这些都需要在实际编程过程中积累。

高级职位作为应聘者肯定对技术或某个行业有相当程度的了解,这时主要是看你与职位的契合程度、企业文化配比性及整体感觉。管理职位的话,考查更多的将是管理技巧、沟通技巧和性格因素;架构师一般会考查行业背景与软件架构方面的知识,比如UML或建模工具的使用等;技术专家的职位则会针对相关技术进行深度考查,而不会再考查一般性的编码能力。

面谈的时候,要与面试官保持目光接触,显示出你的友好、真诚、自信和果断。如果你不与对方保持目光接触,或者习惯性地瞟着左上角或者右上角的话,会传达给对方你对目前话题表现冷淡、紧张、说谎或者感觉缺乏安全感。

如果对方问到某个问题你不是很熟悉,有一段沉默的话,请不要尴尬和紧张。面试过程中允许沉默,你完全可以用这段时间来思考。可以用呼吸调整自己的状态。如果过于紧张,可以直接告诉对方。表达出自己的紧张情绪,能够起到很好的舒缓作用。而且紧张本来也是正常的表现。

在面试过程中,应聘者也保有自己的权利。比如面试时间过长,从上午一直拖到下午,而你未进午餐就被要求开始下午的面试的话,你完全可以要求进餐后再开始。面试是一个双方信息沟通及达成合作目的的会谈,是一个双方彼此考量和认知的过程。不要忽略自己应有的权利。

面谈后,如果对方觉得你技术、沟通、态度各方面都不错,也许会增加一个素质测评确认一下对你的判断。

素质测评一般是考查性格、能力、职业等方面,以判断你的价值观是否与企业相符。我们不需要去猜测这些题目到底是考查些什么,凭着你的第一感觉填写就可以了。在几十道甚至上百道题目中,都有几道题是从不同角度考查一个方向,凭猜测答题反而会前后有悖。

当然,要先看清楚题目,搞清楚是选择一个最适合你自己的,还是描述得最不恰当的。在通过面试之后,如果有多家公司和职位可以选择的话,我们可以将公司的行业排名、公司性质、人员规模、发展前景、企业文化、培训机制,结合自身的生活水平、职业生涯发展规划来进行排列,选出最适合自己的公司和职位。

建议准备一本日程本,记录每一次宣讲会、笔试和面试的时间,这样一旦公司打电话来预约面试,马上查找日程本上的空闲时间,不至于发生时间上的冲突;每投一份简历,记录下公司的职位和要求,如果一段时间以后(1个月或更长)有面试机会,可以翻出来看看,有所准备;根据不同的公司,准备不同的简历,千万不要一概而论,不同的公司关心的东西不一样;每参加完一次笔试或面试,把题目回忆一下,核对一下答案,不会做的题目更要好好弄懂;同学之间信息共享,总有人有你没有的信息;如果投了很多份简历,一点回音都没有,你得好好看看简历是否有问题,增加一些吸引HR眼球的东西。2.4 签约

首先向你表示衷心地祝贺!如果看到这部分,那说明你已经顺利通过了笔试、面试,并能上岗。一般来说,面试成功后,一般就会有口头Offer或者是电话Offer了。正式的Offer应该提供以下几项:

1.薪水(税前还是税后)

2.补助(税前还是税后)

3.工作职位

4.工作时间、地点

5.保险公积金等福利

常见格式如下(美国XXXX公司发给笔者的邮件):

在签约前,一定要向HR或其他人打听清楚以下信息:

1)户口

要问清楚,这个单位是“保证解决户口”、“尽力解决户口”、“不保证解决户口”,还是“不管户口”。尤其在进行校园招聘时,对于签约北京、上海单位的同学,这点非常重要。因为北京、上海对于外地人落户非常严格,所以,用人单位能否给你解决户口,这点非常重要。

对于户口来讲,大多数国企、事业单位、研究所、公务员都是有能力解决户口的,但是外企和私企解决户口的能力跟前面的单位比要差很多,但是不同的单位也有很大的差别,像IBM、华为每年就能拿到很多名额。所以,对于这些单位,更要问清楚,到底有多大可能性解决户口。如果企业不能解决户口,你就只能办理临时居住证了。

如果你想在一个城市长期发展的话,户口的作用是非常大的,以北京为例:如果没有北京户口,当你想跳槽时,会发现能选择的单位很有限,因为很多单位招人时,往往都要求北京生源、北京户口。这是户口带给我们的直接影响,长远地看,还有结婚、出国、子女就学、业务往来等各方面都会受到影响。当然,如果你将来想出国,或不想在北京常住,那么户口可能就不重要了。

所以,对于大多数人来说,要想获得北京、上海户口,基本上只有毕业这一次机会。这点,请一定要计算清楚。特别说明的是,对于那些“尽力解决户口”、“不保证解决户口”的单位,跟你签了协议,实际上你就要承担一定风险。一旦最后没给你落户,大多数情况下,户口和档案会被打回原籍,因为那时再签约别的单位就会比较麻烦。

在日益激烈的就业形势下,户口和薪水很难两全,既解决户口、薪水又高的单位是很少的。一定要在两者中间权衡轻重,不要做出让自己后悔的决定。

2)待遇

签约前必然要谈的部分。这里面的因素非常多,待遇主要包括:工资、奖金、补贴、福利、股票(期权)、保险、公积金。以下具体介绍各部分应注意的细节。(1)工资:一定要问清楚是税前还是税后,这点不用多说。另外,还要问清楚,发多少个月。例如,税前工资7000,发13个月,则年收入700×13=91000。很多单位有年底双薪,还有一些单位会发14~16个月不等。(2)奖金:很多单位奖金都占收入很大一部分。例如,联想、百度、中航信都有季度奖、年终奖,另外还有项目奖,华为也有项目奖、年终奖,瞬联就没有奖金。不同的单位情况不同,奖金的数额也不一样,通常几千至数万不等,所以关于这一点,一定要问清楚,而且要问确定能拿到的奖金,取最低数。(3)补贴:有些单位会有各种补贴,例如,通信补贴、住房补贴、伙食补贴等,例如,华为有800~1000的餐补。有些单位这些补贴加一块收入会非常可观,也要问清楚。(4)福利:对于一些国企和事业单位来说,往往会有一些福利,例如,过节费、防暑降温费、取暖费、购物券、电影票、生活用品等。(5)股票:对于很多公司来说,股票是他们提供的非常有诱惑力的福利,一般来说,已经上市的公司提供股票的可能性不大,反倒是一些即将上市的公司提供股票的可能性很大,对此,一定要看准机遇,不要轻易错过。(6)保险、公积金:即常说的“五险一金”。五险指的是:养老保险、医疗保险、失业保险、人身意外伤害保险、生育保险,一金指的是住房公积金。这些是国家规定的,企业不得以任何理由拒绝为你缴纳,而且个人和企业出的比例是有规定的(但是也有一些企业就是不缴纳公积金的例子)。这里要注意的是:缴费基数。很多单位在这上面做文章。例如,你的工资是5000,他们以2000为缴费基数,也就是说,用它去乘固定的比例给你缴纳五险一金,对此,一定要注意问清楚缴费基数。有些单位公积金比例上得非常高,所以你工资扣得也很多,那意味着公司交的钱更多,而一旦买房时,这些钱都是你自己的,所以,这部分收入不能忽视。此外,有些单位还会向你提供补充医疗保险、补充养老保险、补充意外保险、住房无息贷款或经济适用房等,也要问清楚。

把这些收入加起来,得到年收入。然后再考虑工作地的工资水平和消费水平。例如,年薪8万在西安,无疑是比年薪10万在上海要高多了。(7)年假:即每年除了法定节假日之外可以休息的天数,这个自然是高校最多(寒暑假),研究所、外企可能会少些,比如ppform公司一年是15~20天年假,30天探亲假(不可以同时休);Nortel是第一年12天年假,然后每年递增,直到21天为止;华为没有年假,要靠每月最后一天周六加班来攒假期作为自己的年假。不上班的时候觉得假期无足轻重,上了班就会觉得假期弥足珍贵。

3)工作内容

要问清楚自己的具体职位,这个职位的工作内容,在公司所处的地位。一般来讲,如果是公司的核心业务部门,会比较受重视,发展前景会更好,如果是其他辅助部门,可能受重视程度会差一些,当然没有绝对的,关键还有看你的工作有没有技术含量,对于你个人能力的提高、职业生涯有没有帮助,对于你跳槽、升职有没有帮助。

4)加班/出差情况

对于有些公司来说,加班是在所难免的,如华为、中兴、微软、IBM……基本上绝大多数WIT企业都要加班;而对于有些职位来说,频繁地出差是在所难免的,例如,现场工程师、市场、销售等。对于这些,要提前有所了解,有思想准备,像中兴海外可能会派到非洲若干年,条件很苦。如果自己不能忍受长期的加班、出差,建议不要签劳动合同。另外,要问清楚加班是否有加班费。现在很多公司加班都是没有加班费的,对于加班,国家有规定:如果周六、周日加班的话,可以获得正常工资2倍的加班费,如果是五一、十一这些法定假日加班的话,可以获得正常工资3倍的加班费。另外就是出差补贴。一般来讲,出差基本是不需要你花钱的,而且很多公司会有额外的出差补贴,例如,华为非洲区好像是每天补助40~70美金不等。这个也要问清楚,因为都是自己的合法权益。

5)培训

对于应届毕业生来说,公司的培训体系是一个非常重要的考虑因素,如果一家公司有非常好的培训体系的话,那么可以让你在几年内迅速成长为一个出色的人才,对你的职业生涯无疑是有巨大帮助的。像宝洁、SAP、infosys,最出名的都是它们完善的培训体系,确实可以让你在短时间内个人能力得到极大的提高,所以每年才吸引那么多同学去应聘。从某种程度上来讲,良好的培训是比优厚的待遇更有吸引力的。所以,在签约前,一定要问清楚单位有哪些培训计划,再看这些培训计划对个人的成长是否有帮助。

6)发展机会

这也是非常关键的一个因素。如果有一个很好的工作机会,可以让你直接接触最先进、最核心的业务,或者可以接触到公司的高层,或者可以获得一些非常有用的客户资源,或者可以在短期内迅速进入管理层,这就是非常理想的机遇。当然,如果你希望稳定,进入高校研究所这样的单位也是不错的选择。在考虑发展机会这个因素时,应主要考虑三个方面:(1)行业背景:要综合考虑公司所处这个行业的背景和发展现状,更重要的是,要对这个行业的发展前景有准确的预测。(2)公司背景:要考虑这家公司在行业中所处的地位,目前的发展状况、经营业绩,以及未来的发展预期。(3)个人机会:要看自己所处的部门在公司的地位,自己的职位的升职机会、发展前景。

7)签约年限及违约金

一般单位签3年,也有签5年的,还有的单位签1年,如华为。此外,很多单位还有保密合同,不同单位情况不一样。同时,违约金也会有相关规定。一般来讲,违约金特别高的,要慎重签约。

除此之外,签约时还要考虑很多实在的个人因素:比如说双亲在哪,以后回家照顾老人是否方便;配偶或者男(女)朋友的问题,会不会两地分居。因为感情的融洽不是金钱能够衡量的,所以不要把钱看得太重了,毕竟对于一个人来说,生活的和谐还是要放在首位的。2.5 违约

其实拒绝别人虽不像被别人拒绝那样痛苦,但同样是一件痛苦的事情。

大部分人准备违约,无外乎一个原因:遇到了更好的单位。于是,违约也成了非常普遍的现象。决定违约前一定要计算违约成本,想清楚以下问题:(1)新单位是否比原单位高一个档次?即:是否值得为了新单位而违约原单位?如果两家单位差不多,建议最好不要违约。(2)新单位给的最晚签约期限是什么时候?如果跟原单位提出违约,能否在新单位的签约期限前办完?如果没有把握,建议不要违约。(3)原单位以前是否有过成功违约的案例?影响如何?如果以前的违约案例大多不顺利,建议不要违约。

这里面,最关键的因素就是:原单位对待你违约的态度。毕竟,这算一个不是很好的行为,对原单位造成损失,对个人声誉和学校声誉也会造成不好的影响。这个态度决定了你能否顺利违约、违约需要的时间,以及能否及时与新单位签约。

如果一定要违约最好能做到以下几点:(1)与新单位坦诚相告,说明自己的情况,询问能否宽限时间。如果新单位不给你放宽时间,你就没必要违约。当然,你也可以不说,但你必须确保,在新单位签约期限前,你能顺利跟原单位办完违约,否则,你极有可能面临竹篮打水一场空的危险。(2)与原单位一定要好好协商,态度诚恳一些。首先要感谢对方的知遇之恩,其次说清楚自己为什么违约,并为自己的行为向对方道歉。同时,要尽可能减少你的违约给原单位声誉造成的损失,因为那家单位很有可能因为你的违约而改变对你的印象。所以,要想办法来弥补。通常,可以向单位推荐几个自己的同学或朋友,希望能给他们机会。当你放弃机会的同时,别忘记了给周围的人争取机会。

对于应届毕业生来说违约可能会更麻烦,一个基本的违约流程如下:(1)与原单位协商,向原单位接收违约,按照三方协议规定,交纳违约金(有些单位不收违约金),从原单位开出退函。(2)从新单位获取接收函。(3)拿着原单位退函和新单位接收函到就业指导中心领新的三方协议(有时也不需要接收函)。(4)拿新的三方协议与新单位签约。

在这个过程中,关键在于第一步:如何与原单位协商,拿到退函。具体的情况,不同单位不一样,有的单位可能会拖很久,例如,华为通常到3月份才给开退函。所以,如果新单位的签约时间很紧,而原单位又不会很快给你开退函的话,那结果很可能是:你两家单位都签不了。

总之,就业时要经过慎重考虑,不要轻易地签约,更不要轻易地违约,那样无论对谁都是巨大的伤害。对于你的每一个决定,自己都要为此承担相应的后果和代价!我们都是职场中人,如果你还要在职场里继续做下去,就一定要遵守职场规则。

最后,祝愿每个读者都能顺利签约自己满意的单位!第3章 简历书写

据统计,80%的简历都是不合格的。不少人事管理者抱怨收到的许多简历在格式上很糟糕。简历应该如何做到在格式上简洁明了,重点突出?求职信应该如何有足够的内容推销自己?如何控制长度,言简意赅?相信读了本章你会对简历的撰写有一个新的认识。3.1 简历注意事项

1.简历不要太长

一般的简历普遍都太长。其实简历内容过多反而会淹没一些有价值的闪光点。而且,每到招聘的时候,一个企业,尤其是大企业会收到很多份简历,工作人员不可能都仔细研读,一份简历一般只用1分钟就看完了,再长的简历也超不过3分钟。所以,简历要尽量短。我们做过一个计算,一份中文简历压缩在2页左右就可以把所有的内容突出了。1页显得求职者过于轻浮,三四页就太多了。

简历过长的一个重要原因是有的人把中学经历都写了上去,其实这完全没有必要,除非你中学时代有特殊成就,比如在奥林匹克竞赛中获过奖。一般来说,学习经历应该从大学开始写起。

很多学生的求职简历都附了厚厚一摞成绩单、荣誉证书的复印件,其实简历上可以不要这些东西,只需要在简历上列出所获得的比较重要的荣誉。如果企业对此感兴趣,会要求求职者在面试时把这些带去。

2.简历一定要真实客观

求职简历一定要按照实际情况填写,任何虚假的内容都不要写。即使有的人靠含有水分的简历得到面试的机会,面试时也会露出马脚的。千万不要为了得到一次面试机会就编写虚假简历。被招聘方发现后,你几乎就再也没有机会进入这家公司了。而且对于应届生来说,出现这种情况后,还有可能影响到同校的其他同学。

北京某高校一位计算机专业本科毕业的女孩子,简历上写的是04年毕业,但面试中被发现她是05年毕业的,而且没有任何工作经验。这女孩儿比较诚实,说是同学教她这样做的。

她这种编制虚假简历的做法应该否定,因为谁都不希望被骗。作为面试官来说,首先希望应聘者是一个诚实的人。我希望她在听到同学那个不明智的建议时,首先不应选择这种做法,其次要尽力阻止其他人这样做。因为,就像面试官代表公司形象一样,她在某种程度上也代表了她所毕业的学校来参加面试!最起码在她传达给HR的信息中,与她同专业应届生的简历可信度较差。

3.不要过分谦虚

简历中不要注水并不等于把自己的一切,包括弱项都要写进去。有的学生在简历里特别注明自己某项能力不强,这就过分谦虚了,实际上不写这些并不代表说假话。有的求职学生在简历上写道:“我刚刚走入社会,没有工作经验,愿意从事贵公司任何基层工作。”这也是过分谦虚的表现,这会让招聘者认为你什么职位都适合,其实也就是什么职位都不适合。

4.简历要写上求职的职位

求职简历上一定要注明求职的职位。每份简历都要根据你所申请的职位来设计,突出你在这方面的优点,不能把自己说成是一个全才,任何职位都适合。不要只准备一份简历,要根据工作性质有侧重地表现自己。如果你认为一家单位有两个职位都适合你,可以向该单位同时投两份简历。

在笔者曾看到的一些简历中,经常有如下的错误:简历上描述的多为Windows操作系统下C/C++开发经验,但申请的目标职位为“Linux操作系统下的C/C++开发工程师”。这样当然不容易得到应聘职位的面试机会。还有就是去应聘ERP、CRM方面的职位,而简历里却大肆强调自己在嵌入式编程方面的优势。就算你非常优秀,你对这个企业还是没有用处。

有些简历里面没有详细的项目描述及责任描述,在责任描述栏仅仅填写“软件开发”或者在工作业绩栏仅仅填写“可以”两字。这样的信息传达无疑是不成功的。

作为求职的开始,我们要编写一份或者几份有针对性的简历,也就是按照对方的要求突出自己相关的经历。只要你的优势与招聘方的需要吻合,并且比其他应聘者突出的话,你就胜利了。

5.在文字、排版、格式上不要出现错误

用人单位最不能容忍的事就是简历上出现错别字或是在格式、排版上有技术性错误,以及简历被折叠得皱皱巴巴、有污点,这会让用人单位认为你连自己求职这样的事都不用心,那工作也不会用心。

6.简历不必做得太花哨

一般来说简历不必做得太花哨,用质量好一些的白纸就可以了,尽量用A4规格的纸。笔者曾看到过一份简历封面上赫然写着4个大字“通缉伯乐”,给人的感觉就像是在威胁用人单位。现在学生简历中比较流行做封面的形式,其实没有必要,这会增加简历的厚度,实际上完全可以不用封皮。

7.简历言辞要简洁直白

大学生的求职简历很多言辞过于华丽,形容词、修饰语过多,这样的简历一般不会打动招聘者。简历最好多用动宾结构的句子,简洁直白。

8.不要写上对薪水的要求

在简历上写上对工资的要求要冒很大的风险,最好不写。如果薪水要求太高,会让企业感觉雇不起你;如果要求太低,会让企业感觉你无足轻重。对于刚出校门的大学生来说,第一份工作的薪水不重要,不要在这方面费太多脑筋。

9.不要写太多个人情况

不要把个人资料写得如此详细,姓名、电话是必需的,出生年月可有可无。如果应聘国家机关、事业单位,应该写政治面貌。如果到外企求职,这一项也可省去,其他都可不写。

10.不要用怪字怪体

笔者见过一份简历,用中空字体,还有斜体字。这些都是很忌讳的。试想一个HR挑了一天的简历,很累了,还要歪着头看你的简历。想想你的胜算能有多大?其实用简单的宋体5号字就很好了,不用标新立异。3.2 简历模板

一份合格的求职简历应该包括以下内容。

姓名、电话(或其他联系方式)等个人资料应该放在简历的最上面,这主要是为了方便用人单位与求职者及时取得联系。紧接着是毕业的学校、专业和时间。下面应该注明应聘的职位和目标。

接下去就是简历上最重要的部分:工作经历。对于初出茅庐的大学生来说,这部分包括勤工助学、课外活动、义务工作、参加各种各样的团体组织、实习经历和实习单位的评价等。这部分内容要写得详细些,指明你在社团中,在活动中做了哪些工作,取得了什么样的成绩。用人单位要通过求职者的这些经历考查其团队精神、组织协调能力等。

兴趣爱好也最好列上两三项,用人单位可就此观察求职者的工作、生活态度。

如果应聘外资企业、大的跨国公司,一定要附上英文简历,而且要把最近的经历放在最前面,简历前面最好附一封推荐信。一定要认真对待英文简历的编写,因为它会泄露你的实际英文水平。

下面是作者的一份简历模板。第4章 职业生涯发展规划

在一般情况下,我们工作一年之后,对自己的喜好及擅长都有了更加深刻的了解,这时会有较为明确的职业发展规划。4.1 缺乏工作经验的应届毕业生

即将毕业的学生们自己的目标职位很模糊,只要是计算机相关的工作都想试一下。但是现在公司看重的除了学生的基本素质,即沟通能力、团队协作、学习能力、外语水平等之外,也会关注应届毕业生在校及实习经历中与目标职位相关的经验。假设与导师做的课题或者实习中接触到J2EE企业级开发,那么在应聘时寻找一份相关要求的工作就更为容易。而这样的经历去找一份C/C++开发的职位可能就略微难些。

上海某高校的一位学生在课余时间开发了一个基于校园网内部的搜索引擎。比起商用的搜索引擎,其搜索效率、数据量不算出色,但是该生通过编写自己的搜索引擎,详细了解了网络编程、网页爬虫等领域的知识。这个搜索引擎也表现出了他专业技能的水平,从而为他赢得了前往国际某著名网络公司应聘的机会。

所以,在大学期间,我们可以通过参加创新杯比赛、著名软件公司举行的各种编程大赛、各种技术社团的活动来增加编程经验,以获取公司对你专业技能的肯定。各种编程大赛中获得的名次、实践大赛中的作品,都可以作为工作经验的替代。

通过校园招聘招人的大公司,一份有分量的简历只是第一步。有分量指的是成绩尚可,有让他们感兴趣的实习经历,有一定的获奖经历,担任过一定的职务,英语能力还行。这仅仅是第一步。它能让你从众多应聘者中被选出来参加初试,接下来就看你的真正功力和造化了。

初试的要点是基本功扎实,自信乐观,英语交流能力不错,够聪明,够机灵。基本功扎实并且聪明尤为重要。某位毕业生参加Sybase公司面试,过了印度技术官的英语技术面试,第二天参加他们的Aptitude Test(智商测试),误认为是态度测试(Attitude Test),结果没发挥好。智商测试通常让你在很短的时间内做大量的逻辑题和智力题。不要在前面的题目上浪费太多时间,后面的题目往往更加简单。

另一位求职者通过了微软公司的笔试和电话面试,后来去参加了正式的面试。一连3轮,面试官全都是微软高级技术经理。面对这么高级别的面试官,求职者难免紧张。3轮全英文面试,写了6个程序,不算难,但是考得很细,注重求职者的逻辑思维能力、反应能力和编程技巧。写完程序之后马上设计测试用例。或许是没有参加过特别正规的项目开发的缘故,他表现一般,有几个程序有疏漏,面试官加以提醒,虽然最后能够改正,但是加重了他的紧张情绪,没能闯入下一轮。

没必要因为自己的学校而显得不够自信,只要打好技术功底,多参加正规的实践项目,找工作的时候自然会顺利。此外在求职过程中,整体形势和个人形势没有必然联系。整体形势好了,个人形势未必好。往往整体形势好了,个人容易盲目乐观,在准备不充分的情况下,很容易被莫名其妙地淘汰。即使明年的整体形势比今年还要好,招人的公司比今年还要多,还是建议大家脚踏实地,做好充分的知识储备和心理准备,找工作绝对是一场硬仗。

有一种说法:80%的Offer掌握在20%的牛人手中。每年10月、11月应届毕业生刚刚开始找工作的时候,正是牛人们发威的时候,笔试、面试都有他们的份,到了发Offer的时候他们手中集中了很多好Offer。这时候我们得摆正心态,尽自己最大努力,发挥出自己的最好水平就行了,不用太在意结果。晚些时候往往反而会有好Offer。写论文的同时抽空复习一下基础的课程:数据结构、C、C++、TCP/IP、操作系统、计算机网络、UML、OOA&OOP、自己做过的项目的知识,等等。不要怕笔试和面试,笔试得多了,感觉就来了。

可能你找到了工作,并且不止一个。但手头的Offer再多,也只能跟一家公司签约,面对的诱惑再多,也只能选择一个。不用羡慕那些手头有很多好Offer的人,他们其实很痛苦,这是一种甜蜜的烦恼。罗列出你最在意的方面,把几家公司做详细的比较(见下表)。做选择有时候很感性,理性的数据往往不如公司的一名普通员工给你的印象更能影响你的决定。4.2 更换工作的程序员们

如果你是跳槽者中的一员,我们要明白频繁跳槽对我们的职业生涯发展是有害无益的,招聘方也十分关注求职者的稳定性。一般来说,每份工作都要维持一年以上,能够在某家公司工作满3年,才会对公司所在行业及这家公司有比较深入的了解。决定更换工作时,我们要先问问自己要在哪个方向继续自己的职业生涯。假设目前你是某家公司的开发人员,要应聘更大规模公司的同等职位,我们应该注意下面两点。

首先,比起创业型公司,大公司的开发流程要求会更加规范和严格,有的时候我们必须放弃一些编程的习惯。严格的开发流程对文档的依赖性很大,我们必须做到文档优先。这样的一种环境,可能是初入大公司的程序员最难接受的一点。

其次,小公司里那种Superman型的程序员在大公司里很少见到。我曾经听一个程序员朋友抱怨他们公司的架构师连ASP代码都不会写,其实这是很正常的事情。架构师的工作是将业务需求变成计算机软件的模块和类,他们不需要了解具体代码的编写,只需要分析几种软件平台之间的实现难度和效率差异就够了。当然,大公司也有所谓的技术高手,但这种技术高手并不是精通几种开发语言的“万能钥匙”,而是对某种技术有深入理解,能够解决深层次问题的人。

中国的IT界,“技则优而仕”的比较多。很多技术出身的人员做到管理岗位后,关注的仍然是技术细节。但实际上,人员的管理也是一门很大的学问。技术主管的个人风格会影响整个团队的氛围。如果主管不善沟通、只关心Dead Line,那么整个团队将会毫无活力,主管的技术再高超也不会得到信服。如果主管善于沟通、关心下属,那么整个团队就会生机勃勃,即使加班也有劲头。

假设你已不想再做开发,想要转向测试或其他相关岗位,如实施、技术支持,甚至培训、售前等,那你一定要认真向目前在做这份工作的人员了解他们的实际职责与相关要求,确认是否可以接受转换岗位后带来的挑战。如果确定,则可以选择具有相同行业背景的目标职位,并且调整好自己的心理状态,给自己一段较长的时间来适应这种改变。刚开始时感觉无从下手或者有较大落差是很正常的,最起码要在半年之后才能证实你和这个岗位的匹配度。

如果你现在已经有了较为明确的职业生涯发展规划,推荐大家使用倒推法使之切合实际并行之有效。以一个普通程序员为例,我们可以首先为自己的目标设置一个年限,并列出实现这个目标所需要的专业技能,然后使用倒推法确定我们的阶段目标,直至将这个阶段目标倒推至一个月后,那它就会是一个很具体的目标了。只要你坚持去做,就会逐步实现自己的最终目标。

当然,除此之外,你还要时时关注业界动态,尽可能多地参加在职培训并且补充外语方面的技能。这样才能保持你继续前进的步伐。

当然,最重要的是我们要把握好自己,把握好自己要走的路。其实任何一个职位都需要我们努力工作,任何一份工作都无法“钦定”我们的终身。4.3 快乐地工作

人一生很漫长,你无法想象你还能够经历什么;人一生也很短暂,在你觉得还没经历些什么的时候就已经老了。别人的经历,其实都是故事。别人的成功,也不能复制。

在中国,大概很少有人是一份职业做到底的,虽然如此,第一份工作还是有些需要注意的地方,有两件事情格外重要,第一件是入行,第二件事情是跟人。第一份工作对人最大的影响就是入行,现代的职业分工已经很细,我们基本上只能在一个行业里成为专家,不可能在多个行业里成为专家。很多案例也证明即使一个人在一个行业非常成功,到另外一个行业,往往完全不是那么回事情,“你想改变世界,还是想卖一辈子汽水?”是乔布斯邀请百事可乐总裁约翰·斯考利加盟苹果时所说的话,结果这位在百事非常成功的约翰,到了苹果表现平平。其实没有哪个行业特别好,也没有哪个行业特别差,或许有报道说哪个行业的平均薪资比较高,但是他们没说的是,那个行业的平均压力也比较大。看上去很美的行业一旦进入才发现很多地方其实并不那么完美,只是外人看不见。

说实话,笔者自己都没有发财,所以我的建议只是让人快乐工作的建议,不是如何发财的建议,我们只讨论一般普通打工者的情况。我认为选择什么行业并没有太大关系,看问题不能只看眼前。例如,从2005年开始,国家开始整顿医疗行业,很多医药公司开不下去,很多医药行业的销售开始转行。其实医药行业的不景气是针对所有公司的,并非针对一家公司,大家的日子都不好过,这个时候撤资是非常不划算的,大多数正规的医药公司即使不做新生意支撑两三年是能撑的,光景总归还会好起来的,那个时候别人都跑了而你没跑,现在的日子应该会好过很多。有的时候觉得自己这个行业不行了,问题是,再不行的行业,做得人少了也变成了好行业,当大家都觉得不好的时候,往往却是最好的时候。大家都觉得金融行业好,金融行业门槛高不说,有多少人削尖脑袋要钻进去,竞争激励,进去以后还要时时提防,一个疏忽,就被后来的人挤掉了,压力巨大,又如何谈得上快乐?也就未必是“好”工作了。

太阳能这个东西至今还不能进入实际应用的阶段,但是中国已经有7家和太阳能有关的公司在纽交所上市了,国美苏宁永乐其实是贸易型企业,也能上市,鲁泰纺织连续10年利润增长超过50%,卖茶的一茶一座,卖衣服的海澜之家都能上市……其实选什么行业真的不重要,关键是怎么做。事情都是人做出来的,关键是人。

有一点是需要记住的,在这个世界上,有史以来直到我们能够预见得到的未来,成功的人总是少数,有钱的人总是少数,大多数人是一般的、普通的、不太成功的。因此,大多数人的做法和看法,往往都不是距离成功最近的做法和看法。因此大多数人说好的东西不见得好,大多数人说不好的东西不见得不好。大多数人都去炒股的时候说明跌只是时间问题,大家越是热情高涨的时候,跌的日子越近。少数人买房子的时候,房价不会涨,而房价涨的差不多的时候,大多数人才开始买房子。不会有这样一件事情让大家都变成功,发了财,历史上不曾有过,将来也不会发生。有些东西即使一时运气好而得到了,还是会在别的时候别的地方失去的。

年轻人在职业生涯的刚开始,尤其要注意的是,要做自己快乐的事情,不要让自己今后几十年的人生总是提心吊胆,更不值得为了一份工作赔上自己的青春年华。人还是要看长远一点。很多时候,看起来最近的路,其实是最远的路,看起来最远的路,其实是最近的路。要让自己在职业的道路上走得更远,首先要让自己工作得快乐,如果一份工作让你觉得不快乐,甚至很受罪,那么你就是那个坚持不到终点的选手,即使你坚持到终点了,这样痛苦的人生有意思么?其次要对未来做好规划,尽量让自己劳逸结合,要知道那是个很漫长的过程,不要在一开始就把力气和耐心耗尽了,当力气耐心耗尽而又遭遇挫折,大多数人就会陷入沮丧悲观,跳槽换工作也就变成很自然的事情。对于初入职场还不能很好控制自己心态的人,掌握好自己的节奏,不要跟着别人的脚步乱了自己的节奏,清楚自己在做什么,清楚自己的目标,至于别人上去了还是下去了,让他去吧,就当没看见。

对一个初入职场的人来说,入行后一定要跟个好领导好老师。刚进社会的人做事情往往没有经验,需要有人言传身教。对于一个人的发展来说,一个好领导是非常重要的。所谓“好”的标准,不是他让你少干活多拿钱,而是以下三点。

首先,好领导要有宽广的心胸,如果一个领导每天都会发脾气,那几乎可以肯定他不是个心胸宽广的人,能发脾气的时候却不发脾气的领导,多半是非常厉害的领导。有些领导最大的毛病是容忍不了能力比自己强的人,所以常常可以看到的一个现象是,领导很有能力,手下一群庸才或者手下一群闲人。如果看到这样的环境,还是不要去的好。

其次,领导要愿意从下属的角度来思考问题,这一点其实是从面试的时候就能发现的,如果这位领导总是从自己的角度来考虑问题,几乎不听你说什么,这就危险了。从下属的角度来考虑问题并不代表同意下属的说法,但他必须了解下属的立场,下属为什么要这么想,然后他才有办法说服你,只关心自己怎么想的领导往往难以获得下属的信服。

第三,领导敢于承担责任,如果出了问题就把责任往下推,有了功劳就往自己身上揽,这样的领导不跟也罢。选择领导,要选择关键时刻能扛得住的领导,能够为下属的错误买单的领导,因为这是他作为领导的责任。

有可能,你碰不到好领导,因为他坐领导的位置,所以他的话就比较有道理,这是传统观念官本位的误区,可能有大量的这种无知无能的领导。但是,这对于你其实是好事,如果将来有一天你要超过他,你希望他比较聪明还是比较笨?相对来说这样的领导其实不难搞定,只是你要把自己的身段放下来而已。多认识一些人,多和比自己强的人打交道,同样能找到好的老师,不要和一群同样郁闷的人一起控诉社会,控诉老板,这帮不上你,只会让你更消极,职场上最忌讳的是你还在这家公司却又不停抱怨公司本身。正确的做法和那些比你强的人打交道,看他们是怎么想的,怎么做的,学习他们,然后最终提升自己的能力才是最重要的。

希望所有读者都能快乐工作,不断提升。第2部分 C#程序设计C#programdesign

本部分主要以C#语言为基础,通过大量实际的例子分析各大公司C#面试题目,从技术上分析问题的内涵。许多面试题看似简单,却需要深厚的基本功才能给出完美的解答。企业要求面试者写一个最简单的静态构造函数都可看出面试者在技术上究竟达到了怎样的程度。读者可从本部分中关于C#的几个常见考点,如委托问题、反射问题、面向对象及接口等方面看看自己属于什么样的层次。此外,还有一些面试题考查面试者敏捷的思维能力。

分析这些面试题,本身包含很强的趣味性;而作为一名研发人员,通过对这些面试题的深入剖析则可进一步增强自身的内功。第5章 C#程序设计基本概念

作为一个求职者或是应届毕业生,公司除了对项目经验有所问询之外,最好的考量办法就是你的基本功:包括编程风格、应对赋值语句、递增语句、类型转换、数据交换等程序设计基本概念的理解。当然,在考试之前最好对自己所掌握的程序概念知识有所复习,尤其是各种细致的考点要尤其加以重视。以下的考题来自真实的笔试资料,希望读者先不要看答案,自己解答后再与答案加以比对,找出自己的不足。5.1 常见概念

面试例题1:试解释如下两个概念:CLR和CTS。

答案:CLR是公用语言运行时(Common Language Runtime)。

.NET提供了一个运行时环境,叫做公用语言运行时(Common Language Runtime),是一种多语言执行环境,支持众多的数据类型和语言特性。它管理着代码的执行,并使开发过程变得更加简单。这是一种可操控的执行环境,其功能通过编译器与其他工具共同展现。而依靠一种以运行时为目标的编译器开发的代码叫做可操控代码。为了使运行时环境可以向可操控代码提供服务,语言编译器需要产生一种元数据,它将提供在使用语言中的类型、成员、引用的信息。运行时环境使用元数据定位并载入类,在内存中展开对象实例,解决方法调用,产生本地代码,强制执行安全性,并建立运行时环境的边界。

CTS:公共类型系统(Common Type System)类似于COM定义的标准二进制格式,.NET定义了一个称为通用类型系统Common Type System(CTS)的类型标准。这个类型系统不但实现了COM的变量兼容类型,而且还定义了通过用户自定义类型的方式来进行类型扩展。任何以.NET平台作为目标的语言必须建立它的数据类型与CTS的类型间的映射。所有.NET语言共享这一类型系统,实现它们之间无缝的互操作。该方案还提供了语言之间的继承性。例如,用户能够在Visual Basic.NET中派生一个由C#编写的类。

面试例题2:property和attribute的区别是什么?

答案:property和attribute这两个名词中文都被翻译为“属性”,实际上两者是大相径庭的。其实它们来源于两个不同的领域,attribute属于OOA/OOD的概念,而property属于编程语言中的概念。Attributes是Microsoft.NET Framework文件的元数据,可以用来向运行时描述你的代码,或者在程序运行的时候影响应用程序的行为。property属性是面向对象编程的基本概念,提供了对私有字段的访问封装,在C#中以get和set访问器方法实现对可读可写属性的操作,提供了安全和灵活的数据访问封装。

可以说两者没有可比性,只要记住Attribute是派生于System、Attribute类之下,它的主要作用是描述,比如为了描述某个方法是来自于外部的dll,写如下代码,这就是一个Attribute,它是一个描述(或者说声明):

而Property是指编程过程中的字段,即类的成员,如下:

面试例题3:.NET现在是什么版本?谈谈.NET各版本的兼容性。

解析:这是个聊天题。面试官期待面试者关注新技术的发展变化。这里要注意的是不要把.NET版本和Visual Studio.NET版本混为一谈。

答案:.NET技术不断翻新,.NET框架的版本从1.0开始,经过1.1、2.0、3.0,现在已到了3.5。.NET Framework 3.5版以.NET Framework 2.0版和.NET Framework 3.0版为基础,包括.NET Framework 2.0和3.0版的Service Pack。

.NET Framework 2. 0 SP1更新包含在.NET Framework 2.0中的程序集。

.NET Framework 3. 0还包含.NET Framework 3.0中引入的技术所必需的程序集,如WPF等。

.NET Framework 3. 0 Service Pack 1更新在.NET Framework 3.0中引入的程序集(PresentationFramework.dll, PresentationCore.dll等)。

应用程序无论针对的是.NET Framework 2.0、3.0还是3.5版,都使用相同的程序集。.NET Framework 2.0、3.0和3.5版之间的关系不同于1.0、1.1和2.0版之间的关系。.NET Framework 1.0、1.1和2.0版是彼此完全独立的,对于其中任何一个版本来说,无论计算机上是否存在其他版本,自己都可以存在于该计算机上。当1.0、1.1和2.0版位于同一台计算机上时,每个版本都有自己的公共语言运行库、类库和编译器等。

关于版本的兼容性,.NET Framework对向后和向前兼容性的支持与版本相关。.NET Framework只对使用1.1版创建的应用程序支持向后和向前兼容性。在使用2.0版创建的应用程序中,.NET Framework不支持向前兼容性。向后兼容性意味着使用.NET Framework的较早版本创建的应用程序可以在更高的版本上运行。相反,向前兼容性意味着使用.NET Framework的更高版本创建的应用程序可以在较早的版本上运行。

.NET Framework提供高度的向后兼容性支持。例如,大多数使用1.0版创建的应用程序将在1.1版上运行,使用1.1版创建的应用程序将在2.0版上运行。只有对于1.1版,.NET Framework还支持向前兼容性。但是,对于向前兼容性,可能需要修改应用程序以使应用程序按预期的方式运行。使用2.0版创建的应用程序将不在.NET Framework的早期版本上运行。

面试例题4:什么是强类型?什么是弱类型?C#是强类型还是弱类型?JavaScript呢?

答案:强/弱类型是指类型检查的严格程度。语言有无类型、弱类型和强类型3种。无类型的不检查,甚至不区分指令和数据。弱类型的检查很弱,仅能严格地区分指令和数据。强类型则严格地在编译期进行检查。

弱类型语言允许将一块内存看做多种类型。比如直接将整型变量与字符变量相加。C和C++是静态语言,也是弱类型语言;Perl和PHP是动态语言,但也是弱类型语言。强类型语言在没有强制类型转化前,不允许两种不同类型的变量相互操作。Java、C#和Python等都是强类型语言。

使用哪种语言还是要按需而定。编写简单而小的应用程序,使用弱类型语言可节省很多代码量,有更高的开发效率。而对于构建大型项目,使用强类型语言可能会比使用弱类型更加规范可靠。

C#是强类型语言,因此每个变量和对象都必须具有声明类型。JavaScript是弱类型语言;因此它可以不先定义类理和对象,或用var定义所有变量。

面试例题5:什么是GAC?

解析:GAC全称是Global Assembly Cache,它的作用是可以存放一些有很多程序都要用到的公共Assembly,例如System.Data、System.Windows.Forms等。这样,很多程序就可以从GAC里面取得Assembly,而不需要再把所有要用到的Assembly都复制到应用程序的执行目录下面。举例而言,如果没有GAC,那么势必每个WinForm程序的目录下就都要从Microsoft.NET\Framework目录下面复制一份System.Windows.Forms.dll,这样显然不如从GAC里面取用方便,也有利于Assembly的升级和版本控制。

答案:Gloal Assembly Cache,全局应用程序集缓存。它解决了几个程序共享某一个程序集的问题。不必再将那个被共享的程序集复制到应用程序目录中,.NET应用程序在加载的时候,会首先查看全局应用程序集缓存,如果有就可以直接使用,没有再到应用程序目录进行查找。

面试例题6:什么叫JIT?什么是NGEN?它们分别有什么限制和好处?

答案:Just In Time是指即时编译,它是在程序第一次运行的时候才进行编译,而NGEN是预先JIT,是指在运行前事先就将生成程序集的本机镜像,并保存到全局缓存中,使用NGEN可以提高程序集的加载和执行速度,因为它可以从本机镜像中还原数据代码和数据结构,而不必像JIT那样动态生成它们。NGEN原理和缓存的道理大同小异。5.2 C++和C#的区别

面试例题:解释一下C#与C++有什么区别?

答案:区别很多,首先是托管与非托管的区别,托管代码不允许进行对内存的操作,而是由固定的垃圾回收机制来完成,而C++则不然。其次C#和Java类似,都是运行在虚拟机上的(分别是.NET虚拟机和Java虚拟机),而C++不需要这样一个平台。最后C#是完全面向对象的,在C#里,万物皆是类,绝对不存在一个超越类以上的函数或是变量,C++也是面向对象的,但其仍然保留面向过程语言的特点(比如说C++存在全局变量)。最后,C#摒弃了C++中的多重继承等不易掌握的特点,代之以接口等,使编程变得更加轻松和简便。5.3 数据类型

面试例题1:引用类型和值类型区别是什么?[美国著名数据库公司S面试题,2008年10月]

解析:CLR支持两种类型:值类型和引用类型。用Jeffrey Richter(《CLR via C#》作者)的话来说,“不理解引用类型和值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能问题”。这就要求我们正确理解和使用值类型和引用类型。

值类型包括C#的基本类型(用关键字int、char、float等来声明),结构(用struct关键字声明的类型),枚举(用enum关键字声明的类型);而引用类型包括类(用class关键字声明的类型)和委托(用delegate关键字声明的特殊类)。

C#中的每一种类型要么是值类型,要么是引用类型。所以每个对象要么是值类型的实例,要么是引用类型的实例。值类型的实例通常是在线程栈上分配的(静态分配),但是在某些情形下可以存储在堆中。引用类型的对象总是在进程堆中分配(动态分配)。(1)在C#中,变量是值还是引用仅取决于其基本数据类型。

C#的基本数据类型都与平台无关。C#的预定义类型并没有内置于语言中,而是内置于.NET Framework中。.NET使用通用类型系统(CTS)定义可以在中间语言(IL)中使用的预定义数据类型。C#中所有的数据类型都是对象。它们可以有方法、属性等。例如,在C#中声明一个int变量时,声明实际上是CTS(通用类型系统)中System.Int32的一个实例:

下图说明了CTS中各个类型是如何相关的。(2)System. Object和System.ValueType。

引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,而值类型则继承其子类,即直接继承System.ValueType。作为所有类型的基类,System.Object提供了一组方法,这些方法在所有类型中都能找到。其中包含toString方法及clone等方法。System.ValueType继承System.Object。它没有添加任何成员,但覆盖了所继承的一些方法,使其更适合于值类型。(3)值类型。

C#的所有值类型均隐式派生自System.ValueType:

结构体:struct(直接派生于System.ValueType)。

数值类型:整型,sbyte(System.SByte的别名),short(System.Int16),int(System.Int32),long(System.Int64),byte(System.Byte),ushort(System.UInt16),uint(System.UInt32),ulong(System.UInt64),char(System.Char)。

浮点型:float(System.Single),double(System.Double)。

用于财务计算的高精度decimal型:decimal(System.Decimal)。

bool型:bool(System.Boolean的别名)。

用户定义的结构体(派生于System.ValueType)。

枚举:enum(派生于System.Enum)。

可空类型。

每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。例如:

等价于:

使用new运算符时,将调用特定类型的默认构造函数并对变量赋予默认值。在上例中,默认构造函数将值0赋给了i。

所有的值类型都是密封(seal)的,所以无法派生出新的值类型。

值得注意的是,System.ValueType直接派生于System.Object。即System.ValueType本身是一个类类型,而不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。可以用Type.IsValueType属性来判断一个类型是否为值类型:(4)引用类型

C#有以下一些引用类型:

数组(派生于System.Array)

用户需定义以下类型。

类:class(派生于System.Object);

接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。接口只是表示一种contract约定[contract])。

委托:delegate(派生于System.Delegate)。

object(System. Object的别名);

字符串:string(System.String的别名)。

可以看出:

引用类型与值类型相同的是,结构体也可以实现接口;引用类型可以派生出新的类型,而值类型不能;引用类型可以包含null值,值类型不能;引用类型变量的赋值只复制对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值。(5)内存分配。

值类型的实例经常会存储在栈上的。但是也有特殊情况。如果某个类的实例有个值类型的字段,那么实际上该字段会和类实例保存在同一个地方,即堆中。不过引用类型的对象总是存储在堆中。如果一个结构的字段是引用类型,那么只有引用本身是和结构实例存储在一起的(在栈或堆上,视情况而定)。如下例所示:

单看valueTypeStructInstance,这是一个结构体实例,感觉似乎是整块都在栈上。但是字段referenceTypeObject是引用类型,局部变量referenceTypeLocalVarible也是引用类型。

referenceTypeClassInstance也有同样的问题,referenceTypeClassInstance本身是引用类型,似乎应该整块部署在托管堆上。但字段_valueTypeField是值类型,局部变量valueTypeLocalVariable也是值类型,它们究竟是在栈上还是在托管堆上?

对上面的情况正确的分析是:引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。为了方便,简称引用类型部署在托管堆上。值类型总是分配在它声明的地方,作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。(6)辨明值类型和引用类型的使用场合。

在C#中,我们用struct/class来声明一个类型为值类型/引用类型。考虑下面的例子:

如果SomeType是值类型,则只需要一次分配,大小为SomeType的100倍。而如果SomeType是引用类型,刚开始需要100次分配,分配后数组的各元素值为null,然后再初始化100个元素,结果总共需要进行101次分配。这将消耗更多的时间,造成更多的内存碎片。所以,如果类型的职责主要是存储数据,值类型比较合适。

一般来说,值类型(不支持多态)适合存储供C#应用程序操作的数据,而引用类型(支持多态)应该用于定义应用程序的行为。通常我们创建的引用类型总是多于值类型。如果满足下面情况,那么我们就应该创建为值类型:

该类型的主要职责用于数据存储。

该类型的共有接口完全由一些数据成员存取属性定义。

该类型永远不可能有子类。

该类型不具有多态行为。

答案:在C#中,变量是值还是引用仅取决于其数据类型。

C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。

C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。数组的元素,不管是引用类型还是值类型,都存储在托管堆上。

引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。简称引用类型部署在托管推上。值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。值类型在内存管理方面具有更好的效率,并且不支持多态,适合用做存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

面试例题2:下列选项中,______是引用类型。

A.enum类型 B.struct类型 C.string类型 D.int类型

解析:理解引用关系。

在C#中引用的概念,是一个介于C++引用和指针之间的概念。如同C++的引用一样,C#的引用也是到一个对象的引用,所有该对象可访问的公有成员都通过圆点“”运算符来访问。C#引用的一个特点是引用可以为空,这点与C++的引用不同,类似C++指针。C#引用另一个特点是可以修改。也就是说,给定的引用可以改,而指向另一个对象。C#的引用无法用来访问对象的物理空间(即对象的地址)。

C#所有的类都是引用类型,而所有的引用类型都是类或者接口。所有的类都是从System.Object类继承的。类型、类和实现体这几个概念之间很容易产生混淆。类型的含义是随着上下文而变化的。当我们说引用的类型时,是表示声明引用的那个类或者接口。而当我们提到对象的类型时,是指它的实现体。对象的实现体是指该对象作为实例的类或者结构。类型这个词要比实现体一词更加抽象和一般化,而实现体又要比类更一般化。类的实例只能通过引用来操纵。如下例所示:

类是用class关键字来声明的。首先,应当了解ref1、ref2和ref3是3个到对象的引用,即到Person类实例的引用。一开始,ref1是使用null关键字初始化的。这意味着ref1一开始没有引用任何对象。这个时候不能通过ref1访问任何Person类的成员。

因为Person是引用类型,所以两个Persion类的对象将被分配在堆中。我们分别称这两个对象为ZhangSan和Lisi。与我们所知的值类型不同,创建ZhangSan和Lisi的时候必须使用new操作符。到此为止我们已经从Person类实例化了两个对象ZhangSan和Lisi。我们还有3个引用,ref1为空,ref2引用ZhangSan, ref3引用Lisi。接下来ref1也设置为引用ZhangSan,并且将ZhangSan的年龄增加了5岁。然后ref1又引用了Lisi,也将年龄增加了5岁。

最终ZhangSan和Lisi这两个对象都被改动了,但却没有通过ref2和ref3这两个引用。进一步说,ZhangSan和Lisi这两个对象都不是直接被操作的。C#的语法仅允许通过引用来操作引用类型的实例。如果没有任何引用,ZhangSan和Lisi这两个对象就无法再使用了。因此这两个对象被标记成不活动状态,将会被垃圾收集器所销毁。

答案:C。

面试例题3:解释一下装箱和拆箱。为什么要装箱和拆箱?

解析:在.NET中的通用类型系统(Common Type system, CTS)中,万物皆是对象(object),都派生自System.Object。CTS支持两组类型:值类型和引用类型。装箱(box)就是将值类型转换为引用类型的过程。拆箱(unbox)就是将引用类型转换为值类型的过程。

1.装箱

当一个值类型被装箱(boxing)时,一个对象实例就被分配,且值类型的值被复制给新的对象。看以下例子:

第二行的赋值暗示调用一个装箱(boxing)操作。nFunny整型变量的值被复制给oFunny对象。现在整型变量和对象变量都同时存在于栈中,但对象的值居留在堆中。这也就意味着它们的值互相独立——在它们之间没有连接(oFunny没有引用nFunny的值)。以下代码说明了结果:

程序输出结果是:20012000

这个box指令内部相对于内存进行如下操作:(1)在堆上分配内存。因为值类型最终有一个对象代表,所有堆上分配的内存量必须是值类型的大小加上容纳此对象及其内部结构(比如虚拟方法表)所需的内存量。(2)值类型的值被复制到新近分配的内存中。(3)新近分配的对象地址被放到堆栈上,现在它指向一个引用类型。

2.拆箱

拆箱和装箱(boxing)相比,拆箱(unboxing)是显式操作。必须告诉编译器,你想从对象中抽取出哪一种值类型。当执行拆箱操作时,C#检测所请求的值类型实际上存储在对象实例中。经过成功的校验,该值被拆箱。

这就是拆箱如何执行:

如果错误地请求一个double值,如double nNotSoFunny=(double)oFunny;通用语言运行时(CLR)将会引发一个InvalidCastException异常。

拆箱unbox的内部过程如下:(1)因为一个对象将被转换,所以编译器必须先判断堆栈上指向合法对象的地址,以及这个对象类型是否可以转换为unbox指令中调用的值类型。如果检查失败就抛出InvalidCastException异常。(2)校验通过后,就返回指向对象内的值的指针。装箱操作会创建转换类型的副本,而拆箱就不会。

答案:装箱就是将值类型转换为引用类型的过程,并从栈中搬到堆中。而拆箱就是把引用类型转换为值类型。

装箱和拆箱的过程中涉及堆和栈的转换。直接影响性能,使用装拆箱是C#面向对象的精髓。处理大型的程序和软件,特别是有大批量数据的时候,这个很有必要的,比如代码片段中:

如果不使用装箱,就必须使用string[]strList=new string[5000000],这样就造成严重的性能问题。

面试例题4:explicit和implicit的区别是什么?

解析:有时候,我们可能需要将一个类型的对象转换为另一个类型的对象。C#语言中的转换操作符声明引入了一个用户定义的类型转换,它增加了预定义的隐式和显式的转换。

使用关键词implicit的转换操作符声明引入一个用户定义的隐式转换。隐式转换可能在各种情况下发生,包括功能成员调用,表达式执行和赋值。

使用关键词explicit的转换操作符声明引入一个用户定义的显式转换。显式转换可以在强制类型转换表达式中发生。

通常,用户定义的隐式转换应该被设计成不会抛出异常而且不会丢掉信息。如果一个用户定义的转换将产生一个异常(例如因为源变量超出了范围)或丢掉信息(例如丢掉高位),那么这个转换应该被定义为一个显式转换。以下程序实现了值类型double到自定义类型Distance的隐式转换及自定义类型Distance到值类型double的显式转换。

答案:explicit和implicit属于转换运算符,这两者可以让我们自定义的类型支持相互交换。

explicti表示显式转换,如从A→B必须进行强制类型转换(B=(B)A)。

implicit表示隐式转换,如从B→A只需直接赋值(A=B)。

隐式转换可以让我们的代码看上去更简洁易懂,所以最好多使用implicit运算符。但如果对象本身在转换时会损失一些信息(如精度),那么只能使用explicit运算符,以便在编译期就能警告客户调用端。

面试例题5:DateTime是否可以为null?

答案:不能,因为其为Struct类型,而结构属于值类型,值类型不能为null,只有引用类型才能被赋值null。

面试例题6:DateTime.Parse(myString);这行代码有什么问题?

答案:有问题,当myString不能满足时间格式要求的时候,会引发异常,建议使用DateTime.TryParse()。5.4 extern

面试例题:extern是什么意思?[欧盟著名通信公司N面试题,2008]

答案:在方法声明中使用extern修饰符指示在外部实现方法。外部修饰符的常见用法是与DllImport属性一起使用。自己编写的类,直接添加引用即可,不需要COM交互,不必使用这种方式。将abstract和extern修饰符一起使用来修改同一成员是错误的。使用extern修饰符意味着方法在C#代码的外部实现,而使用abstract修饰符意味着在此类中未提供此方法的实现。因为外部方法声明不提供实现,所以没有方法体;此方法声明只是以一个分号结束(在签名后没有大括号{})。例如:

示例如下:在该示例中,程序接收来自用户的字符串并将该字符串显示在消息框中。程序使用从User32.dll库导入的MessageBox方法。5.5 其他

面试例题1:编写一段代码,其功能是打印代码本身(要完全使用程序代码生成一段和自己的代码一模一样的字符串)。[欧盟著名通信公司N面试题,2008]

解析:一道很有趣的面试题,就好像要求一个魔术师,扯着头发把自己拔起来的感觉。本题在编写时要注意计算代码长度。

答案:用C#编写代码如下(仅一行):

面试例题2:谈谈final、finally、finalize的区别。[欧盟著名通信公司N面试题,2008]

答案:final修饰符用于指定类不能扩展或者方法或属性不能重写。它将防止其他类通过重写重要的函数来更改该类的行为。带有final修饰符的方法可以由派生类中的方法来隐藏或重载。finally块用于清除在try块中分配的任何资源。控制总是传递给finally块,与try块的存在方式无关。finalize允许Object在“垃圾回收”回收Object之前尝试释放资源并执行其他清理操作。第6章 const、异常和反射

本章涉及const、异常和反射这3个C#编程中比较难理解且时常考到的内容。

可以说,实现了反射机制的系统都具有开放性,但具有开放性的系统并不一定采用了反射机制,开放性是反射系统的必要条件。反射让我们可以在运行时加载、探知、使用编译期间完全未知的classes。换句话说,程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括方法定义),并生成其对象实例,或对其fields设值,或唤起其方法。

学习反射机制我们可以和高手又接近一步了。6.1 const

面试例题1:const和static readonly区别是什么?

解析:

这个问题虽然很简单,但有时候也能困扰我们。const和static readonly的确很像,都在程序中只读,都是一旦初始化则不再可以改写,都是属于语言的静态。并且在多数情况下可以混用。

关于const,两者的区别如下:(1)在编译期间解析的常量。(2)必须在声明就初始化。(3)既可用来修饰类中的成员,也可修饰函数体内的局部变量。

而关于static readonly,两者的区别如下:(1)在运行期间解析的常量。(2)既可以在声明时初始化,也可以在构造器中初始化。(3)只可以用于修饰类中的成员。

这里有几个例子:

下面有一个项目,有一个MyInt的属性,定义如下:

在另一个项目中引用该属性。

编译运行程序,I=5;kk=StaticReadonly,这是当然的。但是如果我们改变MYClass中的MyInt和strStaticReadonly的值:

然后编译MYClass所在的项目,生成dll。再运行程序AnotherClass(只是运行,而不是编译后运行),发现I还是=5,而不是6,但是strStaticReadonly却变成了changed。为什么会这样?因为在AnotherClass中,I已经被定义为5,而不是运行时再去取dll的值,所以说const是编译时就唯一确定了。

答案:C#拥有两种不同的常量:静态常量(compile-time constants)和动态常量(runtime constants)。它们有不同的特性,错误地使用不仅会损失效率,还可能造成错误。相比之下,静态常量在速度上会稍稍快一些,但是灵活性却比动态常量差很多。

静态常量在编译时会将其替换为所对应的值,也就是说,下面这两句话通过编译器编译后产生的中间语言是一样的。

动态常量的值是在运行时获得的。编译器将其标为只读常量,而不是用常量的值代替。静态常量只能被声明为简单的数据类型(内建的int和浮点型)、枚举或字符串。下面的程序段是通不过编译的。你不能用new关键字初始化一个静态常量,即便是对一个值类型来说。

只读数据也是常量的一种,它们不能在构造器初始化之后被修改。但是它同静态常量不同,它的值是在运行时才被指派的,因此就会获得更大的灵活性。动态常量可以是任意的数据类型。二者最大的差别在于:静态常量在编译时会将其换为对应的值,这就意味着对于不同的程序集来说,当你改变静态常量的时候需要将其重新编译,否则常量的值不会发生变化,可能引发潜在的问题,而动态常量就不会有这种情况。用const定义的常量(隐式是静态的),需要像访问静态成员那样去访问const定义的常量,而用对象的成员方式去访问会出编译错误。声明的同时要设置常量值。从另一方面来说,如果你的确要声明一些从不改变且处处唯一的常量,例如钩子函数SetWindowsHookEx的idHook参数或序列化时的版本等,就应该使用静态常量。但是用到这样的常量的机会不多。一般来说我们应该使用灵活性更高的动态常量。

两者区别如下:6.2 异常

面试例题1:分析以下代码:

请问以上代码所使用的异常处理方法,是否所有在test方法内的异常都可以被捕捉并显示出来?

答案:只可以捕捉数据库连接中的异常。在finally中,有可能出现一些别的引发异常的操作,所以理论上并非所有异常都会被捕捉。

面试例题2:在C#异常处理中,一个try可以有几个通用catch块?

解析:“通用catch块”的意思是可以捕获到所有错误的catch块,举例来说:

在上面这个例子方法中,最后一个“catch(Exception)”就是“通用catch块”,它负责捕获Exception类型的错误,Exception类型表示“在应用程序执行期间发生的错误。”,也就是说,无论发生了什么错误,这个catch块都可以将它捕获,C#中的所有错误类型都是从这个Exception类派生出来的,C#语法硬性规定一个try/catch块中只能有一个通用catch块。catch(Exception)块必须放在一个try/catch结构的最后,否则将产生编译器警告,因为这个块会捕获所有的错误,所以后面的catch语句将永远不会被执行。

答案:一个try/catch块中只能有一个通用catch块。

面试例题3:定制异常类应继承哪个类?应包含哪些构造函数?

解析:如果需要用生成错误的类来描述错误时,定制异常类就很有用。例如可以使用异常来描述导致错误的特定行为,或只是参数为满足某个必要条件等。一般来讲,优先考虑特定的系统异常,再考虑建立自己的异常类。

答案:定制的异常类应从ApplicationException派生。

按照约定,异常名应以Exception结尾。应包含Exception基类定义的3个公共构造函数:

1.默认的无参构造函数。

2.带一个字符串参数(通常是消息)的构造函数。

3.带一个字符串参数和一个Exception对象参数的构造函数。6.3 反射

面试例题1:什么是Reflection?其他语言有这种特点么?

解析:本题考的是.NET中的反射机制。

1.什么是反射

Reflection中文翻译为反射。这是.NET中获取运行时类型信息的方式,.NET的应用程序由几个部分:程序集(Assembly)、模块(Module)、类型(class)组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如,Assembly类可以获得正在运行的装配件信息,也可以动态地加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等,通过Type类可以得到这些要素的信息,并且调用之。MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。诸如此类,还有FieldInfo、EventInfo等,这些类都包含在System.Reflection命名空间下。

我们用一个电视里面的例子来讲解什么是反射及反射的基本使用方式。《亮剑》中李云龙奇袭日军炮楼穿的军服从哪儿来?显然不是临时找裁缝赶制的,肯定是从俘虏身上扒下来的。好,那我们就来补充一点儿《亮剑》的镜头看看什么是反射。

一天傍晚,一队伪军士兵在乡间小路上蹒跚地走着,他们的任务是进行搜寻,并伺机扫荡根据地,没想到中了八路军埋伏,全被俘虏,听候处置。

这件事情被上报到李云龙那里,于是老李立刻开始审问他们——注意,反射开始了!“哪儿的?”“伪军蒋德龙部1团2排。”“叫什么名字?”“李茂才。”“这次执行什么任务?”“潜入贵军阵地搜寻并实施清乡扫荡。”

……

一番软硬兼施之后,老李掌握了这队伪军所有的信息,而伪军身上的行头也被换了下来,换上了专门为战俘准备的棉衣。老李决定使用他们的方式,先发制人,他换上衣服之后连夜潜入敌方阵地,敌人不是想扫荡我们么,那我们就用他们的方式,他和战友潜入敌方阵地后冷静地进行了周密的观察,并通过有效地部署和协同友军楚云飞部,成功地引导我军取得了这次重大胜利。

现在看一下代码吧,首先,我们得有个倒霉的伪军士兵做引子:

然后,我们用代码来实现李云龙所完成的行动:

这段代码很好理解,第5行我们审问了被俘伪军士兵并得到了他提供的信息和服装,然后让老李用他的服装化装成伪军士兵。第7行,老李以牙还牙,绑定伪军的方法来消灭敌人!接下来,李云龙潜入敌方阵地并引导了我军发起总攻。

这里展示了反射的一些基本应用,例如获取类型信息,利用获取的类型动态生成对象,并动态调用其方法。当然,反射机制能做的事情不仅仅是这几样,不过目标都一样,由程序自己去获取信息,做出反应。在不用担心性能开销的情况下,反射可以使你的程序更加灵活强大!

2.命名空间与装配件的关系

很多人对这个概念可能还是很不清楚,对于合格的.NET程序员,有必要对这点进行澄清。命名空间类似Java的包,但又不完全等同,因为Java的包必须按照目录结构来放置,命名空间则不需要。装配件是.NET应用程序执行的最小单位,编译出来的.dll、.exe都是装配件。

装配件和命名空间的关系不是一一对应的,也不互相包含,一个装配件里面可以有多个命名空间,一个命名空间也可以在多个装配件中存在,这样说可能有点模糊,举个例子:

装配件A:

装配件B:

这两个装配件中都有N1和N2两个命名空间,而且各声明了两个类,这样是完全可以的,然后我们在一个应用程序中引用装配件A,那么在这个应用程序中,我们能看到N1下面的类为AC1和AC2,N2下面的类为AC3和AC4。接着我们去掉对A的引用,加上对B的引用,那么我们在这个应用程序下能看到的N1下面的类变成了BC1和BC2,N2下面也一样。

如果我们同时引用这两个装配件,那么N1下面我们就能看到4个类:AC1、AC2、BC1和BC2。到这里,我们可以清楚一个概念了,命名空间只是说明一个类型是哪个属性的,比如有人是汉族,有人是回族;而装配件表明一个类型住在哪里,比如有人住在北京,有人住在上海;那么北京有汉族人,也有回族人,上海有汉族人,也有回族人,这是不矛盾的。装配件是一个类型居住的地方,那么在一个程序中要使用一个类,就必须告诉编译器这个类住在哪儿,编译器才能找到它,也就是说,必须引用该装配件。

如果在编写程序的时候,不确定这个类在哪里,仅仅只是知道它的名称,就不能使用了吗?答案是可以,这就是反射了,就是在程序运行的时候提供该类型的地址,而去找到它。

3.运行期得到类型信息有什么用

有人会问,既然在开发时就能够写好代码,干嘛还放到运行期去做。这是因为,晚绑定能够带来很多设计上的便利,合适的使用能够大大提高程序的复用性和灵活性,但是任何东西都有两面性,使用的时候,需要再三衡量。很多人在享受虚函数带来的好处的时候,还没有意识到他已经用上了晚绑定。

那么在运行期得到类型信息到底有什么用呢?

还是举个例子来说明,很多软件开发者喜欢在自己的软件中留下一些接口,其他人可以编写一些插件来扩充软件的功能。比如我有一个媒体播放器,我希望以后可以很方便地扩展识别的格式,那么我声明一个接口:

这个接口中包含一个Extension属性,这个属性返回支持的扩展名,另一个方法返回一个解码器的对象(这里我假设了一个Decoder的类,这个类提供把文件流解码的功能,扩展插件可以派生之),通过解码器对象我就可以解释文件流。

那么我规定所有的解码插件都必须派生一个解码器,并且实现这个接口,在GetDecoder方法中返回解码器对象,并且将其类型的名称配置到我的配置文件里面。

这样的话,我就不需要在开发播放器的时候知道将来扩展的格式的类型,只需要从配置文件中获取现在所有解码器的类型名称,而动态创建媒体格式的对象,将其转换为IMediaFormat接口来使用。这就是一个反射的典型应用。

4.如何使用反射获取类型

获得类型信息有两种方法,一种是得到实例对象;这个时候仅仅是得到这个实例对象,得到的方式也许是一个object的引用,也许是一个接口的引用,但是并不知道它的确切类型,这时用户需要了解,那么就可以通过调用System.Object上声明的方法GetType来获取实例对象的类型对象,比如在某个方法需要判断传递进来的参数是否实现了某个接口,如果实现了,则调用该接口的一个方法:

另外一种获取类型的方法是通过Type.GetType及Assembly.GetType方法,如下:

需要注意的是,要查找一个类,必须指定它所在的装配件,或者在已经获得的Assembly实例上面调用GetType。

5.如何根据类型来动态创建对象

我们知道,C#编译后的PE文件主要由IL代码和元数据组成,元数据为.NET组件提供了丰富的自描述特性,它使得我们可以在代码运行时获得组件中的类型等重要的信息。先看一个示例,在此首先创建一个简单的类型:

用编译命令csc/t:library SimpleType.cs编译上面的文件得到Simple Type.dll。接下来实现查询类型的测试程序:

用编译命令csc/r:simpletype.dll test.cs编译后执行,可得到下面的输出:

在上面的例子中,首先通过typeof(MyClass)获得MyClass类的类型信息,当然也可以通过创建对象实例,然后调用对象实例的GetType方法来获得(每个类都从object根类中继承获得此方法)。在拥有了类型信息(变量t)后,便可以获得其类型的名字、该类型含有的公有域、公有属性、公有方法。注意:这里C#的反射机制只允许获取类型的公有信息,这符合面向对象的封装原则。这也是为什么虽然实现了count域,查询类型得到的输出却是“The 0 Fields:”——如果将SimpleType.cs中的count域改为public公有,将会得到它的查询信息。其中4个方法(GetHashCode、Equals、ToString、GetType)都是继承自object类的公有方法,而方法get_Count和set_Count则是实现Count属性的“副产物”——这符合前面讲述的属性本质上为方法的变体。实际上,System.Type类各种各样的成员使得我们能够获得几乎所有与类型相关的公有信息。在System.Reflection命名空间下的各个类都可以获得各个编程元素较详细的信息,如方法的参数与返回值、域的类型、枚举的各个值等。

6.动态创建与调用

实际上反射远不止动态地获知组件的类型信息,它还能在获得类型信息的基础上,在代码运行时进行类型的动态创建与方法的动态调用,甚至动态地创建并执行IL代码。

动态调用为C#的组件提供了迟绑定功能,它使组件之间在运行时集成变得极为方便。利用前面创建的简单组件SimpleType.dll来看一看怎样完成对象的动态创建和方法的动态调用:

用编译命令csc/r:simpletype.dll dynamicexe.cs编译后执行,可以动态地装载组件。Activator.CreateInstance允许动态地创建类型(这里只通过无参数的构造器来创建),实际上用它创建出来的类型和用“MyClass o=new MyClass()”创建出来的类型一样。进而,还可以在查询到的成员的基础上,对它们进行动态调用。

答案:

反射是一种晚绑定,它可以被开发者用来设计出更具灵活性的代码,而代价则是花费更多的系统资源开销使得应用程序可以在运行时获取一些未知信息。

反射是指通过程序集内的元数据和Runtime的支持在运行时读取程序集、模块、类型和成员的信息,以及在运行时通过这种途径访问对象的成员或执行对象的方法,甚至动态改变类型和方法的组成。

在编写代码时,开发者可能还不知道或不能确定一些对象的信息,于是把决定权交给代码本身,将来在需要的时候由代码自己去获取和判断这些信息并做出相应的反应。这样的方式固然可以使代码更加灵活,但在使用反射的时候,也有可能让机器背负沉重的包袱去做一些意义不大的事情。第7章 传递与引用

传值与引用问题下静态变量、私有变量、clone等问题也是各大公司的常备考点。本章不对传值与引用基本知识进行回顾和分析(请参考其他经典著作),通过对各公司面试题目进行全面仔细的解析帮读者解决其中的难点。

以下的考题来自真实的笔试资料,希望读者先不要看答案,自己解答后再与答案加以比对,找出自己的不足。7.1 传值

面试例题1:请输出下列程序结果。[中国台湾地区杀毒软件公司T面试题]

A.x=60y=40 B.x=20y=60 C.x=20y=40 D.x=60y=60

解析:x按引用传递,所以会改变对应值。y按值传递,只是改变一个复制,所以原值不变。

答案:A。

面试例题2:在C#中,a.Equals(b)和a==b一样吗?

解析:"=="判断符号左右两个变量(object)是否指向同一内存地址;"equals()"判断两个object是否“一样”(所有成员值一样……)。

例1:

例2:

在例1中,“abc”是放在常量池(constant pool)里头的,所以,虽然a、b都“=”“abc”,但是内存中只有一份copy,所以“==”返回true。

但是在例2中,new方法决定了两个不同的string“abc”被创建放在了内存heap区中,分别被a和b所指向,因此,“==”返回了false。

答案:两者不一样。7.2 静态变量与私有变量

面试例题1:关于静态变量的创建哪一个选项是正确的?

A.一旦一个静态变量被分配,它就不允许改变

B.一个静态变量在一个方法中创建,它在被调用的时候值保持不变

C.在任意多个类的实例中,一个静态变量的实例只存在一个

D.一个静态的标示符只能被应用于primitive value

解析:

选项A是对常量的描述。选项B是为了混淆那些习惯使用Visual Basic的人。选项D所说的静态标示符可以被应用到类中(但只是一个内部类),方法和变量中。

答案:C。

面试例题2:静态成员和非静态成员的区别?

答案:静态变量使用static修饰符进行声明,在类被实例化时创建,通过类进行访问。不带有static修饰符声明的变量称为非静态变量,在对象被实例化时创建,通过对象进行访问。一个类的所有实例的同一静态变量都是同一个值,同一个类的不同实例的同一非静态变量可以是不同的值,在静态函数的实现里不能使用非静态成员,如非静态变量、非静态函数等。7.3 输入/输出流

面试例题1:在下列对象中,哪个对象不是从已有的FileStream创建?

A.FileInfo

B.SteamReader

C.BufferedStream

B.SteamWriter

答案:A。

面试例题2:可用下列语句创建FileStream对象:

下列哪个语句能使用一个已有的FileInfo对象fi创建与上面相同的FileStream?

A.filesys=fi.OpenWrite();

B.filesys=fi.Create();

C.filesys=fi.CreateText();

D.filesys=fi.OpenRead();

答案:A。7.4 程序集

面试例题1:调用Assembly.Load算静态引用还是动态引用?

答案:动态引用。

面试例题2:何时使用Assembly.LoadFrom?何时使用Assembly.LoadFile?

答案:Assembly.LoadFrom加载程序时如同一首新疆民歌中唱的:“带上你的嫁妆,坐着你的马车,带着你的妹妹来”。用它加载的是程序集,这就要求同时将此程序集所依赖的其他程序集也加载进来。而LoadFile仅是加载程序集文件的内容,只将传入参数的文件加载,不考虑程序集依赖,但如果有相同实现,但位置不同的文件用LoadFrom是不能同时加载进来的,而LoadFile却可以。由于LoadFile加载的是文件,所以调用它之后,可能因为缺少必要的依赖造成无法被执行。

面试例题3:什么叫Assembly Qualified Name?它是一个文件名吗?

答案:Assembly Qualified Name不是一个文件名,相比文件名,Assembly Qualified Name(程序集限定名称)包含文件名,但同时包含版本、公钥和区域。因为同样一个名称的文件可能有不同的版本和区域,此时单独靠文件名称,可能会造成不能确定程序集的正确性。

面试例题4:Assembly.Load("foo.dll");这样的写法是否正确?

答案:错误。

正确的应该是Assembly.Load("foo"),或者Assembly.LoadFrom("foo.dll")。

面试例题5:下列说法正确的是______。

A.一个进程可以包含多个应用域

B.一个应用域可以包含多个程序集

C.一个应用域可以包含EXE程序集,但不可以包含DLL程序集

D.可以将一个AppDomain从进程中卸载

解析:一个应用域既可以包含EXE程序集,也可包含DLL程序集。

答案:C。

面试例题6:程序集的强名包含哪几部分?

答案:简单名,版本号,文化信息,公钥令牌。7.5 序列化

面试例题1:如何理解C#的序列化?[英国著名图形公司A面试题]

答案:序列化是将一个对象保存到存储介质上或者将对象进行转换,使其能够在网络上传送的行为。能对一个类进行序列化的条件是:该类的任何基类可序列化;该类应用了Serializable特性。

序列化的常见应用如下:

1.配置程序的加载和保存

我们可以创建一个类,它包含了应用程序的配置信息。当应用程序加载时,配置对象被反序列化到内存中的配置类;当用户在程序运行的过程中对配置文件进行修改的时候,可以把内存的配置类序列化到硬盘。这样,方便地实现了配置文件的读写。

2.分布式计算

序列化的最大优势在于分布式计算。两台机器拥有相同的程序集,则可以利用序列化技术进行通信。A机器通过序列化技术向B机器发送对象的快照,B机器能快速、正确地重建出该对象来。

在.NET Framework中,有3种序列化机制:二进制、XML和简单对象访问协议(SOAP)。它们的优缺点阐述如下:(1)二进制序列化的最大优点是,类型数据可以准确地表示出来。因为二进制序列化对象的共有和私有成员,所以在反序列化的时候可以忠诚地重建出该对象的状态。(2)XML只序列化对象的公共属性和字段。在XML序列化时,私有字段和其他实例对象就丢失了。(3)XML和SOAP是开发标准,具有很好的移植性。

扩展知识

进行序列化和反序列化的代码很简单,三者对应的名称空间及对应的格式化类分别如下。

二进制:System.Runtime.Serialization.Formatters.Binary—Binary Formatter

XML:System. Xml.Serialization—XmlSerializer

SOAP:System. Runtime.Serialization.Formatters.SOAP—Soap Formatter

序列化和反序列化的代码如下:

序列化的内容是可以制定的,可以通过继承ISerializable接口,重载里面的构造函数和GetObjectData方法即可。第8章 循环、条件和概率

递归过程的执行总是一个过程体未执行完,就带着本次执行的结果又进入另一轮过程体的执行……如此反复,不断深入,直到某次过程的执行遇到终止递归调用的条件成立时,则不再深入,而执行本次的过程体余下的部分,然后又返回到上一次调用的过程体中,执行其余下的部分……如此反复,直到回到起始位置上,才最终结束整个递归过程的执行,得到相应的执行结果。递归过程的程序设计的核心就是参照这种执行流程,设计出一种适合“逐步深入,而后又逐步返回”的递归调用模型,以解决实际问题。

在程序设计面试中,一个能够完成任务的解决方案是最重要的;解决方案的执行效率要放在第二位考虑。因此,除非试题另有要求,你应该从最先想到的解决方案入手。如果它是一个递归性的方案,你不妨向面试官说明一下递归算法天生的低效率问题——表示你知道这些事情。有时候你同时想到两个解决方案:递归的和循环的,并且实现方式差不多,你可以把两个都向考官介绍一下。8.1 foreach

面试例题1:C#中要使一个类支持FOREACH遍历,实现过程怎样?

解析:本题考两个知识点:foreach和IEnumerable接口。

foreach既可用于队列,又可用在list表,举例如下:

由关系的定义我们可以知道,关系就是一种集合,集合是一个抽象的概念,.NET Framework有一个最简单的集合访问器:IEnumerable接口(.NET中的所有集合访问器都从此接口继承)。所以,关系数据也可以用IEnumerable来访问,任何一个关系都是某个类型的所有对象的集合。关系是一个强类型的集合,我们用IEnumerable<T>来在.NET Framework表示。

答案:方案1:让这个类实现IEnumerable接口;方案2:这个类有一个有public的GetEnumerator()的实例方法,并且返回的类型中有public的bool MoveNext()实例方法和public的Current实例属性。

扩展知识

对于一个类支持FOREACH遍历的C#示例代码如下:8.2 递归与回朔

面试例题1:设计递归算法x(x(8))需要调用几次函数x(int n)。[日本著名企业S面试题]

解析:单计算x(x(8))的值自然是9,但是本题要考查的是调用了几次x函数。可以把x(8)理解为一个二叉树。树的结点个数就是调用的次数。

x(8)的结果为9;x(x(8))也就是x(9)的二叉树形式。

结点数为9,也就是又调用了9次函数。所以一共调用的次数是18次。

答案:18。

面试例题2:八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是19世纪著名的数学家高斯1850年提出的:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

现在请将代码补充完整。[中国著名因特网公司M面试题,2008年12月]

答案:任意两个皇后都不能处于同一行、同一列或同一斜线上。所以答案应该是((j==x)||(j==x-d)||(j==x+d))。

面试例题3:1,1,2,3,5,8,13,21,34……用C#递归写出算法,算出第30个数。

解析:本题是典型的fibonacci数列。(1)写出return Foo(i-1)+Foo(i-2)。(2)写出if(i>0&&i<=2)return 1。

答案:C#具体代码如下:8.3 条件语言

面试例题1:在1~1000的整数中,找出同时符合以下条件的数:

a.必须是质数。

b.该数字各位数字之和为偶数,如数字12345,各位数字之和为1+2+3+4+5=15,不是偶数。[中国著名通信公司H面试题2008年9月]

解析:本题考了两个地方:(1)质数的理解:

质数就是在所有比1大的整数中,除了1和它本身以外,不再有别的约数。2是一个不是奇数的质数,它既是质数也是偶数,面试者极容易忽略这点。判断数N是否为质数要直接从3开始判断(如果N不是2),首先不能是偶数,然后再判断是否能被3,5,7……整除,直到sqrt(N)止。(2)求各位数字之和,可以通过循环取余数的办法。

答案:代码如下:8.4 随机数

面试例题1:产生20个不同的两位整数的随机数,并且对它们进行由小到大排序。特别提醒:程序要自动生成20个不同的整数,而且这些整数必须是两位的,如3不是两位整数,58是两位整数。[中国著名通信公司H面试题,2008年10月]

解析:随机数的使用很普遍,可用它随机显示图片,防止论坛灌水及加密信息等。本题讨论如何在一段数字区间内随机生成若干个互不相同的随机数。

.NET. Framework中提供了一个专门产生随机数的类System.Random,此类默认情况下已被导入,编程过程中可以直接使用。我们知道,计算机并不能产生完全随机的数字,它生成的数字被称为伪随机数,它是以相同的概率从一组有限的数字中选取的,所选的数字并不具有完全的随机性,但就实用而言,其随机程度已经足够了。

我们可以用以下两种方法初始化一个随机数发生器;

第一种方法不指定随机种子,系统自动选取当前时间作为随机种子:

第二种方法是指定一个int型的参数作为随机种子:

我们一般使用Random.Next()方法产生随机数。

它返回一个大于或等于零而小于2147483647的数,这并不满足我们的需要,下面我们介绍它的重载函数和其他一些方法。

用法:ra.next(20)

返回一个小于所指定最大值(此处为20)的正随机数。

用法:ra.next(1,20)

返回一个指定范围内(此处为1~20之间)的随机数。

类System.Random还有几个方法分别如下。

公共方法:

NextBytes用随机数填充指定字节数组的元素。

NextDouble返回一个介于0.0和1.0之间的随机数。

受保护的方法:

Sample返回一个介于0.0和1.0之间的随机数,只允许子类对象访问。

上面介绍了随机数的基本用法,题目中要在一段数字区间内随机生成若干个互不相同的随机数,比如在从10~100间随机生成20个互不相同的整数。其中随机数是这样创建的:

但是为什么不用Random ra=new Random();(系统自动选取当前时间作为随机种子)呢?这是因为用系统时间作为随机种子并不保险,如果应用程序在一个较快的计算机上运行,则该计算机的系统时钟可能没有时间在此构造函数的调用之间进行更改,Random的不同实例的种子值可能相同。在这种情况下,我们就需要另外的算法来保证产生的数字的随机性。所以为了保证产生的随机数足够“随机”,我们不得不使用复杂一点的方法来获得随机种子。在上面语句中,首先使用系统时间作为随机种子,然后将上一次产生的随机数跟循环变量和一个与系统时间有关的整型参数相乘,以它作为随机种子,从而得到了每次都不同的随机种子,保证了产生足够“随机”的随机数。

答案:这道题在产生随机数后,会遇到随机数有可能相同的问题,在这种情况下,我们要在队列中查看队列内元素是否与随机数相同,如果不同则将随机数加入队列,否则重新产生随机数。具体代码如下:第9章 关于面向对象的面试问题

面向对象其实是现实世界模型的自然延伸。现实世界中任何实体都可以看做是对象。对象之间通过消息相互作用。另外,现实世界中任何实体都可归属于某类事物,任何对象都是某一类事物的实例。如果说传统的过程式编程语言是以过程为中心,以算法为驱动的话,面向对象的编程语言则是以对象为中心,以消息为驱动。用公式表示,过程式编程语言为:程序=算法+数据;面向对象编程语言为:程序=对象+消息。

所有面向对象编程语言都支持3个概念:封装、多态性和继承,C#也不例外。现实世界中的对象均有属性和行为,映射到计算机程序上,属性则表示对象的数据,行为表示对象的方法(其作用是处理数据或同外界交互)。所谓封装,就是用一个自主式框架把对象的数据和方法联在一起形成一个整体。可以说,对象是支持封装的手段,是封装的基本单位。C#语言完全面向对象,因为C#无全程变量,无主函数,在C#中万物都是对象。“这个世界是由什么组成的?”这个问题如果让不同的人来回答会得到不同的答案。如果是一个化学家,他也许会告诉你“还用问嘛?这个世界是由分子、原子、离子等的化学物质组成的”。如果是一个画家呢?他也许会告诉你,“这个世界是由不同的颜色所组成的”。但如果让一个分类学家来考虑问题就有趣得多了,他会告诉你,“这个世界是由不同类型的物与事所构成的”。作为面向对象的程序员来说,我们要站在分类学家的角度去考虑问题。这个世界是由动物、植物等组成的。动物又分为单细胞动物、多细胞动物、哺乳动物等;哺乳动物又分为人、大象、老虎……就这样地分下去了!

现在,站在抽象的角度,我们给“类”下个定义吧!我的意思是,站在抽象的角度,你回答我“什么是人类?”首先让我们来看看人类所具有的一些特征,这个特征包括属性(一些参数,数值)及方法(一些行为,他能干什么)。每个人都有身高、体重、年龄、血型等一些属性。人会劳动、人都会直立行走、人都会用自己的头脑去创造工具等这些方法。人之所以能区别于其他类型的动物,是因为每个人都具有人这个群体的属性与方法。“人类”只是一个抽象的概念,它仅仅是一个概念,它是不存在的实体。但是所有具备“人类”这个群体的属性与方法的对象都叫人。这个对象“人”是实际存在的实体。每个人都是人这个群体的一个对象。老虎为什么不是人?因为它不具备人这个群体的属性与方法,老虎不会直立行走,不会使用工具等,所以说老虎不是人。

由此可见,类描述了一组有相同特性(属性)和相同行为(方法)的对象。在程序中,类实际上就是数据类型。例如,整数、小数等。整数也有一组特性和行为。面向过程的语言与面相对象的语言的区别就在于,面向过程的语言不允许程序员自己定义数据类型,而只能使用程序中内置的数据类型。而为了模拟真实世界,为了更好地解决问题,往往我们需要创建解决问题所必需的数据类。

面向对象编程为我们提供了解决方案。以下的考题来自真实的笔试资料,希望读者先不要看答案,自己解答后再与答案加以比对,找出自己的不足。9.1 面向对象的基本概念

面试例题1:面向对象的三要素是什么?

答案:封装、继承、多态。

面试例题2:对象与实例到底有什么区别呢?

答案:对象和实例从宏观的角度看的区别是:对象是同类事物的一个抽象表现形式,而实例是对象的具体化,一个对象可以实例化很多实例,对象就是一个模型,实例是照着这个模型生产的最终产品。实际上就是这样,一个对象可以实例化N个实例。就像根据一个模型可以制造多个实际的产品一样。

面试例题3:Which is incorrect about the class?(关于类下面哪个是错误的)[中国著名软件公司J面试题]

A.A class is a blueprint to objects.(类是对象的蓝图。)

B.We use the keyword class to create a class construct.(我们使用关键字来创建类的构造。)

C.Once a class is declared, the class name becomes a type name and can be used to declare variables.(类被声明后可以作为类型名用来声明变量。)

D.The class is same as the struct, and there are no different between class and struct.(类和结构没有区别。)

解析:类的概念问题。

答案:D。

面试例题4:Which is incorrect about the OOP?(下面关于面向对象技术哪个是错误的。)[中国著名软件公司J面试题]

A.The central idea of OOP is to build programs using software objects.(面向对象的核心理念是通过使用对象来构造程序。)

B.The OOP focuses mainly on the step-by-step procedure as procedure-oriented programing.(面向对象关键在于它是关注过程的。)

C.The OOP offers many advantages:simplicity, modularity, modifiability, extensibility, and so on.(面向对象有很多优势:简洁、模块化、可复用、扩展性等。)

D.The key concept of object orientation is the attachment of procedure to datA.(面向对象最重要在于数据程序的加载。)

解析:OOP的概念问题。面向对象和面向过程不能混为一谈。

答案:B和D。

面试例题5:For the following description about OOP, which is right?(面向对象程序设计的描述,哪条是对的?)[美国著名软件公司M面试题,2008年10月]

1.An object can inherit the feature of another object(一个对象可以继承另一个对象的特性)

2.A sub class can contain base class attribute or behaviors(一个子类能包含基类的方法和属性)

3.Encapsulation is used to hide as MUCH as possible about the inner working of the interface(封装用来隐藏尽可能多的内部工作接口)

4.Encapsulation prevents the program from becoming independent(封装允许程序不独立运行)

5.polymorphism allows the methods have different signature but with same name(多态允许同名的方法有多个不同的用法)

A.12

B.14

C.23

D.35

E. 45

解析:

1.明显的错误,对象不存在继承,只有类能继承类。

2.是一个子类可以包含基类的属性或行为,这是正确的,子类可以有子类的属性或行为,而且能重载基类的。

3.是正确的,封装的目的正是如此。

4.不正确。

5.实际说的是重载,而重载本质来说与多态无关。

答案:C。

面试例题6:说说哪两个类不能实例化?

答案:抽象类(abstract class)或者包含私有构造函数的类都不能实例化。

面试例题7:阐述面向接口、面向对象、面向方面编程的区别?

答案:面向接口更关注的是概念,它的原则是先定义好行为规范,再根据行为规范创建实现,面向接口是面向对象中的一部分,因为面向对象也强调的是依赖倒置原则,也就是实现依赖于抽象,而抽象不依赖于具体实现,面向接口更加灵活,但实现时稍微有些代码冗余,面向对象是对复杂问题的分解。面向方面的编程是一种新概念,它解决了很多面向对象无法解决的问题,比如面向对象技术只能对业务相关的代码模块化,而无法对和业务无关的代码模块化。而面向方面正是解决这一问题的方案,能够将应用程序中的商业逻辑与对其提供支持的通用服务进行分离。9.2 访问修饰符

面试例题1:为internal成员加上了protected修饰符,请问internal protected表示什么意思?

A.只有同一个程序集中的子类可以访问

B.同一个程序集中的所有类都可以访问

C.所有程序集中的子类都可以访问

D.同一个程序集中的所有类,以及所有程序集中的子类都可以访问

解析:可以访问一个成员的代码范围叫做该成员的可访问域(accessibility domain)。访问修饰符用来控制所修饰成员的可访问域。访问修饰符使类或者类的成员在不同的范围内具有不同的可见性,用于实现数据和代码的隐藏。

C#定义了如下的5种访问修饰符:public、protected internal、protected、internal或private。除protected internal组合外,指定一个以上的访问修饰符会导致编译时错误。访问修饰符应该出现在成员的类型或者返回值类型之前。5种访问修饰符的意义如下表所示。

下面做一个事例来说明:

建立一个Test1项目,其中包含类Class1:

相同项目(也是Test1)中的Class2类可以访问到Class1的strInternal成员,当然也可以访问到strInternalProtected成员,因为它们在同一个程序集里。

不同项目(Project1)中的Class3类继承了Class1类,所以它也可以访问到strInternalProtected成员,但不能访问Class1的strInternal成员。Project1项目的Program类既无法访问到Class1的strInternal成员,也无法访问到strInternalProtected成员,因为它们既不在同一个程序集里,也不存在继承关系。

所以internal protected的语意应该是internal||protected,而不是internal&&protected。

答案:D。9.3 类和结构

面试例题1:在C#中,structure和class有什么区别?[中国台湾地区杀毒软件公司T面试题,2008]

解析:

1.类与结构的示例比较

从上面的例子中我们可以看到,类的声明和结构的声明非常类似,只是限定符后面是struct还是class的区别,而且使用时,定义新的结构和定义新类的方法也非常类似。那么类和结构的具体区别是什么呢?

2.类与结构的差别

1)值类型与引用类型

结构是值类型:值类型在栈上分配地址,所有的基类型都是结构类型。例如,int对应System.int32结构,string对应system.string结构,通过使用结构可以创建更多的值类型。

类是引用类型:引用类型在堆上分配地址。

栈的执行效率要比堆的执行效率高,可是栈的资源有限,不适合处理大的逻辑复杂的对象。所以结构处理作为基类型对待的小对象,而类处理比较大的逻辑对象。

因为结构是值类型,所以结构之间的赋值可以创建新的结构,而类是引用类型,类之间的赋值只是复制引用。虽然结构与类的类型不一样,可是它们的基类型都是对象(Object),C#中所有类型的基类型都是Object。虽然结构的初始化也使用了new操作符,可是结构对象依然分配在栈上而不是堆上,如果不使用“新建”(new),那么在初始化所有字段之前,字段将保持未赋值状态,且对象不可用。

2)继承性

结构:在C#中,不能从另外一个结构或者类继承,本身也不能被继承,虽然结构没有明确地用sealed声明,可是结构是隐式的sealed。虽然结构不能被继承,可是结构能够继承接口,方法和类继承接口一样。

在C++中是允许结构作为基类,并且继承的。但.NET反而不允许这样做,这是否是说.NET是“退步”了呢?答案并非如此,.NET更好地区别了结构相对于类所扮演的角色:使得结构不再是一个“伪”类,而是作为一个数据结构。另外,它还为结构提供了更高效的实现。由于对结构有所限制,使得编译器尽可能地减少支持继承所需的管理代码。

类:完全可扩展的,除非显式地声明sealed,否则类可以继承其他类和接口,自身也能被继承。

3)内部结构

3.选择结构还是类

讨论了结构与类的相同之处和差别之后,下面讨论如何选择使用结构或类:

1)堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。

2)结构表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有1000个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。

3)在表现抽象和多级别的对象层次时,类是最好的选择。

4)在大多数情况下,该类型只是一些数据时,结构是最佳的选择。

答案:1.结构是值类型在栈上分配(虽然栈的访问速度比堆要快,

但栈的资源有限),结构的赋值将分配产生一个新的对象。类是引用类型在堆上分配,类的实例进行赋值只是复制了引用,都指向同一段实际对象分配的内存。

2.结构不支持被继承,类可以。

3.结构没有析构函数,类有。

扩展知识

下面通过建立一个三维坐标接口Ipoint,以及相关的结构pointStruct,类pointClass,以便读者更好地理解三者之间的关联。

面试例题2:Object是所有类的父类,任何类都默认继承Object。请问Object类到底实现了哪些方法?

答案:

1)clone方法

保护方法,实现对象的浅拷贝,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

2)getClass方法

final方法,获得运行时类型。

3)toString方法

用得比较多,一般子类都有覆盖。

4)finalize方法

释放资源,因为无法确定该方法什么时候被调用,很少使用。

5)equals方法

非常重要的一个方法,一般equals和==是不一样的,但是Object中两者是一样的,子类一般都要重写这个方法。

6)hashCode方法

用于哈希查找,重写了equals方法一般都要重写hashCode方法,这个方法在一些具有哈希功能的Collection中用到。一般必须满足:obj1.equals(obj2)==true,可以推出obj1.hashCode()==obj2.hashCode(),但是hashCode相等不一定就满足equals,但是为了提高效率,应该尽量使上面两个条件接近等价。

7)wait方法

wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁.wait()方法一直等待,直到获得锁或者被中断,wait(long timeout)设定有一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生:(1)其他线程调用了该对象的notify方法。(2)其他线程调用了该对象的notifyAll方法。(3)其他线程调用了interrupt中断该线程。(4)时间间隔到了。

该线程就可以被调度了,如果是被中断的话就抛出一个Interrupted-Exception异常。

8)notify方法

该方法唤醒在该对象上等待的某个线程。

9)notifyAll方法

该方法唤醒在该对象上等待的所有线程。9.4 Static

面试例题1:下面这段程序标识处是否正确?并说明理由。

解析:类的成员要么是静态的,要么是动态的,如果将类的某个成员声明为static,则该成员是静态成员。类的静态成员属于类所有,不必产生类的实例就可以访问它,即只用类名就可以访问。静态成员为类的所有实例所共享,无论这个类创建了多少个实例,一个静态成员在内存中只占有一块区域。类的非静态成员属于类的实例所有,每创建一个类的实例,都在内存中为非静态成员开辟了一块区域,静态方法只能访问类例的静态字段,而非静态方法可以访问类例的所有字段。

答案:对程序标识处分析如下:

面试例题2:下面这段程序输出结果是多少?

A.0

B.1

C.2

D.3

解析:本题考的是静态构造函数的使用方法,只有在第一次实例化对象时才启动static Class1()函数,以后再实例化对象时该函数不起作用。

答案:D。9.5 密封类

面试例题:下列关于密封类的说法错误的是________。

A.密封类中不能再声明新的虚函数

B.密封关键字sealed修饰符不可以与override关键字一起使用

C.密封类从不用做别的类的基类

D.密封关键字sealed修饰符不能和abstract同时使用

解析:C#提出了一个密封类(sealed class)的概念,密封类在声明中使用sealed修饰符,这样就可以防止该类被其他类继承。如果试图将一个密封类作为其他类的基类,C#将提示出错。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。

当对一个类应用sealed修饰符时,此修饰符会阻止其他类从该类继承。在下面的示例中,类B从类A继承,但是任何类都不能从类B继承。

还可以在覆盖基类中的虚方法或虚属性的方法或属性上使用sealed修饰符。这将使你能够允许类从某些类继承,并防止它们覆盖特定的虚方法或虚属性。在下面的示例中,C从B继承,但C无法覆盖在A中声明并在B中密封的虚函数F。

密封类要注意以下几点:(1)将abstract修饰符用于密封类是错误的做法,因为抽象类必须被实体类继承。(2)当应用于方法或属性时,sealed修饰符必须与override一起使用。(3)密封类的主要作用是用于防止派生。密封类的这种特性使其从不作为基类,因此对密封类成员的调用速度略快。(4)在密封类中,不允许再声明新的虚函数,但是对其父类的虚函数会隐式转化为非虚函数,如下所示。

答案:B。9.6 构造函数和析构函数

面试例题1:下面哪个选项的说法是正确的?

A.构造函数不能被重载

B.构造函数不能被覆盖

C.一个构造函数可以返回一个私有的或一个对象的引用

D.构造函数代码执行时是从当前的类层级到它祖先的类

解析:重载构造函数是一个主要的技术,可以允许多种方式初始化一个类。通过定义,构造函数是没有返回值的。所以选项C是错误的,这种说法并没有任何意义。选项D中构造函数代码的执行是从它最老的祖先类开始向下执行调用。可以写一个继承一个基类的类来测试一下,当创建一个子类的时候,会发现它的祖先类的构造函数被调用。

答案:B。

面试例题2:析构函数可以为virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?

答案:虚函数是采用一种虚调用的办法。虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许调用一个只知道接口,不知道其准确对象类型的函数。但是如果要创建一个对象,势必要知道对象的准确类型。因此构造函数不能为虚。

如果构造函数为虚函数的话,它将在执行期间被构造,而执行期则需要对象已经建立,构造函数所完成的工作就是为了建立合适的对象,因此在没有构建好的对象上不可能执行多态(虚函数的目的就在于实现多态性)的工作。

在继承体系中,构造的顺序就是从基类到派生类,其目的就在于确保对象能够成功地构建。构造函数同时承担着虚函数表的建立,如果它本身都是虚函数的话,如何确保虚表的构建成功。

面试例题3:下列关于构造函数的描述错误的是_______。

A.构造函数可以声明返回类型

B.构造函数可以用private修饰

C.构造函数必须与类名相同

D.构造函数可以带几个参数

解析:对一个构造函数指定一个返回类型是一个错误,因为这样会引入构造函数的地址,这意味着将无法处理出错。

答案:A。

面试例题4:下列代码的输出结果是______。

A.x=1,y=-1

B.x=1,y=0

C.x=1,y=1

D.x=0,y=0

解析:初始化对象objectB会调用B的构造函数,由于B类从A类继承,会先调用A的构造函数;A的构造函数使用了方法PrintFields();所以将调用方法PrintFields();而A类的PrintFields()是虚方法且被B类覆盖,实际上调用的是B类的方法PrintFields(),要求的是打印x, y的值:x值为1,而y的值没有定义所以默认为0;因此打印结果是x=1,y=0。

答案:B。9.7 虚函数

面试例题1:为什么虚函数效率低?

答案:一般的函数可以在编译时定位到函数的地址,虚函数(动态类型调用)是要根据某个指针定位到函数的地址。需要一次间接的寻址,而且会传一个index索引,所以相对来说效率低下。

第二个原因是与CPU流水线相关:

如果不是虚函数,那么在编译时期,其相对地址是确定的,编译器可以直接生成jmp/invoke指令;如果是虚函数,除了要多进行一次查找vtable所带来的开销,关键在于这个函数地址是动态的,例如取到的地址在eax寄存器里,则在call eax之后的那些已经被预取进入流水线的所有指令都将失效。流水线越长,一次分支预测失败的代价也就越大。

扩展知识

CPU流水线:流水线是Intel首次在486芯片中开始使用的。流水线的工作方式就像工业生产上的装配流水线。在CPU中由5~6个不同功能的电路单元组成一条指令处理流水线,然后将一条X86指令分成5~6步后再由这些电路单元分别执行,这样就能实现在一个CPU时钟周期完成一条指令,因此提高CPU的运算速度。经典奔腾每条整数流水线都分为4级流水,即指令预取、译码、执行、写回结果,浮点流水又分为8级流水。

面试例题2:如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数?

答案:不行,这是因为虚函数是有代价的:由于每个虚函数的对象都必须维护一个v表,因此在使用虚函数的时候都会产生一个系统开销。如果仅是一个很小的类,且不想派生其他类,根本没必要使用虚函数。9.8 静态构造函数与私有构造函数

面试例题1:下面代码输出结果是多少?[中国著名软件公司J面试题]

A.12

B.23

C.01

D.00

解析:一般来说静态声明赋值语句先于静态构造函数执行,没有赋值的类成员声明会被初始化成该类型的默认值,也就是说:

这两行语句比各自所在的静态构造函数先执行,前一句X没有赋值,默认就是0,后一句的Y在没有赋值之前也是0,赋值后就是A.X+1的值。类的静态初始化包括成员变量的声明赋值,静态构造函数的执行。静态初始化只有在类第一次被访问的时候才执行,而且是优先于第一次访问该类的代码执行。

因为Main函数在class B中,所以程序先执行的是上面的第二条语句,声明一个Y,再给Y赋值。在赋值的时候又用到了A类中的X静态,当第一次访问A.X的时候,会先调用A类的静态构造函数,这里执行赋值X=B.Y+1,而重新去访问B类的成员,因为前面说的静态初始化只有第一次被访问的时候会执行,所以再次访问B类的时候不会重复进行静态初始化的。这时会因为前一次初始化还未完成,特别是B.Y还没有赋值完成,所以根据上面说的,B.Y现在处理只是声明完成的状态,所以现在B.Y的值就是0,相应地得到的X值就是1了,在A类的静态构造函数执行完成的时候,程序会再回到B中Y的赋值语句上来,这时候得到A.X的值就是1,而Y赋值完成后,此时值就变成2了。

因此最终输出的结果就是X=1,Y=2。

答案:A。

扩展知识

C#静态构造函数:静态构造函数是C#的一个特性,当我们想初始化一些静态变量的时候就需要用到它了。这个构造函数是属于类的,而不是属于哪个实例的,也就是说,这个构造函数只会被执行一次。也就是在创建第一个实例或引用任何静态成员之前,由.NET自动调用。以下是一段代码:

在使用静态构造函数的时候应该注意几点:(1)静态构造函数既没有访问修饰符,也没有参数。因为是.NET调用的,所以像public和private等修饰符就没有意义了。(2)在创建第一个类实例或任何静态成员被引用时,.NET将自动调用静态构造函数来初始化类,也就是说,我们无法直接调用静态构造函数,也就无法控制什么时候执行静态构造函数了。(3)一个类只能有一个静态构造函数。(4)无参数的构造函数可以与静态构造函数共存。尽管参数列表相同,但一个属于类,一个属于实例,所以不会冲突。(5)静态构造函数最多只运行一次。(6)静态构造函数不可以被继承。(7)如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。

面试例题2:为何C#中需要使用私有构造函数?

解析:构造函数为私有的类,往往提供一些静态的函数来生成该类的实例,只要这个类有公开的属性、方法,得到实例引用的一方还是可以调用的,一个例子是singleton。

或者这个类只提供静态方法,而不提供任何实例方法,就可以把构造器设成私有,以避免生成实例。但在C#2.0中,一般是将类设成静态的。

这里一般是用于工具类,例如,字符串的验证、枚举类型转换等,通常只做成静态接口被外部调用就可以了。

那么外部使用的时候,只需要A.validateString(string)就能使用了,外部无须构造A类,为了避免外部构造A类,也就是A a=ne A()这种情况出现,使用私有构造函数就可以。

还有在空实体也可以用私有构造函数,总之目的就是避免外面new这个类,导致一些不可预料的结果。

而在单实体模式中,也可以用到私有构造函数。

私有构造函数用法很多,主要目的就是避免外部构造此类。

答案:C#中使用私有构造函数基于以下考虑。(1)去掉默认构造函数。因为一个类如果不写“任何”构造函数,那么系统就会自动为这个类加上一个默认构造函数,但有时候这个类根本不需要实例,就需要一个调用不了的构造函数来把默认构造函数给排除掉。同时保证不向外提供类的构造函数,但是提供静态的方法可以构造对象。(2)避免被错误的构造。有时候一个类的实例构造不能简单地由构造函数来完成,或者构造函数在语法上并不合适,这时一般会提供静态的创建方法来创建实例,但类型的构造必须经过构造函数,所以可以提供一个私有的构造函数给静态创建方法调用。同时,Clone或类似的方法也属于此种情况。

扩展知识

私有构造函数往往用于单例模式。

单例模式的特点有3种。(1)单例类只能有一个实例。(2)单例类必须自己创建自己的唯一实例。(3)单例类必须给所有其他对象提供这一实例。

Singleton模式包含的角色只有一个,就是Singleton。Singleton拥有一个私有构造函数,确保用户无法通过new直接实例它。除此之外,该模式中包含一个静态私有成员变量instance与静态公有方法Instance()。Instance方法负责检验并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。单例模式的结构图如下:

C#的独特语言特性决定了C#拥有实现Singleton模式的独特方法。下面是利用C#语言的优势实现Singleton模式的代码:

Singleton类被声明为sealed,以此保证它自己不会被继承,其次没有了Instance的方法,将原来_instance成员变量变成public readonly,并在声明时被初始化。通过这些改变,我们确实得到了Singleton的模式,原因是在JIT的处理过程中,如果类中的static属性被任何方法使用时,.NET Framework将对这个属性进行初始化,于是在初始化Instance属性的同时,Singleton类实例得以创建和装载。而私有的构造函数和readonly(只读)保证了Singleton不会被再次实例化,这正是Singleton设计模式的意图。9.9 多态的概念

面试例题1:什么是多态?

答案:

开门、开窗户、开电视。在这里的“开”就是多态。

多态性可以简单地概括为“一个接口,多种方法”。在程序运行的过程中才决定调用的函数。多态性是面向对象编程领域的核心概念。

多态(Polymorphisn)按字面的意思就是“多种形状”。多态性是允许你将父对象设置成为和它的一个或更多的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单地说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pasca和C++中都是通过虚函数(Virtual Function)实现的。

扩展知识

多态的作用。

虚函数就是允许被其子类重新定义的成员函数。而子类重新定义父类虚函数的做法,称为“覆盖”(override),或者称为“重写”。这里有一个初学者经常混淆的概念。覆盖(override)和重载(overload)上面说了,覆盖是指子类重新定义父类的虚函数的做法。而重载是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称进行修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如有两个同名函数:function func(p:integer):integer;和function func(p:string):integer。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态)地调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关。

引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”

那么,多态的作用是什么呢?封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用。而且现实往往是,要有效重用代码很难,而真正最具有价值的重用是接口重用,因为“接口是公司最有价值的资源。设计接口比用一堆类来实现这个接口更费时间。而且接口需要耗费更昂贵的人力的时间。”其实,继承的为重用代码而存在的理由已经越来越薄弱,因为“组合”可以很好地取代继承的扩展现有代码的功能,而且“组合”的表现更好(至少可以防止“类爆炸”)。因此笔者个人认为,继承的存在很大程度上是作为“多态”的基础,而非扩展现有代码的方式。

那么什么是接口重用呢?举一个简单的例子,假设我们有一个描述飞机的基类(Object Pascal语言描述):

然后,我们从plane派生出两个子类,直升机(copter)和喷气式飞机(jet):

现在,要完成一个飞机控制系统,有一个全局的函数plane_fly,它负责让传递给它的飞机起飞,那么只需要这样:

就可以让所有传给它的飞机(plane的子类对象)正常起飞。不管是直升机还是喷气机,甚至是现在还不存在的,以后会增加的飞碟。因为,每个子类都已经定义了自己的起飞方式。

可以看到plane_fly函数接受参数的是plane类对象引用,而实际传递给它的都是plane的子类对象,现在回想一下开头所描述的“多态”:多态性是允许你将父对象设置成为和一个或更多的它的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。很显然,parent=child;就是多态的实质。因为直升机“是一种”飞机,喷气机也“是一种”飞机,因此,所有对飞机的操作,都可以对它们操作,此时,飞机类就作为一种接口。多态的本质就是将子类类型的指针赋值给父类类型的指针(在OP中是引用),只要这样的赋值发生了,多态也就产生了,因为实行了“向上映射”。

应用多态的例子非常普遍,在Delphi的VCL类库中,最典型的就是:TObject类有一个虚拟的Destroy虚构函数和一个非虚拟的Free函数。Free函数中是调用Destroy的。因此,当对任何对象(都是TObject的子类对象)调用.Free();之后,都会执行TObject.Free(),它会调用所使用的对象的析构函数Destroy()。这就保证了任何类型的对象都可以正确地被析构。

多态性是面向对象最重要的特性。

面试例题2:重载和覆盖有什么不同?

答案:虚函数总是在派生类中被改写,这种改写被称为“override”。

override是指派生类重写基类的虚函数,就像我们前面B类中重写了A类中的foo()函数。重写的函数必须有一致的参数表和返回值。这个单词好像一直没有什么合适的中文词汇来对应,有人译为“覆盖”还贴切一些。

overload约定俗成地被翻译为“重载”,是指编写一个与已有函数同名,但是参数表不同的函数。例如一个函数既可以接受整型数作为参数,也可以接收浮点数作为参数。重载不是一种面向对象的编程,而只是一种语法规则,重载与多态没有什么直接关系。9.10 索引器

面试例题1:下列关于C#中索引器理解正确的是______。

A.索引器的参数必须是两个或两个以上

B.索引器的参数类型必须是整数型

C.索引器没有名字

D.以上都不是

解析:C#支持一种名为索引器的特殊“属性”,能够用引用数组元素的方式来引用对象。

索引器又称为带参数的属性,它的声明方式与属性的声明十分相似。这里与属性声明不同的地方在于:索引器的名称必须是关键字this, this后面一定要跟一对方括号([和]),在方括号之间指定索引的参数表,其中至少必须有一个参数;索引器不能被定义为静态成员,而只能是实例成员。其他的定义都与属性完全一样,也是通过get访问器来取值,通过set访问器来赋值。索引器使用方式不同于属性的使用方式,需要使用元素访问运算符[],并在其中指定参数来进行引用。

下面的示例声明了一个Team类,该类实现了一个索引器,用于访问组中的各个成员。

答案:C。第10章 继承与接口

接口在实际语言中,如C#、C++等,都有广义和狭义之分。

从广义上说,凡是一个类提供给外部使用的部分都可以被称为接口。但是在引入继承和抽象类之前,这个广义接口并没有太大的意义。广义接口的真正意义是在类的继承中体现多态的功能,这种接口又被称为抽象类接口。

狭义接口是指特定的函数集合,一般是用Interface声明的,它表示一个方法集合,这个集合被称为一个命名接口。一个命名接口中的方法必须在一个类中实现后才能被使用。一个类继承实现一个接口,称为这个类实现了该接口。一个接口可以被多个类实现,一个类也可以继承多个接口,这样就形成了一种灵活的接口调用方式,从而实现更加灵活和节省资源的多态。

从上述认识来看,接口实际上是结合着多态而来的,它的最大的任务就是实现多态。而多态又是面向对象最精华的理论,掌握了多态,也就掌握了面向对象的精髓。但掌握多态必须先理解和掌握接口,只有充分理解接口的意义,才能更好地应用多态。

在面试过程中,各大企业会考量你对虚函数、纯虚函数、私有继承和多重继承等知识点的掌握程度。因此,这是本书比较难掌握的一章。10.1 继承基础问题

面试例题1:下面哪一项说法是正确的?

A.在一个子类中一个方法不是public的就不能被重载

B.覆盖一个方法只需要满足相同的方法名和参数类型就可以了

C.覆盖一个方法必须需要相同的方法名参数和返回类型

D.一个覆盖的方法必须有相同的方法名、参数名和参数类型

解析:在同一可访问区内被声明的几个具有不同参数列的(参数的类型、个数和顺序不同)同名函数,程序会根据不同的参数列来确定具体调用哪个函数,这种机制叫重载。重载不关心函数的返回值类型。覆盖是指派生类中存在重新定义的函数,其函数名、参数列和返回值类型必须同父类中相对应的被覆盖的函数严格一致。覆盖函数和被覆盖函数只有函数体(花括号中的部分)不同,当派生类对象调用子类中该同名函数时会自动调用子类中的覆盖版本,而不是父类中的被覆盖函数版本,这种机制就称为覆盖。

成员函数被重载的特征有:(1)相同的范围(在同一个类中);(2)函数名称相同;(3)参数不同;(4)virtual关键字可有可无。

覆盖的特征有:(1)不同的范围(分别位于派生类与基类);(2)函数名称相同;(3)参数相同;(4)基类函数必须有virtual关键字。

答案:C。

面试例题2:写出下面程序的输出结果。[中国某外包公司W面试题,2008]

A.编译出错

B.输出“BaseClase Constructor”

C.输出“BaseClase Constructor”和“BaseClase Method”

D.输出“BaseClase Constructor”和“Hello”

解析:这段程序由于隐藏了继承的方法,所以会出现编译错误,编译器会提示ChildClass.someMethod隐藏了继承的方法BaseClase.someMethod,必须使用关键字new。如果使用了new,该代码将打印“BaseClase Constructor”。

答案:A。

面试例题3:写出下面程序的输出结果。[中国某外包公司W面试题,2008]

解析:a.Fun2(b)是指先执行A类下的Fun2函数。Fun2函数下第一步是a.Fun1(1),但实际参数是b,也就是执行b.Fun1(1)。b.Fun1(1)函数是在B类当中重载过的,其内容是base.Fun1(i+1),也就是执行Fun1(1+1),结果是2。然后执行Fun1(5),结果是5。

b. Fun2(a)是指先执行B类下的Fun2函数,可B类本身没有Fun2函数,只有执行它的基类的Fun2函数。但实际参数是a,也就是执行a.Fun1(1),结果是1。然后执行Fun1(5)。注意,这里的Fun1(5)实际上是b.Fun1(5),实际执行的结果是base.Fun1(5+1),结果是6。

答案:2516。

扩展知识

这是非常让人头晕的一个题目,要搞清楚:一个函数的执行必须在一个具体的对象中实现。如果函数明确指出是哪个对象,则在该对象下执行;如果没有,则在默认的对象下执行。

面试例题4:写出下面程序的输出结果。[美国著名搜索公司Y面试题]

解析:主函数A a=new B();首先声明了A的一个对象a,但被赋给不同于A的对象B,在此期间分别调用了A类的构造函数和B类的构造函数。然后调用a的fun()函数,因为a的fun()函数是虚函数被b的fun()函数覆盖,所以实际执行的是b的fun()函数。

答案:

A

B

B.Fun()

扩展知识

如果把上面的程序稍作修改,即把

改成

答案就会变成:

A

B

A.Fun()

因为没有覆盖,a.Fun()还是执行A类的Fun函数。

面试例题5:写出下面程序的输出结果。[美国著名搜索公司Y面试题]

请问,此程序输出结果是________。

A.BaseClass

B.BassClass Class1

C.Class1

D.Class1 BassClass

解析:三重继承问题。孙类只是继承父类,而不直接继承祖父类。

答案:C。10.2 关于new

面试例题1:C#中的new关键字有几种用法?

解析:在C#中,new关键字可用作运算符、修饰符或约束。(1)new运算符:用于创建对象和调用构造函数。这种大家都比较熟悉,这里就不再介绍了。(2)new修饰符:new修饰符与new操作符是两个概念。

new修饰符用于声明类或类的成员,表示隐藏了基类中同名的成员,而new操作符用于实例化一个类型。new修饰符只能用于继承类,一般用于弥补基类设计的不足,要想访问被隐藏的基类的成员变量、属性或方法,办法之一就是将子类造型为父类,然后通过基类访问被隐藏的成员变量、属性或方法。new修饰符和override修饰符不可同时用在一个成员上,因为这两个修饰符在含义上互相排斥。一个new修饰符的例子如下:

第二个例子:

结果:(3)new约束:用于在泛型声明中约束可能用做类型参数参数类型。举例如下:

答案:new关键字可用作运算符、修饰符或约束。

面试例题2:在C#的子类中使用new和override,两者的区别是什么?

解析:举下面的例子来说明override和new之间的区别。

override覆盖基类的方法,让基类的方法以子类的内容实现,new不用来覆盖基类的方法,而是全新定义一个子类的方法。这个方法只属于子类,与基类的方法无关,只是名字上相同而已。这一例子是建立在用子类对象加框成基类对象的基础上,目的是实现用基类句柄调用子类方法,以实现覆盖的多态性。

答案:如果想调用子类的new方法,用子类的句柄(绝对不能用基类句柄)来调用。之所以使用new是因为在为子类定义方法名时,发现方法名的与基类的方法相同。但这个方法只在子类中起作用,而不影响基类的方法。也就是说,new方法就是子类全新定义的方法。用override是真正意义上的覆盖,是把基类的同名方法“遮盖”。10.3 this问题

面试例题1:C#中this保留字的作用是什么?静态构造函数里面可以使用this吗?为什么?

解析:

C#中的this随处可见,用法也多,有如下几点。

1.this是指当前对象自己

当在一个类中要明确指出使用对象自己的变量或函数时就应该加上this关键字。如下面这个例子:

运行结果:

在这个例子中,构造函数Hello中,参数s与类Hello的变量s同名,这时如果直接对s进行操作则是对参数s进行操作。若要对类Hello的成员变量s进行操作就应该用this进行引用。运行结果的第一行就是直接对构造函数中传递过来的参数s进行打印结果;第二行是对成员变量s的打印;第三行是先对成员变量s赋传过来的参数s值后再打印,所以结果是HelloWorld!

2.把this作为参数传递

当要把自己作为参数传递给别的对象时,也可以用this。如:

运行结果:

在执行A x=new A();时,类A的构造方法里调用类B的print函数,此时类B被调用,类B的构造方法里对a赋值:

B构造完后,类B的print函数开始执行:

a对象的print方法打印出Hello from A!然后打印出Hello from B!

程序结束本例同样描述了类关系的一种“has a”:B has aA,即B的实例中有一个A类型的成员变量。除了“has a”,类关系最常见的还有“is a”和“like a”等。

3.在构造函数中,通过this可以调用同一class中别的构造函数

如:

值得注意的是:(1)在构造函数中调用另一个构造函数,调用动作必须置于最起始的位置。(2)不能在构造函数以外的任何函数内调用构造函数。(3)在一个构造函数内只能调用一个构造函数。

答案:this是一个保留字,仅限于在构造函数和方法成员中使用。在引用类型中,它表示所调用的对象实例的引用。在值类型中,它是一个符号,将this上的各种操作转嫁给所调用的值类型对象实例。

在类的构造函数中出现表示对正在构造的对象本身的引用;在类的方法中出现表示对调用该方法的对象的引用;在结构的构造上函数中出现表示对正在构造的结构的引用;在结构的方法中出现表示对调用该方法的结果的引用。this保留字不能用于静态成员的实现里,因为这时对象或结构并未实例化。在C#系统中,this实际上是一个常量,所以不能使用this++这样的运算。

this保留字一般用于限定同名的隐藏成员、将对象本身作为参数、声明索引访问器和判断传入参数的对象是否为本身。10.4 base问题

面试例题1:下题的输出结果是什么?

A.A without any parameter B without any parameters

B.A without any parameter B with a parameter

C.A with a parameter B without any parameters

D.A with a parameter B with a parameter

解析:因为子类被构造时一定会先调用父类的构造函数,但可以用base关键字选择调用哪个构造函数,决定调用哪一个,但不能哪个都不调用(至少选一个)。如果不指定的话,一般会调用无参数的构造函数,因为这是一个类的默认的构造函数。如果选定调用A(int i),则会得到D的结果。

答案:B。10.5 抽象类

面试例题1:下列关于抽象类说法错误的是哪个?

A.抽象类中可以不存在任何抽象的方法

B.抽象类可以被抽象类所继承,结果仍是抽象类

C.抽象类不能同时又是密封的

D.如果一个非抽象类从抽象类中派生,不一定要通过覆盖来实现继承的抽象成员

E.抽象类允许被声明

解析:有时候,基类并不与具体的事物相联系,而是只表达一种抽象的概念,用以为它的派生类提供一个公共的界面。因此,C#中引入了抽象类(abstract class)的概念。

C++程序员在这里最容易犯错误。C++中没有对抽象类进行直接声明的方法,而认为只要在类中定义了纯虚函数,这个类就是一个抽象类。

纯虚函数的概念比较晦涩,直观上不容易被人们接受和掌握,因此C#抛弃了这一概念。而通过显式声明抽象类并使用abstract修饰符的办法。如:

一个抽象类要注意以下几点。(1)抽象类只能作为其他类的基类,它不能直接被实例化,而且对抽象类不能使用new操作符。抽象类如果含有抽象的变量或值,则它们要么是null类型,要么包含了对非抽象类实例的引用。(2)抽象类允许包含抽象成员,但这不是必需的(可以允许一个抽象类中没有任何抽象成员),抽象类中可以有非抽象方法。(3)抽象类不能同时又是密封的。如果试图将一个密封类作为其他类的基类,C#将提示出错。当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。(4)如果一个非抽象类从抽象类中派生,则其必须通过覆盖来实现所有继承而来的抽象成员。(5)抽象类可以被抽象类所继承,结果仍是抽象类。(6)抽象类允许被声明。

通过以下一个例子来说明:

下面来研究汽车类的例子。从“交通工具”这个角度来理解Vehicle类的话,它应该表达一种抽象的概念,我们可以把它定义为抽象类。由轿车类Car和卡车类Truck来继承这个抽象类,它们作为可以实例化的类。

答案:D。

扩展知识1

C#中接口与纯粹的抽象类

先看下面的类,观察它的特点。

抽象类是指一个包含抽象方法的类。以上的类是一个纯粹的抽象类,它的所有方法都是抽象的。接口极其类似于纯粹的抽象类,如果把上面的定义稍加改动,把关键字class改成interface,可将cShape类改为Ishape接口:

接口定义基本结构如下:

IName是接口名称,以I打头是为了表明它是一个接口,当然用别的也可以,只是这样做比较清晰。member是接口成员,可以是方法、属性、事件和索引器,但不能是常量、域、操作符、构造函数或析构函数,而且不能包含任何静态成员。

与Java不同,C#中的接口不能包含域(Field)。另外还要注意,在C#中,接口内的所有方法默认都是公用方法。在Java中,方法定义可以带有public修饰符(即使这并非必要),但在C#中,显式为接口的方法指定public修饰符是非法的。例如,下面的C#接口将产生一个编译错误。

另外,有些地方必须使用到函数指针才能完成给定的任务,特别是异步操作的回调和其他需要匿名回调的结构。另外,像线程的执行和事件的处理,如果缺少了函数指针的支持也是很难完成的。

扩展知识2

C++中的抽象类

在C++中,将抽象类中的抽象函数赋值为0,对抽象类的要求更加严格。他不但不能实例化,甚至不能声明,如下面程序所示:

此外,C++继承抽象类抽象方法的子类的方法也可以没有body,实例化子类的时候,可以通过指针,程序如下所示:

面试例题2:抽象类可以继承一个普通类吗?如果可以的话,能用抽象函数重写基类中的虚函数吗?

答案:抽象类可以继承一个普通类。可以用抽象函数重写基类中的虚函数,方法有两种:(1)需使用new修饰符显式声明,表示隐藏了基类中该函数的实现。(2)增加override修饰符,表示抽象重写了基类中该函数的实现。

示例如下:

扩展知识

abstract修饰符不可以和static、virtual和sealed修饰符一起使用,但是可以和override修饰符一起使用。

面试例题3:抽象类和接口的区别是什么?

解析:对于一个抽象类,它也可以有具体方法;对于一个接口,所有的方法必须都是抽象的。实现了一个接口的类必须为接口中的所有方法提供具体的实现,否则只能声明为抽象类。

在C#中,多重继承(Multiple Inheritance)只能通过实现多个接口得到。抽象类只能单继承。接口定义的是一个契约,其中只能包含4种实体,即方法、属性、事件和索引器。因此接口不能包含常数、域、操作符、构造器、析构器、静态构造器或类型。所以,对比一个类,接口的特殊性是,当定义一个类时,可以派生自多重接口,而只能可以从仅有的一个类派生。

同时,一个接口还不能包含任何类型的静态成员。修饰符abstract、public、protected、internal、private、virtual和override都是不允许出现的,因为它们在这种环境中是没有意义的。类中实现的接口成员必须具有公有的可访问性。

接口和类都可以继承多个接口。而类可以继承一个基类,接口根本不能继承类。这种模型避免了C++的多继承问题,C++中不同基类中的实现可能出现冲突。因此也不再需要诸如虚拟继承和显式作用域这类复杂机制。C#的简化接口模型有助于加快应用程序的开发。

一个接口定义一个只有抽象成员的引用类型。在C#中,一个接口实际上仅仅存在着方法标志,根本就没有执行代码。这就暗示了不能实例化一个接口,只能实例化一个派生自该接口的对象。

答案:抽象类可以包含功能定义和实现,接口仅包含功能定义。

抽象类和接口的使用细节差别如下:(1)抽象类可以有构造方法;(2)抽象类可以有终结器;(3)抽象类可以有字段;(4)抽象类可以有静态成员;(5)抽象类可以没有抽象方法;(6)抽象类可以有实例方法;(7)一个类只能继承一个抽象类,而可以实现多个接口;(8)抽象类的成员可以带有访问性级别;(9)抽象类之间不能多继承,接口则可以;(10)抽象类可以有静态构造方法。

面试例题4:以下描述错误的是______。

A.在C++中支持抽象类而在C#中不支持抽象类。

B.C++中可在头文件中声明类的成员而在C++文件中定义类的成员,在C#中没有头文件,并且在同一处声明和定义类的成员。

C.在C#中可使用new修饰符显式隐藏从基类继承的成员。

D.在C#中要在派生类中重新定义基类的虚函数必须在前面加Override。

答案:A。10.6 接口

面试例题1:为什么C#、Java需要提供接口,而C++却可以没有接口?

解析:很多人学习C#、Java时,都被接口和抽象类所搞混,不知什么时候该用接口,什么时候该用抽象类,但学C++时却很单纯,因为C++只用一套虚方法和多重继承就全部解决了。

面向对象的三个特色:封装、继承和多态。其中最重要的多态,C++是靠继承和动态绑定达成,动态绑定的前提就是虚方法。接口概念如同功能,类似于只有定义而没有实体的类,C++中使用抽象基类来达成,一个类可能必须实现多个功能,C++因为有多重继承机制,所以使用类多重继承即可实现多个功能。

答案:在C#和Java中取消了多重继承,取消的原因是减少程序的复杂性,减少出错的可能性,以及受到单根继承和反射限制等多种方面影响。因为C#、Java都没有多重继承,所以无法使用类继承来达到同时实现多个功能的要求。于是另外提出了接口的概念,当然这和抽象类功能有所重复,所以接口和抽象类的核心差别在于,一个类可以同时实现多个接口,但只能继承一个抽象类。

由此看出,接口只是抽象类的一个特例,而且是由于C#、Java本身没有多重继承之下不得不出现的产物,接口和抽象类之间的定位有些模糊。这点C++反而比较清楚,纯定义接口用抽象基类,全部使用纯虚方法,一个类实现多个抽象基类也能用多重继承完成,若想做出C#那种抽象类,只要在抽象基类上,另外加上虚方法即可,没有什么抽象类概念。

C#、Java作为比C++更新的语言,很多地方在概念上比较复杂,接口和抽象类便是一例,其根源就是C#、Java没有多重继承。又如C#还分虚方法和抽象方法,而C++仅用虚方法就实现了,概念反而比较简单。

扩展知识

C++中类多重继承

多重继承在语言上并没有什么很严重的问题,但是标准本身只对语义做了规定,而对编译器的细节没有做规定。所以在使用时(即使是继承),最好不要对内存布局等有什么假设。此类的问题还有虚析构函数等。为了避免由此带来的复杂性,通常推荐使用复合。但是,在《C++设计新思维》(Andrei Alexandrescu)一书中对多重继承和模板有极为精彩的运用。(1)多重继承本身并没有问题,如果运用得当可以收到事半功倍的效果。不过大多数系统的类层次往往有一个公共的基类,就像MFC中的Cobject, Java中的Object。而这样的结构如果使用多重继承,稍有不慎,将会出现一个严重现象——菱形继承,这样的继承方式会使得类的访问结构非常复杂。但并非不可处理,可以用virtual继承(并非唯一的方法)及Loki库中的多继承框架来掩盖这些复杂性。(2)从哲学上来说,C++多重继承必须要存在,这个世界本来就不是单根的。从实际用途上来说,多重继承不是必需的,但这个世界上有多少东西是必需的呢?对象不过是一组有意义的数据集合及其上的一组有意义的操作,虚函数(晚期绑定)也不过是一堆函数入口表,重载也不过是函数名扩展,这些东西都不是必需的,而且对它们的不当使用都会带来问题。但是没有这些东西行吗?很显然,不行。(3)多重继承在面向对象理论中并非是必要的——因为它不提供新的语意,可以通过单继承与复合结构来取代。而Java则放弃了多重继承,使用简单的interface取代。多重继承是把双刃剑,应该正确地对待。况且,它不像goto,不破坏面向对象语义。跟其他任何威力强大的东西一样,用好了会带来代码的极大精简,用坏了那就不用说了。

C++是为实用而设计的,在语言里有很多东西存在着各种各样的“缺陷”。所以,对于这种有“缺陷”的东西,它的优劣就要看使用它的人。C++不回避问题,它只是把问题留给使用者,从而给大家更多的自由。像Ada、Pascal这类定义严格的语言,从语法上回避了问题,但并不是真正解决了问题,而使人做很多事时束手束脚(当然,习惯了就好了)。(4)多重继承本身并不复杂,对象布局也不混乱,语言中都有明确的定义。真正复杂的是使用了运行时多态(virtual)的多重继承(因为语言对于多态的实现没有明确的定义)。为什么非要说多重继承不好呢?如果这样的话,指针不是更容易出错,运行时多态不是更不好理解吗?

因为C++中没有interface这个关键字,所以不存在所谓的“接口”技术。但是C++可以很轻松地做到这样的模拟,因为C++中的不定义属性的抽象类就是接口。(5)要了解C++,就要明白有很多概念是C++试图考虑但是最终放弃的设计。你会发现很多Java、C#中的东西都是C++考虑后放弃的。不是说这些东西不好,而是在C++中它将破坏C++作为一个整体的和谐性,或者C++并不需要这样的东西。用一个例子来说,C#中有一个关键字base用来表示该类的父类,C++却没有对应的关键字。为什么没有?其实C++中曾经有人提议一个类似的关键字inherited,用来表示被继承的类,即父类。这样一个好的建议为什么没有被采纳呢?这本书中就说得很明确,因为这样的关键字既不必须又不充分。不必须是因为C++有一个typedef*inherited,不充分是因为有多个基类,你不可能知道inherited指的是哪个基类。

很多其他语言中存在的时髦的东西在C++中都没有,这之中有的是待改进的地方,有的是不需要,我们不能一概而论,而要具体问题具体分析。

面试例题2:接口是一种引用类型,在接口中可以声明______。

A.方法、属性、索引器、事件

B.方法、属性信息、属性、公有域

C.索引器、字段、公有域

D.事件、字段、私有成员变量

解析:接口是一组包含了函数型方法的数据结构。通过这组数据结构,客户代码可以调用组件对象的功能。

定义接口的一般形式为:

说明如下。(1)attributes(可选):附加的定义性信息。(2)modifiers(可选):允许使用的修饰符有new和4个访问修饰符。分别是new、public、protected、internal、private。在一个接口定义中,同一修饰符不允许出现多次,new修饰符只能出现在嵌套接口中,表示覆盖了继承而来的同名成员。public, protected, internal和private修饰符定义了对接口的访问权限。(3)指示器和事件。(4)identifier:接口名称。(5)base-list(可选):包含一个或多个显式基接口的列表,接口间由逗号分隔。(6)interface-body:对接口成员的定义。(7)接口可以是命名空间或类的成员,并且可以包含下列成员的签名:方法、属性和索引器。(8)一个接口可从一个或多个基接口继承。

接口的关键词是interface,一个接口可以扩展一个或者多个其他接口。按照惯例,接口的名字以大写字母“I”开头。下面的代码是C#接口的一个例子,它与Java中的接口完全一样。

如果从两个或者两个以上的接口派生,父接口的名字列表用逗号分隔,如下面的代码所示:

C#中的接口不能包含域(Field)和字段。另外还要注意,在C#中接口内的所有方法默认都是公用方法。显式为接口的方法指定public修饰符是非法的。例如,下面的C#接口将产生一个编译错误。

下例接口IInterface从两个基接口IBase1和IBase2继承:

接口可由类实现。实现的接口的标识符出现在类的基列表中。例如:

类的基列表同时包含基类和接口时,列表中首先出现的是基类。例如:

以下的代码段定义接口IFace,它只有一个方法。

不能从这个定义实例化一个对象,但可以用它派生一个类。因此,该类必须实现ShowMyFace抽象方法。如:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载