﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-点滴-随笔分类-骗子的软件设计师之路</title><link>http://www.cnitblog.com/charester/category/4440.html</link><description>

</description><language>zh-cn</language><lastBuildDate>Tue, 27 Sep 2011 05:06:33 GMT</lastBuildDate><pubDate>Tue, 27 Sep 2011 05:06:33 GMT</pubDate><ttl>60</ttl><item><title>Linux/Unix环境下的make和makefile详解(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/04/21504.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 04 Jan 2007 06:55:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/04/21504.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21504.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/04/21504.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21504.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21504.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Linux/Unix环境下的make和makefile详解　　 																		　　无论是在Linux还是在Unix环境中，make都是一个非常重要的编译命令。不管是自己进行项目开发还是安装应用软件，我们都经常要用到make或make install。利用make工具，我们可以将大型的开发项目分解成为多个更易于管理的模块，对于一个包括几百个源文件的应用程序，使用make...&nbsp;&nbsp;<a href='http://www.cnitblog.com/charester/archive/2007/01/04/21504.html'>阅读全文</a><img src ="http://www.cnitblog.com/charester/aggbug/21504.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-04 14:55 <a href="http://www.cnitblog.com/charester/archive/2007/01/04/21504.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux操作系统下c语言编程入门(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/04/21503.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Thu, 04 Jan 2007 06:50:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/04/21503.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21503.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/04/21503.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21503.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21503.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: linux操作系统下c语言编程入门								linux操作系统下c语言编程入门 整理编写：007xiong 原文：Hoyt等 								(一)目录介绍 								1)Linux程序设计入门--基础知识 2)Linux程序设计入门--进程介绍 3)Linux程序设计入门--文件操作 4)Linux程序设计入门--时间概念 5)Linux程序设计入门--信号处理 6)Li...&nbsp;&nbsp;<a href='http://www.cnitblog.com/charester/archive/2007/01/04/21503.html'>阅读全文</a><img src ="http://www.cnitblog.com/charester/aggbug/21503.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-04 14:50 <a href="http://www.cnitblog.com/charester/archive/2007/01/04/21503.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何阅读源代码(三)(ZT) </title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21427.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:42:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21427.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21427.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21427.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21427.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21427.html</trackback:ping><description><![CDATA[在parse_record分析完数据之后，做日期的分析，把日志中的月份等数据转换成机器可读（可理解)的数据，并存入到log_rec中去。<br /><br /><br />if ((i&gt;=12)||(rec_min&gt;59)||(rec_sec&gt;59)||(rec_year&lt;1990))<br />{<br />total_bad++; /* if a bad date, bump counter */<br />if (verbose)<br />{<br />fprintf(stderr,"%s: %s [%lu]",<br />msg_bad_date,log_rec.datetime,total_rec);<br />......<br /><br /><br /><br />　　 如果日期，时间错误，则把total_bad计数器增加1，并且打印错误信息到标准错误输出。<br /><br /><br />good_rec = 1;<br />/* get current records timestamp<br />(seconds since epoch) */<br />req_tstamp=cur_tstamp;<br />rec_tstamp=((jdate(rec_day,rec_month,rec_year)-epoch)<br />*86400)+<br />(rec_hour*3600)+(rec_min*60)+rec_sec;<br />/* Do we need to check for duplicate records?<br />(incremental mode) */<br />if (check_dup)<br />{<br />/* check if less than/equal to last record processed */<br />if ( rec_tstamp &lt;= cur_tstamp )<br />{<br />/* if it is, assume we have already<br />processed and ignore it */<br />total_ignore++;<br />continue;<br />}<br />else<br />{<br />/* if it isn't.. disable any more checks this run */<br />check_dup=0;<br />/* now check if it's a new month */<br />if (cur_month != rec_month)<br />{<br />clear_month();<br />cur_sec = rec_sec; /* set current counters */<br />cur_min = rec_min;<br />cur_hour = rec_hour;<br />cur_day = rec_day;<br />cur_month = rec_month;<br />cur_year = rec_year;<br />cur_tstamp= rec_tstamp;<br />f_day=l_day=rec_day; /* reset first and last day */<br />}<br />}<br />}<br />/* check for out of sequence records */<br />if (rec_tstamp/3600 &lt; cur_tstamp/3600)<br />{<br />if (!fold_seq_err &amp;&amp; ((rec_tstamp+SLOP_VAL)<br />/3600<br /><br /><br />　　如果该日期、时间没有错误，则该数据是一个好的数据，将good_record计数器加1，并且检查时间戳，和数据是否重复数据。这里有一个函数，jdate()在主程序一开头我们就遇到了，当时跳了过去没有深究，这里留给读者做一个练习。（提示：该函数根据一个日期产生一个字符串，这个字符串是惟一的，可以检查时间的重复性，是一个通用函数，可以在别的程序中拿来使用）<br /><br /><br />/*********************************************/<br />/* DO SOME PRE-PROCESS FORMATTING */<br />/*********************************************/<br />/* fix URL field */<br />cp1 = cp2 = log_rec.url;<br />/* handle null '-' case here... */<br />if (*++cp1 == '-') { *cp2++ = '-'; *cp2 = ''; }<br />else<br />{<br />/* strip actual URL out of request */<br />while ( (*cp1 != ' ') &amp;&amp; (*cp1 != '') ) cp1++;<br />if (*cp1 != '')<br />{<br />/* scan to begin of actual URL field */<br />while ((*cp1 == ' ') &amp;&amp; (*cp1 != '')) cp1++;<br />/* remove duplicate / if needed */<br />if (( *cp1=='/') &amp;&amp; (*(cp1+1)=='/')) cp1++;<br />while ((*cp1 != ' ')&amp;&amp;(*cp1 != '"')&amp;&amp;(*cp1 != ''))<br />*cp2++ = *cp1++;<br />*cp2 = '';<br />}<br />}<br />/* un-escape URL */<br />unescape(log_rec.url);<br />/* check for service (ie: <a onfocus="this.blur()" href="http://)/" target="_blank"><font color="#000033" size="2">http://)</font></a> and lowercase if found */<br />if ( (cp2=strstr(log_rec.url,"://")) != NULL)<br />{<br />cp1=log_rec.url;<br />while (cp1!=cp2)<br />{<br />if ( (*cp1&gt;='A') &amp;&amp; (*cp1&lt;='Z')) *cp1 += 'a'-'A';<br />cp1++;<br />}<br />}<br />/* strip query portion of cgi scripts */<br />cp1 = log_rec.url;<br />while (*cp1 != '')<br />if (!isurlchar(*cp1)) { *cp1 = ''; break; }<br />else cp1++;<br />if (log_rec.url[0]=='')<br />{ log_rec.url[0]='/'; log_rec.url[1]=''; }<br />/* strip off index.html (or any aliases) */<br />lptr=index_alias;<br />while (lptr!=NULL)<br />{<br />if ((cp1=strstr(log_rec.url,lptr-&gt;string))!=NULL)<br />{<br />if ((cp1==log_rec.url)||(*(cp1-1)=='/'))<br />{<br />*cp1='';<br />if (log_rec.url[0]=='')<br />{ log_rec.url[0]='/'; log_rec.url[1]=''; }<br />break;<br />}<br />}<br />lptr=lptr-&gt;next;<br />}<br />/* unescape referrer */<br />unescape(log_rec.refer);<br />......<br /><br /><br /><br />　　这一段，做了一些URL字符串中的字符转换工作，很长，我个人认为为了程序的模块化，结构化和可复用性，应该将这一段代码改为函数，避免主程序体太长，造成可读性不强和没有移植性，和不够结构化。跳过这一段乏味的代码，进入到下面一个部分---后处理。<br /><br /><br />if (gz_log) gzclose(gzlog_fp);<br />else if (log_fname) fclose(log_fp);<br />if (good_rec) /* were any good records? */<br />{<br />tm_site[cur_day-1]=dt_site; /* If yes, clean up a bit */<br />tm_visit[cur_day-1]=tot_visit(sd_htab);<br />t_visit=tot_visit(sm_htab);<br />if (ht_hit &gt; mh_hit) mh_hit = ht_hit;<br />if (total_rec &gt; (total_ignore+total_bad))<br />/* did we process any? */<br />{<br />if (incremental)<br />{<br />if (save_state()) /* incremental stuff */<br />{<br />/* Error: Unable to save current run data */<br />if (verbose) fprintf(stderr,"%s ",msg_data_err);<br />unlink(state_fname);<br />}<br />}<br />month_update_exit(rec_tstamp); /* calculate exit pages */<br />write_month_html(); /* write monthly HTML file */<br />write_main_index(); /* write main HTML file */<br />put_history(); /* write history */<br />}<br />end_time = times(&amp;mytms);<br />/* display timing totals? */<br />if (time_me' '(verbose&gt;1))<br />{<br />printf("%lu %s ",total_rec, msg_records);<br />if (total_ignore)<br />{<br />printf("(%lu %s",total_ignore,msg_ignored);<br />if (total_bad) printf(", %lu %s) ",total_bad,msg_bad);<br />else printf(") ");<br />}<br />else if (total_bad) printf("(%lu %s) ",total_bad,msg_bad);<br />/* get processing time (end-start) */<br />temp_time = (float)(end_time-start_time)/CLK_TCK;<br />printf("%s %.2f %s", msg_in, temp_time, msg_seconds);<br />/* calculate records per second */<br />if (temp_time)<br />i=( (int)( (float)total_rec/temp_time ) );<br />else i=0;<br />if ( (i&gt;0) &amp;&amp; (i&lt;=total_rec) ) printf(", %d/sec ", i);<br />else printf(" ");<br />}<br /><br /><br /><br />　　这一段，做了一些后期的处理。接下来的部分，我想在本文中略过，留给感兴趣的读者自己去做分析。原因有两点：<br /><br />1、这个程序在前面结构化比较强，而到了结构上后面有些乱，虽然代码效率还是比较高，但是可重用性不够强, 限于篇幅，我就不再一一解释了。 2、前面分析程序过程中，也对后面的代码做了一些预测和估计，也略微涉及到了后面的代码，而且读者可以根据上面提到的原则来自己分析代码，也作为一个实践吧。<br />　　最后，对于在这篇文章中提到的分析源代码程序的一些方法做一下小结，以作为本文的结束。<br /><br />　　分析一个源代码，一个有效的方法是：<br /><br />　　1、阅读源代码的说明文档，比如本例中的README, 作者写的非常的详细，仔细读过之后，在阅读程序的时候往往能够从README文件中找到相应的说明，从而简化了源程序的阅读工作。<br /><br />　　2、如果源代码有文档目录，一般为doc或者docs， 最好也在阅读源程序之前仔细阅读，因为这些文档同样起了很好的说明注释作用。<br /><br />　　3、从makefile文件入手，分析源代码的层次结构，找出哪个是主程序，哪些是函数包。这对于快速把握程序结构有很大帮助。<br /><br />　　4、从main函数入手，一步一步往下阅读，遇到可以猜测出意思来的简单的函数，可以跳过。但是一定要注意程序中使用的全局变量（如果是C程序），可以把关键的数据结构说明拷贝到一个文本编辑器中以便随时查找。<br /><br />　　5、分析函数包（针对C程序），要注意哪些是全局函数，哪些是内部使用的函数，注意extern关键字。对于变量，也需要同样注意。先分析清楚内部函数，再来分析外部函数，因为内部函数肯定是在外部函数中被调用的。<br /><br />　　6、需要说明的是数据结构的重要性：对于一个C程序来说，所有的函数都是在操作同一些数据，而由于没有较好的封装性，这些数据可能出现在程序的任何地方，被任何函数修改，所以一定要注意这些数据的定义和意义，也要注意是哪些函数在对它们进行操作，做了哪些改变。<br /><br />　　7、在阅读程序的同时，最好能够把程序存入到cvs之类的版本控制器中去，在需要的时候可以对源代码做一些修改试验，因为动手修改是比仅仅是阅读要好得多的读程序的方法。在你修改运行程序的时候，可以从cvs中把原来的代码调出来与你改动的部分进行比较(diff命令), 可以看出一些源代码的优缺点并且能够实际的练习自己的编程技术。<br /><br />　　8、阅读程序的同时，要注意一些小工具的使用，能够提高速度，比如vi中的查找功能，模式匹配查找，做标记，还有grep，find这两个最强大最常用的文本搜索工具的使用。<br /><br />　　对于一个Unix/Linux下面以命令行方式运行的程序，有这么一些套路，大家可以在阅读程序的时候作为参考。<br /><br />　　1、在程序开头，往往都是分析命令行，根据命令行参数对一些变量或者数组，或者结构赋值，后面的程序就是根据这些变量来进行不同的操作。<br /><br />　　2、分析命令行之后，进行数据准备，往往是计数器清空，结构清零等等。<br /><br />　　3、在程序中间有一些预编译选项，可以在makefile中找到相应部分。<br /><br />　　4、注意程序中对于日志的处理，和调试选项打开的时候做的动作，这些对于调试程序有很大的帮助。<br /><br />　　5、注意多线程对数据的操作。（这在本例中没有涉及）<br /><img src ="http://www.cnitblog.com/charester/aggbug/21427.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:42 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21427.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何阅读源代码(二)(ZT) </title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21426.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:41:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21426.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21426.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21426.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21426.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21426.html</trackback:ping><description><![CDATA[我们所阅读的这个软件是用来分析日志并且做出统计的，那么这个函数的名字已经告诉了我们，这是一个初始化计数器的函数。简略的看看吧！<br /><br /><br />$ grep init_counters *.h<br />webalizer.h:extern void init_counters();<br />在webalizer.c中找到：<br />void init_counters()<br />{<br />int i;<br />for (i=0;i<br /><br /><br />　　根据在最开始读过的README文件，这个page_type是用来定义处理的页面的类型的。在README文件中，<br /><br /><br />-P name Page type. This is the extension of files you consider to<br />be pages for Pages calculations (sometimes called 'pageviews').<br />The default is 'htm*' and 'cgi' (plus whatever HTMLExtension<br />you specified if it is different). Don't use a period!<br /><br /><br /><br />　　我们在程序中也可以看到，如果没有在命令行中或者config文件中指定，则根据处理的日志文件的类型来添加缺省的文件类型。比如对于CLF文件(WWW日志)，处理html, htm, cgi文件<br /><br /><br />if (log_type == LOG_FTP)<br />{<br />/* disable stuff for ftp logs */<br />ntop_entry=ntop_exit=0;<br />ntop_search=0;<br />}<br />else<br />.....<br />这一段是对于FTP的日志格式，设置搜索列表。<br />for (i=0;i<br /><br /><br />　　清空哈西表，为下面即将进行的排序工作做好准备。关于哈西表，这是数据结构中常用的一种用来快速排序的结构，如果不清楚，可以参考相关书籍，比如清华的&lt;&lt;数据结构&gt;&gt;教材或者&lt;&lt;数据结构的C++实现&gt;&gt;等书。<br /><br /><br />if (verbose&gt;1)<br />{<br />uname(&amp;system_info);<br />printf("Webalizer V%s-%s (%s %s) %s ",<br />version,editlvl,system_info.sysname,<br />system_info.release,language);<br />}<br /><br /><br /><br />　　这一段，是打印有关系统的信息和webalizer程序的信息（可以参考uname的函数说明）。<br /><br /><br />#ifndef USE_DNS<br />if (strstr(argv[0],"webazolver")!=0)<br />{<br />printf("DNS support not present, aborting... ");<br />exit(1);<br />}<br />#endif /* USE_DNS */<br /><br /><br /><br />　　这一段，回忆我们在看README文件的时候，曾经提到过可以在编译的时候设置选项开关来设定DNS支持，在源代码中可以看到多次这样的代码段出现，如果不指定DNS支持，这些代码段则会出现（ifdef)或者不出现(ifndef).下面略过这些代码段，不再重复。<br /><br /><br />/* open log file */<br />if (gz_log)<br />{<br />gzlog_fp = gzopen(log_fname,"rb");<br />if (gzlog_fp==Z_NULL)<br />{<br />/* Error: Can't open log file ... */<br />fprintf(stderr, "%s %s ",msg_log_err,log_fname);<br />exit(1);<br />}<br />}<br />else<br />{<br />if (log_fname)<br />{<br />log_fp = fopen(log_fname,"r");<br />if (log_fp==NULL)<br />{<br />/* Error: Can't open log file ... */<br />fprintf(stderr, "%s %s ",msg_log_err,log_fname);<br />exit(1);<br />}<br />}<br />}<br /><br /><br /><br />　　这一段，回忆在README文件中曾经读到过，如果log文件是gzip压缩格式，则用gzopen函数打开（可以猜想gz***是一套针对gzip压缩格式的实时解压缩函数），如果不是，则用fopen打开。<br /><br /><br />/* switch directories if needed */<br />if (out_dir)<br />{<br />if (chdir(out_dir) != 0)<br />{<br />/* Error: Can't change directory to ... */<br />fprintf(stderr, "%s %s ",msg_dir_err,out_dir);<br />exit(1);<br />}<br />}<br /><br /><br /><br />　　同样，回忆在README文件中读到过，如果参数行有-o out_dir, 则将输出结果到该目录，否则，则输出到当前目录。在这一段中，如果输出目录不存在(chdir(out_dir) != 0)则出错。<br /><br /><br />#ifdef USE_DNS<br />if (strstr(argv[0],"webazolver")!=0)<br />{<br />if (!dns_children) dns_children=5; /* default dns children if needed */<br />if (!dns_cache)<br />{<br />/* No cache file specified, aborting... */<br />fprintf(stderr,"%s ",msg_dns_nocf); /* Must have a cache file */<br />exit(1);<br />}<br />}<br />......<br /><br /><br /><br />　　在上面曾经提到过，这是DNS解析的代码部分，可以略过不看，不会影响对整个程序的理解。<br /><br /><br />/* prep hostname */<br />if (!hname)<br />{<br />if (uname(&amp;system_info)) hname="localhost";<br />else hname=system_info.nodename;<br />}<br /><br /><br /><br />　　这一段继续处理参数做准备工作。如果在命令行中指定了hostname(机器名）则采用指定的名称，否则调用uname查找机器名，如果没有，则用localhost来作为机器名。(同样在README中说得很详细）<br /><br /><br />/* get past history */<br />if (ignore_hist) {if (verbose&gt;1) printf("%s ",msg_ign_hist); }<br />else get_history();<br /><br /><br /><br />　　如果在命令行中指定了忽略历史文件，则不读取历史文件，否则调用get_history()来读取历史数据。在这里，我们可以回想在README文件中同样说过这一细节，在命令行或者配置文件中都能指定这一开关。需要说明的是，我们在这里并不一定需要去看get_history这一函数，因为从函数的名称，README文件和程序注释都能很清楚的得知这一函数的功能，不一定要去看代码。而如果要猜想的话，也可以想到，history是webalizer在上次运行的时候记录下来的一个文件，而这个文件则是去读取它，并将它的数据包括到这次的分析中去。不信，我们可以来看看。<br /><br /><br />void get_history()<br />{<br />int i,numfields;<br />FILE *hist_fp;<br />char buffer[BUFSIZE];<br /><br />/* first initalize internal array */<br />for (i=0;i&lt;12;i++)<br />{<br />hist_month[i]=hist_year[i]=hist_fday[i]=hist_lday[i]=0;<br />hist_hit[i]=hist_files[i]=hist_site[i]=hist_page[i]=hist_visit[i]=0;<br />hist_xfer[i]=0.0;<br />}<br />hist_fp=fopen(hist_fname,"r");<br />if (hist_fp)<br />{<br />if (verbose&gt;1) printf("%s %s ",msg_get_hist,hist_fname);<br />while ((fgets(buffer,BUFSIZE,hist_fp)) != NULL)<br />{<br />i = atoi(buffer) -1;<br />if (i&gt;11)<br />{<br />if (verbose)<br />fprintf(stderr,"%s (mth=%d) ",msg_bad_hist,i+1);<br />continue;<br />}<br />/* month# year# requests files sites xfer firstday lastday */<br />numfields = sscanf(buffer,"%d %d %lu %lu %lu %lf %d %d %lu %lu",<br />&amp;hist_month[i],<br />&amp;hist_year[i],<br />&amp;hist_hit[i],<br />&amp;hist_files[i],<br />&amp;hist_site[i],<br />&amp;hist_xfer[i],<br />&amp;hist_fday[i],<br />&amp;hist_lday[i],<br />&amp;hist_page[i],<br />&amp;hist_visit[i]);<br /><br />if (numfields==8) /* kludge for reading 1.20.xx history files */<br />{<br />hist_page[i] = 0;<br />hist_visit[i] = 0;<br />}<br />}<br />fclose(hist_fp);<br />}<br />else if (verbose&gt;1) printf("%s ",msg_no_hist);<br />}<br />/*********************************************/<br />/* PUT_HISTORY - write out history file */<br />/*********************************************/<br />void put_history()<br />{<br />int i;<br />FILE *hist_fp;<br /><br />hist_fp = fopen(hist_fname,"w");<br /><br />if (hist_fp)<br />{<br />if (verbose&gt;1) printf("%s ",msg_put_hist);<br />for (i=0;i&lt;12;i++)<br />{<br />if ((hist_month[i] != 0) &amp;&amp; (hist_hit[i] != 0))<br />{<br />fprintf(hist_fp,"%d %d %lu %lu %lu %.0f %d %d %lu %lu ",<br />hist_month[i],<br />hist_year[i],<br />hist_hit[i],<br />hist_files[i],<br />hist_site[i],<br />hist_xfer[i],<br />hist_fday[i],<br />hist_lday[i],<br />hist_page[i],<br />hist_visit[i]);<br />}<br />}<br />fclose(hist_fp);<br />}<br />else<br />if (verbose)<br />fprintf(stderr,"%s %s ",msg_hist_err,hist_fname);<br />}<br /><br /><br /><br />　　在preserve.c中，这两个函数是成对出现的。get_history()读取文件中的数据，并将其记录到hist_开头的一些数组中去。而put_history()则是将一些数据记录到同样的数组中去。我们可以推测得知，hist_数组是全局变量（在函数中没有定义），也可以查找源代码验证。同样，我们可以找一找put_history()出现的地方，来验证刚才的推测是否正确。在webalizer.c的1311行，出现：<br /><br /><br />month_update_exit(rec_tstamp); /* calculate exit pages */<br />write_month_html(); /* write monthly HTML file */<br />write_main_index(); /* write main HTML file */<br />put_history(); /* write history */<br />可以知道，推测是正确的。再往下读代码，<br />if (incremental) /* incremental processing? */<br />{<br />if ((i=restore_state())) /* restore internal data structs */<br />{<br />/* Error: Unable to restore run data (error num) */<br />/* if (verbose) fprintf(stderr,"%s (%d) ",msg_bad_data,i); */<br />fprintf(stderr,"%s (%d) ",msg_bad_data,i);<br />exit(1);<br />}<br />......<br />}<br /><br /><br /><br />　　同样，这也是处理命令行和做数据准备，而且和get_history(), put_history()有些类似，读者可以自己练习一下。下面，终于进入了程序的主体部分, 在做完了命令行分析，数据准备之后，开始从日志文件中读取数据并做分析了。<br /><br /><br />/*********************************************/<br />/* MAIN PROCESS LOOP - read through log file */<br />/*********************************************/<br /><br />while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE) != Z_NULL):<br />(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin) != NULL))<br /><br /><br /><br />　　我看到这里的时候，颇有一些不同意作者的这种写法。这一段while中的部分写的比较复杂而且效率不高。因为从程序推断和从他的代码看来，作者是想根据日志文件的类型不同来采用不同的方法读取文件，如果是gzip格式，则用our_gzgets来读取其中一行，如果是普通的文本文件格式，则用fgets()来读取。但是，这段代码是写在while循环中的，每次读取一行就要重复判断一次，明显是多余的而且降低了程序的性能。可以在while循环之前做一次这样的判断，然后就不用重复了。<br /><br /><br />total_rec++;<br />if (strlen(buffer) == (BUFSIZE-1))<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_rec);<br />if (debug_mode) fprintf(stderr,": %s",buffer);<br />else fprintf(stderr," ");<br />}<br />total_bad++; /* bump bad record counter */<br />/* get the rest of the record */<br />while ( (gz_log)?(our_gzgets(gzlog_fp,buffer,BUFSIZE)!=Z_NULL):<br />(fgets(buffer,BUFSIZE,log_fname?log_fp:stdin)!=NULL))<br />{<br />if (strlen(buffer) &lt; BUFSIZE-1)<br />{<br />if (debug_mode &amp;&amp; verbose) fprintf(stderr,"%s ",buffer);<br />break;<br />}<br />if (debug_mode &amp;&amp; verbose) fprintf(stderr,"%s",buffer);<br />}<br />continue; /* go get next record if any */<br />}<br /><br /><br /><br />　　这一段代码，读入一行，如果这一行超过了程序允许的最大字符数（则是错误的日志数据纪录），则跳过本行剩下的数据，忽略掉（continue进行下一次循环）。同时把total_bad增加一个。如果没有超过程序允许的最大字符数（则是正确的日志数据纪录），则<br /><br /><br />/* got a record... */<br />strcpy(tmp_buf, buffer); /* save buffer in case of error */<br />if (parse_record(buffer)) /* parse the record */<br /><br /><br /><br />　　将该数据拷贝到一个缓冲区中，然后调用parse_record()进行处理。我们可以同样的推测一下，get_record()是这个程序的一个主要处理部分，分析了日志数据。在parse_record.c中，有此函数，<br /><br /><br />/*********************************************/<br />/* PARSE_RECORD - uhhh, you know... */<br />/*********************************************/<br />int parse_record(char *buffer)<br />{<br />/* clear out structure */<br />memset(&amp;log_rec,0,sizeof(struct log_struct));<br />/*<br />log_rec.hostname[0]=0;<br />log_rec.datetime[0]=0;<br />log_rec.url[0]=0;<br />log_rec.resp_code=0;<br />log_rec.xfer_size=0;<br />log_rec.refer[0]=0;<br />log_rec.agent[0]=0;<br />log_rec.srchstr[0]=0;<br />log_rec.ident[0]=0;<br />*/<br />#ifdef USE_DNS<br />memset(&amp;log_rec.addr,0,sizeof(struct in_addr));<br />#endif<br /><br />/* call appropriate handler */<br />switch (log_type)<br />{<br />default:<br />case LOG_CLF: return parse_record_web(buffer); break;<br />/* clf */<br />case LOG_FTP: return parse_record_ftp(buffer); break;<br />/* ftp */<br />case LOG_SQUID: return parse_record_squid(buffer); break;<br />/* squid */<br />}<br />}<br /><br /><br /><br />　　可以看到，log_rec是一个全局变量，该函数根据日志文件的类型，分别调用三种不同的分析函数。在webalizer.h中，找到该变量的定义，从结构定义中可以看到，结构定义了一个日志文件所可能包含的所有信息（参考CLF，FTP, SQUID日志文件的格式说明）。<br /><br /><br />/* log record structure */<br />struct log_struct { char hostname[MAXHOST]; /* hostname */<br />char datetime[29]; /* raw timestamp */<br />char url[MAXURL]; /* raw request field */<br />int resp_code; /* response code */<br />u_long xfer_size; /* xfer size in bytes */<br />#ifdef USE_DNS<br />struct in_addr addr; /* IP address structure */<br />#endif /* USE_DNS */<br />char refer[MAXREF]; /* referrer */<br />char agent[MAXAGENT]; /* user agent (browser) */<br />char srchstr[MAXSRCH]; /* search string */<br />char ident[MAXIDENT]; }; /* ident string (user) */<br /><br />extern struct log_struct log_rec;<br /><br /><br /><br />　　先看一下一个parser.c用的内部函数，然后再来以parse_record_web()为例子看看这个函数是怎么工作的，parse_record_ftp, parse_record_squid留给读者自己分析作为练习。<br /><br /><br />/*********************************************/<br />/* FMT_LOGREC - terminate log fields w/zeros */<br />/*********************************************/<br />void fmt_logrec(char *buffer)<br />{<br />char *cp=buffer;<br />int q=0,b=0,p=0;<br /><br />while (*cp != '')<br />{<br />/* break record up, terminate fields with '' */<br />switch (*cp)<br />{<br />case ' ': if (b || q || p) break; *cp=''; break;<br />case '"': q^=1; break;<br />case '[': if (q) break; b++; break;<br />case ']': if (q) break; if (b&gt;0) b--; break;<br />case '(': if (q) break; p++; break;<br />case ')': if (q) break; if (p&gt;0) p--; break;<br />}<br />cp++;<br />}<br />}<br /><br /><br /><br />　　从parser.h头文件中就可以看到，这个函数是一个内部函数，这个函数把一行字符串中间的空格字符用''字符（结束字符）来代替，同时考虑了不替换在双引号，方括号，圆括号中间的空格字符以免得将一行数据错误的分隔开了。（请参考WEB日志的文件格式，可以更清楚的理解这一函数）<br /><br /><br />int parse_record_web(char *buffer)<br />{<br />int size;<br />char *cp1, *cp2, *cpx, *eob, *eos;<br />size = strlen(buffer); /* get length of buffer */<br />eob = buffer+size; /* calculate end of buffer */<br />fmt_logrec(buffer); /* seperate fields with 's */<br />/* HOSTNAME */<br />cp1 = cpx = buffer; cp2=log_rec.hostname;<br />eos = (cp1+MAXHOST)-1;<br />if (eos &gt;= eob) eos=eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) ) *cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_host);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />/* skip next field (ident) */<br />while ( (*cp1 != '') &amp;&amp; (cp1 &lt; eob) ) cp1++;<br />if (cp1 &lt; eob) cp1++;<br />/* IDENT (authuser) field */<br />cpx = cp1;<br />cp2 = log_rec.ident;<br />eos = (cp1+MAXIDENT-1);<br />if (eos &gt;= eob) eos=eob-1;<br />while ( (*cp1 != '[') &amp;&amp; (cp1 &lt; eos) ) /* remove embeded spaces */<br />{<br />if (*cp1=='') *cp1=' ';<br />*cp2++=*cp1++;<br />}<br />*cp2--='';<br />if (cp1 &gt;= eob) return 0;<br />/* check if oversized username */<br />if (*cp1 != '[')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_user);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while ( (*cp1 != '[') &amp;&amp; (cp1 &lt; eob) ) cp1++;<br />}<br />/* strip trailing space(s) */<br />while (*cp2==' ') *cp2--='';<br />/* date/time string */<br />cpx = cp1;<br />cp2 = log_rec.datetime;<br />eos = (cp1+28);<br />if (eos &gt;= eob) eos=eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) ) *cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_date);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />/* minimal sanity check on timestamp */<br />if ( (log_rec.datetime[0] != '[') ||<br />(log_rec.datetime[3] != '/') ||<br />(cp1 &gt;= eob)) return 0;<br />/* HTTP request */<br />cpx = cp1;<br />cp2 = log_rec.url;<br />eos = (cp1+MAXURL-1);<br />if (eos &gt;= eob) eos = eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) ) *cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_req);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />if ( (log_rec.url[0] != '"') ||<br />(cp1 &gt;= eob) ) return 0;<br />/* response code */<br />log_rec.resp_code = atoi(cp1);<br />/* xfer size */<br />while ( (*cp1 != '') &amp;&amp; (cp1 &lt; eob) ) cp1++;<br />if (cp1 &lt; eob) cp1++;<br />if (*cp1&lt;'0'||*cp1&gt;'9') log_rec.xfer_size=0;<br />else log_rec.xfer_size = strtoul(cp1,NULL,10);<br />/* done with CLF record */<br />if (cp1&gt;=eob) return 1;<br />while ( (*cp1 != '') &amp;&amp; (*cp1 != ' ') &amp;&amp; (cp1 &lt; eob) )<br />cp1++;<br />if (cp1 &lt; eob) cp1++;<br />/* get referrer if present */<br />cpx = cp1;<br />cp2 = log_rec.refer;<br />eos = (cp1+MAXREF-1);<br />if (eos &gt;= eob) eos = eob-1;<br />while ( (*cp1 != '') &amp;&amp; (*cp1 != ' ') &amp;&amp; (cp1 != eos) )<br />*cp2++ = *cp1++;<br />*cp2 = '';<br />if (*cp1 != '')<br />{<br />if (verbose)<br />{<br />fprintf(stderr,"%s",msg_big_ref);<br />if (debug_mode) fprintf(stderr,": %s ",cpx);<br />else fprintf(stderr," ");<br />}<br />while (*cp1 != '') cp1++;<br />}<br />if (cp1 &lt; eob) cp1++;<br />cpx = cp1;<br />cp2 = log_rec.agent;<br />eos = cp1+(MAXAGENT-1);<br />if (eos &gt;= eob) eos = eob-1;<br />while ( (*cp1 != '') &amp;&amp; (cp1 != eos) )<br />*cp2++ = *cp1++;<br />*cp2 = '';<br />return 1; /* maybe a valid record, return with TRUE */<br />}<br /><br /><br /><br />　　该函数，一次读入一行（其实是一段日志数据中间的一个域，因为该行数据已经被fmt_logrec分开成多行数据了。根据CLF中的定义，检查该数据并将其拷贝到log_rec结构中去，如果检查该数据有效，则返回1。回到主程序,<br /><br /><br />/* convert month name to lowercase */<br />for (i=4;i&lt;7;i++)<br />log_rec.datetime[i]=tolower(log_rec.datetime[i]);<br />/* get year/month/day/hour/min/sec values */<br />for (i=0;i&lt;12;i++)<br />{<br />if (strncmp(log_month[i],&amp;log_rec.datetime[4],3)==0)<br />{ rec_month = i+1; break; }<br />}<br />rec_year=atoi(&amp;log_rec.datetime[8]);<br />/* get year number (int) */<br />rec_day =atoi(&amp;log_rec.datetime[1]);<br />/* get day number */<br />rec_hour=atoi(&amp;log_rec.datetime[13]);<br />/* get hour number */<br />rec_min =atoi(&amp;log_rec.datetime[16]);<br />/* get minute number */<br />rec_sec =atoi(&amp;log_rec.datetime[19]);<br />/* get second number */<br />....<br /><img src ="http://www.cnitblog.com/charester/aggbug/21426.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:41 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21426.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何阅读源代码(一)(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21425.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:39:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21425.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21425.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21425.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21425.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21425.html</trackback:ping><description><![CDATA[由于工作的关系，我常常需要读一些源代码，并在上面做一些修改并且拿来使用，或者是借鉴其中的某些部分。可以说，open source对于程序员来说，是很有意义的事情。根据我的经验，读源代码，至少有3个好处。第一个好处是可以学习到很多编程的方法，看好的源代码，对于提高自己的编程水平，比自己写源代码的帮助更大。当然不是说不用自己写，而是说，自己写代码的同时，可以从别人写的好的源代码中间学习到更多的编程方法和技巧。第二个好处是，可以提高自己把握大规模源代码的能力。一个比较大型的程序，往往都是经过了很多个版本很长的时间，有很多人参与开发，修正错误，添加功能而发展起来的。所以往往源代码的规模都比较大，少则10-100多k, 多的有好几十个MB. 在阅读大量源代码的时候，能够提高自己对大的软件的把握能力，快速了解脉络，熟悉细节，不仅仅是编程技巧，还能在程序的架构，设计方面提高自己的能力。（这里说一句题外话，&lt;&lt;设计模式&gt;&gt;这本书相信很多人都看过，而且很多人对它推崇备至，奉为经典。现在也出了不少书，都是冠以"设计模式"这一名称。在书中就提到，设计模式并不是一本教材，不是教你如何去编程序，而是把平时编程中一些固定的模式记录下来，加以不断的测试和改进，分发给广大程序员的一些经验之谈。我在看这本书的时候，有一些地方一些设计方法往往让我有似曾相识的感觉，另外一些则是我以前就常常用到的。而这些经验的获得，一部分得益于自己的编码过程，另外一个很重要的来源就是阅读别人写的源代码。)阅读源代码第三个好处，就是获得一些好的思想。比如，有很多人在开始一个软件项目之前都喜欢到sourceforge.net上去找一下，是否有人以前做过相同或者相似的软件，如果有，则拿下来读一读，可以使自己对这个软件项目有更多更深的认识。我以前曾经想找一本关于如何阅读源代码的书来看看，却没有找到。相反，倒是找到了不少分析源代码的书，比如Linux kernel, Apache source, 等等。所以我想，为什么不自己来写下一些经验和大家交流呢？（当然不是写书，没有那个能力也没有那个时间。）所以在这里我准备用一个例子来写一下如何阅读源代码，分享一些经验，算是抛砖引玉吧！<br /><br />　　我找的例子是一个统计日志的工具，webalizer. (这个工具我以前用过，似乎记得以前的版本是用perl写的，不知道现在为什么作者把它完全修改成了C，可能是为了效率，也可能根本就是我记错了。）之所以选择这个软件来作为例子，一方面是因为它是用C写的，流程比较简单，没有C++的程序那么多的枝节，而且软件功能不算复杂，代码规模不大，能够在一篇文章的篇幅里面讲完; 另外一个方面是因为恰巧前段时间我因为工作的关系把它拿来修改了一下，刚看过，还没有忘记。 :-)我采用的例子是webalizer2.01-09, 也可以到它的网站<a onfocus="this.blur()" href="http://www.mrunix.net/webalizer/" target="_blank"><font color="#000033" size="2">http://www.mrunix.net/webalizer/</font></a> 下载最新的版本。这是一个用C写的，处理文本文件（简单的说是这样，实际上它支持三种日志文本格式：CLF, FTP, SQUID), 并且用html的方式输出结果。读者可以自己去下载它的源代码包，并一边读文章，一边看程序。解压缩它的tar包(我download的是它的源代码tar包），在文件目录中看到这样的结果：<br /><br /><br />$ ls<br />aclocal.m4 dns_resolv.c lang output.h webalizer.1<br />CHANGES dns_resolv.h lang.h parser.c webalizer.c<br />configure graphs.c linklist.c parser.h webalizer.h<br />configure.in graphs.h linklist.h preserve.c webalizer_lang.h<br />COPYING hashtab.c Makefile.in preserve.h webalizer.LSM<br />Copyright hashtab.h Makefile.std README webalizer.png<br />country-codes.txt INSTALL msfree.png README.FIRST<br />DNS.README install-sh output.c sample.conf<br /><br /><br /><br />　　首先，我阅读了它的README(这是很重要的一个环节）, 大体了解了软件的功能，历史状况，修改日志，安装方法等等。然后是安装并且按照说明中的缺省方式来运行它，看看它的输出结果。(安装比较简单，因为它带了一个configure, 在没有特殊情况出现的时候，简单的./configure, make, make install就可以安装好。)然后就是阅读源代码了。我从makefile开始入手（我觉得这是了解一个软件的最好的方法）在makefile开头，有这些内容：<br /><br /><br />prefix = /usr/local<br />exec_prefix = ${prefix}<br />BINDIR = ${exec_prefix}/bin<br />MANDIR = ${prefix}/man/man1<br />ETCDIR = /etc<br />CC = gcc<br />CFLAGS = -Wall -O2<br />LIBS = -lgd -lpng -lz -lm<br />DEFS = -DETCDIR="/etc" -DHAVE_GETOPT_H=1 -DHAVE_MATH_H=1<br />LDFLAGS=<br />INSTALL= /usr/bin/install -c<br />INSTALL_PROGRAM=${INSTALL}<br />INSTALL_DATA=${INSTALL} -m 644<br /># where are the GD header files?<br />GDLIB=/usr/include<br /><br /><br /><br />　　这些定义了安装的路径，执行程序的安装路径，编译器，配置文件的安装路径，编译的选项，安装程序，安装程序的选项等等。要注意的是，这些并不是软件的作者写的，而是./configure的输出结果。呵呵. :-)下面才是主题内容，也是我们关心的。<br /><br /><br /># Shouldn't have to touch below here!<br />all: webalizer<br />webalizer: webalizer.o webalizer.h hashtab.o hashtab.h<br />linklist.o linklist.h preserve.o preserve.h<br />dns_resolv.o dns_resolv.h parser.o parser.h<br />output.o output.h graphs.o graphs.h lang.h<br />webalizer_lang.h<br />$(CC) ${LDFLAGS} -o webalizer webalizer.o hashtab.o linklist.o preserv<br />e.o parser.o output.o dns_resolv.o graphs.o ${LIBS}<br />rm -f webazolver<br />ln -s webalizer webazolver<br />webalizer.o: webalizer.c webalizer.h parser.h output.h preserve.h<br />graphs.h dns_resolv.h webalizer_lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c webalizer.c<br />parser.o: parser.c parser.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c parser.c<br />hashtab.o: hashtab.c hashtab.h dns_resolv.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c hashtab.c<br />linklist.o: linklist.c linklist.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c linklist.c<br />output.o: output.c output.h webalizer.h preserve.h<br />hashtab.h graphs.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c output.c<br />preserve.o: preserve.c preserve.h webalizer.h parser.h<br />hashtab.h graphs.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -c preserve.c<br />dns_resolv.o: dns_resolv.c dns_resolv.h lang.h webalizer.h<br />$(CC) ${CFLAGS} ${DEFS} -c dns_resolv.c<br />graphs.o: graphs.c graphs.h webalizer.h lang.h<br />$(CC) ${CFLAGS} ${DEFS} -I${GDLIB} -c graphs.c<br /><br /><br /><br />　　好了，不用再往下看了，这些就已经足够了。从这里我们可以看到这个软件的几个源代码文件和他们的结构。webalizer.c是主程序所在的文件，其他的是一些辅助程序模块。对比一下目录里面的文件，<br /><br /><br />$ ls *.c *.h<br />dns_resolv.c graphs.h lang.h output.c parser.h webalizer.c<br />dns_resolv.h hashtab.c linklist.c output.h preserve.c webalizer.h<br />graphs.c hashtab.h linklist.h parser.c preserve.h webalizer_lang.h<br /><br /><br /><br />　　于是，让我们从webalizer.c开始吧。<br /><br />　　作为一个C程序，在头文件里面，和C文件里面定义的extern变量，结构等等肯定不会少，但是，单独看这些东西我们不可能对这个程序有什么认识。所以，从main函数入手，逐步分析，在需要的时候再回头来看这些数据结构定义才是好的方法。（顺便说一句，Visual C++, 等windows下的IDE工具提供了很方便的方法来获取函数列表，C++的类列表以及资源文件，对于阅读源代码很有帮助。Unix/Linux也有这些工具，但是，我们在这里暂时不说，而只是通过最简单的文本编辑器vi来讲)。跳过webalizer.c开头的版权说明部分（GPL的），和数据结构定义，全局变量声明部分，直接进入main()函数。在函数开头，我们看到：<br /><br /><br />/* initalize epoch */<br />epoch=jdate(1,1,1970); /* used for timestamp adj. */<br />/* add default index. alias */<br />add_nlist("index.",&amp;index_alias);<br /><br /><br /><br />　　这两个函数暂时不用仔细看，后面会提到，略过。<br /><br /><br />sprintf(tmp_buf,"%s/webalizer.conf",ETCDIR);<br />/* check for default config file */<br />if (!access("webalizer.conf",F_OK))<br />get_config("webalizer.conf");<br />else if (!access(tmp_buf,F_OK))<br />get_config(tmp_buf);<br /><br /><br /><br />　　从注释和程序本身可以看出，这是查找是否存在一个叫做webalizer.conf的配置文件，如果当前目录下有，则用get_config来读入其中内容，如果没有，则查找ETCDIR/webalizer.conf是否存在。如果都没有，则进入下一部分。(注意：ETCDIR = @ETCDIR@在makefile中有定义）<br /><br /><br />/* get command line options */<br />opterr = 0; /* disable parser errors */<br />while ((i=getopt(argc,argv,"a:A:c:C:dD:e:E:fF:<br />g:GhHiI:l:Lm:M:n:N:o:pP:qQr:R:s:S:t:Tu:U:vVx:XY"))!=EOF)<br />{<br />switch (i)<br />{<br />case 'a': add_nlist(optarg,&amp;hidden_agents); break;<br />/* Hide agents */<br />case 'A': ntop_agents=atoi(optarg); break;<br />/* Top agents */<br />case 'c': get_config(optarg); break;<br />/* Config file */<br />case 'C': ntop_ctrys=atoi(optarg); break;<br />/* Top countries */<br />case 'd': debug_mode=1; break;<br />/* Debug */<br />case 'D': dns_cache=optarg; break;<br />/* DNS Cache filename */<br />case 'e': ntop_entry=atoi(optarg); break;<br />/* Top entry pages */<br />case 'E': ntop_exit=atoi(optarg); break;<br />/* Top exit pages */<br />case 'f': fold_seq_err=1; break;<br />/* Fold sequence errs */<br />case 'F': log_type=(optarg[0]=='f')?<br />LOG_FTP:(optarg[0]=='s')?<br />LOG_SQUID:LOG_CLF; break;<br />/* define log type */<br />case 'g': group_domains=atoi(optarg); break;<br />/* GroupDomains (0=no) */<br />case 'G': hourly_graph=0; break;<br />/* no hourly graph */<br />case 'h': print_opts(argv[0]); break;<br />/* help */<br />case 'H': hourly_stats=0; break;<br />/* no hourly stats */<br />case 'i': ignore_hist=1; break;<br />/* Ignore history */<br />case 'I': add_nlist(optarg,&amp;index_alias); break;<br />/* Index alias */<br />case 'l': graph_lines=atoi(optarg); break;<br />/* Graph Lines */<br />case 'L': graph_legend=0; break;<br />/* Graph Legends */<br />case 'm': visit_timeout=atoi(optarg); break;<br />/* Visit Timeout */<br />case 'M': mangle_agent=atoi(optarg); break;<br />/* mangle user agents */<br />case 'n': hname=optarg; break;<br />/* Hostname */<br />case 'N': dns_children=atoi(optarg); break;<br />/* # of DNS children */<br />case 'o': out_dir=optarg; break;<br />/* Output directory */<br />case 'p': incremental=1; break;<br />/* Incremental run */<br />case 'P': add_nlist(optarg,&amp;page_type); break;<br />/* page view types */<br />case 'q': verbose=1; break;<br />/* Quiet (verbose=1) */<br />case 'Q': verbose=0; break;<br />/* Really Quiet */<br />case 'r': add_nlist(optarg,&amp;hidden_refs); break;<br />/* Hide referrer */<br />case 'R': ntop_refs=atoi(optarg); break;<br />/* Top referrers */<br />case 's': add_nlist(optarg,&amp;hidden_sites); break;<br />/* Hide site */<br />case 'S': ntop_sites=atoi(optarg); break;<br />/* Top sites */<br />case 't': msg_title=optarg; break;<br />/* Report title */<br />case 'T': time_me=1; break; /* TimeMe */<br />case 'u': add_nlist(optarg,&amp;hidden_urls); break;<br />/* hide URL */<br />case 'U': ntop_urls=atoi(optarg); break;<br />/* Top urls */<br />case 'v':<br />case 'V': print_version(); break;<br />/* Version */<br />case 'x': html_ext=optarg; break;<br />/* HTML file extension */<br />case 'X': hide_sites=1; break;<br />/* Hide ind. sites */<br />case 'Y': ctry_graph=0; break;<br />/* Supress ctry graph */<br />}<br />}<br />if (argc - optind != 0) log_fname = argv[optind];<br />if ( log_fname &amp;&amp; (log_fname[0]=='-')) log_fname=NULL;<br />/* force STDIN? */<br />/* check for gzipped file - .gz */<br />if (log_fname) if (!strcmp((log_fname+strlen(log_fname)-3),".gz"))<br />gz_log=1;<br /><br /><br /><br />　　这一段是分析命令行参数及开关。（getopt()的用法我在另外一篇文章中讲过，这里就不再重复了。）可以看到，这个软件虽然功能不太复杂，但是开关选项还是不少。大多数的unix/linux程序的开头部分都是这个套路，初始化配置文件，并且读入分析命令行。在这段程序中，我们需要注意一个函数：add_nlist(). print_opts(), get_config()等等一看就明白，就不用多讲了。这里我们已经是第二次遇到add_nlist这个函数了，就仔细看看吧。<br /><br /><br />$ grep add_nlist *.h<br />linklist.h:extern int add_nlist(char *, NLISTPTR *);<br />/* add list item */<br /><br /><br /><br />　　可以发现它定义在linklist.h中。<br /><br />　　在这个h文件中，当然会有一些数据结构的定义，比如：<br /><br /><br />struct nlist { char string[80];<br />/* list struct for HIDE items */<br />struct nlist *next; };<br />typedef struct nlist *NLISTPTR;<br />struct glist { char string[80];<br />/* list struct for GROUP items */<br />char name[80];<br />struct glist *next; };<br />typedef struct glist *GLISTPTR;<br /><br /><br /><br />　　这是两个链表结构。还有<br /><br /><br />extern GLISTPTR group_sites ; /* "group" lists */<br />extern GLISTPTR group_urls ;<br />extern GLISTPTR group_refs ;<br /><br /><br /><br />　　这些都是链表， 太多了，不用一一看得很仔细，因为目前也看不出来什么东西。当然要注意它们是extern的， 也就是说，可以在其他地方(文件）看到它们的数值（类似于C++中的public变量）。这里还定义了4个函数：<br /><br /><br />extern char *isinlist(NLISTPTR, char *);<br />/* scan list for str */<br />extern char *isinglist(GLISTPTR, char *);<br />/* scan glist for str */<br />extern int add_nlist(char *, NLISTPTR *);<br />/* add list item */<br />extern int add_glist(char *, GLISTPTR *);<br />/* add group list item */<br /><br /><br /><br />　　注意，这些都是extern的，也就是说，可以在其他地方见到它们的调用(有点相当于C++中的public函数）。再来看看linklist.c，<br /><br /><br />NLISTPTR new_nlist(char *); /* new list node */<br />void del_nlist(NLISTPTR *); /* del list */<br /><br />GLISTPTR new_glist(char *, char *); /* new group list node */<br />void del_glist(GLISTPTR *); /* del group list */<br />int isinstr(char *, char *);<br /><br /><br /><br />　　这5个函数是内部使用的（相当于C++中的private), 也就是说，这些函数只被isinlist(NLISTPTR, char *), isinglist(GLISTPTR, char *), add_nlist(char *, NLISTPTR *), add_glist(char *, GLISTPTR *)调用，而不会出现在其他地方。所以，我们先来看这几个内部函数。举例来说，<br /><br /><br />add_nlist(char *)<br />NLISTPTR new_nlist(char *str)<br />{<br />NLISTPTR newptr;<br />if (sizeof(newptr-&gt;string) &lt; strlen(str))<br />{<br />if (verbose)<br />fprintf(stderr,"[new_nlist] %s ",msg_big_one);<br />}<br />if (( newptr = malloc(sizeof(struct nlist))) != NULL)<br />{strncpy(newptr-&gt;string, str, sizeof(newptr-&gt;string));<br />newptr-&gt;next=NULL;}<br />return newptr;<br />}<br /><br /><br /><br />　　这个函数分配了一个struct nlist, 并且把其中的string赋值为str, next赋值为NULL.这实际上是创建了链表中的一个节点。verbose是一个全局变量，定义了输出信息的类型，如果verbose为1，则输出很详细的信息，否则输出简略信息。这是为了调试或者使用者详细了解程序情况来用的。不是重要内容，虽然我们常常可以在这个源程序的其他地方看到它。另外一个函数：<br /><br /><br />void del_nlist(NLISTPTR *list)<br />{<br />NLISTPTR cptr,nptr;<br />cptr=*list;<br />while (cptr!=NULL)<br />{<br />nptr=cptr-&gt;next;<br />free(cptr);<br />cptr=nptr;<br />}<br />}<br /><br /><br /><br />　　这个函数删除了一个nlist（也可能是list所指向的那一个部分开始知道链表结尾），比较简单。看完了这两个内部函数，可以来看<br /><br /><br />/*********************************************/<br />/* ADD_NLIST - add item to FIFO linked list */<br />/*********************************************/<br />int add_nlist(char *str, NLISTPTR *list)<br />{<br />NLISTPTR newptr,cptr,pptr;<br />if ( (newptr = new_nlist(str)) != NULL)<br />{<br />if (*list==NULL) *list=newptr;<br />else<br />{<br />cptr=pptr=*list;<br />while(cptr!=NULL) { pptr=cptr; cptr=cptr-&gt;next; };<br />pptr-&gt;next = newptr;<br />}<br />}<br />return newptr==NULL;<br />}<br /><br /><br /><br />　　这个函数是建立了一个新的节点，把参数str赋值给新节点的string, 并把它连接到list所指向链表的结尾。另外的三个函数：new_glist(), del_glist(), add_glist()完成的功能和上述三个差不多，所不同的只是它们所处理的数据结构不同。看完了这几个函数，我们回到main程序。接下来是，<br /><br /><br />/* setup our internal variables */<br />init_counters(); /* initalize main counters */<br /><br /><br /><img src ="http://www.cnitblog.com/charester/aggbug/21425.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:39 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21425.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入理解C语言指针(ZT)</title><link>http://www.cnitblog.com/charester/archive/2007/01/02/21424.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Tue, 02 Jan 2007 12:37:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2007/01/02/21424.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21424.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2007/01/02/21424.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21424.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21424.html</trackback:ping><description><![CDATA[指针的概念 <br /><br />　　指针是一个特殊的变量，它里面存储的数值被解释成为内存里的一个地址。 要搞清一个指针需要搞清指针的四方面的内容：指针的类型，指针所指向的 类型，指针的值或者叫指针所指向的内存区，还有指针本身所占据的内存区。让我们分别说明。 <br /><br />　　先声明几个指针放着做例子： <br /><br />　　例一： <br /><br />　　(1)int*ptr; <br /><br />　　(2)char*ptr; <br /><br />　　(3)int**ptr; <br /><br />　　(4)int(*ptr)[3]; <br /><br />　　(5)int*(*ptr)[4]; <br /><br /><br />　　指针的类型<br /><br />　　从语法的角度看，你只要把指针声明语句里的指针名字去掉，剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型： <br /><br />　　(1)int*ptr;//指针的类型是int* <br /><br />　　(2)char*ptr;//指针的类型是char* <br /><br />　　(3)int**ptr;//指针的类型是int** <br /><br />　　(4)int(*ptr)[3];//指针的类型是int(*)[3] <br /><br />　　(5)int*(*ptr)[4];//指针的类型是int*(*)[4] <br /><br />　　<br />　　指针所指向的类型<br /><br />　　当你通过指针来访问指针所指向的内存区时，指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。 <br /><br />　　从语法上看，你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉，剩下的就是指针所指向的类型。例如： <br /><br />　　(1)int*ptr;//指针所指向的类型是int <br /><br />　　(2)char*ptr;//指针所指向的的类型是char <br /><br />　　(3)int**ptr;//指针所指向的的类型是int* <br /><br />　　(4)int(*ptr)[3];//指针所指向的的类型是int()[3] <br /><br />　　(5)int*(*ptr)[4];//指针所指向的的类型是int*()[4] <br /><br />　　在指针的算术运算中，指针所指向的类型有很大的作用。 <br /><br />　　指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时，你会发现，把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念，是精通指针的关键点之一。我看了不少书，发现有些写得差的书中，就把指针的这两个概念搅在一起了，所以看起书来前后矛盾，越看越糊涂。 <br /><br />　　<br /><br /><br /><br />指针的值，或者叫指针所指向的内存区或地址<br /><br />　　指针的值是指针本身存储的数值，这个值将被编译器当作一个地址，而不是一个一般的数值。在32位程序里，所有类型的指针的值都是一个32位整数，因为32位程序里内存地址全都是32位长。 指针所指向的内存区就是从指针的值所代表的那个内存地址开始，长度为si zeof(指针所指向的类型)的一片内存区。以后，我们说一个指针的值是XX，就相当于说该指针指向了以XX为首地址的一片内存区域；我们说一个指针指向了某块内存区域，就相当于说该指针的值是这块内存区域的首地址。 <br /><br />　　指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中，指针所指向的类型已经有了，但由于指针还未初始化，所以它所指向的内存区是不存在的，或者说是无意义的。 <br /><br />　　以后，每遇到一个指针，都应该问问：这个指针的类型是什么？指针指的类型是什么？该指针指向了哪里？ <br /><br />　　指针本身所占据的内存区<br /><br />　　指针本身占了多大的内存？你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里，指针本身占据了4个字节的长度。 <br /><br />　　指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。<br /><br /><br /><br />指针的算术运算 <br /><br />　　指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如： <br /><br />　　例二： <br /><br />　　1、chara[20]; <br /><br />　　2、int*ptr=a; <br /><br />　　... <br /><br />　　... <br /><br />　　3、ptr++; <br /><br />　　在上例中，指针ptr的类型是int*,它指向的类型是int，它被初始化为指向整形变量a。接下来的第3句中，指针ptr被加了1，编译器是这样处理的：它把指针ptr的值加上了sizeof(int)，在32位程序中，是被加上了4。由于地址是用字节做单位的，故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。 <br />由于char类型的长度是一个字节，所以，原来ptr是指向数组a的第0号单元开始的四个字节，此时指向了数组a中从第4号单元开始的四个字节。 <br /><br />　　我们可以用一个指针和一个循环来遍历一个数组，看例子：<br /><br />　　例三： <br /><br /><br />intarray[20]; <br />int*ptr=array; <br />... <br />//此处略去为整型数组赋值的代码。 <br />... <br />for(i=0;i&lt;20;i++) <br />{ <br />　(*ptr)++; <br />　ptr++； <br />} <br /><br /><br />　　这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1，所以每次循环都能访问数组的下一个单元。 <br /><br />　　再看例子： <br /><br />　　例四： <br /><br />　　1、chara[20]; <br /><br />　　2、int*ptr=a; <br /><br />　　... <br />　　... <br /><br />　　3、ptr+=5;<br /><br />　　在这个例子中，ptr被加上了5，编译器是这样处理的：将指针ptr的值加上5乘sizeof(int)，在32位程序中就是加上了5乘4=20。由于地址的单位是字节，故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说，向高地址方向移动了20个字节。在这个例子中，没加5前的ptr指向数组a的第0号单元开始的四个字节，加5后，ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题，但在语法上却是可以的。这也体现出了指针的灵活性。 <br /><br />　　如果上例中，ptr是被减去5，那么处理过程大同小异，只不过ptr的值是被减去5乘sizeof(int)，新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。 <br /><br />　　总结一下，一个指针ptrold加上一个整数n后，结果是一个新的指针ptrnew，ptrnew的类型和ptrold的类型相同，ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说，ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。 <br /><br />　　一个指针ptrold减去一个整数n后，结果是一个新的指针ptrnew，ptrnew的类型和ptrold的类型相同，ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节，就是说，ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。<br /><br /><br /><br />运算符&amp;和* <br /><br /><br />　　这里&amp;是取地址运算符，*是...书上叫做"间接运算符"。 <br /><br />　　&amp;a的运算结果是一个指针，指针的类型是a的类型加个*，指针所指向的类型是a的类型，指针所指向的地址嘛，那就是a的地址。 <br /><br />　　*p的运算结果就五花八门了。总之*p的结果是p所指向的东西，这个东西有这些特点：它的类型是p指向的类型，它所占用的地址是p所指向的地址。 <br /><br />　　例五： <br /><br /><br />inta=12; <br />intb; <br />int*p; <br />int**ptr; <br />p=&amp;a;<br />//&amp;a的结果是一个指针，类型是int*，指向的类型是int，指向的地址是a的地址。 <br />*p=24;<br />//*p的结果，在这里它的类型是int，它所占用的地址是p所指向的地址，显然，*p就是变量a。 <br />ptr=&amp;p;<br />//&amp;p的结果是个指针，该指针的类型是p的类型加个*，在这里是int **。该指针所指向的类型是p的类型，这里是int*。该指针所指向的地址就是指针p自己的地址。 <br />*ptr=&amp;b;<br />//*ptr是个指针，&amp;b的结果也是个指针，且这两个指针的类型和所指向的类型是一样的，所以用&amp;b来给*ptr赋值就是毫无问题的了。 <br />**ptr=34;<br />//*ptr的结果是ptr所指向的东西，在这里是一个指针，对这个指针再做一次*运算，结果就是一个int类型的变量。 <br /><br /><br />指针表达式 <br /><br />　　一个表达式的最后结果如果是一个指针，那么这个表达式就叫指针表式。 <br /><br />　　下面是一些指针表达式的例子： <br /><br />　　例六： <br /><br /><br />inta,b; <br />intarray[10]; <br />int*pa; <br />pa=&amp;a;//&amp;a是一个指针表达式。 <br />int**ptr=&amp;pa;//&amp;pa也是一个指针表达式。 <br />*ptr=&amp;b;//*ptr和&amp;b都是指针表达式。 <br />pa=array; <br />pa++;//这也是指针表达式。 <br /><br /><br />　　例七： <br /><br /><br />char*arr[20]; <br />char**parr=arr;//如果把arr看作指针的话，arr也是指针表达式 <br />char*str; <br />str=*parr;//*parr是指针表达式 <br />str=*(parr+1);//*(parr+1)是指针表达式 <br />str=*(parr+2);//*(parr+2)是指针表达式 <br /><br /><br />　　由于指针表达式的结果是一个指针，所以指针表达式也具有指针所具有的四个要素：指针的类型，指针所指向的类型，指针指向的内存区，指针自身占据的内存。<br /><br />　　好了，当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话，这个指针表达式就是一个左值，否则就不是一个左值。 <br /><br />　　在例七中，&amp;a不是一个左值，因为它还没有占据明确的内存。*ptr是一个左值，因为*ptr这个指针已经占据了内存，其实*ptr就是指针pa，既然pa已经在内存中有了自己的位置，那么*ptr当然也有了自己的位置。 <br /><br />　　数组和指针的关系 <br /><br />　　如果对声明数组的语句不太明白的话，请参阅我前段时间贴出的文章&lt;&lt;如何理解c和c++的复杂类型声明&gt;&gt;。 <br /><br />　　数组的数组名其实可以看作一个指针。看下例： <br /><br />　　例八： <br /><br /><br />intarray[10]={0,1,2,3,4,5,6,7,8,9},value; <br />... <br />... <br />value=array[0];//也可写成：value=*array; <br />value=array[3];//也可写成：value=*(array+3); <br />value=array[4];//也可写成：value=*(array+4); <br /><br /><br />　　上例中，一般而言数组名array代表数组本身，类型是int[10]，但如果把array看做指针的话，它指向数组的第0个单元，类型是int*，所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理，array+3是一个指向数组第3个单元的指针，所以*(array+3)等于3。其它依此类推。 <br /><br /><br />例九： <br /><br /><br />char*str[3]={ <br />　"Hello,thisisasample!", <br />　"Hi,goodmorning.", <br />　"Helloworld" <br />}; <br />chars[80]； <br />strcpy(s,str[0]);//也可写成strcpy(s,*str); <br />strcpy(s,str[1]);//也可写成strcpy(s,*(str+1)); <br />strcpy(s,str[2]);//也可写成strcpy(s,*(str+2)); <br /><br /><br />　　上例中，str是一个三单元的数组，该数组的每个单元都是一个指针，这些指针各指向一个字符串。把指针数组名str当作一个指针的话，它指向数组的第0号单元，它的类型是char**，它指向的类型是char*。 <br />*str也是一个指针，它的类型是char*，它所指向的类型是char，它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址，即'H'的地址。 str+1也是一个指针，它指向数组的第1号单元，它的类型是char**，它指向的类型是char*。 <br /><br />　　*(str+1)也是一个指针，它的类型是char*，它所指向的类型是char，它指向 "Hi,goodmorning."的第一个字符'H'，等等。 <br /><br />　　下面总结一下数组的数组名的问题。声明了一个数组TYPEarray[n]，则数组名称array就有了两重含义：第一，它代表整个数组，它的类型是TYPE[n]；第二 ，它是一个指针，该指针的类型是TYPE*，该指针指向的类型是TYPE，也就是数组单元的类型，该指针指向的内存区就是数组第0号单元，该指针自己占有单独的内存区，注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的，即类似array++的表达式是错误的。 <br /><br />　　在不同的表达式中数组名array可以扮演不同的角色。 <br /><br />　　在表达式sizeof(array)中，数组名array代表数组本身，故这时sizeof函数测出的是整个数组的大小。 <br />在表达式*array中，array扮演的是指针，因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。 <br /><br />　　表达式array+n（其中n=0，1，2，....。）中，array扮演的是指针，故array+n的结果是一个指针，它的类型是TYPE*，它指向的类型是TYPE，它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。 <br /><br />　　例十： <br /><br /><br />intarray[10]; <br />int(*ptr)[10]; <br />ptr=&amp;array; <br /><br /><br />　　上例中ptr是一个指针，它的类型是int(*)[10]，他指向的类型是int[10] ，我们用整个数组的首地址来初始化它。在语句ptr=&amp;array中，array代表数组本身。 <br /><br />　　本节中提到了函数sizeof()，那么我来问一问，sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小？答案是前者。例如： <br /><br />int(*ptr)[10]; <br /><br />　　则在32位程序中，有： <br /><br /><br />sizeof(int(*)[10])==4 <br />sizeof(int[10])==40 <br />sizeof(ptr)==4 <br /><br /><br />　　实际上，sizeof(对象)测出的都是对象自身的类型的大小，而不是别的什么类型的大小。<br /><br />　　指针和结构类型的关系 <br /><br /><br />　　可以声明一个指向结构类型对象的指针。 <br /><br />　　例十一： <br /><br /><br />structMyStruct <br />{ <br />　inta; <br />　intb; <br />　intc; <br />} <br />MyStructss={20,30,40};<br />//声明了结构对象ss，并把ss的三个成员初始化为20，30和40。 <br />MyStruct*ptr=&amp;ss;<br />//声明了一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。 <br />int*pstr=(int*)&amp;ss;<br />//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。 <br /><br /><br />　　请问怎样通过指针ptr来访问ss的三个成员变量？ <br /><br />答案： <br /><br />ptr-&gt;a; <br />ptr-&gt;b; <br />ptr-&gt;c; <br /><br />　　又请问怎样通过指针pstr来访问ss的三个成员变量？ <br /><br />　　答案： <br /><br />*pstr；//访问了ss的成员a。 <br />*(pstr+1);//访问了ss的成员b。 <br />*(pstr+2)//访问了ss的成员c。 <br /><br />　　虽然我在我的MSVC++6.0上调式过上述代码，但是要知道，这样使用pstr来访问结构成员是不正规的，为了说明为什么不正规，让我们看看怎样通过指针来访问数组的各个单元： <br /><br />　　例十二： <br /><br />intarray[3]={35,56,37}; <br />int*pa=array; <br /><br />　　通过指针pa访问数组array的三个单元的方法是： <br /><br />*pa;//访问了第0号单元 <br />*(pa+1);//访问了第1号单元 <br />*(pa+2);//访问了第2号单元 <br /><br />　　从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。 <br /><br />　　所有的C/C++编译器在排列数组的单元时，总是把各个数组单元存放在连续的存储区里，单元和单元之间没有空隙。但在存放结构对象的各个成员时，在某种编译环境下，可能会需要字对齐或双字对齐或者是别的什么对齐，需要在相邻两个成员之间加若干个"填充字节"，这就导致各个成员之间可能会有若干个字节的空隙。 <br /><br />　　所以，在例十二中，即使*pstr访问到了结构对象ss的第一个成员变量a，也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节，说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节，嘿，这倒是个不错的方法。 <br /><br />　　通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。<br /><br /><br /><br />指针和函数的关系 <br /><br />　　可以把一个指针声明成为一个指向函数的指针。 <br /><br /><br />intfun1(char*,int); <br />int(*pfun1)(char*,int); <br />pfun1=fun1; <br />.... <br />.... <br />inta=(*pfun1)("abcdefg",7);//通过函数指针调用函数。 <br /><br /><br />　　可以把指针作为函数的形参。在函数调用语句中，可以用指针表达式来作为实参。 <br /><br />　　例十三： <br /><br /><br />intfun(char*); <br />inta; <br />charstr[]="abcdefghijklmn"; <br />a=fun(str); <br />... <br />... <br />intfun(char*s) <br />{ <br />intnum=0; <br />for(inti=0;i{ <br />num+=*s;s++; <br />} <br />returnnum; <br />} <br /><br /><br />　　这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了，数组的名字也是一个指针。在函数调用中，当把str作为实参传递给形参s后，实际是把str的值传递给了s，s所指向的地址就和str所指向的地址一致，但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算，并不意味着同时对str进行了自加1运算。<br /><br />指针类型转换 <br /><br /><br />　　当我们初始化一个指针或给一个指针赋值时，赋值号的左边是一个指针，赋值号的右边是一个指针表达式。在我们前面所举的例子中，绝大多数情况下，指针的类型和指针表达式的类型是一样的，指针所指向的类型和指针表达式所指向的类型是一样的。 <br /><br />　　例十四： <br /><br />　　1、floatf=12.3; <br /><br />　　2、float*fptr=&amp;f; <br /><br />　　3、int*p; <br />　<br />　　在上面的例子中，假如我们想让指针p指向实数f，应该怎么搞？是用下面的语句吗？ <br /><br />　　p=&amp;f; <br /><br />　　不对。因为指针p的类型是int*，它指向的类型是int。表达式&amp;f的结果是一个指针，指针的类型是float*,它指向的类型是float。两者不一致，直接赋值的方法是不行的。至少在我的MSVC++6.0上，对指针的赋值语句要求赋值号两边的类型一致，所指向的类型也一致，其它的编译器上我没试过，大家可以试试。为了实现我们的目的，需要进行"强制类型转换"： <br /><br /><br />p=(int*)&amp;f; <br /><br /><br />　　如果有一个指针p，我们需要把它的类型和所指向的类型改为TYEP*TYPE， 那么语法格式是： <br /><br />　　(TYPE*)p； <br /><br />　　这样强制类型转换的结果是一个新指针，该新指针的类型是TYPE*，它指向的类型是TYPE，它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。 <br /><br />　　一个函数如果使用了指针作为形参，那么在函数调用语句的实参和形参的结合过程中，也会发生指针类型的转换。 <br /><br />　　例十五： <br /><br /><br />voidfun(char*); <br />inta=125,b; <br />fun((char*)&amp;a); <br />... <br />... <br />voidfun(char*s) <br />{ <br />charc; <br />c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; <br />c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; <br />} <br />} <br /><br /><br />　　注意这是一个32位程序，故int类型占了四个字节，char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗？在函数调用语句中，实参&amp;a的结果是一个指针，它的类型是int*，它指向的类型是int。形参这个指针的类型是char*，它指向的类型是char。这样，在实参和形参的结合过程中，我们必须进行一次从int*类型到char*类型的转换。结合这个例子，我们可以这样来想象编译器进行转换的过程：编译器先构造一个临时指针char*temp， 然后执行temp=(char*)&amp;a，最后再把temp的值传递给s。所以最后的结果是：s的类型是char*,它指向的类型是char，它指向的地址就是a的首地址。 <br /><br />我们已经知道，指针的值就是指针指向的地址，在32位程序中，指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢？就象下面的语句： <br /><br /><br />unsignedinta; <br />TYPE*ptr;//TYPE是int，char或结构类型等等类型。 <br />... <br />... <br />a=20345686; <br />ptr=20345686;//我们的目的是要使指针ptr指向地址20345686（十进制 <br />） <br />ptr=a;//我们的目的是要使指针ptr指向地址20345686（十进制） <br /><br /><br />　　编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗？不，还有办法： <br /><br /><br />unsignedinta; <br />TYPE*ptr;//TYPE是int，char或结构类型等等类型。 <br />... <br />... <br />a=某个数，这个数必须代表一个合法的地址； <br />ptr=(TYPE*)a；//呵呵，这就可以了。 <br /><br /><br />　　严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址，否则的话，在你使用ptr的时候，就会出现非法操作错误。 <br /><br />　　想想能不能反过来，把指针指向的地址即指针的值当作一个整数取出来。完 全可以。下面的例子演示了把一个指针的值当作一个整数取出来，然后再把这个整数当作一个地址赋给一个指针： <br /><br />　　例十六： <br /><br /><br />inta=123,b; <br />int*ptr=&amp;a; <br />char*str; <br />b=(int)ptr;//把指针ptr的值当作一个整数取出来。 <br />str=(char*)b;//把这个整数的值当作一个地址赋给指针str。 <br /><br /><br />　　现在我们已经知道了，可以把指针的值当作一个整数取出来，也可以把一个整数值当作地址赋给一个指针。 <br /><br />　　指针的安全问题 <br /><br />　　看下面的例子： <br /><br />　　例十七： <br /><br /><br />chars='a'; <br />int*ptr; <br />ptr=(int*)&amp;s; <br />*ptr=1298； <br /><br />　　指针ptr是一个int*类型的指针，它指向的类型是int。它指向的地址就是s的首地址。在32位程序中，s占一个字节，int类型占四个字节。最后一条语句不但改变了s所占的一个字节，还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的？只有编译程序知道，而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据，也许这三个字节里正好是程序的一条代码，而由于你对指针的马虎应用，这三个字节的值被改变了！这会造成崩溃性的错误。 <br /><br />　　让我们再来看一例： <br /><br />　　例十八： <br /><br />　　1、chara; <br /><br />　　2、int*ptr=&amp;a; <br /><br />　　... <br />　　... <br /><br />　　3、ptr++; <br /><br />　　4、*ptr=115; <br /><br />　　该例子完全可以通过编译，并能执行。但是看到没有？第3句对指针ptr进行自加1运算后，ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么？我们不知道。有可能它是一个非常重要的数据，甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据！这是严重的错误。所以在使用指针时，程序员心里必须非常清楚：我的指针究竟指向了哪里。在用指针访问数组的时候，也要注意不要超出数组的低端和高端界限，否则也会造成类似的错误。 <br /><br />　　在指针的强制类型转换：ptr1=(TYPE*)ptr2中，如果sizeof(ptr2的类型)大于sizeof(ptr1的类型)，那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型)，那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么，读者结合例十七来想一想，应该会明白的。<br /><img src ="http://www.cnitblog.com/charester/aggbug/21424.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2007-01-02 20:37 <a href="http://www.cnitblog.com/charester/archive/2007/01/02/21424.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>程序员考试补课笔记(ZT)</title><link>http://www.cnitblog.com/charester/archive/2006/12/24/21017.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Sat, 23 Dec 2006 17:36:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2006/12/24/21017.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/21017.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2006/12/24/21017.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/21017.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/21017.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 准备明年考高程,所以就到网上找了点资料(发现这方面的东西比较少,  ),看了一下这个东西,觉得不错,很多都是些基本的东西,特转来与大家分享,特别是也想考高程的朋友,也希望有这方面经验的朋友给予帮助.     这些东西好像是原作者为了考程序员而参加相关的C语言培训时做的听课笔记.好像还没有写完,如果想知道最新的情况请到下面附的网址去看.感谢原作者!程序员考试补课笔记huoniaolinx  http...&nbsp;&nbsp;<a href='http://www.cnitblog.com/charester/archive/2006/12/24/21017.html'>阅读全文</a><img src ="http://www.cnitblog.com/charester/aggbug/21017.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2006-12-24 01:36 <a href="http://www.cnitblog.com/charester/archive/2006/12/24/21017.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>软考站前准备-聊天记录</title><link>http://www.cnitblog.com/charester/archive/2006/12/15/20511.html</link><dc:creator>天空</dc:creator><author>天空</author><pubDate>Fri, 15 Dec 2006 06:40:00 GMT</pubDate><guid>http://www.cnitblog.com/charester/archive/2006/12/15/20511.html</guid><wfw:comment>http://www.cnitblog.com/charester/comments/20511.html</wfw:comment><comments>http://www.cnitblog.com/charester/archive/2006/12/15/20511.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/charester/comments/commentRss/20511.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/charester/services/trackbacks/20511.html</trackback:ping><description><![CDATA[
		<p>1：我找了几个编译器，一个是ＢＯＲＬＡＮＤ公司原来的ＴＵＲＢＯ　Ｃ还有一个是普林斯顿大学作的ＬＣＣ<br />我在２０上下的那东西对Ｃ＋＋的支持毕竟好，这个ＬＣＣ不太清楚能否支持Ｃ＋＋。 <br /><br />2：对了，你准备这个考试的话还得学学Ｃ＋＋或者ＪＡＶＡ。因为每年下午考试都有这种编程的题目。 <br />是两个都要学。还是只要求一门 <br />是这样的，一个是考察单纯面向过程的编程能力，另一个是考察面向对象编程能力。 <br />所以说你要看的要写的东西还够多呢，Ｃ＋＋的到时候我给你找点资料看看，对付那个考试应该没问题。 <br /><br />3：你准备好那个数据结构和算法的书没？最好准备那个清华的Ｃ语言版的数据结构。算法的话也有好几本，到时候详细和你说。 <br />没事，熟能生巧。多练练就没什么了。<br />一定要抓紧时间练啊，书上的那些程序最好都敲一遍跑一下，然后再找一堆程序看看跑跑。<br /> <br />4：那个软件设计师的教材你买了没？ <br /><br />买了。   <br /><br />那书上面关于数据结构的程序一定要都走走。自己编一下让它们都跑过。<br />那里面有的程序还有一点问题的。 <br /><br />你会发现改里面的错误也蛮有意思的。 <br /><br />5：其实你可以去借一些书来准备复习。<br />长沙定王台那边有个书店，专门卖计算机方面的书的，可以去办借书卡，一年１００块钱，一次可以借两本书。 <br /><br />6：  <br /><br />对了2007年的考纲应该没出来把 <br />  <br />这个不是每年一变，基本上是好几年都是一样的考纲。 <br /><br />那我打印2006年的考纲了 <br />  <br />那已经是最新的了。你打一份对照着复习吧。 <br /><br />7：1<br />软件设计师教程（第2版）<br />  【作者】     陈平 褚华[同作者作品]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试指定用书<br />   【出版社】  清华大学出版社</p>
		<p>
				<a href="http://www.china-pub.com/computers/common/info.asp?id=30709">http://www.china-pub.com/computers/common/info.asp?id=30709<br /></a>
				<br />1<br />软件设计师备考训练--计算机与软件工程知识<br />【作者】     刘克武 等[同作者作品]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试参考用书<br />   【出版社】  清华大学出版社<br /><a href="http://www.china-pub.com/computers/common/info.asp?id=29417">http://www.china-pub.com/computers/common/info.asp?id=29417</a><br /><br />1<br />软件设计师考试辅导   <br />【作者】     谢树煜[同作者作品]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试辅导用书<br />   【出版社】  清华大学出版社 <br /><a href="http://www.china-pub.com/computers/common/info.asp?id=26470">http://www.china-pub.com/computers/common/info.asp?id=26470</a></p>
		<p>
				<a href="http://www.china-pub.com/computers/common/info.asp?id=30830">
				</a> </p>2<br />软件设计师考试疑难问题解答  <br />  【作者】     王勇[同作者作品]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试疑难问题解答<br />   【出版社】  电子工业出版社<br /><a href="http://www.china-pub.com/computers/common/info.asp?id=26980">http://www.china-pub.com/computers/common/info.asp?id=26980</a><p><a href="http://www.china-pub.com/computers/common/info.asp?id=29417"></a> </p><p>2<br />计算机与软件工程知识——考点解析及模拟训练  <br />    刘克武[同作者作品]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试参考用书<br />   【出版社】  清华大学出版社<br /><a href="http://www.china-pub.com/computers/common/info.asp?id=23105">http://www.china-pub.com/computers/common/info.asp?id=23105</a><br /></p><p>2<br />软件设计师考试试题分类精解<br />  【作者】     王勇[同作者作品]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试试题分类精解<br />   【出版社】  电子工业出版社<br /><a href="http://www.china-pub.com/computers/common/info.asp?id=26979">http://www.china-pub.com/computers/common/info.asp?id=26979</a><br /></p><p>3<br />软件设计师考试冲刺指南  <br />【作者】     徐锋 吴兰陟[同作者作品]   [作译者介绍]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试冲刺指南<br />   【出版社】  电子工业出版社</p><p><a href="http://www.china-pub.com/computers/common/info.asp?id=25747">http://www.china-pub.com/computers/common/info.asp?id=25747</a></p><p>3<br />软件设计师考试全真模拟试题及解析  <br />  【作者】     谢树煜[同作者作品]<br />   【丛书名】  全国计算机技术与软件专业技术资格（水平）考试参考用书<br />   【出版社】  清华大学出版社   <br /><a href="http://www.china-pub.com/computers/common/info.asp?id=30830">http://www.china-pub.com/computers/common/info.asp?id=30830</a><br /></p><p>8：<br />你现在可以去找一些教材看看，顺便看一些针对每个知识点试题进行讲解的书。 <br /><br />9：<br />你看教材的时间最多只能有２个月，因为后面还要看那些辅导还要做题目，还要编程。 <br /><br />关键在于要针对那个考点给看明白，具体到每本书要看的不是很多的。<br />我把以前的教材也拿过来吧，里面做了一些标记，可能对你有点帮助。<br /><br />其实考这个可以不用那些辅导，只要把书看明白，把历年的试题弄明白，再多编编程，就好办了。 <br /><br />10：<br />这方面的书好多的，C语言的教材也可以啊。  <br /><br />那就是C教材。我有本号称C语言圣经的那本书 <br />   <br />就是C语言的作者写的那本吧？K&amp;R的那本？ <br /><br />现在出了那个配套的习题解答的。<br />我有那本C的习题解答。讲得相当好。 <br /><br />11：另外你再学学C++或者JAVA吧。<br />考试的时候还会考一个面向对象语言编程的。 <br />嗯，我觉得你看C++的话就特别要注意那边介绍面向对象的那部分，考的几率很大的。 <br /><br />12：你先看C那本书吧，然后结合教程看数据结构的。<br />最好是每天都看程序写程序。<br />这一部分是最难啃的，另外，你还要准备看一些介绍面向对象和UML的东西，那些东西书上介绍的很简单，但是一直是考试的重点。<br /> 你看完数据结构之后就看算法吧，到时候看面向对象和UML的时候搭配C++ 看。 <br />你现在就赶紧看C和数据结构，然后天天写程序吧。如果时间还有充裕的话再看看其他课程的。 <br /><br />13：每年考试的重点主要就是在于一下几个方面：数据结构和算法，数据库，面向对象与UML，还有操作系统 <br /><br />14：<br />我们公司那本应用UML和模式的书，现在好像是拿着在看吧，就是一本很经典的教材。  <br /><br />我抽屉也有本UML用户指南，如何 <br />   <br />有那本当然最好不过了。<br />那是UML的创始人写的。 <br /><br />15：那个数据库的书你准备了没？关于数据库的理论和实践上午下午的考试基本上都会考。 <br /><a href="http://www.dearbook.com/book/122904">http://www.dearbook.com/book/122904</a> <br />  <br />好像是这本 <br />   <br />这是最经典的教材，你重点看看中间的关系理论，数据库设计和规范化以及SQL的应用。 <br /><br />其他那些牵涉到具体实现的就可以不用看了。<br /><br />17：你有那本操作系统概念的书吗？也是刚才那本书的作者写的。 <br /><a href="http://www.china-pub.com/computers/common/info.asp?id=18247">http://www.china-pub.com/computers/common/info.asp?id=18247</a><br /><br />18：<br />好的，还有体系结构什么的，。。。 <br />   <br /><br />那个高教的那本体系结构你有没？<br />我以前和你说过的。  <br /><br />有。。哈哈。都可以找同学借 <br />  <br />那就好办，体系结构和操作系统考得比较多，而且还比较深入，最好都认真详细的看看。<br /> <br /><br />体系结构你重点看看流水线和CACHE那部分吧，考得比较多而且比较难。<br />操作系统就要重点看看进程的同步互斥还有段、页表的生成处理过程。 <br /><br />另外还有就是操作系统和体系结构都有涉及到的衔接部分，两本书上都有将，要好好看看。 <br /><br />19：<br />其他的什么软件工程，多媒体就不需要专门看教材了把 <br />   <br /><br />软件工程你要是有一些教材的话最好看看，那也是考得多的一个地方。 <br /><br />主要考察一些常用的软件过程，软件方法，还有比如面向过程和面向对象的方法。这些以前在下午的考试中是必考的。 <br /><br />软件工程方面的你看一些介绍性的理论的东西，这个东西的考试主要是考应用，一直都比较难的，只能多做题目，没别的办法。 <br /><br />20<br />你总体说说该怎么复习好。一门一门的顺序 <br />   <br />你现在开始看C和数据结构吧，同时坚持写程序看程序。然后如果还有点时间的话就看体系结构吧。<br />这算是第一部分的内容。  <br /><br />然后呢 <br />   <br />等你把这部分搞得差不多了就看算法，看操作系统和面向对象。同时练C++ <br />然后 <br />   <br /><br />面向对象还有C++ 最好结合UML一起搞。 <br /><br />然后看软件工程还有编译原理，还有计算机组成原理，计算机网络。 <br /><br />这是第3部分。<br />  <br /><br />接着 <br />   <br /><br />然后还要看的就是一些比如多媒体，还有知识产权，还有专业英语阅读。 <br /><br />总之考纲上规定的还有什么没看，就第4部分一起搞了。  <br /><br />几月份考试啊 <br />  <br /><br />还有第五部分吗 <br />   <br /><br />明年5月份就考了 ，时间很紧的。 <br /><br />你最好能够到时候有一个月的时间拿来回顾  <br />回顾什么？ <br />   <br /><br />这4个部分的时间就看你怎么安排了，看书的时候搭配一些习题的书一起做。从一开始就要坚持每天看程序写程序，一直到最后考试。 <br />回顾的话就是总结一下自己学的东西，而且还要分析以前做过的历年考试试题啊。 <br /><br />21：最好在第3部分看完了就要死命的做题目了。<br />我当年是把历年所有的题目都的打出来，然后一天一套的做，做完之后就对答案分析。<br /><br />其他的那些模拟试题倒无所谓做不做了，就是要把那10几套题目反反复复的做。这个在第3部分后面和第4部分一起搞啊。 <br /><br /><br />那就不太清楚了，他们那个系统分析员顾问团，出了好多的书，而且最新的那一套书应对这个考试确实效果相当理想。 <br /><br />前面那4个阶段你最多4个月就要搞完。然后你还要看希赛出的那套书。后面的复习看他们的书确实效果不错。 <br /><br />22：<a href="http://www.china-pub.com/computers/common/info.asp?id=8904">http://www.china-pub.com/computers/common/info.asp?id=8904</a><br /><br /><a href="http://www.china-pub.com/computers/common/info.asp?id=15267">http://www.china-pub.com/computers/common/info.asp?id=15267</a><br />这两本书都还可以。<br />第2本这个讲了太多的数据结构东西在里面了。不过后面讲算法的还是很经典。 <br /><br />前面那本可能不是特别好懂，不过那些题目很有代表性，而且有的题目考试还出现过。 <br /><br />怎么没看到那本算法导论啊<br /> <br /><br />我还下了视频的 <br /><br />太大了，你没那么多时间搞，而且那本讲得比较深，有的也不会考，可以等你考完这个专门花时间看。 <br /><br />你先看那本翻译的吧，再看国内出的那本。 <br /><br />这个数据结构和算法是最磨人的，把这些搞定就好办了。<br />加油。<br /></p><img src ="http://www.cnitblog.com/charester/aggbug/20511.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/charester/" target="_blank">天空</a> 2006-12-15 14:40 <a href="http://www.cnitblog.com/charester/archive/2006/12/15/20511.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>