根网科技面试题

昨天中午通过面试,今天上午去面试,下面我来说下这个坑爹的公司的面试题吧

 1.比较两个double类型的数据的大小

double类型比较

 2.怎么在C#中调用C的API

[Dllimport("调用的DLL名称",EntryPoint="SetZero")]

public static extern bool SetZero(int OutNo,bool isOn);这个是通过P/Invok调用的方法。

 3.string和stringbuilder的区别

对于string,stringbuffer,stringbuilder这三个类,先总体来理解一下:
对String,你创建了一个String,你能通过set方法改变它的长度length吗?显然是不行的!

但 StringBuffer 可以!
StringBuilder 类被设计用作 StringBuffer 的一个简易替换,它比 StringBuffer 要快。

下面我们来详细理解下这三个类的区别:

 String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象,因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。这里尝试举个不是很恰当的例子:

view plaincopy to clipboardprint?
String S1 = “abc”;   
For(int I = 0 ; I < 10000 ; I ++) // For 模拟程序的多次调用    
{   
S1 + = “def”;   
S1 = “abc”;   
} 
如果是这样的话,到这个 for 循环完毕后,如果内存中的对象没有被 GC 清理掉的话,内存中一共有 2 万多个了,惊人的数目。而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer ,特别是字符串对象经常改变的情况下
基本来说都是在性能上都是 StringBuilder > StringBuffer > String

 4.C中的指针在C#中怎么实现

   C#语言中通过unsafe关键字声明不安全上下文代码来定义和使用指针

  5.多线程中的同步怎么实现

多线程同步方法汇总
我们在编程的时候,有时会使用多线程来解决问题,比如你的程序需要在后台处理一大堆数据,但还要使用户界面处于可操作状态;或者你的程序需要访问一些外部资源如数据库或网络文件等。这些情况你都可以创建一个子线程去处理,然而,多线程不可避免地会带来一个问题,就是线程同步的问题。如果这个问题处理不好,我们就会得到一些非预期的结果。
  在网上也看过一些关于线程同步的文章,其实线程同步有好几种方法,下面我就简单的做一下归纳。
  一、volatile关键字
  volatile是最简单的一种同步方法,当然简单是要付出代价的。它只能在变量一级做同步,volatile的含义就是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我。(【转自www.bitsCN.com 】)因此,当多线程同时访问该变量时,都将直接操作主存,从本质上做到了变量共享。
  能够被标识为volatile的必须是以下几种类型:(摘自MSDN)
Any reference type.
Any pointer type (in an unsafe context).
The types sbyte, byte, short, ushort, int, uint, char, float, bool.
An enum type with an enum base type of byte, sbyte, short, ushort, int, or uint.
  如:

 Code 
public class A
{
private volatile int _i;
public int I
{
get { return _i; }
set { _i = value; }
}
}

  但volatile并不能实现真正的同步,因为它的操作级别只停留在变量级别,而不是原子级别。如果是在单处理器系统中,是没有任何问题的,变量在主存中没有机会被其他人修改,因为只有一个处理器,这就叫作processor Self-Consistency。但在多处理器系统中,可能就会有问题。 每个处理器都有自己的data cach,而且被更新的数据也不一定会立即写回到主存。所以可能会造成不同步,但这种情况很难发生,因为cach的读写速度相当快,flush的频率也相当高,只有在压力测试的时候才有可能发生,而且几率非常非常小。
  二、lock关键字
  lock是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。它可以保证当一个线程在关键代码段的时候,另一个线程不会进来,它只能等待,等到那个线程对象被释放,也就是说线程出了临界区。用法:

 Code 
public void Function() 
{
object lockThis = new object (); 
lock (lockThis)
{
// Access thread-sensitive resources. 
}
}

 
  lock的参数必须是基于引用类型的对象,不要是基本类型像bool,int什么的,这样根本不能同步,原因是lock的参数要求是对象,如果传入int,势必要发生装箱操作,这样每次lock的都将是一个新的不同的对象。最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。特别是不要使用字符串作为lock的参数,因为字符串被CLR“暂留”,就是说整个应用程序中给定的字符串都只有一个实例,因此更容易造成死锁现象。建议使用不被“暂留”的私有或受保护成员作为参数。其实某些类已经提供了专门用于被锁的成员,比如Array类型提供SyncRoot,许多其它集合类型也都提供了SyncRoot。
  所以,使用lock应该注意以下几点: 
  、如果一个类的实例是public的,最好不要lock(this)。因为使用你的类的人也许不知道你用了lock,如果他new了一个实例,并且对这个实例上锁,就很容易造成死锁。
  、如果MyType是public的,不要lock(typeof(MyType))
  、永远也不要lock一个字符串
  三、System.Threading.Interlocked
  对于整数数据类型的简单操作,可以用 Interlocked 类的成员来实现线程同步,存在于System.Threading命名空间。Interlocked类有以下方法:Increment , Decrement , Exchange 和CompareExchange 。使用Increment 和Decrement 可以保证对一个整数的加减为一个原子操作。Exchange 方法自动交换指定变量的值。CompareExchange 方法组合了两个操作:比较两个值以及根据比较的结果将第三个值存储在其中一个变量中。比较和交换操作也是按原子操作执行的。如:

 Code 
int i = 0 ;
System.Threading.Interlocked.Increment( ref i);
Console.WriteLine(i);
System.Threading.Interlocked.Decrement( ref i);
Console.WriteLine(i);
System.Threading.Interlocked.Exchange( ref i, 100 );
Console.WriteLine(i);
System.Threading.Interlocked.CompareExchange( ref i, 10 , 100 );

Output:

  四、Monitor
  Monitor类提供了与lock类似的功能,不过与lock不同的是,它能更好的控制同步块,当调用了Monitor的Enter(Object o)方法时,会获取o的独占权,直到调用Exit(Object o)方法时,才会释放对o的独占权,可以多次调用Enter(Object o)方法,只需要调用同样次数的Exit(Object o)方法即可,Monitor类同时提供了TryEnter(Object o,[int])的一个重载方法,该方法尝试获取o对象的独占权,当获取独占权失败时,将返回false。
  但使用 lock 通常比直接使用 Monitor 更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过 finally 中调用Exit来实现的。事实上,lock 就是用 Monitor 类来实现的。下面两段代码是等效的:

  Code 
lock (x)
{
DoSomething();
}
等效于

object obj = ( object )x;
System.Threading.Monitor.Enter(obj);
try 
{
DoSomething();
}
finally 
{
System.Threading.Monitor.Exit(obj);
}

 
关于用法,请参考下面的代码:

  Code 
private static object m_monitorObject = new object ();
[STAThread]
static void Main( string [] args)
{
Thread thread = new Thread( new ThreadStart(Do));
thread.Name = " Thread1 " ;
Thread thread2 = new Thread( new ThreadStart(Do));
thread2.Name = " Thread2 " ;
thread.Start();
thread2.Start();
thread.Join();
thread2.Join();
Console.Read();
}
static void Do()
{
if ( ! Monitor.TryEnter(m_monitorObject))
{
Console.WriteLine( " Can't visit Object " + Thread.CurrentThread.Name);
return ;
}
try 
{
Monitor.Enter(m_monitorObject);
Console.WriteLine( " Enter Monitor " + Thread.CurrentThread.Name);
Thread.Sleep( 5000 );
}
finally 
{
Monitor.Exit(m_monitorObject);
}
}

  当线程1获取了m_monitorObject对象独占权时,线程2尝试调用TryEnter(m_monitorObject),此时会由于无法获取独占权而返回false,输出信息如下:

  另外,Monitor还提供了三个静态方法Monitor.Pulse(Object o),Monitor.PulseAll(Object o)和Monitor.Wait(Object o ) ,用来实现一种唤醒机制的同步。关于这三个方法的用法,可以参考MSDN,这里就不详述了。
  五、Mutex
  在使用上,Mutex与上述的Monitor比较接近,不过Mutex不具备Wait,Pulse,PulseAll的功能,因此,我们不能使用Mutex实现类似的唤醒的功能。不过Mutex有一个比较大的特点,Mutex是跨进程的,因此我们可以在同一台机器甚至远程的机器上的多个进程上使用同一个互斥体。尽管Mutex也可以实现进程内的线程同步,而且功能也更强大,但这种情况下,还是推荐使用Monitor,因为Mutex类是win32封装的,所以它所需要的互操作转换更耗资源。
  六、ReaderWriterLock
  在考虑资源访问的时候,惯性上我们会对资源实施lock机制,但是在某些情况下,我们仅仅需要读取资源的数据,而不是修改资源的数据,在这种情况下获取资源的独占权无疑会影响运行效率,因此.Net提供了一种机制,使用ReaderWriterLock进行资源访问时,如果在某一时刻资源并没有获取写的独占权,那么可以获得多个读的访问权,单个写入的独占权,如果某一时刻已经获取了写入的独占权,那么其它读取的访问权必须进行等待,参考以下代码:

Code
private static ReaderWriterLock m_readerWriterLock = new ReaderWriterLock();
private static int m_int = 0;
[STAThread]
static void Main(string[] args)
{
Thread readThread = new Thread(new ThreadStart(Read));
readThread.Name = "ReadThread1";
Thread readThread2 = new Thread(new ThreadStart(Read));
readThread2.Name = "ReadThread2";
Thread writeThread = new Thread(new ThreadStart(Writer));
writeThread.Name = "WriterThread";
readThread.Start();
readThread2.Start();
writeThread.Start();
readThread.Join();
readThread2.Join();
writeThread.Join();

Console.ReadLine(); 
}
private static void Read()
{
while (true)
{
Console.WriteLine("ThreadName " + Thread.CurrentThread.Name + " AcquireReaderLock");
m_readerWriterLock.AcquireReaderLock(10000);
Console.WriteLine(String.Format("ThreadName : {0} m_int : {1}", Thread.CurrentThread.Name, m_int));
m_readerWriterLock.ReleaseReaderLock();
}
}

private static void Writer()
{
while (true)
{
Console.WriteLine("ThreadName " + Thread.CurrentThread.Name + " AcquireWriterLock");
m_readerWriterLock.AcquireWriterLock(1000);
Interlocked.Increment(ref m_int);
Thread.Sleep(5000);
m_readerWriterLock.ReleaseWriterLock();
Console.WriteLine("ThreadName " + Thread.CurrentThread.Name + " ReleaseWriterLock");
}
}

在程序中,我们启动两个线程获取m_int的读取访问权,使用一个线程获取m_int的写入独占权,执行代码后,输出如下:

可以看到,当WriterThread获取到写入独占权后,任何其它读取的线程都必须等待,直到WriterThread释放掉写入独占权后,才能获取到数据的访问权,应该注意的是,上述打印信息很明显显示出,可以多个线程同时获取数据的读取权,这从ReadThread1和ReadThread2的信息交互输出可以看出。
  七、SynchronizationAttribute
  当我们确定某个类的实例在同一时刻只能被一个线程访问时,我们可以直接将类标识成Synchronization的,这样,CLR会自动对这个类实施同步机制,实际上,这里面涉及到同步域的概念,当类按如下设计时,我们可以确保类的实例无法被多个线程同时访问
  1). 在类的声明中,添加System.Runtime.Remoting.Contexts.SynchronizationAttribute属性。
    2). 继承至System.ContextBoundObject
    需要注意的是,要实现上述机制,类必须继承至System.ContextBoundObject,换句话说,类必须是上下文绑定的。
    一个示范类代码如下:
Code
[System.Runtime.Remoting.Contexts.Synchronization]
public class SynchronizedClass : System.ContextBoundObject
{

}
 
  八、MethodImplAttribute
  如果临界区是跨越整个方法的,也就是说,整个方法内部的代码都需要上锁的话,使用MethodImplAttribute属性会更简单一些。这样就不用在方法内部加锁了,只需要在方法上面加上 [MethodImpl(MethodImplOptions.Synchronized)] 就可以了,MehthodImpl和MethodImplOptions都在命名空间System.Runtime.CompilerServices 里面。但要注意这个属性会使整个方法加锁,直到方法返回,才释放锁。因此,使用上不太灵活。如果要提前释放锁,则应该使用Monitor或lock。我们来看一个例子:

  Code 
[MethodImpl(MethodImplOptions.Synchronized)]
public void DoSomeWorkSync()
{
Console.WriteLine( " DoSomeWorkSync() -- Lock held by Thread " + 
Thread.CurrentThread.GetHashCode());
Thread.Sleep( 1000 );
Console.WriteLine( " DoSomeWorkSync() -- Lock released by Thread " + 
Thread.CurrentThread.GetHashCode());
}
public void DoSomeWorkNoSync()
{
Console.WriteLine( " DoSomeWorkNoSync() -- Entered Thread is " + 
Thread.CurrentThread.GetHashCode());
Thread.Sleep( 1000 );
Console.WriteLine( " DoSomeWorkNoSync() -- Leaving Thread is " + 
Thread.CurrentThread.GetHashCode());
}

[STAThread]
static void Main( string [] args)
{
MethodImplAttr testObj = new MethodImplAttr();
Thread t1 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
Thread t2 = new Thread( new ThreadStart(testObj.DoSomeWorkNoSync));
t1.Start();
t2.Start();
Thread t3 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
Thread t4 = new Thread( new ThreadStart(testObj.DoSomeWorkSync));
t3.Start();
t4.Start();

Console.ReadLine(); 
}

这里,我们有两个方法,我们可以对比一下,一个是加了属性MethodImpl的DoSomeWorkSync(),一个是没加的DoSomeWorkNoSync()。在方法中Sleep(1000)是为了在第一个线程还在方法中时,第二个线程能够有足够的时间进来。对每个方法分别起了两个线程,我们先来看一下结果:

可以看出,对于线程1和2,也就是调用没有加属性的方法的线程,当线程2进入方法后,还没有离开,线程1有进来了,这就是说,方法没有同步。我们再来看看线程3和4,当线程3进来后,方法被锁,直到线程3释放了锁以后,线程4才进来。
  九、同步事件和等待句柄
  用lock和Monitor可以很好地起到线程同步的作用,但它们无法实现线程之间传递事件。如果要实现线程同步的同时,线程之间还要有交互,就要用到同步事件。同步事件是有两个状态(终止和非终止)的对象,它可以用来激活和挂起线程。
  同步事件有两种:AutoResetEvent和 ManualResetEvent。它们之间唯一不同的地方就是在激活线程之后,状态是否自动由终止变为非终止。AutoResetEvent自动变为非终止,就是说一个AutoResetEvent只能激活一个线程。而ManualResetEvent要等到它的Reset方法被调用,状态才变为非终止,在这之前,ManualResetEvent可以激活任意多个线程。
  可以调用WaitOne、WaitAny或WaitAll来使线程等待事件。它们之间的区别可以查看MSDN。当调用事件的 Set方法时,事件将变为终止状态,等待的线程被唤醒。
  来看一个例子,这个例子是MSDN上的。因为事件只用于一个线程的激活,所以使用 AutoResetEvent 或 ManualResetEvent 类都可以。

Code
static AutoResetEvent autoEvent;

static void DoWork()
{
Console.WriteLine(" worker thread started, now waiting on event");
autoEvent.WaitOne();
Console.WriteLine(" worker thread reactivated, now exiting");
}

[STAThread]
static void Main(string[] args)
{
autoEvent = new AutoResetEvent(false);

Console.WriteLine("main thread starting worker thread");
Thread t = new Thread(new ThreadStart(DoWork));
t.Start();

Console.WriteLine("main thrad sleeping for 1 second");
Thread.Sleep(1000);

Console.WriteLine("main thread signaling worker thread");
autoEvent.Set();

Console.ReadLine(); 
}

我们先来看一下输出:

在主函数中,首先创建一个AutoResetEvent的实例,参数false表示初始状态为非终止,如果是true的话,初始状态则为终止。然后创建并启动一个子线程,在子线程中,通过调用AutoResetEvent的WaitOne方法,使子线程等待指定事件的发生。然后主线程等待一秒后,调用AutoResetEvent的Set方法,使状态由非终止变为终止,重新激活子线程。

  6.winform与WPF区别

MFC WPF WINFORM对比
 1 对比MFC ,Winform ,WPF
 2 对比MFC ,Winform ,WPF
 3 MFC 生成本机代码,自然是很快。可是,消息循环,减缓了界面显示速度。
 4 winform 封装了 win32 的api,多次进行P/invoke 操作 (大部分使用p/invoke操作封装),速度慢。
 5 wpf是一种新的模型,不再使用win32 模型,自己新建模型,使用dx 作为新的显示技术,直接访问驱动程序,加快了运行速度,可是,这种模型,需要支持dx 9 的显卡,硬件要求高(你还能找到现代机器不支持dx9 的吗?)
 6 开发效率上,MFC <WPF <winform
 7 尽管MFC开发界面执行效率高但是开发效率低,作为现在的项目开发来说时间跟开发效率往往能决定项目的成败,所以除非有特别的需求,否则都回尽量避免用mfc来做开发,MFC只是一个弱封装器。
 8 开发成本,MFC〉wpf〉winform
 9 用MFC开发成本太高,对开发者能力要求更高,作为客服当然希望开发的费用越少越好,开发者当然希望钱赚得越多越好,这样一比,这也是MFC没落的一个很大的原因。
10 界面执行效率上,MFC==WPF〉winform
11 随着计算机硬件的性能提高,多核cpu的普及,它们的差距会越来越小。
12 开发灵活性上:wpf〉MFC〉winform
13 美观上:Wpf〉winform〉MFC
14 这一项中MFC下要开发出一个华丽的ui极其困难,也许你可以说你可以用控件,但是商业开发控件是要收费的!!Wpf很容易就可以做出vista那样的ui特效。mfc要写出这种效果不知要写到何年何月。
15 这样一来MFC存在的价值就更低了。效率和美观不如Wpf,开发效率又不如winform,预计不出10年,随着vista取代xp,mfc将会退出历史舞台。
16 内存使用上:wpf〉winform〉MFC
17 随着计算机硬件的性能提高wpf这个缺点会被忽略。
18 使用范围:wpf〉MFC==winform
19 有以上可知:WPF 大有取代winform 和MFC之势,从未来net的发展来看,MFC以后只会变成一种经典,作为一种技术来供开发者学习,winform和WPF两者会并存发展,但最终都会被WPF取代,最终实现桌面应用程序和浏览器应用程序的统一

 7.谈一谈你对反射的理解

反射
 1 反射是一种晚绑定,它可以被开发者用来设计出更具灵活性的代码,而代价则是花费更多的系统资源开销使得应用程序可以在运行时获取一些未知信息。
 2 说白了,在编写代码的时候,开发者可能还不知道或不能确定一些对象的信息,于是把决定权交给代码本身,将来在需要的时候由代码自己去获取和判断这些信息并作出相应的反应。这样的方式固然可以使代码更加灵活,但在想要使用反射的时候,必须先考虑好在性能与灵活之间的一个权衡,不能盲目地因为C#提供了反射机制就一个劲儿地用,我们应该发扬John Carmack“榨干PC机3D图像性能”的精神,而不要因为硬件越来越便宜就让机器背负沉重的包袱去做一些意义不大的事情。
 3 我们还是继续本系列一贯的风格,用生活中的例子来讲解什么是反射以及反射的基本使用方式。
 4 我先问大家一个问题:“《集结号》中谷子地穿的南朝鲜军服从哪儿来?”显然不是临时找裁缝赶制的,肯定是从俘虏身上扒下来的。好,那我们就来补充一点儿《集结号》的镜头看看什么是反射。
 5 一天傍晚,一名又饥又渴的南朝鲜掉队士兵在乡间小路上蹒跚地走着,他甚至都快扛不动身上的枪了,也不知道队伍在哪里,只好认准一个方向努力寻找。“不许动!”从路边草丛中跳出的两名埋伏着的中国士兵喝道,这名南朝鲜士兵本来就精疲力尽,哪受得了这番惊吓,当时就一屁股坐在地上了。中国士兵把他押回营部并向上级报告,他们在附近巡逻的时候意外俘获一名南朝鲜掉队士兵,听候处置。
 6 这件事情被上报到王团长那里,王团长正拿着放大镜跟几名军官在地图前研究作战方案,听到这件事,不紧不慢地跟他身边的高连长说道:“高诚啊,你去看看,没啥大问题就送到后方战俘营去。”“是!”高连长接到命令便转身出去了。
 7 高连长来到一件狭小的房间,那名南朝鲜士兵正半躺在地上,手里拿着中国士兵给他的馒头和水,一口一口地嚼着,看到高连长进来了,眼睛里流露出紧张和恐惧的神情。高连长简单介绍了中国方面善待俘虏的政策,便开始审问他——注意,反射开始了!
 8 “哪儿的?”“联合国军李承晚系部队3团2排1班。”
 9 “叫什么名字?”“思密达。”
10 “这次执行什么任务?”“潜入贵军阵地并指引炮兵进行射击。”
11 ……
12 一番软硬兼施之后,高连长掌握了这个小兵所有的信息,整理好材料去跟王团长汇报去了,而这个小兵被带到一个小屋子里继续啃馒头去了,身上的行头也被换了下来,换上了专门为战俘准备的棉衣。
13 高连长一手拿着笔录材料,一手拎着南朝鲜士兵的全套行头去见了王团长,汇报之后王团长会心地笑了:“小高啊,不错,你父亲当初把你交给我,我就叫他放心,你是块好料子,怎么样,我没说错吧,呵呵,不过你也莫要骄傲,我们不能轻敌,这样,你带着这身行头去找谷子地,叫他换上之后连夜潜入敌方阵地,敌人不是想拿炮弹砸我们吗,那我们就用他们的方式,先发制人,让老谷指引我军炮火明日凌晨发起总攻。”
14 谷子地换上了这身洋行头,揣了半条烟就出发了,他潜入敌方阵地后冷静地掏出敌人的望远镜进行了周密地观察,并通过无线电给后方友军传递射击参数,成功地引导我军取得了这次重大胜利。
15 上面的故事情节描述得有点儿多了,赶紧来看代码吧,首先,我们得有个倒霉的南朝鲜士兵做引子:
16   1: namespace UN
17    2: {
18  
19    3:     internal class SouthKoreaArmy
20  
21    4:     {
22  
23    5:         public string Name { get; set; }
24  
25    6:  
26  
27    7:         public string Search(string destination)
28  
29    8:         {
30  
31    9:             return "OK, fire!";
32  
33   10:         }
34  
35   11:     }
36  
37   12: }
38 然后,我们用代码来实现谷子地所完成的行动:
39  
40    1: internal class ChineseArmy
41  
42    2: {
43  
44    3:     public static void Action(object soldier)
45  
46    4:     {
47  
48    5:         Type type = soldier.GetType();
49  
50    6:         object ziDiGu = Activator.CreateInstance(type);
51  
52    7:         MethodInfo methodInfo = type.GetMethod("Search");
53  
54    8:  
55  
56    9:         Console.WriteLine(methodInfo.Invoke(ziDiGu, new object[] { "Position" }));
57  
58   10:     }
59  
60   11: }
61  
62 这段代码很好理解,第5行我们审问了被俘的南朝鲜士兵并得到了他提供的信息和服装,然后让老谷用他的服装化装成南朝鲜士兵。第7行,小样,你不是想侦查我吗,好,咱就来个以牙还牙,用你的方式来搞定你!接下来,谷子地潜入地方阵地并引导了我军发起总攻。
63 这里展示了反射的一些基本应用,例如获取类型信息、利用获取的类型动态生成对象,并动态调用其方法。当然,反射机制能做的事情不仅仅是这几样,不过目标都一样,由程序自己去获取信息、做出反应。在不用担心性能开销的情况下,反射可以使你的程序更加灵活强大!

 8.谈一谈keyPress和keydown哪个事件先触发

三个事件比较
 1 (一)键事件按下列顺序发生: 
 2 KeyDown
 3 KeyPress
 4 KeyUp
 5  (二)KeyDown触发后,不一定触发KeyUp,当KeyDown 按下后,拖动鼠标,那么将不会触发KeyUp事件。
 6  (三)定义
 7 KeyDown:在控件有焦点的情况下按下键时发生。
 8 KeyPress:在控件有焦点的情况下按下键时发生。(下面会说和KeyDown 的区别)
 9 KeyUp:在控件有焦点的情况下释放键时发生。
10  (四)KeyPress 和KeyDown 、KeyPress之间的区别
11           1.KeyPress主要用来捕获数字(注意:包括Shift+数字的符号)、字母(注意:包括大小写)、小键盘等除了F1-12、SHIFT、Alt、Ctrl、Insert、Home、PgUp、Delete、End、PgDn、ScrollLock、Pause、NumLock、{菜单键}、{开始键}和方向键外的ANSI字符
12              KeyDown 和KeyUp 通常可以捕获键盘除了PrScrn所有按键(这里不讨论特殊键盘的特殊键)
13            2.KeyPress 只能捕获单个字符
14                KeyDown 和KeyUp 可以捕获组合键。
15           3.KeyPress 可以捕获单个字符的大小写
16           4.KeyDown和KeyUp 对于单个字符捕获的KeyValue 都是一个值,也就是不能判断单个字符的大小写。
17           5.KeyPress 不区分小键盘和主键盘的数字字符。
18                KeyDown 和KeyUp 区分小键盘和主键盘的数字字符。
19           6.其中PrScrn 按键KeyPress、KeyDown和KeyUp 都不能捕获
原文地址:https://www.cnblogs.com/lipengjiushiwo/p/2580081.html