C语言趣味题目
Wednesday, October 8th, 2008这些是我搜集到的觉得有点意思的C语言题目,都不复杂,却有些难度,有兴趣的人可以一试(有些可能也不能说难,但可能不容易想答案):
- a+++++b是合法的C表达式吗?为什么?
- 如何用宏定义实现SWAP(a,b),交换a和b的值。
- 写一个C程序,不使用分号,实现输出Hello,World这个字符串。
- 想一个算法,判断一个链表是否成环?
这些是我搜集到的觉得有点意思的C语言题目,都不复杂,却有些难度,有兴趣的人可以一试(有些可能也不能说难,但可能不容易想答案):
下雨了,秋老虎似乎可以过去了。。
今天用到一个功能,需要从Mysql表中随机选择一些行出来 ,原以为会有点复杂,没想到看了Google,只要在select后面加上order by rand()就可以了, 再加上Limit就可以限制选择的行数了,真是没想到Sql这么强大。
Select * from tbl_aaa ORDER by rand() LIMIT 10
上面的句子就是随机选择10行出来了。哦,方便!
这个还是很强的,DHT-Distributed Hash Tables:分布式hash表(我怎么感觉通常带Distributed的都比较牛)。DHT是一种算法,把通常意义的hash表分布到了一个网络上,可能的应用有很多,比如把Web Proxy的负载分布到多个Proxy服务器上,又比如把一些集中的运算分布到网络上去,近些年最热门的应用则是在P2P网络中,比如在bittorrent/emule中,都集成了某种形式的DHT,在utorrent这个客户端中,就叫做DHT,而在eMule中被称为KAD。
这篇文章使用Python代码简单的介绍了DHT的基本原理和核心算法,通俗易懂,不过老实说,一些细节我还没有弄懂,比如第一次加入网络的节点是否只能依靠手动建立路由等。也许应该去参考一下eMule的源码(它是开源的),不过现在暂时没有时间(懒的借口)。这个项目则有对KAD更严谨的Python实现。
注:早期的DHT算法很多都是大学里的研究成果,因此在Google上有很多相关论文可以检索到。
去了书店,看了《Java并发编程实践》,确实是一本不错的书,翻译的也不错。在书店只看了前三章 ,应该是整本书的基础。介绍Java中同步/互斥与共享的基本概念,同步/互斥其实是和语言不是很密切相关的,别的语言如C/C++都会遇到这个问题,但共享我却只在Java中看到,这个是Java平台(虚拟机)的实现约定,也就是多线程间共享变量时,一个线程写变量,另一个线程何时得到刷新的问题。以前从来没有想到Java会有这样的约定,挺奇怪的,应该是为了多CPU(或多核)的优化,幸而看了这本书。
后面的章节都没看,内容还很丰富,不过我向来认为这些技术类的书,应该先粗读一遍,理解概念,细节在用到时再看要好一些。基本上我觉得可以推荐一下这本书了,不错。
有好几天没有更新blog了,但生活还要继续,希望所有的人平平安安。
理解Java越多,就越发觉Java和C++的差异,这次是volatile。
共享
在C/C++中,volatile用于指示编译器对于这类变量的访问不要做优化,常用于一些硬件相关的读写中,在Java中,volatile的意思与C相似,但除了硬件相关外,还有一个更常见的使用场合,是用于多线程,在Java中,如果一个线程写变量,另一个线程读变量,读变量的线程可能不能“即时”获得被修改后的值,这是Java的一个实现约定,也是和JVM的实现相关,所以为了保证这种多线程共享变量的可见性,需要使用volatile。
互斥
要注意到,Java中synchronized也可以达到和volatile相同的作用,即使用synchronized做互斥时自动也达到了共享的目的,但反过来volatile就不能代替synchronized来做互斥,比如说非原子类型的变量(long/double之类)即使加volatile做++操作,也不是线程安全的,这时是需要使用synchronized的。
这样看来,Java中volatile的使用场合应该不是很多(因为多线程情况下,需要共享又不要互斥的情况不多),常见的使用诸如线程的控制循环退出的变量,一些标志变量等等,这些地方不涉及互斥,所以可以用volatile来简化。
这个是Java中的一种异常,表示“被中断”,由于它是一个“checked exception”,所以在可能产生这种异常的场合下,你必须处理它。我一般看到的代码都是将之catch之后,什么也不做,为了确认这样的处理是否合适,找了一些资料来看,IBM Developerworks上的这篇文章做了比较详细的解释。
Java的线程都有一个interrupt标志,线程在运行时,如果别的线程想中断这个线程的运行(通过调用Thread.interrupt),会出现几种情况:
Thread.sleep()和Object.wait(),这时就会触发InterruptedException这个异常了,调用者必须处理一下这个异常,通常就是清理工作喽,或者继续往上抛,由调用者去决定后续的动作。更多的细节,据说在《Java Concurrency in Practice》书中有详细的讲解(Amazon上5星哦),有些想看看,记得在书店应该是有中文版的《Java并发编程实践》,周末去看一看。
Java里做互斥还是很有意思的,不象C/C++,互斥依赖于平台实现。Java中的互斥是语言天生就提供的,Java有一个synchronized关键字,用来代替一般意义的lock/unlock,这样的好处起码是保证了lock/unlock的成对使用,另外代码看起来也清楚一些。而且Java里也不需要创建和删除互斥量,每个对象(Object)都带有一个互斥量,一般用这个就够用了。synchronized有好几种用法:
synchronized方法
这个是最常见的,直接在方法前面加synchronized,效果相当于进入方法时会lock这个对象,退出时unlock,这种方法加锁简单可靠,比如
public synchronized void method1(){
//do something under lock
}
但这种方法显然可能造成互斥区域扩大的风险,因为函数里的所有语句都自动放在互斥区内部了,这有时会影响效率。
synchronized语句
为了减小互斥区,可以直接使用synchronized语句,象这样:
private Object mutex = new Object();
public void method2(){
synchronized(mutex)
{
//do something under lock
}
//do otherthing
}
这种方法使用上更接近于C/C++的用法了,只是用一个Object来做互斥量了。
静态synchronized方法
语法上,Java也是支持在static method上加synchronized的,不过这就相当于lock了整个Class的锁了(非static的synchronized锁是基于对象的),粒度更大,这种用法我还没用过,书上如是说的。
最后,不要和Java1.5新的semphore混淆,Java1.5所提供的Semphore相当于大部分操作系统提供的计数器信号量,一般不用于做互斥(因为不能嵌套)。
J2ME里面,直到MIDP2.0都没有Log的功能,本来不是大的问题,可以自己做一个嘛,不过在J2ME里面却不那么简单了,因为不知道把Log往哪里放,最常见的两个Log输出选项在J2ME都不具备(或者说不完全具备):
所以,基本的做法只能是记在内存里,需要时把这个字符串取出来显示,不过这真不是一个好的解决方法,很简单的问题,如果代码崩溃了,就看不到的了。比较理想的一个方式是蓝牙(直接在PC端看Log),不过比记文件条件更高啊,需要两端都有蓝牙,并且设备端支持JSR82(N73好像支持),但起码我的笔记本还没有蓝牙。
在用J2ME的低级UI时,所有的界面绘制要靠自己来实现,主要是在paint消息中来做,这里涉及几个接口:paint,repaint,serviceRepaints,callSerially,我搞了一段时间才把它们弄明白。
paint
paint相当于是个回调函数,被J2ME主线程(处理UI和事件)来调用,任何时候我们都不能自己直接调用paint,程序在paint里面按部就班的画出界面即可,paint会带一个Graphics参数,用这个graphics去作图就可以了。
现在的很多J2ME平台已经默认支持双缓冲,所以在paint执行时,实际上界面并没有画出,而是等到paint执行完成后,一次性刷新到屏幕上的,不过这是和平台相关的。
repaint
这个是通知系统,我们需要刷新界面了,但仅仅是个通知,repaint会立即返回(无阻塞),paint会在合适的时候被系统调用,所以paint和repaint不是一一对应的,有一个repaint并不意味着一定会触发一个paint,多个repaint可能会被合并到一个paint调用。
如果在paint里面调用repaint,那么就意味着这个paint结束后会被再次调用,这在有些需要连续不段的刷屏的程序中可能有用,不过显然不太合适,比如下面这种代码。
public void paint (Graphics g) {
repaint();
}
serviceRepaints
如果调用serviceRepaints,则会直接立刻重绘界面,阻塞式调用,直到paint完成。所以如果我们写出下面的paint接口:
public void paint (Graphics g) {
repaint();
serviceRepaints();
}
后果相当于递归调用,并很快造成内存耗尽(Out of memory)异常,因为serviceRepaints里面又会直接调用paint(),所以使用serviceRepaints一定要小心,可以在某些地方使用serviceRepaints,但绝不能在paint里面调用。
callSerially
这个接口可以将一个Runable的接口抛在系统队列尾部,处理完消息之后,执行这个Runable接口的run,这在有些使用paint绘制的程序中会用到,可以在paint完以后,通过callSerially触发run接口,表示界面已经画完,可以做别的事情了,又不会阻塞系统队列。
这是最近在看的一本软件工程方面的书:《Facts and Fallacies of Software Engineering》(国内的翻译好像是叫软件工程中的真相和谬误),Amazon上的评级是4.5星,2002年出版,不过我是最近才看到。
这本书介绍软件工程中常见的一些“事实”和“误解”(前者55条,后者10条),看了一下这些条目,应该算是经验总结,实践性很强,比较对我的胃口,有些条目给我印象深刻,列举几条如下:
但也不是每一条都很有新意,有些条目相对就比较陈旧一些(虽然是对的):
总的来说,这本书值得一看。