四月杂记

最近一直在纠结是否应该写点什么,哪怕是泛泛而谈甚至无病呻吟也好。但是深受完美主义之毒及拖延之症困扰,我始终觉得没有实质内容的文字该和天朝宪法一样无用。故而打算述之往事,以鉴来者。

有话言曰:在无聊的时候看看旧时记下的文字,感慨那谁都曾傻逼过的日子。这TM才是生活。

PART 0:

四月中旬恰逢世界读书日,各大网上商城也纷纷借机大作活动。作为“不差钱”典范的京东再一次祭出必杀技:满300减100。不过作为重度bibliomania患者,我还是比较乐意上钩的。和几个同学商量了下,凑了一个302一个304.8的单子。也就是说,此次活动,我用近似 75%*60% = 45% 的折扣率买了几本书,从折扣率上来说,倒也没有丢大温州的脸-.-

买的几本书分别是:

  • D.E.Knuth的研究之美。中英双语,挺薄的一本书
  • 计算机体系结构:向量化研究方法 Stanford校长写的书,最新的英文版
  • 程序员的自我修养 其实老早就想看这书了,不过我估计买来之后还是得屯着一段时间…
  • C++语言的演化 这书其实是用来单子的。因为我不太喜欢看翻译的书。不过翻了下评论,似乎裘宗燕的翻译还是不错的..

说完了买书,说说看书好了…

这个月看掉了差不多两本书(差不多…-_-),一本是我的第一本C++。这是给徐总推荐的。不过我估计徐总以后也碰不到C++…

这书的确浅显易懂,适合入门。较之 Essential C++也丝毫不落下风。尤其其中还穿插了不少经验之谈以及最新的C++0x标准。

不过这书也不是没有缺点:一个是作者推荐了Hungarian Notation。对于现在来说,H-N几乎是完全没必要的,而且对于C++来说,明显是个累赘。我在很久之前,也写过这方面的文章。现在,我几乎是没有用H-N了,包括p,psz一类指针前缀。

不过H-N可能有个不明显的好处,就是在VAX下定位变量比较快…

另一个缺点是,此书太专于MSVC一个编译器,没有考虑其他编译器环境。不过总体上,瑕不掩瑜,这本书还是一本非常值得阅读的入门书籍。

看完的第二本书是 The Tao of Programming。在图书馆偶然发现的这本书,比较令人欣慰的是,这书是中英双语的,不用再麻烦我自己去找原文了。

总体而言,这本书是非常考验读者的经验和理解能力。每条aphorism式的小段子都透露着不大不小的设计思想。不过这本书附带的中文翻译实在是差的不能再差了。。网上有更优雅的翻译,推荐阅读。

PART 1:

严格来说,这不是发生在四月份的事情,不过也被我算入了四月假期中。

在五月的第一天,去天津森海塞尔的行货店花490.00RMB买了HD419。总体而言,佩戴舒适度不错,而且音质尚佳。

鉴于高度强迫症,回来后,立马把电脑里的歌曲换成了FLAC无损格式…嗯啊,撸着真爽真舒服…除了有点压镜框之外。

PART 2:

四月间,共看完了10部电影/美剧,小小的做下一句话影评。(似乎现在我懒得都已经不写影评了..)

  1. Game of Throne Season 1: 可能是迄今最好的奇幻史诗级影片。从剧本到人物刻画堪称一流中的一流
  2. Ferris Bueller’s Day Off/跷课天才: 宣传青年个性的反传统影片。中国青少年看着应该会很high
  3. Gone Baby Gone/失踪宝贝:人性,道德与真相的对决。编剧是Ben Affleck,主演是Casey Affleck
  4. The Negotiator/王牌对王牌:经典的警匪片,Kevin Spacy V.S Samuel Jackson。与之相比,港片弱暴了
  5. Distric 9/第九区:奇特的拍摄手法。影片背后的政治意味颇浓
  6. White Collar Season1:对于像我这样逆着过来看的人而言,第一季充满了潜台词
  7. 300/斯巴达300勇士:与历史符合的还行,特效中等。让人受不了的是充斥的CG片段..另外,我怀疑风云2的导演是受到这片子启发
  8. Collage Road Trip/大学之旅:美国社会存在什么都不奇怪,包括代沟
  9. 名侦探柯南:贝克街的亡灵 剧场版中最好的一部。除了结尾烂之外,几乎没有什么缺点。
  10. Drive/亡命驾驶:一个司机与已婚少妇之种种..剧本太弱,节奏反传统。

唔,其实差不多了,我都不想写了,到此结个尾吧。不爽要打别打脸。。

, ,

9 Comments

离散算法小结课后习题笔记

难得为课后习题做一次笔记,大致原因在于,某些习题虽然简单,但是却非常有意思,值得思考。

Q1:若有一个包含n个随机排列的元素的表,求出表中所有出现两次以上的元素

很显然,直接的暴力搜索不仅效率低,还稍加有点烦(要处理重复添加的问题)。

一个比较容易想到的算法是对每个元素进行统计,但是这要求高效率的搜索,一般需要依赖于Search Tree或者Hash Table之类的数据结构,实现起来稍加繁琐。

简单而又有效地方法是:先对数据进行排序,这需要O(nlogn)的渐进时间。而有序在很多情况下,是一个非常重要的推导线索。在这个题目中,直接表现为:重复的元素都是作为一块出现的

这给了我们一个不错的思路:线性扫描元素,并统计相同元素出现的次数。当发生元素切换时,判断元素重复的次数。

如此,我们只需要额外的Θ(n)的渐进时间即可完成题目要求。虽然事先需要O(nlogn)的排序时间,但是很多情况下,输入数据可能就是有序的,这种情况下,Duplicate算法可以很优雅并且高效的解决问题。

// be sure that elements in src are sorted
void Duplicate(const vector<int>& src, vector<int>& dup)
{
	int count = 1;
	int val = src[0];
	for (unsigned int i = 1; i < src.size(); ++i)
	{
		if (val == src[i])
		{
			++count;
		}
		else
		{
			if (count > 2)
			{
				dup.push_back(val);
			}

			count = 1;
			val = src[i];
		}
	}
}

Q2:给出一个算法,求出非降序整数序列中所有的众数

所谓众数(mode),指的是在序列中出现次数最多的那个数。

看到这个定义,再联系上面的题目,是不是觉得马上就要出结果了呢?

首先元素的有序性免去了排序的操作,其次,我们只要遵循算法1的思路,分别记录迄今最大出现次数maxCount和当前元素出现次数count,接着在元素切换时作相应的处理即可。
Read the rest of this entry »

, ,

1 Comment

Huffman-Code生成Demo

自从算法课申请免听后,好长时间没去上课了,连老师布置什么作业都不知道…还是前几天才知道周六要交一份huffman-code上机报告。恰好最近又打算山寨下Davelv曾经写过的基于huffman-code的compression,于是周五抽了时间写了一个编码生成的demo。

huffman-code是一种经典的基于贪心策略的信息压缩编码,又称前缀编码(prefix-code)。huffman算法首先扫描文件,统计出各字符的频数,并以此为权(weight),为每一个字符创建一个树根节点,从而形成一个森林。接着每次从森林中选取权最小的两棵树,合并。直至只剩下一颗树,即huffman tree。

huffman tree保证出现频率最高的字符所使用的编码最短。

为了减少工作量,用了C++ STL的几个容器,e.g. priority_queue,pair,vector和string。并且掠过了字符频数统计,仅实现了构建huffman tree和生成编码的接口

struct Node
{
	char c;
	unsigned int weight;
	Node* left;
	Node* right;
};
Node* ConstructHuffmanTree(const Node leaves[], int count);
void GenerateHuffmanCode(const Node* root, vector<pair<char, string> >& code, char* codeBuff, int depth);
void DestroyHuffmanTree(Node* root);
#define pop_que(x) x.top();x.pop()

int _tmain(int argc, _TCHAR* argv[])
{
	using namespace std;

	Node t[] = {{'a', 10}, {'b', 9}, {'c', 8}, {'d', 7}, {'e', 6}, {'f', 5},
				{'g', 4}, {'h', 3}};
	vector<pair<char, string> > dictCode;
	const int CODE_BUFF_SIZE = 20;
	char buf[CODE_BUFF_SIZE];

	Node* root = ConstructHuffmanTree(t, _countof(t));
	GenerateHuffmanCode(root, dictCode, buf, 0);
	DestroyHuffmanTree(root);

	for (auto iter = dictCode.cbegin(); iter != dictCode.cend(); ++iter)
	{
		cout<<iter->first<<" : "<<iter->second<<endl;
	}

	_getch();
	return 0;
}

// functor
struct CmpNode
{
	bool operator() (const Node* ele1, const Node* ele2)
	{
		return ele1->weight > ele2->weight;
	}
};

Node* ConstructHuffmanTree(const Node leaves[], int count)
{
	using namespace std;

	// copy the root of each character into the priority queue
	// in the order of weight
	priority_queue<Node*, vector<Node*>, CmpNode> prioRoot;
	for (int i = 0; i < count; ++i)
	{
		Node* tmpLeaf = new Node;
		*tmpLeaf = leaves[i];
		prioRoot.push(tmpLeaf);
	}

	// construct huffman tree
	while (prioRoot.size() >= 2)
	{
		Node* child1 = pop_que(prioRoot);
		Node* child2 = pop_que(prioRoot);
		Node* newRoot = new Node;
		assert(newRoot != NULL);

		newRoot->weight = child1->weight + child2->weight;
		newRoot->c = 0;
		newRoot->left = child1;
		newRoot->right = child2;

		prioRoot.push(newRoot);
	}

	return prioRoot.top();
}

void DestroyHuffmanTree(Node* root)
{
	if (!root)
	{
		return;
	}

	Node* left = root->left;
	Node* right = root->right;
	delete root;

	DestroyHuffmanTree(left);
	DestroyHuffmanTree(right);
}

void GenerateHuffmanCode(const Node* root, vector<pair<char, string> >& code,
						char* codeBuff, int depth)
{
	// leaf node
	if (!root->left && !root->right)
	{
		codeBuff[depth] = '\0';
		code.push_back(make_pair(root->c, string(codeBuff)));
		return;
	}

	codeBuff[depth] = '0';
	GenerateHuffmanCode(root->left, code, codeBuff, depth + 1);
	codeBuff[depth] = '1';
	GenerateHuffmanCode(root->right, code, codeBuff, depth + 1);
}

下一步的计划是,打算过段时间,从头完整实现下数据压缩,估计会在每一个阶段,写一篇Post扯下进度~

, , ,

2 Comments

这两天写的最近点算法

上帝告诫我们不要在不想写blog的时候写blog..

前两天清明假期时,看到Data structures and algorithm analysis中的最近点(closest-pair)问题,一时手痒,遂自己实现了下。

注意:这篇不是详细的问题算法描述分析文章,如开场白所说,最近没这个心情。。

最近点问题是分治算法的一个经典应用,其目的是求解一个平面上点集中最近的两个点的距离。主要的分治策略是设想有一条垂线将平面点集分成左右两部分(子问题),并递归的求解左右两部分的最近距离以及在合并时计算垂线中间区域那块的最近距离。

该算法可以在O(nlogn)的运行时间内找到最近距离,具体的细节在此不再阐述,有兴趣的可以翻阅任何一本算法分析书籍,或者有耐心并且喜欢听我扯淡的童鞋可以等我未来写一篇关于最近点的日志:-)

这里主要讲讲实现问题,这可是大部分书上都没有写的。而且根据我的实际实现,这个算法实现起来略微繁琐。

首先我们需要用点结构来保存点的坐标,而算法又要求我们需要同时维护两个分别按照x和y坐标排序的点列表,为了提高性能,我使用点结构指针的列表而不是将点复制一遍。当然,我们还是需要复制一遍点的指针。并且,二级指针在实际操作时的繁琐程度绝对比你考试时要大得多。如果可以使用C++的STL容器,代码应该会简洁许多。

由于分治合并需要对y进行三次扫描分拣,内存开销稍显频繁。如果在算法开始时就分配一块内存池操作应该会获得不错的性能提升。这点可以参照我此前写过的Mergesort的优化。

另外一个点是基本情况的处理。很不巧的是,Weiss的书上并没有提到对于基本情况应该如何处理,于是我自己设计了基本情况。大致如下:
(1). 如果子问题恰好有两个点,则直接计算距离返回
(2). 如果子问题只有一个点,返回哨兵值。实现中我用的是INT_MAX.

我想其实也可以设置一个阈值,比如5,小于这个数的子问题直接枚举求解,避免过度递归的栈开销。

为了更方便的使用,在算法之上包装了一层接口。

个人实现代码如下:

struct Point
{
	int x;
	int y;
};

void CopyAddressofPoint(Point ** pptDes, const Point* ptSrc, int count)
{
	for (int i = 0; i < count; ++i)
	{
		pptDes[i] = const_cast<Point*>(&ptSrc[i]);
	}
}

int cmpX (const void* ele1, const void* ele2)
{
	const Point* p1 = *reinterpret_cast<const Point**>(const_cast<void*>(ele1));
	const Point* p2 = *reinterpret_cast<const Point**>(const_cast<void*>(ele2));

	return (p1->x - p2->x);
}

int cmpY (const void* ele1, const void* ele2)
{
	const Point* p1 = *reinterpret_cast<const Point**>(const_cast<void*>(ele1));
	const Point* p2 = *reinterpret_cast<const Point**>(const_cast<void*>(ele2));

	return (p1->y - p2->y);
}

#ifdef _DEBUG
void print(Point** ary, int count)
{
	for (int i = 0; i < count; ++i)
	{
		wcout<<ary[i]->x<<L" ";
	}
}
#endif // _DEBUG

int Distance(const Point* px, const Point* py)
{
	return ((px->x - py->x) * (px->x - py->x) +
			(px->y - py->y) * (px->y - py->y));
}

int CheckMidStrip(const Point* ptInStrip[], int count, int det)
{
	int dCMin = det;

	for (int i = 0; i < count; ++i)
	{
		for (int j = i + 1; j < count; ++j)
		{
			int diffY = abs(ptInStrip[i]->y - ptInStrip[j]->y);
			if (diffY > dCMin)
			{
				break;
			}
			else if (Distance(ptInStrip[i], ptInStrip[j]) < dCMin)
			{
				dCMin = Distance(ptInStrip[i], ptInStrip[j]);
			}
		}
	}

	return dCMin;
}

int Find(Point* ptX[], int xL, int xR, Point* ptY[], int ptYcount)
{
	if (xL >= xR)
	{
		return INT_MAX;
	}
	else if (xL + 1 == xR)
	{
		return Distance(ptX[xL], ptX[xR]);
	}

	/* {xL...midLine} is PL and {midLine+1...xR} is PR */
	int midLine = xL + ((xR - xL) >> 1);
	Point** ptYLeft = new Point*[midLine - xL + 1];
	Point** ptYRight = new Point*[midLine - xL + 1];
	assert(ptYLeft != NULL && ptYRight != NULL);
	int ptYLeftCount = 0;
	int ptYRightCount = 0;
	int midX = ptX[midLine]->x;

	// scan the Y list sequentially to partition
	for (int i = 0; i < ptYcount; ++i)
	{
		if (ptY[i]->x <= midX)
		{
			ptYLeft[ptYLeftCount++] = ptY[i];
		}
		else
		{
			ptYRight[ptYRightCount++] = ptY[i];
		}
	}

	// slove recursively
	int dL = Find(ptX, xL, midLine, ptYLeft, ptYLeftCount);
	int dR = Find(ptX, midLine + 1, xR, ptYRight, ptYRightCount);
	int det = min(dL, dR);

	delete [] ptYLeft;
	delete [] ptYRight;
	ptYLeft = ptYRight = NULL;

	// pick up the points in order of y coordinates that in strip
	Point** ptYInStrip = new Point*[ptYcount];
	int countInStrip = 0;

	for (int i = 0; i <ptYcount; ++i)
	{
		if (ptY[i]->x >= midX - det && ptY[i]->x <= midX + det)
		{
			ptYInStrip[countInStrip++] = ptY[i];
		}
	}

	// compute points in the strip
	int dC = CheckMidStrip(const_cast<const Point**>(ptYInStrip), countInStrip, det);

	delete [] ptYInStrip;
	ptYInStrip = NULL;

	return min(dC, det);
}

double FindClosestPoint(const Point* pt, int count)
{
	// Use ptrs to the points instead of using points directly
	Point** ptSortByX = new Point*[count];
	Point** ptSortByY = new Point*[count];
	assert(ptSortByX != NULL && ptSortByY != NULL);
	CopyAddressofPoint(ptSortByX, pt, count);
	CopyAddressofPoint(ptSortByY, pt, count);

	qsort(ptSortByX, count, sizeof(Point*), cmpX);
	qsort(ptSortByY, count, sizeof(Point*), cmpY);

	int distance = Find(ptSortByX, 0, count - 1, ptSortByY, count);

	// do it later
	delete [] ptSortByX;
	delete [] ptSortByY;

	return sqrt(static_cast<double>(distance));
}

本来感觉像我这样拿C++当C来描述算法的可能会遭天谴…但是看到Sedgewick拿Java结构化的描述算法,我想我这点罪过上帝应该会予以宽恕的:-)

, , ,

2 Comments

查找两个有序数组的中位数

这题原来是算法课的作业,不算难,但是却可以作为算法优化的一个不错的例子。

题目描述:有两个大小均为n的有序数组A和B,求由两个数组元素组成的有序数列的中位数

思路1:由于题目的本质是求两个数组合并后的有序数组的中位数,很容易想到的是利用MergeSort的Merge过程,将两个数组合并成一个,再求出中位数即可。

算法的时间复杂制度是Θ(n),缺点是需要额外2n的空间。

PS:由于序列是偶数,所以中位数一定是中间两个数的均值,故范围绘制应该为浮点类型

double FindMedian1(int* s1, int* s2, int n)
{
	int* tempAry = new int[2*n];
	int* p = tempAry;
	int l = 0, r = 0;

	while (l < n && r < n)
	{
		*p++ = s1[l] < s2[r] ? s1[l++] : s2[r++];
	}

	while (l < n)
	{
		*p++ = s1[l++];
	}

	while (r < n)
	{
		*p++ = s2[r++];
	}

	double median = (static_cast<double>(tempAry[n-1]) + tempAry[n]) / 2;
	delete [] tempAry;
	return median;
}

思路2:如果说思路I的线性时间复杂度尚可接受,那么其正比于输入规模的空间要求就有点让人不爽。

事实上,完全没有必要使用这个额外的空间。因为我们的目标不是合并两个有序数组,而是找到两个数组合并后的中间两个数。

所以我们只需要借助Merge,在其中增加索引计数即可以解决问题。
Read the rest of this entry »

, ,

7 Comments

Windows Programming笔记:Keyboard

0.Keyboard Basic

Windows并不会将用户的按键消息马上置入应用程序的消息队列,而是将其保存在系统消息队列(system message queue)中。系统消息队列由Windows维护,用户存储来自键盘和鼠标的用户输入信息。仅当程序完成前一条用户输入消息,Windows才会从系统消息队列中取出一条消息,置于应用程序的消息队列中。

此种做法和同步有关。因为用户敲击键盘的速度可能比程序处理按键的速度要快,而某些特定的按键可能导致窗体焦点的改变。如果直接将按键消息置入程序消息队列,那么在焦点改变后,应该送往新窗体的按键消息将会继续传送到原来的窗口。

对于产生可显示字符的组合按键,Windows会向程序发送按键消息和字符消息。对于不产生字符的按键,Windows只产生按键消息。

1.Keystroke Messages

按键时,Windows会产生WM_KEYDOWN或者WM_SYSKEYDOWN消息。松开按键时,相应地会产生WM_KEYUP或WM_SYSKEYUP消息。

可以利用GetMessageTime来获取按键消息的相对时间。

包含Alt的组合键会产生WM_SYSKEYDOWN和WM_SYSKEYUP消息。应用程序通常忽略这两个消息,并将其转交给DefWindowProc函数,因为Win需要自己处理这些消息。如果应用程序需要捕获这两个消息,则处理后仍然需要将其转发给DefWindowProc,以便Win继续处理。

对于WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP消息,wParam保存虚拟键码(Virtual Key Code),以指示那个键被按下或被释放。

32bit的lParam被分为6个字段,分别为:

重复计数:消息所表示的击键数目。大多数情况下是1

OEM扫描码:键盘硬件产生的代码,Windows程序几乎是忽略OEM扫描码,除非程序依赖键盘键位分布

扩展键标记:如果击键结果来自IBM键盘的附加键,则标记为1

内容代码:如果在击键的同时按下Alt,则内容代码为1.WM_SYSKEYUP和WM_SYSKEYDOWN消息的此键位始终为1,而WM_KEYUP和WM_KEYDOWN消息的此键位始终为0。但是存在两种例外情况:
如果窗口最小化,则不具有输入焦点。此时所有的按键都将产生WM_SYSKEYUP和WM_SYSKEYDOWN消息。如果Alt未按下,则此字段为0.
某些非英语的键盘上,某些字符通过shift、ctrl或Alt同另外一个键产生。此时,内容代码为1,但消息不是WM_SYSKEYUP和WM_SYSKEYDOWN消息。

先前状态:若键之前处于释放状态,则此状态为0,否则为1.WM_KEYUP和WM_SYSKEYUP消息的此字段总是1.WM_KEYDOWN和WM_SYSKEYDOWN消息的此字段可能是0或1.若为1,则表明重复击键所发出的消息。

转换状态:如果键正在被按下,状态为0.若正在被释放,状态为1.WM_KEYUP和WM_SYSKEYUP消息的此字段为1


利用GetKeyState可以检查在处理消息时,目标键是否被按下。但是GetKeyState并不是反映按键的实时状态。

2.Character Messages

如果击键消息是WM_KEYDOWN或WM_SYSKEYDOWN,且击键与转移状态组合产生了一个字符,TranslateMessage把字符消息放入程序的消息队列,且被放在击键消息之后。GetMessage可以从消息队列中获取此字符消息。

字符消息可以分成四类:WM_CHAR、WM_DEADCHAR、WM_SYSCHAR和WM_SYSDEADCHAR。前二者来自WM_KEYDOWN,后二者来自WM_SYSKEYDOWN。

四类字符消息中的lParam和产生此字符码的击键消息中的lParam相同。但是wParam不是虚拟键代码,而是ANSI/Unicode字符码

如果需要读取输入到窗口的键盘字符,就处理WM_CHAR。如果要读取光标键、功能键.etc则处理WM_KEYDOWN。一般更偏向将Tab、回车、空格和Esc看作控制字符,在WM_CHAR中处理。

3.The Caret

程序的一个消息队列仅能支持一个caret,所以如果有多个窗口,则必须有效地共享一个Caret。

仅当窗口具有输入焦点时,caret的现实才有意义。故一般在处理WM_SETFOCUS时调用CreateCaret创建Caret,在WM_KILLFOCUS时调用DestroyCaret消除caret.

CreateCaret创建后,插入符号是隐藏的,需要调用ShowCaret使之可见。而当窗口处理非WM_PAINT时需要在窗口内绘制,必须调用HideCaret隐藏,在结束后显示。HideCaret具有叠加效果。

, ,

3 Comments

Best Of Me —— Daniel Powter

谨以此歌纪念那些年逝去的或多或少的激情

made by tobin bell
I was made in the wrong way
won’t you do me the right way
where your’re gonna be tonight
coz I won’t stay too long

maybe you’re the light for me
when you talk to me it strikes me
won’t somebody help me
coz I don’t feel too strong

Was it something that I said?
was it something that I did ?
or the combination of both that did me in?

You know I’m hoping you’ll sing along
Although it’s not your favorite song
Don’t want to be there when there’s nothing left to say
You know that some of us spin again
And when you do you need a friend
Don’t want to be there when ther’s nothing left for me
And I hate the thought of finally being erased
Baby that’s the best of me
Read the rest of this entry »

, ,

No Comments

君问归期未有期

寒假一过,又是要离开的时候。

我是一个很矛盾的人,有时候过了头便有了精神分裂的倾向。至少我自己是这么觉得的。但是,从反证法的角度上讲,一个精神病人承认自己有病的时候,他又是正常的。

时常重复做一件事情却期待不同结果的发生。又或者,在一个地方呆久了之后便心生厌倦,想换一个地方重头再来,但对于新开始的抵触常伴随鸡毛琐事的涌来而剧增。

而时间就在如此矛盾的循环往复中流过。

我希望我能作出改变,而非在一次次的失望后满怀伤感,难以自拔。

明天的这个时候,我期望自己是坐在宿舍的书桌前,看看书,写写代码。

而历经一个学期的阔别,再见时是一个我期望中的自己。

, , ,

12 Comments

NULL和nullptr

0.NULL的前世今生

对于C和C++程序员来说,一定不会对NULL感到陌生。但是C和C++中的NULL却不等价(别惊讶,这是真的)。

NULL表示指针不指向任何对象,但是问题在于,NULL不是关键字,而只是一个宏定义(macro)。

在C中,习惯将NULL定义为void*指针值0:

#define NULL (void*)0

但同时,也允许将NULL定义为整常数0

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.55) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

在C++中,NULL却被明确定义为整常数0:

#define NULL 0

A null pointer constant is an integral constant expression (5.19) rvalue of integer type that evaluates to zero.

导致不同定义的根本原因是:隐式类型转换(implicit conversion)。

C的定义使得存在void*到0的隐式类型转换,而宏观表现则是,void*指针可以直接赋值给其他的指针类型

int* ptr = 0;
int* pptr = (void*)0; // both are legal in c

但是C++却不支持void*到其他类型的隐式转换,所以上面的代码在C++中并不能全部通过编译。

1.C++的解释

为什么C++在NULL上选择不完全兼容C?原因和C++的重载函数有关。

C++通过搜索匹配参数的机制,试图找到最佳匹配(best-match)的函数,而如果继续支持void*的隐式类型转换,则会带来语义二义性(syntax ambiguous)的问题。

考虑下面两个重载函数:

void foo(int i);
void foo(char* p)

foo(NULL); // which is called?

编译器同时找到了两个最佳匹配,以至于连编译器都不知道选择哪个。

为了消除混乱,C++才会将NULL定义为整常数0。

但是,这么做又带来了另一个问题:NULL和整型发生了重叠。

2.C++的选择

为了彻底解决NULL带来的问题,C++0x标准引入了一个新的关键字:nullptr。

nullptr是一个很神奇的关键字,他被定义为std::nullptr_t类型,这是一个泛指针类型(这个术语是我自己加的-.-),支持到其他指针类型的自动转换。并且不支持到除了bool类型以外的整型的转换。

由于nullptr具有类型,且支持到任何兼容的指针类型的转换,所以对于上述重载函数的调用,编译器会很明确的选择第二个函数。

不过,引入新关键字并不代表解决了一切问题。其代价也很明显:向后兼容性/习惯性和编译器的支持程度。

C++0x中保留了原来的NULL,所以旧的代码也可以通过编译。并且为了和C保持最大程度的兼容,在一段时间内,NULL都应该是支持的。

至于程序员对于nullptr的接受程度,那只能另当别论。

对于编译器,Visual Studio 2010已经开始支持C++0x中的大部分特性,自然包括nullptr。而VS2010之前的版本,都不支持此关键字。

Codeblocks10.5附带的G++ 4.4.1不支持nullptr,升级为4.6.1后可支持nullptr(需开启-std=c++0x编译选项)

其余编译器对于nullptr的支持就不是太清楚了。有需要的可自行翻阅Wiki。

考虑到目前的编译环境,用nullptr全面代替NULL不太现实,不过可以在文件头加入说明或使用预编译。

, , , ,

15 Comments

一个常见的概率模型

考虑如下问题:

某个大学教授以随机评判论文而出名。对于每篇论文,教授所给的评分是均匀的分布在集合{A,A-,B+,B,B-,C+}里。那么,为使每个评分等级都至少有一篇论文,应该提交大概多少篇论文?

这道题是MIT所用的教材Introduction to Probability中的一道习题。不仅解法非常巧妙,而且本质模型也很容易运用到生活中。

很显然,提交的论文数应该是一个期望值(上帝都没法保证所谓的“万年难得一遇”不会发生)。问题最后应该转化为一个概率期望的求解。

首先设X为最后提交的论文数,并且假设:当新提交的论文等级之前还未给出,则表示一次成功。即,我们将论文提交事件转化为伯努利试验

再假设Xi为第i次成功和第i+1次成功间所提交的论文数,则有

因为第一次提交论文一定成功,所以没有X0

并且容易发现,每一个随机变量Xi,都服从概率p=(6-i)/6的几何分布。

所以可以得到:

所以,大概提交15篇论文就可以使得每个等级都至少有一篇。

这个问题很容易可以模型化:

假设有一个事件序列,每个事件都是独立同分布,那么至少要进行多少次试验,才可以使得每个事件都至少发生一次?

其实也不难看出,子事件发生的概率,只影响几何分布的概率。

, , ,

No Comments