2009/2

据说是世界上最好的工作。。。

猜猜是什么?科学家?高管?总统?都不是!是大堡礁岛屿看护员!

为什么是世界上最好的呢?首先工作环境狠好!在几乎是天堂的旅游胜地工作——世界自然遗产大堡礁。然后是待遇狠高!半年就能拿到15万澳元!工作也轻松啊,喂个鱼,送个信什么的……此外住宿啊,交通费啊,上网什么的人家全都包!!美死了都,直流口水……那个旅游局真有钱……

有兴趣的看官方网站的介绍吧:职位描述待遇及挑选标准挑选过程

唉,眼红死了都,我是达不到要求的,我还是擦干净口水,老老实实当我的程序员吧~~~!

P.S. 附关于程序员的冷笑话一则:

——我是程序员。
——哦,程先生!
——客气了,叫我序员就好。

《一日重生》

这本书写得很别致,很短小,而且文字并不华丽,但却直指心灵深处,读完后内心深处有一种莫名的触动。

书中的故事无疑是虚构的,但它却不离谱。一个堕落的人走上了自杀的道路,刻意制造了一场车祸,然后昏迷中梦到和逝去的母亲重逢,并一起度过一天,在这一天中还反复穿插着母亲还活着时自己的点点滴滴,对自己的行为感到后悔不已……

看看主人公的话,我们不都是,或者曾经是,和他一样吗?

“如果把应该和妈妈在一起而没有在一起的时间累加起来,恐怕也有一辈子那么长了。”

“妈妈过世后多年后,我给自己列了两个清单,一张单子上列着妈妈为我挺身而出的事情;另一张单子上列着我没有为妈妈挺身而出的事情。很悲哀,两个单子长短差距很大。为什么对于爸爸妈妈,孩子会向其中的一个,索取很多很多,而对另一个,却没有太多的要求呢?
书中的母亲说的几句话更是让我们这些做孩子的感到羞愧:
“孩子们常常因为父母的缘故而感到尴尬,”

“有时候,孩子的话最伤人,是不是,罗丝?你忍不住要问,‘这究竟是谁的孩子?’”

“但通常,他们是因为受了伤才这样做。他们想要找到一个解决的办法。”

一个因为母亲而感到羞愧的孩子,只不过是个没有长大的孩子罢了。
多为爸妈做些什么,相对于他们的付出,其实他们要求的并不多。

C中的宏预处理

宏的预处理这个坑看起来浅,其实还蛮深的。它也是最容易被忽视的几个地方之一。这里斗胆来谈谈,说实话,在写这句话时我也不清楚这坑究竟有多深,没关系,我们摸着石头过河,一起看看到最后这坑能有多深!同时这篇文章也将会是《C语言编程艺术》中的一部分。

从一个相对简单的例子说起吧。
[c]

define f(a,b) a##b

define g(a) #a

define h(a) g(a)

h(f(1,2))
g(f(1,2))
[/c]
相信不少人都见过这个例子。我们不妨再仔细分析一下它的解析过程。应该是这样的:

对于g(f(1,2)),预处理器看到的先是g,然后是(,说明这是一个函数式的宏,好,然后替换后面的实参f(1, 2),得到#f(1,2)(注:直接这么写非法,这里只是为了表示方便而已),因为它前面有个#,所以下一步是不会替换f的参数的!所以进一步得到”f(1, 2)”,解析结束。而对于h(f(1,2)),预处理器看到的先是h,然后(,对其参数f(1, 2)进行替换,得到g(f(1,2)),注意这里的下一步是,预处理器就继续往后走,处理刚得到的f(1,2),而不是回过头去再处理g!得到12,到了这里我们的得到的是一个:g(12),然后重新扫描整个宏,替换g,最后得到”12”。

标准第6.10.3.1节中对此描述的还比较清晰,它这样写道:

After the arguments for the invocation of a function-like macro have been
identified, argument substitution takes place. A parameter in the replacement
list, unless preceded by a # or ## preprocessing token or followed by a ##
preprocessing token (see below), is replaced by the corresponding argument
after all macros contained therein have been expanded.
注意加粗的部分。到了在这里,我们可以简单总结一下函数式宏的基本替换流程:

首先要识别出这是一个函数式宏,通过什么?通过调用中出现的(,没错是左括号!到这里后下一步是参数替换,就是根据该宏的定义把实参全部替换进去,然后接着向后走,除非是遇到了#和##(正如上面例子中的g),把后面替换后的东西中如果还有已知宏的话,进行替换或者同样的展开,直到解析到末尾:所有的参数都已经替换完(或者#或##已经处理完);最后,预处理器还会对整个宏再进行一次扫描,因为前一轮替换中有可能在前面替换出一些新的东西来(比如上面例子中的h)。

这里咋看之下没什么问题,其实问题很多!为什么?因为宏替换不仅允许发生在“调用”宏的时候,而且还发生在它定义时!

问题1:宏的名字本身会被替换吗?

这个问题也可以这样问:宏允许被重新定义吗?不允许,但是允许相同的重新定义。标准这样写道:

An identifier currently defined as an object-like macro shall not be
redefined by another #define preprocessing directive unless the second definition
is an object-like macro definition and the two replacement lists are identical.
Likewise, an identifier currently defined as a function-like macro shall not be
redefined by another #define preprocessing directive unless the second definition
is a function-like macro definition that has the same number and spelling of
parameters, and the two replacement lists are identical.

问题2:宏的参数(形参)会被替换吗?

先举个例子说明这个问题:

define foo 1

define bar(foo) foo + 2

bar(a)

我们是得到a+2还是1+2?a+2!因为形参是不会被替换掉的,你想想啊,如果形参都被替换掉了这个宏就没什么作用了!那实参呢?实参会的,因为实参的替换发生在传递这个参数之前:

Before being substituted, each argument’s preprocessing tokens are
completely macro replaced as if they formed the rest of the preprocessing file

问题3:宏中参数之外的符号会被替换吗?

会,上面提到过“after all macros contained therein have been expanded”,也就是说这个发生在参数替换之前。但是,这里有个非常诡异的问题:如果被替换出来的符号正好和形参一样怎么办?就像下面这个例子:

define foo bar

define baz(bar) bar + foo

baz(1)

我们会得到1+1还是1+bar?后者,因为替换出来的那个bar是不会计算在形参之内的,虽然标准并没有明确这一点。想想吧,如果是的话那个宏的定义也会被破坏了!

另一个例子:

define foo bar

define mac(x) x(foo)

mac(foo)

根据上面所说,我们首先得到foo(foo),然后foo再被替换成bar,最后得到bar(bar)。

好了,到这里我们终于可以看一下更复杂的例子了:

define m !(m)+n

define n(n) n(m)

m(m)

这个例子相当复杂,是我见过的最复杂的一个宏。:-) 刚看到我们可能都有点蒙,没关系,咱们一步一步地来。

第一步很好走,第一个m直接被替换,得到:!(m)+n(m),别犹豫,接着往下走,替换最后一个m,得到:!(m)+n(!(m)+n),这时这一遍扫描已经完成。到这里我们得提出另外一个东西才能继续,你可能知道,递归。标准对此的描述是:

If the name of the macro being replaced is found during this scan of the
replacement list (not including the rest of the source file’s preprocessing
tokens), it is not replaced.

在上次替换中,被替换的是m,所以m在这里的再次出现将不会被替换,所以下一步是会替换第一个n,得到:!(m)+!(m)+n(m),注意这里又替换出一个新的m来,这个m会被替换,因为这次扫描还没完成!下一步得到:!(m)+!(m)+n(!(m)+n),第二遍扫描结束,全部的替换完成。

综上,我们可以总结出两条重要的宏替换规则:1)再复杂的宏也只是被扫描两遍,而且递归是不允许发生的,即使在第2遍时;2)一个替换完成后如果还没扫描完,要从被替换的那里继续。

(全文完)

学计算机太没前途了。。。

靠,我算是看透了,其实学计算机是很没前途的!不信你接着往下看。

首先吧,你要是出去给美女说你是学计算机的,美女八成都会说:计算机高手啊,你快来帮我看看我的电脑怎么老是那么慢啊?而且该美女肯定是在使用windows。所以啊,你看,在美女眼里,计算机高手就等于修电脑的,能把她的电脑搞定你就是高手,如果搞不定,你就是懂再多也没用,什么Linux啊,C语言啊,C++啊,美女听不懂,在她眼里皆为0!你搞定了,下次她修电脑基本上还得找你,你搞不定,八成就开始疏远你了……

你电脑修要是得好,连邻居的大妈都会上门来找你,然后出去逢人就夸:你看啊,人家xxx家的娃电脑学得就是好啊,我们家的电脑出毛病了人家一下子给就修好了!所以啊,学计算机是要学,但不是学这个专业的,会修电脑就成,多了没用!你Linux学得再牛逼再好邻居的大妈都不会知道!

其次啊,搞IT的换工作基本上挺频繁的。所以啊,这又来问题了。过年出去串个门,亲戚都在说:我说小王啊,你换工作了啊,你怎么不试试考公务员呢?你看人家公务员虽然工资不是很高,但人家可是旱涝保收啊!我当时心里就怒了:靠!考你妈B的公务员!!保你妈B的收!!就是TM的HJT请老子去老子都不会看一眼的!!瞧瞧瞧瞧!搞计算机的还不如当政客的呢!!

最后,计算机学得牛逼的基本上都是geek到家的人,虽然我还没学到家但已经先geek到家了。我发现啊,在这个圈里基本上就没有美女,这个圈附近的圈里还是没多少,这方圆几十里之内无美女啊。而且呢,你越是geek你就越找不到美女……这或许该叫“牛与美女不可兼得”吧?

这是tmd什么世道啊!!

还谈数组

作者:西邮 王聪

《C专家编程》中曾两次谈到数组,可是我觉得还不够,仍然没有把数组的本质给说出来。这里我想来个了断,终止一切关于数组和指针关系的讨论。:-)

为了先让你困惑一下,先看下面的程序:
[c]

include

typedef int color[3];

size_t array_size(color a)
{
return sizeof(a);
}
size_t array_size_p(color a)
{
return sizeof(
a);
}

int main(void)
{
color c = {0,};
color *p = &c;

    c[1] = 10;
    *(c+1) = 11;
    printf("%dn", c[1]);
    printf("%dn", sizeof(c));
    printf("%dn", array_size(c));
    printf("%dn", array_size_p(p));

    printf("p=%lxn", (unsigned long)p);
    printf("p+1=%lxn", (unsigned long)(p+1));

    return 0;

}
[/c]

如果你看了上面的程序没感到困惑,那说明:要么你完全理解这里的问题;要么你根本就不懂数组和指针的关系,而且根本就没仔细想过,只是知道结果罢了。

如果你去参考《C专家编程》,你得到的是分情况的讨论,一条一条对应得很好,如果你照这样理解的话(就像我以前一样),也感觉不出不舒服来。但是,它没有点破本后的本质,至少我是没看到。我在这里得说一下。这里的问题究竟是什么?问题出在对数组的定义上,理解这里后面的一切都好办了。数组的定义是什么?太简单了,无非就是:

int a[10];

别小看这里,这里太关键了,以至于很多人都给忽略了。这里究竟发生了什么?这里定义了一个长度为10的int数组,其名字叫作a。这似乎是废话,但你琢磨琢磨这里的词,我是把名字和数组分开说的,这就是关键!!进一步说是,a仅仅是个名字,它并不是指向数组第一个元素的整型指针,也不是一个数组类型!更为关键的是,它在内存中根本就不存在!!这样问题就来了,那a怎么还和数组第一个元素的值相同呢?只是碰巧相同罢了!这好比“解放路1号”和“解放路”的地址相同!如果给a下个定义,那它是什么?我觉得,可以这么定义a:它是一个符号,语法上的;如果我们要它的内容,它就代表了其整个数组;如果我们只要它的值,那么它就是数组的起始地址。

我们把上面的定义一段一段解剖来看。“它是一个符号,语法上的”,这可以很好地解释为什么数组名是个常量,它出现的地方都是在语法上进行了适当的替换,至于如何替换,就是下面两条了。

“如果我们要它的内容,它就代表了其整个数组”,这句可以很好地解释下面的例子:
[c]
color *p = &c;
[/c]
这里取址的意义是“我取c代表的整个数组的地址”。这也可以解释sizeof为什么在函数外面作用于数组时取到的是整个数组的大小。所以,说“数组名就是指向其第一个元素的指针”是错误的!

“如果我们只要它的值,那么它就是数组的起始地址”,这句可以很好地解释为什么数组作为函数参数传递时会是指针,因为在函数参数使用其地址明显要比使用其全部内容要快。这也可以解释c[1]存在的道理,因为这里取的也是它代表的地址。所以,说“数组名是数组类型”也是不对的!(如果真是的话,那么它+1和上面例子中的p+1应该是一样的才对。)

综上,数组名什么都是,它是两者的混合:当它表现出内容特性时,它的类型是数组类型;当它表现出地址特性时,它的类型又是其指向第一个元素的指针类型。它也什么都不是,它只是个名字,它在内存中本身是不存在的,存在的是其背后实实在在的数组,它只是它们的一个语法上的代表罢了。这也就决定了从语法上你根本就不可能直接去定义一个数组类型的变量,你只能定义一个指向数组类型的指针,然后间接地去找到背后那个数组类型!

P.S. 因此,在C语言中,你根本就不可能在函数中直接去看到一个数组参数的真实类型,而C++可以,多亏了引用!看下面的例子:
[cpp]

include

using namespace std;
template
size_t array_size(T & ar)
{
return sizeof(ar) / sizeof(ar[0]);
}
int main()
{
int ar[100] = {0};
cout<<array_size(ar)<<endl;

    return 0;

}

[/cpp]

你看,语法上的问题也只能在语法上解决!这叫什么来着?“以其人之道还治其人之身”!

以上纯属一家之言,读后请自己三思!!

C++ string literals

从一个例子谈起,下面的例子摘自《C++ Templates》。
[cpp]

include

include

template
void ref (T const& x)
{
std::cout << "x in ref(T const&): "
<< typeid(x).name() << 'n';
}

template
void nonref (T x)
{
std::cout << "x in nonref(T): "
<< typeid(x).name() << 'n';
}

int main()
{
ref("hello");
nonref("hello");
}
[/cpp]

用g++编译,得到运行结果如下:

x in ref(T const&): A6_c
x in nonref(T): PKc

(注:A6_c表示:array(A) of 6 chars (c);PKc表示:pointer (P) to const (K) char (c)。)

初看到这个例子我有些惊讶,为什么同样是字符串常量,通过引用传递和非引用传递差别就这么大呢?其实真想想也没什么大惊小怪的。看下面的分析。

首先,对于字符串常量,在C++标准里的类型已经发生了变化,以前是char [],现在是const char[],参考C++06 2.13.4:

An ordinary string literal has type “array of n const char” and static storage duration,
where n is the size of the string as defined below, and is initialized with the given characters.

但为了保持兼容,一部分当char []来使用的代码仍然有效。这不像C99,C99照顾兼容照顾得更厉害,所以这点仍然没变,它只说了一句:

If the program attempts to modify such an array, the behavior is undefined.

有些跑题了……咳,所以,当引用传递时自然是原来的类型const char[]。

而直接传递呢?直接传递其实也是const char[]!我没开玩笑!想想啊,它的类型怎么能会突然就变呢?不可能!问题是为什么typeid看到的是const char *?很简单啊,因为这是函数!当数组作为函数参数传递时会退化为指针,我们耳熟能详的这句话在C++里也适用啊!所以,这里拧把的不是模板,而是引用,别被模板给吓怕了!

如果你仔细看看原书那一节,你会发现还有个问题比较拧把:不同长度的同类型数组其实也不是一个类型,这也不难理解,放C里看看你就知道了:C里的一切类型皆是连续的内存块,数组更是,不同长度的同类型数组长度还是不一样啊,怎么能是同一个类型呢?所以这就有问题了,不同长度的字符串常量还不能作为同一个类型直接传递进模板了?如果我们用引用传递的话,是,可如果不用的话就行了,这正是解决方法!或者更干脆,直接用std::string。

virtual function 的例子

本来这没什么可说的,C++中的虚函数不难理解,可是我看到不少教程中的例子给得不行,多说一句得。

那些教程给出的是类似下面的例子:

Base *p = new Derived;
p->virt_func();

我认为这个例子不行,只能显示虚函数啥样子,却不能说明白为什么要有虚函数。其实,至少我认为,虚函数是为了给函数传递参数用的,我使用基类指针作为函数参数类型的定义,但调用时却可以传递给它子类的指针,可虚函数的行为却是实参类型中的对应方法。我认为这才是虚函数的意义,为此我写了下面一个程序,顺便练练了刚学成的template recursion。

[cpp]

include

template
class C: public C
{
public:
virtual void foo();
};

template
void C::foo()
{
std::cout<<id<<std::endl;
}

template
class C
{
public:
virtual void foo();
};

void C::foo()
{
std::cout<<0<<std::endl;
}

void func(C *arg)
{
arg->foo();
}

int main()
{
C C1;
C C2;
C C3;

func(&amp;C1);
func(&amp;C2);
func(&amp;C3);
return 0;

}
[/cpp]