Userscript:BetterUVA

17 May

最近在做UVAOJ上的题,发现页面布局实在是销魂得很。
所以写了一个userscript,目前只能在Chrome下使用。
需要安装TamperMonkey插件。

Emacs用户的Mac OS

06 May

刚用Mac的时候,觉得Emacs的键绑定怎么都不舒服。
左下角的四个键分别是:Fn, Ctrl, option/alt, command。
我实在无法想象一个正常人怎么用这样的键盘来用Emacs。

所以,我的解决方案是:
1. 在系统自带的键盘设置里交换Ctrl和Caps Lock;
2. 在.emacs里加上“(setq mac-command-modifier ‘meta)”;

然后,对于iTerm:
在iTerm的设置里将左面的command设置成option;设置为用option+数字切换tab。
但只是这样是不够的;还需要将cmd-d,cmd-shift-d,cmd-n,cmd-t,cmd-tab,cmd-`等与emacs无关的键设置为“do not remap modifiers”。否则就会发生无法用左面的cmd加上tab或者“`”切换程序的囧状……
cmd-逗号,还有cmd-q这两个我没有设置,其他的不常用的也直接忽略掉了。

最终的大杀器,KeyRemap4MacBook——
在emacs mode里把所有看着顺眼的选项都选上。这里的选项在所有cocoa程序里都适用。

KeyRemap4MacBook里Emacs模式的键绑定都是设置到option/ctrl(而不是cmd/ctrl)上的。如果有毅力的话,可以自己把所有需要的带有option的选项都重写一份cmd版的。如果你真的这么做了,欢迎把你的private.xml发给我一份。

终端倍化术:tmux

06 Mar

tmux有点像gnome-terminal和kconsole的标签功能,但是远比一个简单的标签来得强大。

============

tmux的所有操作需要用到一个特殊的前缀,来区分正常的命令。默认的前缀是C-b.
由此看来tmux的作者一定是个不知道readline还有快捷键的vim党——不管是在shell命令行下,还是emacs里,C-b都相当于左箭头。

所以,正常人用tmux需要做的第一件事就是把前缀改成一个不那么风骚的快捷键。很多人推荐C-a,可是C-a是回到行首,也就是Home……
而且终端模拟器似乎只支持Ctrl,Alt,Shift这三个修饰符。经过一番研究,我发现其实最好的可能是C-h(emacs里帮助系命令的前缀)。

所以,这篇文章里的修饰符就直接写成C-h了。在其他的文章里一般都是写成蛋疼的C-b的。

于是,这是我现在的tmux配置,还简单得很——

# do you use C-h?
set-option -g prefix C-h
 
# zero-indexing is no good here.
set -g base-index 1
 
# color scheme & style
set -g status-bg black
set -g status-fg white
set-window-option -g window-status-current-bg blue
set-window-option -g window-status-current-attr underscore
 
# useful info on the bottom
set -g status-left "" # this could be harmful sometimes
set -g status-right "#(date '+%H:%M %d-%b')"
 
# move the panes
bind-key j command-prompt -p "join pane from:" "join-pane -s '%%'"
# It's not useful; we could just use <C-b !> instead.
# bind-key s command-prompt -p "send pane to:" "join-pane -t '%%'"

一共五段;第一段是改前缀的;第三段是改配色的。

默认情况下,tmux的底部panel会显示当前tmux client的编号,而这个编号常常是没有用的——因为很少(?)会同时启动多个tmux。
这个编号最大的用途应该是在不同的client之间移动标签;唔,我就当作木有这个功能了。第四段的第一行就是关掉编号的显示。

panel右面的默认文字太长了,没有什么用处(但是如果是在awesome这样的不显示窗口边框的WM下就不好说了……)。
第四段的第二行把它设置成只显示日期和时间。

C-h c创建新的标签。当前标签下的shell停止之后标签会自动关闭。所以可以在shell的一个空行下按C-d,也可以直接用C-h x。

C-h ,更改当前标签的名字。
C-h :进入tmux的命令模式。这个不太常用,事实上我根本就不知道什么其他的命令……

C-h n可以用来切换标签,这里n是0-9之间的一个数字。默认的标签下标是从0开始的,可是在键盘上0在9的右面,略显蛋疼,所以把它设置成从1开始。
C-h l回到上一个标签。

C-h “和C-h %可以在当前标签里分割窗口;C-h o用来切换当前窗口。C-h ;回到上一个窗口。

最后一行用来把其他标签里的内容合并到当前标签。也就是C-h j n。n依然是0-9中的任意一个。
想要分离窗口并将其放到新的标签里,可以用C-b !。

其实这里的n可以是“a:b”这种格式,用来在不同的tmux client之间折腾来折腾去。唔,有兴趣的话可以折腾一下。

=========

tmux还有很变态的页面回滚和内容复制两个功能。

C-h [进入回滚模式;
在回滚模式下可以用vi风格的hjkl,C-f,C-b,C-u,C-d……
但是默认的复制快捷键不是vi风格的;空格开始选择,回车结束。
然后,C-]复制。

=========

个人常用的快捷键可能只有这些了。
用得顺手的话,可以考虑把“gnome-terminal -e tmux”来启动tmux,这样就不用手动启动tmux了。

=========
UPDATE: 现在配置文件里又多了这两段:

# "setting the correct term"?
set -g default-terminal "screen-256color"
 
# clipboard selection integration
##Copy tmux paste buffer to CLIPBOARD
bind C-c run "tmux show-buffer | xclip -i -selection clipboard"
##Copy CLIPBOARD to tmux paste buffer and paste tmux paste buffer
bind C-v run "tmux set-buffer -- \"$(xclip -o -selection clipboard)\"; tmux paste-buffer"

第一段是让emacs的高亮能正确显示的。(其实不太正常……)
第二段是从Arch论坛拖下来的,支持C-h C-c和C-h C-v复制/粘贴。
复制粘贴的时候,所有的反斜杠会被转成“\\”,略蛋疼。

Xlib与窗口切换器

17 Feb

本来只是写下一些零散的话准备以后再组织成完整文章的……结果写完之后懒得改了……
也许某天会重写这篇文章吧……也许吧……也许……

其实这篇文章跟窗口切换器没什么关系……主要是讲Xlib和窗口管理器(Window Manager,也就是WM)的。
文中夹杂的英语基本都是像是术语一样的词,不敢随便写成中文啊~

什么?你问Xlib是什么?呃,如果你只用过M$ Windows,那么这篇文章你可以忽略了。
如果你用过*nix……唔,这么说吧,GNOME/KDE/Openbox/Awesome/FVWM这种东西都是用来管理窗口的,称为窗口管理器(其实GNOME和KDE远远不只是WM而已……);在*nix上图形界面是建立在X Window这个东西上的,而X Window这个东西有点像服务器(本来就叫xorg-server好么……),运行起来之后通过一些API来交互。
说起来有点麻烦……如果你不太清楚Xorg到底是干吗的,那这篇文章对你基本不会有什么帮助……(那对谁有帮助?)
=============
这里是一篇Xlib开发手册。http://tronche.com/gui/x/xlib/

WM有规范,Xlib里的一些变量由WM控制。
也就是说,Xlib与WM之间的关系有点像OpenGL与3D引擎的关系。(其实这个比喻也不太准确……)

那个叫ICCCM什么什么的WM规范最讨厌了~

http://standards.freedesktop.org/wm-spec/wm-spec-latest.html

window的属性类别enum值叫atom,类型是Atom。
atom其实就是数字,由于可以由WM添加所以不是固定的,需要用XInternAtom()来获取。

有了Atom变量之后用XGetWindowProperty()来“get window property”。
注意XGetWindowProperty()有一个req_type参数,类型也是Atom,用来描述window属性的类型(话说这个到底有什么用意义……)。

虚拟桌面有两种实现方式:在root window下面挂载多个virtual root,或者在“切换”的时候把不需要的窗口unmap(?)掉,只map(?)需要的窗口。

_NET_VIRTUAL_ROOTS[req_type: XA_WINDOW]:root window专属属性,所有虚拟桌面的Window结构体指针数组。
_NET_CLIENT_LIST[req_type: XA_WINDOW]:root window专属属性,WM所控制的Window结构体指针数组。

_NET_WM_DESKTOP[req_type: XA_CARDINAL]:window所属的virtual desktop编号(不是Window结构体指针),当值为0xffffffff的时候应该出现在所有virtual desktop上。
_NET_WM_STATE[req_type: ATOM]:window状态。似乎可以用来忽略taskbar(“菜单栏?”“状态栏?”这个怎么翻译?)一类的特殊window。

xprop命令与你同在。
记得还有xprop -root这种东西。

送上demo程序:

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char *argv[]) {
     Display *dpy = XOpenDisplay(NULL);
     if(dpy == NULL) {
          printf("Can't open display\n");
          exit(1);
     }
 
     // XSetErrorHandler
 
     int default_scr = XDefaultScreen(dpy);
 
     // DEBUG
     printf("Default screen number: %d\n", default_scr);
 
     // DEBUG
     printf("Screen count: %d\n", XScreenCount(dpy));
 
     Window root_win = XRootWindow(dpy, default_scr);
     int height = XDisplayHeight(dpy, default_scr);
     int width  = XDisplayWidth(dpy, default_scr);
 
     // DEBUG
     printf("width: %d, height: %d\n", width, height);
 
/*
     Window t1, t2;
     Window *first_level_windows;
     unsigned int first_level_windows_cnt;
     XQueryTree(dpy, root_win, &t1, &t2,
                &first_level_windows,
                &first_level_windows_cnt);
 
     // DEBUG
     printf("Number of 1st level windows: %u\n", first_level_windows_cnt);
 
     XClassHint *class_hint = XAllocClassHint();
     int i;
     int win_io_cnt = 0;
     for(i = 0; i < first_level_windows_cnt; i++) {
          Window win_cur = first_level_windows[i];
          XWindowAttributes win_attrs;
          XGetWindowAttributes(dpy, win_cur, &win_attrs);
 
          // dump info
          if(win_attrs.class == InputOutput) {
               if(i != 0) {
                    printf("\n");
               }
               printf("x: %d, y: %d, w: %d, h: %d\n",
                      win_attrs.x, win_attrs.y,
                      win_attrs.width, win_attrs.height);
 
//               Atom atom_ = XInternAtom(dpy, "WM_CLASS", False);
//               printf("%s\n", XGetAtomName(dpy, atom_));
 
               win_io_cnt++;
 
          XGetClassHint(dpy, win_cur, class_hint);
          printf("res_name: %s, res_class:%s\n",
                 class_hint->res_name, class_hint->res_class);
          // printf("class: %d\n", win_attrs.class);
          }
     }
//     printf("Number of IO windows: %d\n", win_io_cnt);
 
     XFree(class_hint);
     XFree(first_level_windows);
*/
 
     Atom actual_type_ret;
     int actual_fmt_ret;
     unsigned long nitems_ret;
     unsigned long bytes_after_ret;
     unsigned char *prop_ret;
 
     unsigned long i;
     int result;
 
     Atom virtual_roots_atom = XInternAtom(dpy, "_NET_VIRTUAL_ROOTS", False);
     result = XGetWindowProperty(dpy, root_win, virtual_roots_atom, 0,
                                 0x7fffffff, False, XA_WINDOW, 
                                 &actual_type_ret, &actual_fmt_ret,
                                 &nitems_ret, &bytes_after_ret, &prop_ret);
     /*
     if(result == Success) {
          printf("Success!\n");
     } else {
          printf("Fail...\n");
     }
     */
     int *virtual_roots_list = (int *)prop_ret;
     printf("number of virtual desktops: %lu\n", nitems_ret);
     for(i = 0; i < nitems_ret; i++) {
          printf("%d", virtual_roots_list[i]);
     }
     printf("\n");
 
     printf("\n === windows managed by the window manager === \n\n");
 
     Atom client_list_atom = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
     result =
          XGetWindowProperty(dpy, root_win, client_list_atom, 0,
                             0x7fffffff, False, XA_WINDOW,
                             &actual_type_ret, &actual_fmt_ret,
                             &nitems_ret, &bytes_after_ret, &prop_ret);
 
     Window *win_list = (Window *)prop_ret;
     unsigned long win_list_cnt = nitems_ret;
 
     Atom net_wm_desktop_atom = XInternAtom(dpy, "_NET_WM_DESKTOP", False);
 
     XClassHint *class_hint = XAllocClassHint();
     for(i = 0; i < win_list_cnt; i++) {
          XGetClassHint(dpy, win_list[i], class_hint);
          if(i != 0) {printf("\n");} // add blank lines to break output
          printf("res_name: %s, res_class:%s\n",
                 class_hint->res_name, class_hint->res_class);
          result =
               XGetWindowProperty(dpy, win_list[i], net_wm_desktop_atom, 0,
                                  0x7fffffff, False, XA_CARDINAL,
                                  &actual_type_ret, &actual_fmt_ret,
                                  &nitems_ret, &bytes_after_ret, &prop_ret);
          // if(result == Success) {printf("S\n");} else {printf("F\n");}
          printf("on desktop:");
 
          printf(" #%d\n", ((int *)prop_ret)[0]);
          // unsigned long j;
          // for(j = 0; j < nitems_ret; j++) {
          //      printf(" #%d", ((int *)prop_ret)[j]);
          // }
          printf("\n");
     }
 
     XFree(class_hint);
     XCloseDisplay(dpy);
 
     return 0;
}

编译的时候记得加上“-lX11”参数。libX11.so在注视着你。

Protected: 莲之根

11 Feb

This post is password protected. To view it please enter your password below:


Protected: 莲之里

11 Feb

This post is password protected. To view it please enter your password below:


Protected: 莲之表

11 Feb

This post is password protected. To view it please enter your password below:


非tty终端emacs用户必备杀器:xmodmap

11 Feb

其实这并不是什么秘密。
在emacswiki上就推荐了用xmodmap来切换ctrl和caps lock,而xmodmap的man page里也直接就有“xmodmap一般是用来交换ctrl和caps lock”这句话。

但是比较蛋疼的是居然没有一个人给出把右alt也设置成ctrl的代码?
而且xmodmap的man page……反正我是没看懂。

唔,用caps lock来代替ctrl虽然很好,但是现在我发现左手的小指在按ctrl的时候关节是凹下去的,而非像按其他键的时候是凸起来的。
预感这不是一件好事情,就顺手把右alt也用上了。

其实比较合理的设置应该是只把左alt和ctrl对调,但是已经习惯了alt在左面,估计短时间内是改不过来了……

下面的是~/.xmodmap的内容:

! remove Lock = Caps_Lock
! remove Control = Control_L
! keysym Control_L = Caps_Lock
! keysym Caps_Lock = Control_L
! add Lock = Caps_Lock
! add Control = Control_L
 
remove Lock = Caps_Lock
remove Control = Control_L
remove mod1 = Alt_R
keysym Control_L = Caps_Lock
keysym Caps_Lock = Control_L
keysym Alt_R = Control_L
add Lock = Caps_Lock
add Control = Control_L
add mod1 = Alt_R

感叹号开头的部分是被注释掉的只切换左ctrl和caps lock的代码。
执行xmodmap ~/.xmodmap之后,键盘的布局变成:
caps lock => ctrl
左ctrl => caps lock
左alt => 还是alt
右alt => ctrl

在tty终端下xmodmap是不起作用的。
并且,`xmodmap ~/.xmodmap`的作用可以在一个X session中累加(所以不要执行多次!),但是在重启X之后(比如logout之后再login)就会消失。

不管你信不信,反正我是不信。

26 Jan

这篇还是从豆瓣弄来的——这次是给《鞋里的沙》的书评。
行文思路极其混乱,各位看官看过一下笑笑就好。

——————————————————傲娇分割线就是我——————————————————

小学的时候,这一系列的八本书我买了七本,并且反复看过很多遍。

书之间的故事都有重复这种事就不多说了。重点是,看过几百个心灵鸡汤式的故事之后,我开始觉得所有的故事都是bullsh*t。



先说第一个问题:所有的故事都是为作者一瞬间的想法而存在的。

Firefox浏览器前身Netscape的创作者之一的Jamie Zawinski(jwz)曾经在自己的blog中说过这样的一段话:
“I slept at work again last night; two and a half hours curled up in a quilt underneath my desk, from 11am to 1:30pm or so. That was when I woke up with a start, realizing that I was late for a meeting…But it was no big deal, we just had the meeting later. It’s hard for someone to hold it against you when you miss a meeting because you’ve been at work so long that you’ve passed out from exhaustion.”

简而言之就是说,啊,昨天晚上又在工作的时候睡着了。工作很累人的,blablablabla……

TechChurch的创建者Michael Arrington引用了他的这段话写了一篇文章,名为“Startups Are Hard. So Work More, Cry Less, And Quit All The Whining”。

jwz看到之后就不乐意了,马上就写了一篇文章轰回去:
“他这么说是想让你觉得,如果你不疯狂地工作,那你就是一个傻叉。”
“这非常不真实,又虚伪。”
“他会这么说是因为他们的风险投资工作需要你全力投入,这样他们才能捞到足够的回报”。
“抛开他说的这些烂理论,我的推荐是:做你喜欢做的任何事情。如果这意味着长时间的努力工作,那非常好。如果那意味着每天一到下班时间就马上走人上你的潜水织毛衣(原文:underwater basket-weaving,水下编筐,并不是毛衣,这里意译一下)课,那同样非常好。”
(来源:http://www.jwz.org/blog/2011/11/watch-a-vc-use-my-name-to-sell-a-con/)

因为社会需要更多优秀的人,才会有一种“遇到强大的人就要膜拜并且向其学习”的风气。这全踏玛的全都是扯蛋。有个哲学家为了“人到底为什么或者”这个问题想了想得脑子都快破了,最后得出一个结论:人生来是为了追求快乐的。jwz没上过大学,算是大多数人眼里的低素质阶级,但他的理论却与哲学家(哲学家噢!哲学!高贵的哲学!)不谋而合。就算人类进化得再怎么高级,也不过是一个为了繁衍加生存而努力奋斗的存在而已。当有一天你的身体机能彻底停止了,你的一切就都消失了——对于你自己这样一个靠外部物质维护内部稳态的存在,这一切又有什么意义?
当然,如果你已经修炼到了一个非常高端的境界,也许可以非常淡定地说:我愿意把我自己内部稳态保持正常的这段时间奉献给更多生物内部的稳态。那非常好,你可以穷尽你的一生来做慈善,搞科研,写论文。如果你觉得看着银行账户上的那个数字就会让你的肾上腺素疯狂分泌并且你以此为乐并且就算即使哪怕为此死掉也毫不在乎,那你非常适合去搞金融,办公司。如果,如果你觉得,你最喜欢的就是支配更多的生物(就像能控制蚂蚁的真菌一样:http://jandan.net/2011/03/10/zombie_fungus.html),那么,恭喜你,呵呵。(看到“呵呵”请自行脑补某个百度贴吧上的经典签名图片)

另一方面,就像jwz说的,如果你喜欢在水下织篮筐,那你就去尽情地织个够好了。



第二个问题:有的时候作者的观点不一定是完全正确的,甚至绝大多数时间是有误导性的。

乔布斯刚离开的那几天,全世界的果粉非果粉都像疯了一样开始发掘各种老乔的故事;下面这个就是我在那个时候看到的:
一家三口来到苹果总部,找了一个路人要他帮忙给自己拍一张和总部大楼的合影。但是,事实上呢,这个路人就是乔布斯本人。老乔很淡定地接过相机,拍了照,笑了笑,然后走人。
于是,有人说:啊,乔布斯这么牛还这么谦逊,卧槽,成功的人都是谦逊的啊,我踏玛也要这么做。如果他没给那一家人拍照而是当场走开,在那个时空里说要谦逊的人就会说:啊,乔布斯这么牛就是从不妥协啊叛逆啊个性啊,卧槽,成功的人都是有个性的啊,我踏玛也要有个性。

有一句口号叫“细节决定成败”。无数的傻x领导看到它之后就开始在工作会议上,在早晨的演讲台上,一边喷口水一边努力地开始讲细节有多么多么多么多么多么多么重要。
我靠,跟细节比起来大局算个毛啊。是不是。细节最重要了!
如果你跟被这种口号洗了脑的人讲道理,对方就会再抛出一大堆乔布斯拍照式的故事来证明自己的观点;说不定还会把这个故事再抛出来重复利用一下:你看,老乔是个多么注重细节的人,即使是对于来观光的人也要给他们留下一个对苹果好印象!这才是伟大的人!看,他是多么注重细节!
如果你继续反驳,对方往往会使出终极必杀技:你说得这么漂亮,为什么你没变成一个成功的人?


这就引出了第三点,成功人士是怎么成功的有的时候根本不是你想象的那个样子——稍微跑题一下,有的时候,即使是他们自己,也不知道自己究竟是怎么成功的或者是根本不敢讲出来;多数时候,在大多数人面前,他们只是挑一些难以反驳的漂亮话来说而已。
你知道比尔盖茨的母亲是IBM公司的高管么?你知道她给盖茨带来了多么大的帮助么?你不知道,因为你没有仔细研究过计算机的发展史。

甚至有些事情的真伪根本无法证实,或者只能“呵呵”两声。
1. 老毛从小就喜欢在剧院里读书,这样能锻炼自己的毅力。
2. 为中华之崛起而读书!
3. 开国元勋华盛顿先生啊,砍树什么的,你懂的。



最后,我只能说,看完七本之后,我唯一学到的东西,就是不要把时间浪费在这种书上面,就像不能靠着初高中学生写的应试议论文里的理论来生活一样——不管题目是“学会变通”还是“贵在坚持”,他们都是能写出一大堆话并找出一大堆论据的。还有,为了这么一本无聊的书打出这么多的字,也只能说明我也是傻叉一个。

学编程的最初四年

17 Jan

本来是在豆瓣某帖上给别人的回复,写完之后自己都觉得太牛叉了就忍不住再在这儿发一遍。

虽然看起来很好很强大,但是对于阅读大型代码的无力以及面对各种强大的OIer时无助的感觉却也是真实的。
但愿大学四年间能有同样恐怖的变化。

——————————————————我是傲娇分割线——————————————————

楼主有热情?很好。

推荐一篇文章:

http://www.aka.org.cn/Docs/hacker-howto_2001.html

不知道别人的经历是什么样的,既然楼主很纠结那我就说说我的故事吧。
小学五年级的时候,学校开了一个教Quick BASIC的班,目标是参加小学生的一个编程比赛。虽然我的确是成功地从班里刚开始时几十人的时候一直挺到了最后加我只剩六个人并且去省里比了赛还拿了奖,但是不到半年之后我就顺利地把所有东西都忘光了。我还记得比赛的第一题是写杨辉三角形,当时我果断没写出来。
初二结束的暑假,我在书店闲逛的时候看到了一本国人写的VB教材。当时主要是因为看到“BASIC”这个词之后瞬间回想起了小学时代的故事,然后翻了几下发现我居然能看懂(因为VB的确够脑残够傻瓜),于是就买了回去,从此就开始了蛋疼的学VB的生活。
初三过了一半之后,VB最基础的东西我已经学得差不多了,做个简单的按个按钮就蹦出来个吓人图片之类的程序绝对是毫无压力。记得我用VB做的最后一个程序是给txt小说分段的,但是程序一直有我没找出来的错误。做为一个还算有上进心的好少年,我又去书店里买了一本同系列的Java的书。注意,这本书是同系列的,也就是说它是国人写的。我不是鄙视同胞,但是当时那本书的混乱的确给我留下了深刻的阴影。于是我又去书店弄了本老美的书(不是《Java核心编程》也不是另外一本特别厚的,叫什么来着……),很欢乐地继续学了很久。
然后我不知道怎么就知道了Python这个东西,然后又去买了Python的书和《Java核心编程》,唔,这个时候我应该是还在高一上学期。但是只看是没有用的;我虽然懂它们的语法,但是却写不出任何实用的程序。买《Java核心编程》本来应该对此有所帮助的,但是它实在是太踏玛的厚了。

初中结束之后买了自己的电脑,看到了上面给出的那篇《如何成为一名黑客》,然后就投入了linux的怀抱基本再也没回到windows上。

高一下学期开始参加学校的计算机竞赛班,用的是C,主要处理算法问题。当时因为跟数学竞赛冲突就没去上学校自己的课而是买了本《C和指针》自学语法,然后是《算法导论》补理论加usaco做题。数学竞赛太变态,放弃了。一整年没跟学校里那群把所有时间花在打游戏上的人一起上课,最后竞赛前沟通了一下,发现我居然能毫无压力完虐他们。(虽然随后考水了,囧……)
一直到高二的十月份这段时间,我学了算法搞定了C做了一堆题顺便还看完了《Essential C++》和《Unix/Linux编程实践教程》(后者真是好书啊,比APUE之类的砖头式参考书靠谱多了……)。注意,全程自学。

后面的故事如果再讲下去就跑题了。不过我可以很自豪地说我高三的时候就在github上有了项目(虽然也就两千行,不大,但是我觉得的确挺实用的,唔……),竞赛上也完虐了学校里就知道打游戏和扯蛋的那群渣渣(不过被外校有牛X老师和牛X同学的牛人虐了,呃……),而且这一切都不是靠老师教出来的。而且,我全程都学得很轻松,只是回过头看的时候发现:哇,好帅。
(自己这么说自己真的没问题么?大丈夫だ、問題ない!)

楼下轻喷。

Ecstasy::Mirage

Alas I cannot fly