2010-12-13

我的博客好久没有更新了!下次再来!2005年申请的哦!5年了!

2007-04-02

http://video.banma.com/asp/

2007-03-21
C语言之精华总结
作者: 风云浪子   查看次数: 16   发表时间: 2007/3/1 18:35  【论坛浏览】
从研究生二年纪开始学习计算机也差不多两年了,一路走来,有很多的收获,也有不少的遗憾,现在正好有一段闲暇,就想对走过的路留下一些足迹,回忆。每个人都有自己不同的人生,说到这里,就是程序人生了,歌德在《浮士德》中说过:“如果不曾在悲哀中咀嚼过面包,不曾在哭泣中等待过明天,这样的人就不知道你——天的力量。”所以我想记下一些带给我悲哀,带给我哭泣的程序人生。其实学习计算机的基础课程是非常重要的,离散数学,编译原理,操作系统,形式语言……,如果你认真走过了这些路,在以后的日子你会发现你的路会越走越宽,以前的努力和汗水会不断的给你灵感,给你支持,给你前进的武器和勇气。你会发现以后取得的很多成就,不过是朝花夕拾而已!
对于程序语言我喜欢的是C++,它能带给你别的语言无法给予你的无上的智力快感,当然也会给你一门语言所能给你的魔鬼般的折磨。其实Java,C#,Python语言也非常的不错,我也极为喜欢。它们都是非常成功的语言,我从来就不愿意做某一种语言的盲目信仰者,每种语言都有它成功的地方,失败的地方,都有它适合的地方,不如意的地方。所以每一次看到评价语言的文章,我看看,但从来不会发言。
C++的前世是C,而且C所留下的神秘以及精简在C++中是青出于蓝而胜于蓝!C所带给人的困惑以及灵活太多,即使一个有几年经验的高段C程序员仍然有可能在C语言的小水沟里翻船。不过其实C语言真的不难,下面我想指出C语言中最神秘而又诡谲多变的四个地方,它们也继续在C++语言中变幻莫测。
指针,数组,类型的识别,参数可变的函数。
一.指针。
它的本质是地址的类型。在许多语言中根本就没有这个概念。但是它却正是C灵活,高效,在面向过程的时代所向披靡的原因所在。因为C的内存模型基本上对应了现在von Neumann(冯·诺伊曼)计算机的机器模型,很好的达到了对机器的映射。不过有些人似乎永远也不能理解指针【注1】。
注1:Joel Spolsky就是这样认为的,他认为对指针的理解是一种aptitude,不是通过训练就可以达到的
http://www.joelonsoftware.com/pr … /fog0000000073.html
指针可以指向值、数组、函数,当然它也可以作为值使用。
看下面的几个例子:
int* p;//p是一个指针,指向一个整数
int** p;//p是一个指针,它指向第二个指针,然后指向一个整数
int (*pa)[3];//pa是一个指针,指向一个拥有3个整数的数组
int (*pf)();//pf是一个指向函数的指针,这个函数返回一个整数
后面第四节我会详细讲解标识符(identifier)类型的识别。
1.指针本身的类型是什么?
先看下面的例子:int a;//a的类型是什么?
对,把a去掉就可以了。因此上面的4个声明语句中的指针本身的类型为:
int*
int**
int (*)[3]
int (*)()
它们都是复合类型,也就是类型与类型结合而成的类型。意义分别如下:
point to int(指向一个整数的指针)
pointer to pointer to int(指向一个指向整数的指针的指针)
pointer to array of 3 ints(指向一个拥有三个整数的数组的指针)
pointer to function of parameter is void and return value is int (指向一个函数的指针,这个函数参数为空,返回值为整数)
2.指针所指物的类型是什么?
很简单,指针本身的类型去掉 “*”号就可以了,分别如下:
int
int*
int ()[3]
int ()()
3和4有点怪,不是吗?请擦亮你的眼睛,在那个用来把“*”号包住的“()”是多余的,所以:
int ()[3]就是int [3](一个拥有三个整数的数组)
int ()()就是int ()(一个函数,参数为空,返回值为整数)【注2】
注2:一个小小的提醒,第二个“()”是一个运算符,名字叫函数调用运算符(function call operator)。
3.指针的算术运算。
请再次记住:指针不是一个简单的类型,它是一个和指针所指物的类型复合的类型。因此,它的算术运算与之(指针所指物的类型)密切相关。
int a[8];
int* p = a;
int* q = p + 3;
p++;
指针的加减并不是指针本身的二进制表示加减,要记住,指针是一个元素的地址,它每加一次,就指向下一个元素。所以:
int* q = p + 3;//q指向从p开始的第三个整数。
p++;//p指向下一个整数。
double* pd;
……//某些计算之后
double* pother = pd – 2;//pother指向从pd倒数第二个double数。
4.指针本身的大小。
在一个现代典型的32位机器上【注3】,机器的内存模型大概是这样的,想象一下,内存空间就像一个连续的房间群。每一个房间的大小是一个字节(一般是二进制8位)。有些东西大小是一个字节(比如char),一个房间就把它给安置了;但有些东西大小是几个字节(比如double就是8个字节,int就是4个字节,我说的是典型的32位),所以它就需要几个房间才能安置。
注3:什么叫32位?就是机器CPU一次处理的数据宽度是32位,机器的寄存器容量是32位,机器的数据,内存地址总线是32位。当然还有一些细节,但大致就是这样。16位,64位,128位可以以此类推。
这些房间都应该有编号(也就是地址),32位的机器内存地址空间当然也是32位,所以房间的每一个编号都用32位的二进制数来编码【注4】。请记住指针也可以作为值使用,作为值的时候,它也必须被安置在房间中(存储在内存中),那么指向一个值的指针需要一个地址大小来存储,即32位,4个字节,4个房间来存储。
注4:在我们平常用到的32位机器上,绝少有将32位真实内存地址空间全用完的(232 = 4G),即使是服务器也不例外。现代的操作系统一般会实现32位的虚拟地址空间,这样可以方便运用程序的编制。关于虚拟地址(线性地址)和真实地址的区别以及实现,可以参考《Linux源代码情景分析》的第二章存储管理,在互联网上关于这个主题的文章汗牛充栋,你也可以google一下。
但请注意,在C++中指向对象成员的指针(pointer to member data or member function)的大小不一定是4个字节。为此我专门编制了一些程序,发现在我的两个编译器(VC7.1.3088和Dev-C++4.9.7.0)上,指向对象成员的指针的大小没有定值,但都是4的倍数。不同的编译器还有不同的值。对于一般的普通类(class),指向对象成员的指针大小一般为4,但在引入多重虚拟继承以及虚拟函数的时候,指向对象成员的指针会增大,不论是指向成员数据,还是成员函数。【注5】。
注5:在Andrei Alexandrescu的《Modern C++ Design》的5.13节Page124中提到,成员函数指针实际上是带标记的(tagged)unions,它们可以对付多重虚拟继承以及虚拟函数,书上说成员函数指针大小是16,但我的实践告诉我这个结果不对,而且具体编译器实现也不同。一直很想看看GCC的源代码,但由于旁骛太多,而且心不静,本身难度也比较高(这个倒是不害怕^_^),只有留待以后了。
还有一点,对一个类的static member来说,指向它的指针只是普通的函数指针,不是pointer to class member,所以它的大小是4。
5.指针运算符&和*
它们是一对相反的操作,&取得一个东西的地址(也就是指针),*得到一个地址里放的东西。这个东西可以是值(对象)、函数、数组、类成员(class member)。
其实很简单,房间里面居住着一个人,&操作只能针对人,取得房间号码;
*操作只能针对房间,取得房间里的人。
参照指针本身的类型以及指针所指物的类型很好理解。
小结:其实你只要真正理解了1,2,就相当于掌握了指针的牛鼻子。后面的就不难了,指针的各种变化和C语言中其它普通类型的变化都差不多(比如各种转型)。
二.数组。
在C语言中,对于数组你只需要理解三件事。
1.C语言中有且只有一维数组。
所谓的n维数组只是一个称呼,一种方便的记法,都是使用一维数组来仿真的。
C语言中数组的元素可以是任何类型的东西,特别的是数组作为元素也可以。所以int a[3][4][5]就应该这样理解:a是一个拥有3个元素的数组,其中每个元素是一个拥有4个元素的数组,进一步其中每个元素是拥有5个整数元素的数组。
是不是很简单!数组a的内存模型你应该很容易就想出来了,不是吗?:)
2.数组的元素个数,必须作为整数常量在编译阶段就求出来。
int i;
int a;//不合法,编译不会通过。
也许有人会奇怪char str[] = “test”;没有指定元素个数为什么也能通过,因为编译器可以根据后面的初始化字符串在编译阶段求出来,
不信你试试这个:int a[];
编译器无法推断,所以会判错说“array size missing in a”之类的信息。不过在最新的C99标准中实现了变长数组【注6】
注6:如果你是一个好奇心很强烈的人,就像我一样,那么可以查看C99标准6.7.5.2。
3.对于数组,可以获得数组第一个(即下标为0)元素的地址(也就是指针),从数组名获得。
比如int a[5]; int* p = a;这里p就得到了数组元素a[0]的地址。
其余对于数组的各种操作,其实都是对于指针的相应操作。比如a[3]其实就是*(a+3)的简单写法,由于*(a+3)==*(3+a),所以在某些程序的代码中你会看到类似3[a]的这种奇怪表达式,现在你知道了,它就是a[3]的别名。还有一种奇怪的表达式类似a[-1],现在你也明白了,它就是*(a-1)【注7】。
注7:你肯定是一个很负责任的人,而且也知道自己到底在干什么。你难道不是吗?:)所以你一定也知道,做一件事是要付出成本的,当然也应该获得多于成本的回报。
我很喜欢经济学,经济学的一个基础就是做什么事情都是要花成本的,即使你什么事情也不做。时间成本,金钱成本,机会成本,健康成本……可以这样说,经济学的根本目的就是用最小的成本获得最大的回报。
所以我们在自己的程序中最好避免这种邪恶的写法,不要让自己一时的智力过剩带来以后自己和他人长时间的痛苦。用韦小宝的一句话来说:“赔本的生意老子是不干的!”
但是对邪恶的了解是非常必要的,这样当我们真正遇到邪恶的时候,可以免受它对心灵的困扰!
对于指向同一个数组不同元素的指针,它们可以做减法,比如int* p = q+i;p-q的结果就是这两个指针之间的元素个数。i可以是负数。但是请记住:对指向不同的数组元素的指针,这样的做法是无用而且邪恶的!
对于所谓的n维数组,比如int a[2][3];你可以得到数组第一个元素的地址a和它的大小。*(a+0)(也即a[0]或者*a)就是第一个元素,它又是一个数组int[3],继续取得它的第一个元素,*(*(a+0)+0)(也即a[0][0]或者*(*a)),也即第一个整数(第一行第一列的第一个整数)。如果采用这种表达式,就非常的笨拙,所以a[0][0]记法上的简便就非常的有用了!简单明了!
对于数组,你只能取用在数组有效范围内的元素和元素地址,不过最后一个元素的下一个元素的地址是个例外。它可以被用来方便数组的各种计算,特别是比较运算。但显然,它所指向的内容是不能拿来使用和改变的!
关于数组本身大概就这么多,下面简要说一下数组和指针的关系。它们的关系非常暧昧,有时候可以交替使用。
比如 int main(int args, char* argv[])中,其实参数列表中的char* argv[]就是char** argv的另一种写法。因为在C语言中,一个数组是不能作为函数引数(argument)【注8】直接传递的。因为那样非常的损失效率,而这点违背了C语言设计时的基本理念——作为一门高效的系统设计语言。
注8:这里我没有使用函数实参这个大陆术语,而是运用了台湾术语,它们都是argument这个英文术语的翻译,但在很多地方中文的实参用的并不恰当,非常的勉强,而引数表示被引用的数,很形象,也很好理解。很快你就可以像我一样适应引数而不是实参。
dereferance,也就是*运算符操作。我也用的是提领,而不是解引用。
我认为你一定智勇双全:既有宽容的智慧,也有面对新事物的勇气!你不愿意承认吗?:)
所以在函数参数列表(parameter list)中的数组形式的参数声明,只是为了方便程序员的阅读!比如上面的char* argv[]就可以很容易的想到是对一个char*字符串数组进行操作,其实质是传递的char*字符串数组的首元素的地址(指针)。其它的元素当然可以由这个指针的加法间接提领(dereferance)【参考注8】得到!从而也就间接得到了整个数组。
但是数组和指针还是有区别的,比如在一个文件中有下面的定义:
char myname[] = “wuaihua”;
而在另一个文件中有下列声明:
extern char* myname;
它们互相是并不认识的,尽管你的本义是这样希望的。
它们对内存空间的使用方式不同【注9】。
对于char myname[] = “wuaihua”如下
myname
w
u
a
i
h
u
a
{threadcontent}
对于char* myname;如下表
myname
\|/
w
u
a
i
h
u
a
{threadcontent}
注9:可以参考Andrew Konig的《C陷阱与缺陷》4.5节。
改变的方法就是使它们一致就可以了。
char myname[] = “wuaihua”;
extern char myname[];
或者
char* myname = “wuaihua”;//C++中最好换成const char* myname = “wuaihua”。
extern char* myname;
C之诡谲(下)
三.类型的识别。
基本类型的识别非常简单:
int a;//a的类型是a
char* p;//p的类型是char*
……
那么请你看看下面几个:
int* (*a[5])(int, char*); //#1
void (*b[10]) (void (*)()); //#2
doube(*)() (*pa)[9]; //#3
如果你是第一次看到这种类型声明的时候,我想肯定跟我的感觉一样,就如晴天霹雳,五雷轰顶,头昏目眩,一头张牙舞爪的狰狞怪兽扑面而来。
不要紧(Take it easy)!我们慢慢来收拾这几个面目可憎的纸老虎!
1.C语言中函数声明和数组声明。
函数声明一般是这样int fun(int,double);对应函数指针(pointer to function)的声明是这样:
int (*pf)(int,double),你必须习惯。可以这样使用:
pf = &fun;//赋值(assignment)操作
(*pf)(5, 8.9);//函数调用操作
也请注意,C语言本身提供了一种简写方式如下:
pf = fun;// 赋值(assignment)操作
pf(5, 8.9);// 函数调用操作
不过我本人不是很喜欢这种简写,它对初学者带来了比较多的迷惑。
数组声明一般是这样int a[5];对于数组指针(pointer to array)的声明是这样:
int (*pa)[5]; 你也必须习惯。可以这样使用:
pa = &a;// 赋值(assignment)操作
int i = (*pa)[2]//将a[2]赋值给i;
2.有了上面的基础,我们就可以对付开头的三只纸老虎了!:)
这个时候你需要复习一下各种运算符的优先顺序和结合顺序了,顺便找本书看看就够了。
#1:int* (*a[5])(int, char*);
首先看到标识符名a,“[]”优先级大于“*”,a与“[5]”先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向“(int, char*)”,对,指向一个函数,函数参数是“int, char*”,返回值是“int*”。完毕,我们干掉了第一个纸老虎。:)
#2:void (*b[10]) (void (*)());
b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是“void (*)()”【注10】,返回值是“void”。完毕!
注10:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”。
#3. doube(*)() (*pa)[9];
pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是“doube(*)()”【也即一个指针,指向一个函数,函数参数为空,返回值是“double”】。
现在是不是觉得要认识它们是易如反掌,工欲善其事,必先利其器!我们对这种表达方式熟悉之后,就可以用“typedef”来简化这种类型声明。
#1:int* (*a[5])(int, char*);
typedef int* (*PF)(int, char*);//PF是一个类型别名【注11】。
PF a[5];//跟int* (*a[5])(int, char*);的效果一样!
注11:很多初学者只知道typedef char* pchar;但是对于typedef的其它用法不太了解。Stephen Blaha对typedef用法做过一个总结:“建立一个类型别名的方法很简单,在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头”。可以参看《程序员》杂志2001.3期《C++高手技巧20招》。
#2:void (*b[10]) (void (*)());
typedef void (*pfv)();
typedef void (*pf_taking_pfv)(pfv);
pf_taking_pfv b[10]; //跟void (*b[10]) (void (*)());的效果一样!
#3. doube(*)() (*pa)[9];
typedef double(*PF)();
typedef PF (*PA)[9];
PA pa; //跟doube(*)() (*pa)[9];的效果一样!
3.const和volatile在类型声明中的位置
在这里我只说const,volatile是一样的【注12】!
注12:顾名思义,volatile修饰的量就是很容易变化,不稳定的量,它可能被其它线程,操作系统,硬件等等在未知的时间改变,所以它被存储在内存中,每次取用它的时候都只能在内存中去读取,它不能被编译器优化放在内部寄存器中。
类型声明中const用来修饰一个常量,我们一般这样使用:const在前面
const int;//int是const
const char*;//char是const
char* const;//*(指针)是const
const char* const;//char和*都是const
对初学者,const char*;和 char* const;是容易混淆的。这需要时间的历练让你习惯它。
上面的声明有一个对等的写法:const在后面
int const;//int是const
char const*;//char是const
char* const;//*(指针)是const
char const* const;//char和*都是const
第一次你可能不会习惯,但新事物如果是好的,我们为什么要拒绝它呢?:)const在后面有两个好处:
A. const所修饰的类型是正好在它前面的那一个。如果这个好处还不能让你动心的话,那请看下一个!
B. 我们很多时候会用到typedef的类型别名定义。比如typedef char* pchar,如果用const来修饰的话,当const在前面的时候,就是const pchar,你会以为它就是const char* ,但是你错了,它的真实含义是char* const。是不是让你大吃一惊!但如果你采用const在后面的写法,意义就怎么也不会变,不信你试试!
不过,在真实项目中的命名一致性更重要。你应该在两种情况下都能适应,并能自如的转换,公司习惯,商业利润不论在什么时候都应该优先考虑!不过在开始一个新项目的时候,你可以考虑优先使用const在后面的习惯用法。
四.参数可变的函数
C语言中有一种很奇怪的参数“…”,它主要用在引数(argument)个数不定的函数中,最常见的就是printf函数。
printf(“Enjoy yourself everyday!\n”);
printf(“The value is %d!\n”, value);
……
你想过它是怎么实现的吗?
1. printf为什么叫printf?
不管是看什么,我总是一个喜欢刨根问底的人,对事物的源有一种特殊的癖好,一段典故,一个成语,一句行话,我最喜欢的就是找到它的来历,和当时的意境,一个外文翻译过来的术语,最低要求我会尽力去找到它原本的外文术语。特别是一个字的命名来历,我一向是非常在意的,中国有句古话:“名不正,则言不顺。”printf中的f就是format的意思,即按格式打印【注13】。
注13:其实还有很多函数,很多变量,很多命名在各种语言中都是非常讲究的,你如果细心观察追溯,一定有很多乐趣和满足,比如哈希表为什么叫hashtable而不叫hashlist?在C++的SGI STL实现中有一个专门用于递增的函数iota(不是itoa),为什么叫这个奇怪的名字,你想过吗?
看文章我不喜欢意犹未尽,己所不欲,勿施于人,所以我把这两个答案告诉你:
(1)table与list做为表讲的区别:
table:
——-|——————–|——-
item1 | kadkglasgaldfgl | jkdsfh
——-|——————–|——-
item2 | kjdszhahlka | xcvz
——-|——————–|——-
list:
****
***
*******
*****
That’s the difference!
如果你还是不明白,可以去看一下hash是如何实现的!
(2)The name iota is taken from the programming language APL.
而APL语言主要是做数学计算的,在数学中有很多公式会借用希腊字母,
希腊字母表中有这样一个字母,大写为Ι,小写为ι,
它的英文拼写正好是iota,这个字母在θ(theta)和κ(kappa)之间!
你可以
http://www.wikipedia.org/wiki/APL_programming_language

下面有一段是这样的:
APL is renowned for using a set of non-ASCII symbols that are an extension of traditional arithmetic and algebraic notation. These cryptic symbols, some have joked, make it possible to construct an entire air traffic control system in two lines of code. Because of its condensed nature and non-standard characters, APL has sometimes been termed a "write-only language", and reading an APL program can feel like decoding an alien tongue. Because of the unusual character-set, many programmers used special APL keyboards in the production of APL code. Nowadays there are various ways to write APL code using only ASCII characters.
在C++中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任意数量的函数参数。
在标准C语言中定义了一个头文件专门用来对付可变参数列表,它包含了一组宏,和一个va_list的typedef声明。一个典型实现如下【注14】:
typedef char* va_list;
#define va_start(list) list = (char*)&va_alist
#define va_end(list)
#define va_arg(list, mode)
((mode*) (list += sizeof(mode)))[-1]
注14:你可以查看C99标准7.15节获得详细而权威的说明。也可以参考Andrew Konig的《C陷阱与缺陷》的附录A。
ANSI C还提供了vprintf函数,它和对应的printf函数行为方式上完全相同,只不过用va_list替换了格式字符串后的参数序列。至于它是如何实现的,你在认真读完《The C Programming Language》后,我相信你一定可以do it yourself!
使用这些工具,我们就可以实现自己的可变参数函数,比如实现一个系统化的错误处理函数error。它和printf函数的使用差不多。只不过将stream重新定向到stderr。在这里我借鉴了《C陷阱与缺陷》的附录A的例子。
实现如下:
#include
#include
void error(char* format, …)
{
va_list ap;
va_start(ap, format);
fprintf(stderr, “error: “);
vfprintf(stderr, format, ap);
va_end(ap);
fprintf(stderr, “\n”);
exit(1);
}
你还可以自己实现printf:
#include
int printf(char* format, …)
{
va_list ap;
va_start(ap, format);
int n = vprintf(format, ap);
va_end(ap);
return n;
}
我还专门找到了VC7.1的头文件看了一下,发现各个宏的具体实现还是有区别的,跟很多预处理(preprocessor)相关。其中va_list就不一定是char*的别名。
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
其它的定义类似。
经常在Windows进行系统编程的人一定知道函数调用有好几种不同的形式,比如__stdcall,__pascal,__cdecl。在Windows下_stdcall,__pascal是一样的,所以我只说一下__stdcall和__cdecl的区别。
(1)__stdcall表示被调用端自身负责函数引数的压栈和出栈。函数参数个数一定的函数都是这种调用形式。
例如:int fun(char c, double d),我们在main函数中使用它,这个函数就只管本身函数体的运行,参数怎么来的,怎么去的,它一概不管。自然有main负责。不过,不同的编译器的实现可能将参数从右向左压栈,也可能从左向右压栈,这个顺序我们是不能加于利用的【注15】。
注15:你可以在Herb Sutter的《More Exceptional C++》中的条款20:An Unmanaged Pointer Problem, Part 1arameter Evaluation找到相关的细节论述。
(2)__cdecl表示调用端负责被调用端引数的压栈和出栈。参数可变的函数采用的是这种调用形式。
为什么这种函数要采用不同于前面的调用形式呢?那是因为__stdcall调用形式对它没有作用,被调用端根本就无法知道调用端的引数个数,它怎么可能正确工作?所以这种调用方式是必须的,不过由于参数参数可变的函数本身不多,所以用的地方比较少。
对于这两种方式,你可以编制一些简单的程序,然后反汇编,在汇编代码下面你就可以看到实际的区别,很好理解的!
重载函数有很多匹配(match)规则调用。参数为“…”的函数是匹配最低的,这一点在Andrei Alexandrescu的惊才绝艳之作《Modern C++ Design》中就有用到,参看Page34-35,2.7“编译期间侦测可转换性和继承性”。
后记:
C语言的细节肯定不会只有这么多,但是这几个出现的比较频繁,而且在C语言中也是很重要的几个语言特征。如果把这几个细节彻底弄清楚了,C语言本身的神秘就不会太多了。
C语言本身就像一把异常锋利的剪刀,你可以用它做出非常精致优雅的艺术品,也可以剪出一些乱七八糟的废纸片。能够将一件武器用到出神入化那是需要时间的,需要多长时间?不多,请你拿出一万个小时来,英国Exter大学心理学教授麦克.侯威专门研究神童和天才,他的结论很有意思:“一般人以为天才是自然而生、流畅而不受阻的闪亮才华,其实,天才也必须耗费至少十年光阴来学习他们的特殊技能,绝无例外。要成为专家,需要拥有顽固的个性和坚持的能力……每一行的专业人士,都投注大量心血,培养自己的专业才能。”【注16】
注16:台湾女作家、电视节目主持人吴淡如《拿出一万个小时来》。《读者》2003.1期。“不用太努力,只要持续下去。想拥有一辈子的专长或兴趣,就像一个人跑马拉松赛一样,最重要的是跑完,而不是前头跑得有多快。”
推荐两本书:
K&R的《The C Programming language》,Second Edition。
Andrew Konig的《C陷阱与缺陷》。本文从中引用了好几个例子,一本高段程序员的经验之谈。
但是对纯粹的初学者不太合适,如果你有一点程序设计的基础知识,花一个月的时间好好看看这两本书,C语言本身就不用再花更多的精力了

红客入门教程 http://www.cnhonker.com/bbs/simple/index.php?t42_1.html

        最近看到很多朋友问到如何学习编程这个问题,我想这个问题应该是所有初学者都非常关注的问题了,在论坛上回答了很多也讲了很多,但是总是无法让所有朋友得到一个解答,所以我想写一下自己的经验,希望对大家有所帮助,不过在此先声明,我并非什么高手,我也只是刚刚入门而已,也只是希望通过写一点个人的体会帮助一些和我遇到相同问题的朋友,高手就免看了,以免班门弄斧。
  好了,废话就不多说了。在学习程序之前,我想大家首先应该对程序员这个行业熟悉一下,更加要对编程的语言以及所涉及到的工具有所了解。因为如何过了解这些是很难去给自己一个明确的目标的。所以首先我想在这里先为大家介绍一下这个行业以及一些相关的内容。

        程序员,相信在很多人眼中是一个非常神秘,非常特别又或者说非常有趣的职业。因为在这个行业里面实在出现了太多的英雄,每一个英雄的事迹都足以让我们热血沸腾。但是,又有谁知道在这些英雄的背后,藏着多少辛酸,藏着多少的努力。程序员这个行业并非如大家所想象的那样美好。程序员是一个没有白天黑夜概念的行业,程序员是一个让人筋疲力尽的行业。你们看到的是一些英雄的光辉历史,但是却忽略了大多数程序员的艰辛。所以在这里我奉劝各位,如果你是为了这个行业充满传奇色彩而想加入,如果你是为了这个行业薪水高而想加入,那么请你现在退出吧,因为他不适合你。这个行业需要的是拥有努力、认真、坚持的人。

        讲到如何学习编程,那么必不可少的就要讲到编程语言了,相信大家都知道编程语言有很多种,包括C、C++、BASIC、PASIC、ASP、PHP等等,当中还分为很多不同的领域,所以在学习编程之前一定要为自己定下一个目标,一个自己即将要进入的领域。有些人想进行底层开发,有些人想做网站开发,有些人想做商业软件开发等等,由于计算机的普及,软件行业所涉及的领域也就越来越多,一个人是不可能涉及所有的领域的,所以必须在学习之前给自己一个定位,这个是很重要的,如果没有这个定位的话在未来学习编程的日子里将会很迷茫。
  目标的定位当然不能少了工具的因素,因为不同的领域使用的开发工具也不同,在这里想重新再次郑重的向所有朋友声明一次,不要把开发工具和开发语言混为一谈,语言是编程的基础,而工具是用来辅助开发的,例如VC、VB、DELPHI、BCB等等。每种工具都有各自的优势和缺点,至于每种工具的特点我就不再相信讲了,如果大家是有心学编程的话,我相信你一定会去看看关于这些工具的具体内容的,呵呵,让我偷个懒。还有在这里重要提醒一下大家,不要被现在的流行工具所迷惑,学好根本才是最重要
        前面大概的准备工作已经做完了,对这个行业了解了,给自己定了一个目标后,接下来当然是全力向这个目标出发了。那该怎么做呢?看了很多朋友的帖子,都讲到C语言是程序员必学的语言,如果程序员不学C就不算是真正的程序员,我个人认为这种说法有点偏激,当然,我承认学C是会对你有很大的帮助,但是并非一定要学C,大家之所以这样说,可能是因为C语言的影响太大了。但是你总不能要那些去学网站开发的人一定要去学C吧,呵呵。其实我个人认为,一开始学什么都无所谓,但是无论学什么,一定要努力和坚持,做不到这两点,你就注定会是个失败者。在这里我想讲一下学编程最关键的问题,就是思想。也许这是个很抽象的概念,但是没有办法,编程本身就是个很抽象的东西,呵呵。等你真正领悟到这一点的时候,恭喜你,你已经站在编程的门口了,呵呵,只是门口哦。那么编程的思想又该如何去领悟呢?要领悟其思想,那首先学习语言是必然的,语言是思想的一种体现形式,就像一个人的思想需要语言来表达一样,所以如果连一门语言都无法掌握的人是无法领悟其思想的。但是对于一个人,如果不经常说话,不经常使用语言,他又如何去使用语言来表达自己的思想呢?编程也一样,要领悟其精髓,必须先不断地看,不断地使用,才能在这种过程中慢慢有所体验,当然有的人可能快些,有的人可能慢些,但是不管快慢,能够达到就是进步。

        我记得在我仍然迷茫的时候,不知道自己该如何去学,每天就是机械式的看书写代码,根本都不知道自己是不是真的懂,但是就是这样不断的看不断的照着书写,终于有一天,好象突然开窍一样,一下子就把所有不明白的东西全都弄明白了,这个有点像佛家所说的顿悟,呵呵。我最初以C语言开始学习的,在我开始学C语言到顿悟这一刻,我可以告诉大家,我看C语言的书我一共看了5本不同的版本(不过都是像走马观花一样的看,千万不要学我),直到第5本看到指针那里,我才真正有种明白的感觉,也许我太笨了,相信大家看一本就够了,HOHO。自从那一次后,我接着看C++,学习pascal、basic、汇编这些东西,自己感觉都非常容易看懂,可能这就是所谓的一理通百理通的道理吧。呵呵,不过在这里要讲一下,上面所说的几种语言虽然看书都看过了,也看明白了,但是由于很少使用,现在大部分都还给书本了,呵呵。

       看到这里,包括我在内,我都感觉到自己很了不起,很厉害了。可惜,只有我自己知道,虽然看了那么多,学得也多,写得也不少,可是我却不知道如何运用。来来去去就是写一些书本上的题目,自己却感觉像windows那些程序却不知道如何去写,不知道该怎么办,相信很多人都有和我一样的体会吧,把C语言学了,题目也做了,而且都没有问题,可是却不知道如何去做开发,感觉自己学的东西一点用处都没有。这又是为什么呢?

       在这之前,我一直为这个问题烦恼,后来我终于找到了答案,那就是我所学的,所写的都是DOS应用程序,而我们现在所使用的确实windows操作系统,当然也有使用linux系统的。后来看了一篇别人写的文章后,我才知道我所学的和实际运用相差太远了。现在大部分学习编程语言的书籍上的例题都是基于DOS系统开发的,所以我们所写的程序和windows上所运行的不一样,这就是造成我们迷茫的主要原因。那该怎么办呢?那还用问吗,当然就是去学习windows开发啦。在第一次接触windows开发的时候,我真的是大吃一惊,这完全和我以前所学的很不一样,但是又息息相关,如果没有以前所学的,我根本就看不懂windows开发的内容。在这里给大家简单介绍一下,windows开发和DOS开发的区别吧。大家都知道,DOS是一个单任务执行系统,也就是说在DOS下只能在一个程序运行完成或者中断后才能运行其他的程序,而windows是个多任务系统,当然在本质上windows也只能一个时间内执行一个程序,但是由于其利用CPU的运算能力,让这些微妙的时间让人无法感觉到,再就是windows是一个图形界面系统,拥有着良好的用户界面,不像DOS是个指令界面。所有这一切的区别导致DOS和windows开发上也存在很大的区别。所以现在的程序员首要的任务除了学习语言以外,就是要去学习windows开发机制。如果连这个都不懂,那是肯定开发不出一个好的windows软件的。在这里向所有朋友重点推荐美国人charles Perzold所写的《windows程序设计》第五版,这本书可以说是经典中的经典,学习windows开发必看的书,适合有C语言基础的朋友看,它有多经典我就不想细讲了,总之就是绝对不会让你失望,呵呵。这本书市场价是160元整,分上下两册,如果有的朋友觉得太贵的话,我这里有他的电子版,需要的就找我要吧,随时欢迎大家找我要这本书,呵呵。

       说实话写到这里,我自己都不知道自己写了些什么,好象很乱,呵呵,希望大家能够看得懂吧。看完我介绍的这本书后,而且看懂的话,那么接下来自己该如何走,我相信我就不用再多说了,到那个时候你一定知道自己该如何去学习编程了。上面讲了下我个人是如何学习的,当然还有很多细节地方没有涉及到,不过随时欢迎朋友们跟我一起交流。而且我现在才发现上面所写的是从C语言开始学习的,呵呵,其实我并非要大家向我一样学习,我只是把自己学习的经过大概讲了下,当然还有以后该怎么办没有写,其实我也在摸索中,嘿嘿,我写了那么多废话,无非是想要大家明白几个道理:
第一、     首先要给自己定一个明确的目标。
第二、     分清楚语言和工具的区别。
第三、     把思想放在首位,语言在精不在多,学好了一门语言再去学其他的,就易如反掌了。
第四、     工具是经常被淘汰的,不要被工具所迷惑,坚定自己的信念。
第五、     无论选择做什么系统开发,首先要去了解这个系统,只有了解了这个系统才能在上面为所欲为。
大概也就差不多了,不全或者写得不好的地方请指正,个人发觉全是废话,呵呵,就这么多了吧,欢迎大家跟我一起学习,一起进步,一起交流。当然最后仍然是那句:
  努力+坚持=成功

2007-03-14

同样是一个班出来的学生一个老师讲课学到的东西还不一样,考的分数还不一样,所以学的多少在于个人的悟性和理解能力,没法一概而论。

一,先学习脱壳,我学习的是 

黑基破解班26课,

黑防VIP系列脱壳教程19课,

手动脱壳18篇,

手动脱壳进阶13篇,

牧羊教你手动脱壳11课,

BEST超小子脱壳,至此脱壳部分暂时告于段落;

二,注册码分析,我学习的是 

中国共享破解动画   大概是一百多个动画,

华夏VIP破解班17课,(以上全部是视频课程)   

华夏黑客同盟CHM版(电子书)破解115篇。

由脱壳到爆破再到明码直到算法,当然这只是破解的一小部分,还不包括加密狗的内容。

编程类:

黑基汇编20课,

黑基C语言26课,

acc版C语言21课,(特别强调一下,这个C语言是我认为目前最好的,因为它平均一课时只用4.62兆就压缩了八十多分钟的一堂视频课,全部课程才用74.9兆,真是视频压缩界的冠军,可惜对电脑配置要求较高,建议处理器2.66G,内存512兆观看效果保证流畅。)

我说的这些,都是分文未花,
从网上淘金搜索到的,很多内容现在都还可以下载,大家想学破解这些都是非常好的资料。看雪整理了许多学习破解的资料,我受到启发就开始整理破解方面的动画,

下载地址,在百度中搜索关键字:“黑基破解26”就可以找到网址,从中挑选出可以下载的点击用迅雷就可以下了。
资料网址:

http://my.thysea.com/

黑基C语言特训班

http://blog.sina.com.cn/u/4b8cf3ae010009nw

2007-03-13

http://www.girlpsychology.com.tw/

2007-01-08

Windows 控制台注册表工具 – 版权所有 (C) Microsoft Corp. 1981-2001.  保留所有权繰EG Operation [参数列表]

  Operation  [ QUERY   | ADD    | DELETE  | COPY    |
               SAVE    | LOAD   | UNLOAD  | RESTORE |
               COMPARE | EXPORT | IMPORT ]

返回代码: (除了 REG COMPARE)

  0 – 成功
  1 – 失败

要得到有关某个操作的帮助,请键入:

  REG Operation /?

例如:

  REG QUERY /?
  REG ADD /?
  REG DELETE /?
  REG COPY /?
  REG SAVE /?
  REG RESTORE /?
  REG LOAD /?
  REG UNLOAD /?
  REG COMPARE /?
 

TASKKILL [/S system [/U username [/P [password]]]]
         { [/FI filter] [/PID processid | /IM imagename] } [/F] [/T]

描述:
    这个命令行工具可用来结束至少一个进程。
    可以根据进程 id 或图像名来结束进程。

参数列表:
    /S    system           指定要连接到的远程系统。

    /U    [domain\]user    指定应该在哪个用户上下文
                           执行这个命令。

    /P    [password]       为提供的用户上下文指定
                           密码。如果忽略,提示输入。

    /F                     指定要强行终止
                           进程。

    /FI   filter           指定筛选进或筛选出查询的
                           的任务。

    /PID  process id       指定要终止的进程的
                           PID。

    /IM   image name       指定要终止的进程的
                           图像名。通配符 ‘*’
                           可用来指定所有图像名。

    /T                     Tree kill: 终止指定的进程
                           和任何由此启动的子进程。

    /?                     显示帮助/用法。

筛选器:
    筛选器名      有效运算符                有效值
    ———–   —————           ————–
    STATUS        eq, ne                    运行 | 没有响应
    IMAGENAME     eq, ne                    图像名
    PID           eq, ne, gt, lt, ge, le    PID 值
    SESSION       eq, ne, gt, lt, ge, le    会话编号
    CPUTIME       eq, ne, gt, lt, ge, le    CPU 时间,格式为
                                            hh:mm:ss。
                                            hh – 时,
                                            mm – 钟,ss – 秒
    MEMUSAGE      eq, ne, gt, lt, ge, le    内存使用,单位为 KB
    USERNAME      eq, ne                    用户名,格式为
                                            [domain\]user
    MODULES       eq, ne                    DLL 名
    SERVICES        eq, ne                    服务名
    WINDOWTITLE     eq, ne                    窗口标题

注意: 只有带有筛选器的情况下,才能跟 /IM 切换使用通配符 ‘*’。

注意: 远程进程总是要强行终止,
      不管是否指定了 /F 选项。

例如:
    TASKKILL /S system /F /IM notepad.exe /T
    TASKKILL /PID 1230 /PID 1241 /PID 1253 /T
    TASKKILL /F /IM notepad.exe /IM mspaint.exe
    TASKKILL /F /FI "PID ge 1000" /FI "WINDOWTITLE ne untitle*"
    TASKKILL /F /FI "USERNAME eq NT AUTHORITY\SYSTEM" /IM notepad.exe
    TASKKILL /S system /U domain\username /FI "USERNAME ne NT*" /IM *
    TASKKILL /S system /U username /P password /FI "IMAGENAME eq note*"

1.在运行中输入"%windir%\help\keyshort.chm"就可以查看"Windows 键盘快捷键概述"

Windows 键盘快捷键概述

在 Windows 中工作时,利用快捷键代替鼠标。可以利用键盘快捷键打开、关闭和导航“开始”菜单、桌面、菜单、对话框以及网页。键盘还可以让您更简单地与计算机交互。

单击一个标题或按 TAB 键可以突出显示这个标题,然后按 ENTER 键。

常规键盘快捷键

请按 目的
Ctrl + C 复制。
Ctrl + X 剪切。
Ctrl + V 粘贴。
Ctrl + Z 撤消。
DELETE 删除。
Shift + Delete 永久删除所选项,而不将它放到“回收站”中。
拖动某一项时按 CTRL 复制所选项。
拖动某一项时按 CTRL + SHIFT 创建所选项目的快捷键。
F2 重新命名所选项目。
CTRL + 向右键 将插入点移动到下一个单词的起始处。
CTRL + 向左键 将插入点移动到前一个单词的起始处。
CTRL + 向下键 将插入点移动到下一段落的起始处。
CTRL + 向上键 将插入点移动到前一段落的起始处。
CTRL + SHIFT + 任何箭头键 突出显示一块文本。
SHIFT + 任何箭头键 在窗口或桌面上选择多项,或者选中文档中的文本。
Ctrl + A 选中全部内容。
F3 搜索文件或文件夹。
Alt + Enter 查看所选项目的属性。
Alt + F4 关闭当前项目或者退出当前程序。
ALT + Enter 显示所选对象的属性。
Alt + 空格键 为当前窗口打开快捷菜单。
Ctrl + F4 在允许同时打开多个文档的程序中关闭当前文档。
Alt + Tab 在打开的项目之间切换。
Alt + Esc 以项目打开的顺序循环切换。
F6 在窗口或桌面上循环切换屏幕元素。
F4 显示“我的电脑”和“Windows 资源管理器”中的“地址”栏列表。
Shift + F10 显示所选项的快捷菜单。
Alt + 空格键 显示当前窗口的“系统”菜单。
Ctrl + Esc 显示“开始”菜单。
ALT + 菜单名中带下划线的字母 显示相应的菜单。
在打开的菜单上显示的命令名称中带有下划线的字母 执行相应的命令。
F10 激活当前程序中的菜单条。
右箭头键 打开右边的下一菜单或者打开子菜单。
左箭头键 打开左边的下一菜单或者关闭子菜单。
F5 刷新当前窗口。
BackSpace 在“我的电脑”或“Windows 资源管理器”中查看上一层文件夹。
Esc 取消当前任务。
将光盘插入到 CD-ROM 驱动器时按 SHIFT 键 阻止光盘自动播放。

对话框快捷键

请按 目的
Ctrl + Tab 在选项卡之间向前移动。
Ctrl + Shift +Tab 在选项卡之间向后移动。
Tab 在选项之间向前移动。
Shift + Tab 在选项之间向后移动。
ALT + 带下划线的字母 执行相应的命令或选中相应的选项。
Enter 执行活选项动或按钮所对应的命令。
空格键 如果活选项动是复选框,则选中或清除该复选框。
箭头键 活选项动是一组选项按钮时,请选中某个按钮。
F1 显示帮助。
F4 显示当前列表中的项目。
BackSpace 如果在“另存为”或“打开”对话框中选中了某个文件夹,则打开上一级文件夹。

自然键盘快捷键

在“Microsoft 自然键盘”或包含 Windows 徽标键(Windows 徽标) 和“应用程序”键(菜单建) 的其他兼容键盘中,您可以使用以下快捷键。

请按 目的
Windows 徽标 显示或隐藏“开始”菜单。
 Windows 徽标+ BREAK 显示“系统属性”对话框。
 Windows 徽标+ D 显示桌面。
Windows 徽标 + M 最小化所有窗口。
Windows 徽标 + Shift + M 还原最小化的窗口。
Windows 徽标 + E 打开“我的电脑”。
Windows 徽标 + F 搜索文件或文件夹。
CTRL+ Windows 徽标 + F 搜索计算机。
Windows 徽标 + F1 显示 Windows 帮助。
Windows 徽标 + L 如果连接到网络域,则锁定您的计算机,或者如果没有连接到网络域,则切换用户。
Windows 徽标 + R 打开“运行”对话框。
Windows 徽标 显示所选项的快捷菜单。
Windows 徽标 + U 打开“工具管理器”。

辅助键盘快捷键

请按 目的
右侧 SHIFT 键八秒钟 切换“筛选键”的开和关。
左边的 ALT + 左边的 SHIFT + PRINT SCREEN 切换“高对比度”的开和关。
左边的 ALT + 左边的 SHIFT + NUM LOCK 切换“鼠标键”的开和关。
Shift 键五次 切换“粘滞键”的开和关。
Num Lock 键五秒钟 切换“切换键”的开和关。
Windows 徽标 + U 打开“工具管理器”。

“Windows 资源管理器”键盘快捷键

请按 目的
END 显示当前窗口的底端。
主页 显示当前窗口的顶端。
NUM LOCK + 数字键盘的星号 (*) 显示所选文件夹的所有子文件夹。
NUM LOCK + 数字键盘的加号 (+) 显示所选文件夹的内容。
NUM LOCK + 数字键盘的减号 (-) 折叠所选的文件夹。
左箭头键 当前所选项处于展开状态时折叠该项,或选定其父文件夹。
右箭头键 当前所选项处于折叠状态时展开该项,或选定第一个子文件夹。

注意

  • 必须将密码与用户帐户相关联以确保那些未经授权的访问的安全。如果您没有将密码与用户帐户相关联,那么按 Windows 徽标 + L 不会阻止其他用户访问您的帐户信息。
  • 如果在“辅助功能选项”中打开“粘滞键”,则有些快捷键可能不起作用。
  • 如果您通过“Microsoft 终端服务客户”连接到 Windows,则某些快捷键将会更改。详细信息,请参阅“Microsoft 终端服务客户”的联机文档。