最近空间到期,打算换一家空间服务商。由于搬家工程比较浩大,所以这个新空间先暂时这样吧。
无意义的纠结:关于分类目录和标签
经验告诉我,分类绝对是一门大学问。如何把一批东西分门别类的规整好,需要的不仅仅是耐心,而且是智慧。但是,面对我的博客文章分类问题,我发现我就陷入了两难的境地:一方面,把文章分好类,归到相应的目录去,是一件非常好的习惯,但是另一方面,我常常为每一篇文章该归为哪一类而纠结。例如我曾经写的《桌面环境换成fvwm》一文。写完后,我就开始郁闷了,它到底属于哪一类呢?明显不属于算法方面的,肯定不能在ACM下面,也不是TeX方面的……如此排除之后,就只剩TIPS和Linux两类了,但是TIPS类本身是一个很模糊的概念,什么样的博文算是TIPS呢?连我自己也没准,事实上我现在连当初创建这个类别的目的都忘了。那就只剩下Linux了,可是这个类太大了!我发的所有博文几乎都可以归为这个类,那么这个类有和没有都差不多。怎么办呢?设立子目录吧。我在Linux下面设立了fvwm子目录来放置这篇文章。但是过了一段时间,我就发现问题又来了,我又写了一篇《解决本地apache2无法确定ServerName情况》的文章,当时我是觉得这篇文章应该归为Linux类,毕竟和Linux有关嘛。但是后来一想,不对,虽然和Linux有关,但直接归为Linux类肯定是不正确的,还得再细化一下,比如Linux->网络->服务器类?不不不!这样太麻烦了,如果这样的话,几乎每篇文章我都要添加新的目录,而且随着时间的推移,目录层次会越来越深,这太不现实了。而且还存在这样一个问题,wordpress中,一篇文章是既可以属于子类也可以属于父类的——这对我来说就太荒谬了。怎么办呢?
想了半天,我突然觉得,如果一篇文章就增加一个分类的话,那么分类和标签似乎就没有什么不同了——或许是我自己误解了分类的意思,所以一直把分类当作标签使用。OK!想通了这一点,我决定这么做:以后所有文章都不分类了,除了那些明显的可以归为一类的文章,例如我的USACO解题报告(天知道我什么时候才会继续写)。文章将更多的使用标签来分类。
pkg-config小探,顺便吐槽软件版本号
今天无聊研究起了autotools。研究了半天也没有研究透彻,反正我是觉得这套工具很麻烦,好在平时就算写大的项目我也会用anjuta这样的工具,让它帮我去操心configure脚本之类的问题去。不过今天倒是对pkg-config的用法有了点认识。
简单点说,pkg-config就是做了一个文字替换,例如:
pkg-config --cflags gtk+-3.0
就会输出编译需要gtk+-3.0支持的项目的cflags。pkg-config需要读取一些*.pc文件,这些文件大多都在/usr/lib/pkgconfig之类的地方。gtk+-3.0的pc文件内容如下:
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
targets=x11gtk_binary_version=3.0.0
gtk_host=i686-pc-linux-gnuName: GTK+
Description: GTK+ Graphical UI Library
Version: 3.0.12
Requires: gdk-3.0 atk cairo cairo-gobject gdk-pixbuf-2.0 gio-2.0 pangoft2
Libs: -L${libdir} -lgtk-3
Cflags: -I${includedir}/gtk-3.0 -DGSEAL_ENABLE
可以看到里面记录了各种文件的安装位置以及这个库本身的依赖,还有版本号什么的。当调用pkg-config --cflags gtk+-3.0的时候, 把Cflags项和它依赖的项目的Cflags项都输出就好了。看起来挺方便的,不过让我纠结了半天的就是这个软件版本号了。说实话我一直认为整个软件业都需要一个统一的版本号命名规范,可惜这只是个梦想……我见过的软件版本号命名规范多如牛毛,例如linux内核曾经就这么命名:2.6.XX.X-X的命名方法,而且还不全都是数字……虽然只要查查就知道每个字段的意思,但是还是太繁琐了,连Linus自己都说这样的版本号意义不大。至于其他的一些小软件,如果版本号只是X.X形式的话还好办,再复杂一点那就只有作者自己知道什么意思了。最开始让我感兴趣的是pkg-config是如何确定版本号的,比如我需要适用一个名叫xx库,版本号需要大于2.3.4,那么我可以用pkg-config --cflags "xx > 2.3.4"这样,如果版本不合要求,那么就会输出警告信息。但是对于不同的版本号命名规范,pkg-config是怎么识别的呢?我实验了一下,发现pkg-config用了一个极其简单的办法——按字段比较。靠前的字段优先级大于后面的。如果字段内部不是数字,就按照ascii码来比较,例如a>A。在pkg-config中,版本号似乎可以无限延伸,例如1.1.1.1.1.1.1.1都可以,但是只比较给出的部分。例如pkg-config --cflags "xx > 1.1.1.1.1.1.1",如果xx的真实版本号只有1.1.2这三部分的话,pkg-config就只比较前三部分,后面的作废。当然如果前面的比较没有得出结果的话,后面的字段还是起作用的。例如若xx版本号为1.1.1,那么执行pkg-config --cflags "xx > 1.1.1.1.1.1.1"就不会通过,因为1.1.1 < 1.1.1.1.1.1.1。我还实验了如果版本号为汉字的话会怎样,例如若xx的版本号为“你.好”会怎样,结果却有点奇怪,具体怎么回事可能只有看了pkg-config的代码才能明白,不过应该是脱不开那些简单的识别和比较了。
吐槽:GTK
警告:
本文是心智尚不健全的博主的SB吐槽,其内容不具有任何参考价值,并且会给其他人带来不良的心理影响,未成年请不要浏览,成年人请在老年人的陪伴下共同浏览。
开始吐槽
人家都说,牢骚太盛防断肠。可是我还是要对最近几天用到的图形界面开发包吐槽一下。其实也没有用到多少开发包,就是Qt和Gtk两个,不过Qt用的太少,除了发现其形式过于自由外并没有多少感觉,我的郁闷主要是针对Gtk的。
Gtk工具包之间的各种不“兼容”
前几天我还称赞了Gtk在C下面构建的面向对象机制,但是今天我不得不批评它一下了。Gtk3是目前最新版本的开发包,使用的各种特性也是最新的,但是似乎在把原有的工具向新的开发包迁移的时候出了些问题吧,导致其开发工具之间“不兼容”或者说“不一致”。例如Glade这个工具,今天为了测试新的Gtk传说中的CSS布局,我首先用Glade构造了一个简单的窗口,里面只有几个按钮和一张图片。图片是放在一个4×4的GtkTable上的。但是等我编译完执行的时候,总是出现警告信息:
(test_main:7777): Gtk-WARNING **: Unknown property: GtkGrid.n-rows
(test_main:7777): Gtk-WARNING **: Unknown property: GtkGrid.n-colums
后来一看Glade生成的代码才发现,我使用的GtkTable实际上是一个GtkGrid,Glade的“调色板”(英文是Pallete,就是调色板,但其实就是VirtualBasic里面的控件箱)中根本没有GtkTable这个Widget,只有GtkGrid。当然,如果它真的就是一个GtkGrid就好了……这个GtkGrid……根本就是GtkGrid和GtkTable的杂交合体……Glade生成的代码中,除了它的类型叫GtkGrid外,其一半属性来自于GtkGrid,还有一半来自于GtkTable……像上面的n-rows和n-colums其实是GtkTable的属性,而它还有两个height和width属性却是GtkGrid的属性。所以,无论这个控件被当作GtkTable还是被当作GtkGrid,都是不正确的,我只好手动的把Glade生成的代码全部改过来。
Gtk的搞笑文档
之所以说它搞笑是因为它已经让我无奈了。举这么一个例子吧,如果我从来没有用过标准库里面的printf函数,现在我要用,怎么办呢?很自然的,我会使用man,在bash中调用man 3 printf。结果是这样的:
PRINTF(3) Linux Programmer’s Manual PRINTF(3)
NAME
printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf,
vsnprintf – formatted output conversionSYNOPSIS
#include <stdio.h>int printf(const char *format, …);
int fprintf(FILE *stream, const char *format, …);
int sprintf(char *str, const char *format, …);
int snprintf(char *str, size_t size, const char *format, …);
对于我,看到那行#include <stdio.h>
就明白了,如果我要调用printf函数,我必须先在代码中直接或者间接的包含stdio.h头文件。可是Gtk呢?Gtk的最标准的文档除了官方网站上的手册外,就是Devhelp里面的各种文档了,当然实际上这两者内容是完全一样的。但是看看Gtk的文档,我发现,对于每种类型或者说每种Widget,它的说明倒是挺全的,但是当我希望调用它的函数的时候,我就立刻SB了,头文件在哪呢?在哪呢?在哪呢?编译参数在哪呢?在哪呢?在哪呢?对于Gtk库来说还好一点,因为文档在Overview->Getting Started with GTK+一节和Overview->Compiling GTK+ Applications一节里很不正式的提到了应该包含<gtk/gtk.h>头文件,以及它的编译参数。并且说所有调用libgtk+库的程序,都只用包含这一个头文件就可以了(所以其他文档页再也不提这个事了)。但是对于其他的一些库,比如Gnome最新的dconf库,我翻遍了它的文档,没有找到任何地方提到其API的包含头文件,也就是说为了调用dconf库,我有了最权威的文档还不够,我还得谷歌这文档怎么用……当然,不排除文档其实在某个角落里说过这个问题,而我最近又比较SB以至于发现不了……
从SB中恢复过来的思考
好了,不再在这个问题上纠缠了。其实我对于Gtk还是很喜欢的,Gtk库的设计还是很好的(Gtk不是Glade),毕竟现在刚刚出来不久不够完善。其很多设计思想很有意思,例如Gtk3开始采用GtkStyleContext进行渲染,虽然没有做到极致,但是已经明显的体现出把内容与样式抽离的设计思想。这一思想也一直是前端设计的精华所在。相信Gtk能够沿着这条路越走越辉煌。而关于Glade,我个人觉得这个东西在将来会被彻底取代,Glade的核心思路就是通过图形化的设计界面免去直接用代码累界面的痛苦。尽管初衷是好的,但是Glade本身,抛开它的各种Bug不谈,我认为它是一种介于WYSIWYG和WYTIWYG之间的工具,在Glade中设计的界面和最终应用程序生成的界面差异实在太大了,可能是我比较SB的缘故,我总是被它引导,去用所见即所得的方式设计界面,又时时要用所思即所得的方式来审视界面的设计是否正确。打个比方,现实生活中处理电子文档,要么用word这样所见即所得的工具,要么用tex这样所思即所得的工具,而我正在用一种word和tex二者的混合体,这种工具使用word的方式编辑,再生成tex的代码,再由tex编译成ps,所以我既要用鼠标拖动滑块来控制段间距、段落缩进和图像的位置,又要时时考虑这么做生成的tex代码是编译过来是不是我要的结果……所以,我是期望能够出现一种要么纯所见即所得的Gtk界面设计器,要么出现一种纯所思即所得的界面设计器,不要再搞二者的混合啦。
呃……说了不再SB了,但是我觉得上面写的还是很SB……唉,不管了,就这样吧。
gtk的信号和事件及其处理
我理解的Gtk信号与事件的区别和联系
之前对于Gtk的信号和事件之间的区别和联系不甚了解,导致写代码的时候糊里糊涂,有时候对有时候错。今天花了点事件仔细搞明白了二者之间的联系与区别,就记在这里。
首先需要弄清楚gtk和自己写的应用程序之间的关系。我觉得二者的关系是应用程序使用gtk来作为图形前端。从这个角度来说,应用程序本身和gtk应该被分开来看,不应混在一起。于是,信号是由gtk系统发出的,接收者是应用程序。具体的说是被执行动作的gtk对象(g_object)发送给应用程序的。发送这个动作,是通过调用相应的回调函数发生的。而回调函数则是通过g_signal_connect函数注册在gtk系统中的。例如:
g_signal_connect(G_OBJECT(an_object), "clicked", G_CALLBACK(a_function()), NULL)
这行代码就表示当an_object发送clicked信号时,调用a_function()函数。
至于an_object什么时候发出一个信号,则是由事件(event)来确定的。事件严格的讲是gdk层的概念。gtk系统是构建在x window上的,x window包揽了输入输出设备的监听工作。例如鼠标点击,当鼠标在屏幕上某个地方点击以后,最先得到消息的是内核,接下来就是x window,x window按照x协议将这一事件包装成一个包裹发送给gdk层,gdk层将这一事件解包、处理,再包装成一个GdkEvent,发送给gtk,gtk收到这个事件以后,才会发出一个clicked信号。一般而言,gtk对象(g_object)每收到一个事件就会发出一个信号。事件大约是说用户对图形界面进行了某种操作,而信号是图形界面收到事件以后发送给应用程序的,以期应用程序产生某种动作。
除了监听信号以外,应用程序也可以注册回调函数对事件进行监听。唯一让我稍感疑惑的是Gtk中对事件的监听也是用g_signal_connect函数来注册。这似乎有点混用信号和事件的倾向,虽然一般而言这没有问题,但是有时候似乎应该把二者分开来看才好理解。事件和信号有一个重要区别就是二者的回调函数是不同的。信号的回调函数一般有两个参数,一个是信号的发出者,一个是一个gpointer指针。当然据我实验,回调函数也可以不要参数或者只要一个参数。而事件的回调函数,一般会比信号的回调函数多一个GdkEvent类型的指针,用来指明发生的事件类型。我看了看GdkEvent的手册,似乎是一个联合,根据不同的事件种类,会被设置成不同的内容,例如与鼠标有关的事件会被设置为GdkEventButton类型,处理事件的时候会引用其内部的一些值。
信号和事件的处理
前面已经说到,事件和信号都是用g_signal_connect注册函数处理,但是还有一些小的问题很让人头疼。例如g_signal_conncet和g_signal_conncet_swapped的区别。使用g_signal_connect_swapped注册的函数,在接收参数时,会首先收到g_singal_connect_swapped的最后一个参数,然后才是第一个参数。但其实我觉得这个函数是可要可不要的,完全可以使用g_singal_connect来注册,然后在应用程序中颠倒参数的位置即可。
后记
上面的东西虽然早就了解了,但是有些东西还不够深入。在查的时候我也发现,很多网页上的解释很奇怪,例如g_singal_connect_swapped,被解释成由g_signal_connect_swapped注册的回调函数只接收一个参数……此外还要感慨一下gtk系统的面向对象能力……看了一点ooc以后我对于gtk的面向对象实现机制有所了解了,也越发的佩服设计gtk的那帮牛人,硬是在c语言上凭空搭起一个如此巨大的面向对象系统,有如神迹。
python的思考
用python的经历和感受
最近一直在折腾python,不过也顺带看了许多网络编程、设计模式之类的东西。边学边用了将近一个月的python,即使作为一个铁杆的C粉丝,我也不得不承认python有很多值得称道的地方。严格的讲,python的这些好处所有面向对象的语言应该都有,例如对于字符串的连接操作就是一个运算符的重载,C下面必须要用函数来实现,而且还需要程序员自己去考虑内存分配的问题,当然glib已经提供了不需要处理内存分配的字符串连接函数了。但是python的另一个特性——字典,在glib中似乎还没有。不管怎么说python还是比较方便的。不过在我的印象里,一种语言的方便总是以性能和安全性为代价的,我老是觉得python容易内存泄漏,虽然我一直在告诉自己,这一切都是幻觉。
关于面向对象设计
买了一本Booch的《面向对象分析与设计》,看了半天,有点感悟,从对象的角度考虑问题的确比从过程的角度考虑要容易的多。但是根据自己一点微不足道的经验,我觉得似乎不能把任何事情都从面向对象的角度去考虑,如果从过程角度考虑简单的话,不妨从过程的角度去看看。总是从面向对象的角度去考虑问题,容易陷入某种思维误区——即希望把一切都对象化,大的对象可以分解为小的对象,小对象可以再分解为更小的对象,如此循环,没有尽头。额外再说一点,似乎是我的智力水平太低——我竟然无法很好的理解python的继承,虽然看了ooc以后我能理解C中的继承机制——唉!也许老了。
关于python的资料
说实话python的资料不好找……当然我不是说将python基本语法和使用的资料,我说的是有关一些专业库的资料,例如昨天要用python调用gnome下的gconf写一些设置,查了半天google和百度,也只查到一些零星的只言片语,都是别人写的示例代码。虽然根据别人写的代码我也写出来了,但是过程不叫一个艰辛啊——一步一步的试,一步一步的调。可以说每写成100行有用的代码,就有将近300行的陪葬代码。不知道在哪里去找这种有关python的库的全面的文档呢?
关于python和C的思考
总结一下python好用在于其各种库众多(虽然有些库资料很少),外加其一些面向对象带来的优势(但不是全部,我觉得过多的面向对象就是灾难)。而C一直为大家诟病的就是其过于底层,开发难度太大,不方便。我现在有想法看能不能把python下的一些有用的东西用C实现一下,例如常用的urllib和httplib,以及字典等有用的特性。嗯……想法是挺宏伟的,不过不知道能不能做到。
又是一道坑爹的题啊……
题目
简单的讲就是保留一个32位整数最左边的1,把剩下的位都清零。要求和前一道题一样,就是总共的操作步骤被限制在15步内。
此题……虽然是三星,但是我感觉比上一题还要坑爹……想了半天无果后,去网上搜了搜答案,然后我惊奇的发现我想的其实已经和答案想去不远,就是差那么一点点就是正确答案了……唉……就差那么一点点,就是高手和平庸者的差距啊!不说了,上代码:
int leftmost_one(unsigned x)
{
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return (x & ((~x >> 1) | 0x80000000));
}
总结
最近两天做的题目的确有点新意,对于位运算这一块以前一直没有特别注意,这一块的变化很多,技巧很多。Aikilis兄给我推荐的Matrix67的博客上那几篇关于位运算的文章的确是经典无比,看了一篇,剩下的准备有空再详细阅读。这道题Matrix67的博客上有一道类似的题,是计算一个二进制串中前导0的个数,我这道题不需要计算出数字,只需要找到前导0后面那个1,然后把其余位清零即可,比他那道题简单一些,但要求更苛刻,不能使用if语句等。嗯……先写到这里,等我后面再做些题之后,再把最近用到的位运算的技巧全部总结一下吧。
一道想了半个小时的题……还算有点意思
题目来源
看完CSAPP的第二章,突然有兴趣把后面的Homework做一做(比ACM容易的多,算是我偷懒吧。)。于是三天打鱼两天晒网的,做到了2.65题,这是在书中遇到的第一道四星级的题目,激动无比啊……但是一看题目傻眼了,的确有难度啊
题目(稍微变化了一下,方便叙述)
假定字长为32位的一个二进制串,编写一个函数来确定串中是否含有偶数个1,如果是,返回1,否则,返回0。
要求:
- 不能使用if语句,? :语句,循环、switch
- 不能调用其他函数,不能使用宏
- 不能使用显式或者隐式的类型转换
- 不能使用“<、>、<=、>=”关系运算符
- 可以使用移位(逻辑或算数移位)
- 可以使用逻辑操作运算
- 可以使用“==、!=”
- 所有的算数运算、逻辑运算和位移操作加起来最多不能超过12步
看到这道题目,我的第一反应:这哪里是坑爹,简直就是……坑爹啊!不让用循环怎么测试,怎么统计……想了半天也没有结果,于是就想放弃了。顺手拿了一本其他书正在翻,忽然想起CPU的程序状态字里面貌似有这么一位,就是当有偶数个1的时候置1,奇数的时候清0,就在想CPU当中是怎么实现的,于是有了下面这段代码:
int even_ones(unsigned x)
{
unsigned x1 = (x >> 16) ^ x;
unsigned x2 = (x1 >> 8) ^ x1;
unsigned x3 = (x2 >> 4) ^ x2;
unsigned x4 = (x3 >> 2) ^ x3;
unsigned x5 = (x4 >> 1) ^ x4;
return(!(x5 & 0x1));
}
总结
上面的代码恰好用了12次逻辑和位移操作,没有算数操作,应该和作者实自己写的一样,不过我没有仔细测试,只是试了三个数,没有错误,希望这个代码是正确的……我相信这题对于真正的高手肯定是不费吹灰之力的,看来我还差的远,还要继续努力啊……另外就是,不知道还有没有更加坑爹的简便方法来实现这个函数……?
浮点数参数传递时候的隐式类型转换
最近狠心买了一本最新版的csapp,正在艰苦阅读,刚刚重新读完了第二章,自觉对于浮点数的理解终于又上了一个台阶,岂知在做第一个练习题的时候就碰到了麻烦……
课后习题2.55要求自己编译书中的代码show-bytes.c,然后运行测试,于是我照着书上的代码抄了一次,但是在测试的时候发现了问题,正数和指针似乎斗没有问题,但是浮点数却出现了奇怪的情况——show_float函数的输出全部都是0。相关代码片段如下:
void show_bytes(byte_pointer start, int len)
{
int i;
for (i = 0; i < len; i++)
printf(" %.2x", start[i]);
printf("n");
}
void show_float(float x)
{
show_bytes((byte_pointer)&x, sizeof(float));
}
此时若出现类似show_float(0.125f)之类的调用,会返回“00 00 00 00”,但当直接调用show_bytes(&x, sizeof(float))时(x=0.125f),结果则是正确结果“00 00 00 3e”。于是,我将show_float函数中那个sizeof(float)改成sizeof(double),再次调用show_float(0.125f),结果也是正确的“00 00 00 00 00 00 c0 3f”,最终问题应该就出在show_float函数的参数传递上,应该是在参数传递的时候,虽然show_float接受的类型是float,但是最终show_float得到的参数则是将float扩展得到的double类型。
嗯,编译器搞得一点小花招,不知道这是新的C语言规定还是GCC自己搞的扩展……
后续
发完文章后,我去chinaunix上问了一下,原来是我在调用show_float的时候没有声明合适的函数原型所导致的,嗯……看来不关编译器的事情,是我SB了。
解决本地apache2无法确定ServerName情况
前言
很久没有在这上面写东西了,一个原因是忙,另一个原因,我发现我确实没有那种随时有点灵感就记下来的习惯。也许以后要慢慢培养吧,毕竟写博客和写日记是我给自己定的几个大任务之一。
其实我一直在自己电脑上安装着一个本地wordpress系统,也写了一些东西在上面,不过都比较零散,需要慢慢整理和完善,才好意思发到这上面来。今天这是无意中点了博客的写新文章按钮,于是就想着写点什么,不过实在没什么大题目好写,就记一点小tips吧。
解决问题
本地安装的apache2每次在启动和重启的时候都会出现如下警告信息:
Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1 for ServerName
… waiting apache2: Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1 for ServerName
解决办法如下:
进入“/etc/apache2”,编辑httpd.conf文件(本来应该是一个空文件),在其中加入:ServerName localhost
,然后重启apache2就没有问题了。