名企面试官精讲典型编程题之C#篇
C#
C#是微软在推出新的开发平台.NET时同步推出的编程语言。由于Windows至今仍然是用户最多的操作系统,而.NET又是微软近年来力推的开发平台,因此C#无论在桌面软件还是网络应用的开发上都有着广泛的应用,所以我们也不难理解为什么现在很多基于Windows系统开发的公司都会要求应聘者掌握C#。
C#可以看成是一门以C++为基础发展起来的一种托管语言,因此它的很多关键字甚至语法都和C++很类似。对一个学习过C++编程的程序员而言,他用不了多长时间学习就能用C#来开发软件。然而我们也要清醒地认识到,虽然学习C#与C++相同或者类似的部分很容易,但要掌握并区分两者不同的地方却不是一件很容易的事情。面试官总是喜欢深究我们模棱两可的地方以考查我们是不是真的理解了,因此我们要着重注意C#与C++不同的语法特点。下面的面试片段就是一个例子:
面试官:C++中可以用struct和class来定义类型。这两种类型有什么区别?
应聘者:如果没有标明成员函数或者成员变量的访问权限级别,在struct中默认的是public,而在class中默认的是private。
面试官:那在C#中呢?
应聘者:C#和C++不一样。在C#中如果没有标明成员函数或者成员变量的访问权限级别,struct和class中都是private的。struct和class的区别是struct定义的是值类型,值类型的实例在栈上分配内存;而class定义的是引用类型,引用类型的实例在堆上分配内存。
在C#中,每个类型中和C++一样,都有构造函数。但和C++不同的是,我们在C#中可以为类型定义一个Finalizer和Dispose方法以释放资源。Finalizer方法虽然写法与C++的析构函数看起来一样,都是后面跟类型名字,但与C++析构函数的调用时机是确定的不同,C#的Finalizer是在运行时(CLR)做垃圾回收时才会被调用,它的调用时机是由运行时决定的,因此对程序员来说是不确定的。另外,在C#中可以为类型定义一个特殊的构造函数:静态构造函数。这个函数的特点是在类型第一次被使用之前由运行时自动调用,而且保证只调用一次。关于静态构造函数,我们有很多有意思的面试题,比如运行下面的C#代码,输出的结果是什么?
class A
{
public A(string text)
{
Console.WriteLine(text);
}
}
class B
{
static A a1 = new A("a1");
Aa2 = new A("a2");
static B()
{
a1 = new A("a3");
}
public B()
{
a2 = new A("a4");
}
}
class Program
{
static void Main(string[] args)
{
B b = new B();
}
}
在调用类型B的代码之前先执行B的静态构造函数。静态构造函数先初始化类型的静态变量,再执行函数体内的语句。因此先打印a1再打印a3。接下来执行B b = new B(),即调用B的普通构造函数。构造函数先初始化成员变量,再执行函数体内的语句,因此先后打印出a2、a4。因此运行上面的代码,得到的结果将是打印出4行,分别是a1、a3、a2、a4。
我们除了要关注C#和C++不同的知识点之外,还要格外关注C#一些特有的功能,比如反射、应用程序域(AppDomain)等。这些概念还相互关联,要花很多时间学习研究才能透彻地理解它们。下面的代码就是一段关于反射和应用程序域的代码,运行它得到的结果是什么?
[Serializable]
internal class A : MarshalByRefObject
{
public static int Number;
public void SetNumber(int value)
{
Number = value;
}
}
[Serializable]
internal class B
{
public static int Number;
public void SetNumber(int value)
{
Number = value;
}
}
class Program
{
static void Main(string[] args)
{
String assambly = Assembly.GetEntryAssembly().FullName;
AppDomain domain = AppDomain.CreateDomain("NewDomain");
A.Number = 10;
String nameOfA = typeof(A).FullName;
A a = domain.CreateInstanceAndUnwrap(assambly, nameOfA) as A;
a.SetNumber(20);
Console.WriteLine("Number in class A is {0}", A.Number);
B.Number = 10;
String nameOfB = typeof(B).FullName;
B b = domain.CreateInstanceAndUnwrap(assambly, nameOfB) as B;
b.SetNumber(20);
Console.WriteLine("Number in class B is {0}", B.Number);
}
}
上述C#代码先创建一个名为NewDomain的应用程序域,并在该域中利用反射机制创建类型A的一个实例和类型B的一个实例。我们注意到类型A是继承自MarshalByRefObject,而B不是。虽然这两个类型的结构一样,但由于基类不同而导致在跨越应用程序域的边界时表现出的行为将大不相同。
先考虑A的情况。由于A继承自MarshalByRefObject,那么a实际上只是在默认的域中的一个代理实例(Proxy),它指向位于NewDomain域中的A的一个实例。当调用a的方法SetNumber时,是在NewDomain域中调用该方法,它将修改NewDomain域中静态变量A.Number的值并设为20。由于静态变量在每个应用程序域中都有一份独立的拷贝,修改NewDomain域中的静态变量A.Number对默认域中的静态变量A.Number没有任何影响。由于Console.WriteLine是在默认的应用程序域中输出A.Number,因此输出仍然是10。
接着讨论B。由于B只是从Object继承而来的类型,它的实例穿越应用程序域的边界时,将会完整地复制实例。因此在上述代码中,我们尽管试图在NewDomain域中生成B的实例,但会把实例b复制到默认的应用程序域。此时调用方法b.SetNumber也是在缺省的应用程序域上进行,它将修改默认的域上的A.Number并设为20。再在默认的域上调用Console.WriteLine时,它将输出20。
下面推荐两本C#相关的书籍,以方便大家应对C#面试并学习好C#。
《ProfessionalC#》。这本书最大的特点是在附录中有几章专门写给已经有其他语言(如VB、C++和Java)经验的程序员,它详细讲述了C#和其他语言的区别,看了这几章之后就不会把C#和之前掌握的语言相混淆。
JeffreyRichter的《CLR Via C#》。该书不仅深入地介绍了C#语言,同时对CLR及.NET做了全面的剖析。如果能够读懂这本书,那么我们就能深入理解装箱卸箱、垃圾回收、反射等概念,知其然的同时也能知其所以然,通过C#相关的面试自然也就不难了。
面试题2:实现Singleton模式
题目:设计一个类,我们只能生成该类的一个实例。
只能生成一个实例的类是实现了Singleton(单例)模式的类型。由于设计模式在面向对象程序设计中起着举足轻重的作用,在面试过程中很多公司都喜欢问一些与设计模式相关的问题。在常用的模式中,Singleton是唯一一个能够用短短几十行代码完整实现的模式。因此,写一个Singleton的类型是一个很常见的面试题。
不好的解法一:只适用于单线程环境
由于要求只能生成一个实例,因此我们必须把构造函数设为私有函数以禁止他人创建实例。我们可以定义一个静态的实例,在需要的时候创建该实例。下面定义类型Singleton1就是基于这个思路的实现:
public sealed class Singleton1
{
private Singleton1()
{
}
private static Singleton1 instance = null;
public static Singleton1 Instance
{
get
{
if (instance == null)
instance = new Singleton1();
return instance;
}
}
}
上述代码在Singleton的静态属性Instance中,只有在instance为null的时候才创建一个实例以避免重复创建。同时我们把构造函数定义为私有函数,这样就能确保只创建一个实例。
不好的解法二:虽然在多线程环境中能工作但效率不高
解法一中的代码在单线程的时候工作正常,但在多线程的情况下就有问题了。设想如果两个线程同时运行到判断instance是否为null的if语句,并且instance的确没有创建时,那么两个线程都会创建一个实例,此时类型Singleton1就不再满足单例模式的要求了。为了保证在多线程环境下我们还是只能得到类型的一个实例,需要加上一个同步锁。把Singleton1稍做修改得到了如下代码:
public sealed class Singleton2
{
private Singleton2()
{
}
private static readonly object syncObj = new object();
private static Singleton2 instance = null;
public static Singleton2 Instance
{
get
{
lock (syncObj)
{
if (instance == null)
instance = newSingleton2();
}
return instance;
}
}
}
我们还是假设有两个线程同时想创建一个实例。由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁时,第二个线程只能等待。当第一个线程发现实例还没有创建时,它创建出一个实例。接着第一个线程释放同步锁,此时第二个线程可以加上同步锁,并运行接下来的代码。这个时候由于实例已经被第一个线程创建出来了,第二个线程就不会重复创建实例了,这样就保证了我们在多线程环境中也只能得到一个实例。
但是类型Singleton2还不是很完美。我们每次通过属性Instance得到Singleton2的实例,都会试图加上一个同步锁,而加锁是一个非常耗时的操作,在没有必要的时候我们应该尽量避免。
可行的解法:加同步锁前后两次判断实例是否已存在
我们只是在实例还没有创建之前需要加锁操作,以保证只有一个线程创建出实例。而当实例已经创建之后,我们已经不需要再做加锁操作了。于是我们可以把解法二中的代码再做进一步的改进:
public sealed class Singleton3
{
private Singleton3()
{
}
private static object syncObj = new object();
private static Singleton3 instance = null;
public static Singleton3 Instance
{
get
{
if (instance == null)
{
lock (syncObj)
{
if (instance == null)
instance = newSingleton3();
}
}
return instance;
}
}
}
Singleton3中只有当instance为null即没有创建时,需要加锁操作。当instance已经创建出来之后,则无须加锁。因为只在第一次的时候instance为null,因此只在第一次试图创建实例的时候需要加锁。这样Singleton3的时间效率比Singleton2要好很多。
Singleton3用加锁机制来确保在多线程环境下只创建一个实例,并且用两个if判断来提高效率。这样的代码实现起来比较复杂,容易出错,我们还有更加优秀的解法。
强烈推荐的解法一:利用静态构造函数
C#的语法中有一个函数能够确保只调用一次,那就是静态构造函数,我们可以利用C#这个特性实现单例模式如下:
public sealed class Singleton4
{
private Singleton4()
{
}
private static Singleton4 instance = new Singleton4();
public static Singleton4 Instance
{
get
{
return instance;
}
}
}
Singleton4的实现代码非常简洁。我们在初始化静态变量instance的时候创建一个实例。由于C#是在调用静态构造函数时初始化静态变量,.NET运行时能够确保只调用一次静态构造函数,这样我们就能够保证只初始化一次instance。
C#中调用静态构造函数的时机不是由程序员掌控的,而是当.NET运行时发现第一次使用一个类型的时候自动调用该类型的静态构造函数。因此在Singleton4中,实例instance并不是第一次调用属性Singleton4.Instance的时候创建,而是在第一次用到Singleton4的时候就会被创建。假设我们在Singleton4中添加一个静态方法,调用该静态函数是不需要创建一个实例的,但如果按照Singleton4的方式实现单例模式,则仍然会过早地创建实例,从而降低内存的使用效率。
强烈推荐的解法二:实现按需创建实例
最后的一个实现Singleton5则很好地解决了Singleton4中的实例创建时机过早的问题:
public sealed class Singleton5
{
Singleton5()
{
}
public static Singleton5 Instance
{
get
{
return Nested.instance;
}
}
class Nested
{
static Nested()
{
}
internal static readonly Singleton5 instance = new Singleton5();
}
}
在上述Singleton5的代码中,我们在内部定义了一个私有类型Nested。当第一次用到这个嵌套类型的时候,会调用静态构造函数创建Singleton5的实例instance。类型Nested只在属性Singleton5.Instance中被用到,由于其私有属性他人无法使用Nested类型。因此当我们第一次试图通过属性Singleton5.Instance得到Singleton5的实例时,会自动调用Nested的静态构造函数创建实例instance。如果我们不调用属性Singleton5.Instance,那么就不会触发.NET运行时调用Nested,也不会创建实例,这样就真正做到了按需创建。
解法比较
在前面的5种实现单例模式的方法中,第一种方法在多线程环境中不能正常工作,第二种模式虽然能在多线程环境中正常工作但时间效率很低,都不是面试官期待的解法。在第三种方法中我们通过两次判断一次加锁确保在多线程环境能高效率地工作。第四种方法利用C#的静态构造函数的特性,确保只创建一个实例。第五种方法利用私有嵌套类型的特性,做到只在真正需要的时候才会创建实例,提高空间使用效率。如果在面试中给出第四种或者第五种解法,毫无疑问会得到面试官的青睐。
源代码:
本题完整的源代码详见02_Singleton项目。
本题考点:
考查对单例(Singleton)模式的理解。
考查对C#的基础语法的理解,如静态构造函数等。
考查对多线程编程的理解。
本题扩展:
在前面的代码中,5种单例模式的实现把类型标记为sealed,表示它们不能作为其他类型的基类。现在我们要求定义一个表示总统的类型President,可以从该类型继承出FrenchPresident和AmericanPresident等类型。这些派生类型都只能产生一个实例。请问该如何设计实现这些类型?

本文选自《剑指Offer——名企面试官精讲典型编程题》一书
图书详细信息:http://blog.csdn.net/broadview2006/article/details/7043805
分享到:
相关推荐
读书笔记:剑指Offer——名企面试官精讲典型编程题C#版
收录于CSDN专辑《程序员面试(各种PDF书籍)》。该专辑包含: Android高薪之路:Android程序员面试宝典-李宁(高清PDF完整版)、 ...剑指offer 名企面试官精讲典型编程题-何海涛(PDF扫描版+配套源代码)、、、
收录于CSDN专辑《程序员面试(各种PDF书籍)》。该专辑包含: Android高薪之路:Android程序员面试宝典-李宁(高清PDF完整版)、 ...剑指offer 名企面试官精讲典型编程题-何海涛(PDF扫描版+配套源代码)、、、
编码访谈本项目是对《剑指要约-名企面试官精讲典型编程题(纪念版)》一书处理的笔记,以及书中67个编程变量的代码实现和解析。全书共8章,因此,我的笔记也分为相应的8个章节,可以从以下目录进行快速访问:笔记中...
单片机开发教程代码涉及多个方面,包括硬件连接、软件编程、调试与优化等。以下是一个基于51单片机的简单教程代码示例,以及相关的开发步骤和解释。 ### 一、硬件连接 在进行单片机开发之前,首先需要正确连接硬件。以51单片机为例,通常需要将单片机的各个引脚与外围设备(如LED灯、按键、传感器等)进行连接。以下是一个简单的硬件连接示例: 1. 将单片机的P1.0引脚与LED灯的正极相连,LED灯的负极接地。 2. 将单片机的P3.2、P3.3、P3.4、P3.5引脚分别与四个按键的一端相连,按键的另一端接地。 ### 二、软件编程 在进行软件编程时,需要选择合适的编程语言(如C语言)和编译环境(如Keil C51)。以下是一个简单的51单片机程序示例,用于控制LED灯的亮灭和按键的扫描: ```c #include <reg51.h> sbit LED = P1^0; // 定义LED灯连接的引脚 void delay(unsigned int time) { unsigned int i, j; for (i = 0; i < time; i++) {
《顶刊复现》(复现程度90%),Reinforcement Learning-Based Fixed-Time Trajectory Tracking Control for Uncertain Robotic Manipulators With Input Saturation,自适应强化学习机械臂控制,代码框架方便易懂,适用于所有控制研究爱好者。 ,《深度强化学习复现:自适应控制框架下的机械臂轨迹跟踪控制研究》,强化学习机械臂控制的自适应轨迹跟踪:高复现度与易懂代码框架研究报告,核心关键词:顶刊复现; 强化学习; 固定时间轨迹跟踪控制; 不确定机械臂; 输入饱和; 自适应控制; 代码框架; 控制研究爱好者。,《基于强化学习的机械臂固定时间轨迹跟踪控制:复现程度高达90%》
通过分析企业对于飘香水果购物网站的需求,创建了一个计算机管理飘香水果购物网站的方案。文章介绍了飘香水果购物网站的系统分析部分,包括可行性分析等,系统设计部分主要介绍了系统功能设计和数据库设计。 本飘香水果购物网站管理员功能有,个人中心管理,用户管理,会员管理,会员卡管理,开通会员记录管理,积分管理,水果管理,购买水果订单管理,积分兑换管理,积分兑换记录管理,加积分记录管理,减积分记录管理。用户可以注册登录,在首页开通会员卡,查看水果,购买水果,查看水果信息,以及个人中心修改个人资料,在自己的后台查看自己的购买记录等。因而具有一定的实用性。 本站是一个B/S模式系统,采用Spring Boot框架作为开发技术,MYSQL数据库设计开发,充分保证系统的稳定性。系统具有界面清晰、操作简单,功能齐全的特点,使得飘香水果购物网站管理工作系统化、规范化。 关键词:飘香水果购物网站;Spring Boot框架;MYSQL数据库
地区:全国都有。时间:近半年的都有,之前的需要查数据库。数据来源:百度慧眼 数据形式:含坐标的CSV点数据;SHP数据;TIFF栅格数据;多种数据形式可选。任意精度,10,30,50m均可。 价格:市为单位,每天有24个时间点。数据格式不同价格不同。 用途:城市/街道活力,人口统计,选址分析,商圈分析,活力分析等等。
1998-2022年各地级市第三产业占GDP比重/地级市第三产业占比数据(市辖区) 1、时间:1998-2022年 2、指标:地级市第三产业占GDP比重/地级市第三产业占比 3、来源:城市统计年鉴 4、范围:299个地级市 5、缺失情况:缺失情况与年鉴一致,表内附有年鉴第三产业占比原始数据,以2022年地级市名单进行统计整理,为市辖区数据
网站精美前端设计,使用jQuery+CSS开发,源码适用于参考学习使用。希望对你学习和开发有所帮助
本项目是基于SSM框架开发的固定设备资产管理系统,旨在实现企业资产全生命周期数字化管理。系统采用B/S架构,前端通过JSP、Vue等技术实现交互界面,后端以Spring、SpringMVC和MyBatis为核心框架构建业务逻辑层,数据库采用MySQL存储资产信息16。
内容概要:本文基于对称折叠、卷式折叠和环向折叠三种气囊折叠方式,通过虚拟试验平台,采用气囊试验、碰块试验和转向盘试验,深入分析了各种气囊折叠方式在两个阶段内的气压特性及其对乘员保护性能的影响。结果发现气囊的展开过程中存在两阶段特征(初期展开阶段为0~20ms,完全展开工作阶段为20~100ms),不同折叠层数会导致不同的展开阻力,影响展开时间和内部压力;特别是环向折叠方式因其展开阻力较小,能够更快且平稳地提供保护力,并且能在早期阶段对靠近模块的离位乘客施加较小的力量,适合于大体型正常坐姿的乘客保护;卷式折叠适用于小型体态乘客;而对称折叠则是标准体型乘客的最佳选择。 适合人群:从事车辆被动安全性研究的技术人员、安全系统工程师、交通安全领域的研究人员,以及对高级辅助驾驶技术和安全性能感兴趣的学术界人士和专业学生。 使用场景及目标:该研究表明,通过对不同气囊折叠方法的研究,能够指导实际产品优化设计,提升乘客保护性能,同时也有助于制定科学合理的法规和规范,确保乘客在突发交通事故条件下能够得到最大程度的安全保障。 其他说明:本研究由国家自然科学基金资助,并借助有限元程序LS-DYNA进行数值模拟
MATLAB gui界面设计 MATLAB图像处理 gui界面开发 傅立叶变,灰度图,二值化,直方图均衡,高通滤波器,低通滤波器,巴特沃斯滤波器,噪声处理,边缘检测 ,MATLAB图像处理与GUI界面开发:实现傅立叶变换及高级滤波算法应用与解析,MATLAB GUI界面开发及应用实践:图像处理、滤波与边缘检测的完整解决方案,MATLAB GUI界面设计; MATLAB图像处理; gui界面开发; 图像处理技术; 傅立叶变换; 图像灰度化; 二值化处理; 直方图均衡化; 滤波器(高通/低通/巴特沃斯); 噪声处理; 边缘检测。,MATLAB图像处理与GUI界面开发实践:高级图像处理技术与应用
资源的描述略
矢量边界,行政区域边界,精确到乡镇街道,可直接导入arcgis使用
网站精美前端设计,使用jQuery+CSS开发,源码适用于参考学习使用。希望对你学习和开发有所帮助
Kotlin语言基础入门:Kotlin简介 在2019年Google I/O大会上,Google 宣布今后将优先采用 Kotlin 进行 Android 开发。 一,简介 Kotlin 是一种富有表现力且简洁的编程语言,不仅可以减少常见代码错误,还可以轻松集成到现有应用中。 Google 列举的 Kotlin 的优势: • 富有表现力且简洁:可以使用更少的代码实现更多的功能。表达自己的想法,少编写样板代码。 • 更安全的代码:Kotlin 有许多语言功能,可帮助你避免null指针异常等常见编程错误。 • 可互操作:可以在 Kotlin 代码中调用 Java 代码,或者在 Java 代码中调用 Kotlin 代码。Kotlin 可完全与 Java 编程语言互操作。 • 结构化并发:Kotlin 协程让异步代码像阻塞代码一样易于使用。协程可大幅简化后台任务管理。 更重要的是,Jetpack Compose 仅支持 Kotlin,而不再支持 Java。 Google 提到多平台项目可使用 Kotlin 来开发。
本项目是基于JSP与JavaEE技术栈开发的传统文化学习系统,旨在通过数字化手段实现中华优秀传统文化的传承与创新。系统采用经典的MVC架构模式,后端通过Servlet处理业务逻辑,JSP动态生成交互页面,结合MySQL数据库存储用户信息、课程内容及学习记录等核心数据3。主要功能模块涵盖用户注册登录、课程分类学习(如诗词鉴赏、书法入门)、多媒体资源管理(含视频讲解与电子书库)、在线测试评估及社区互动交流等,支持管理员对课程体系和用户权限的集中管理16。开发过程中整合了文件上传、缓存优化等实用技术,前端界面采用响应式设计,适配PC端与移动端浏览。该项目不仅为传统文化爱好者提供系统化学习路径,也为教育工作者搭建了资源共享与教学管理平台,助力文化自信的培育37。毕设项目源码常年开发定制更新,希望对需要的同学有帮助。
资源的描述略
冷链物流路径优化与调度模型研究 - 遗传算法求解及应用