
今天使用 在命令符下使用ping命令和ipconfig命令时,提示:不是内部命令,不是外部命令或者可执行性文件。产生错误的 这种原因要么是文件不存在,要么是路径不对。我的问题出现的原因是后者,原来是我安装JDK后修改了系统变量。解决方法为: 在我的电脑图标上点右键->属性->高级->环境变量,找到path这个变量,看看有没有%SystemRoot%\system32这个路径,没有的话加上。 方法:直接将如下路径加到PATH里 %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;
一个工程师应该在工程和生活中培养自己思维的习惯、深度和广度,以及思维方式和思维素材的选取。要成为一个合格工程师确实有很多品质是先天的,学校的教育及专业素养的养成是构成一个合格工程师的基础。自然科学是一个美丽而又广阔的园林,这园林是由那些科学巨匠是设计、规划和建造的。一个合格的电子工程师应该在哲学的高度上对自然科学的架构有清醒的认识。许多学电子认为现在是数字的时代对模拟技术不认真的学,其实我们的世界本来就是模拟的,所有的物理量都是模拟的。所以你对自然科学的最根本看法和世界观直接决定你水平的高低和以后的成就,所以不要认为自己是学理工的就放弃对哲学的深入学习,这就是为什么博士学位要叫PhD(Doctor of Philosophy)。 思考问题要有深度,思维有深度应该成为一种习惯。作为一个工程师要培养深邃的思考习惯。要知其然,更要知其所以然。。但是作为一个工程师更重要的是实践。自然科学是在浩如烟海的实验过程和数据上一步一步的发展起来的。所以不要谈空洞的理论。现在很多院所(包括很N的学校和研究所)总是谈一些空洞的理论,甚至在技术上是根本不可能的实现的理论。实践可以提高我们对自然科学的认识甚至改变着自然科学发展。有这种认识才可能创造和应用有价值的理论。理论是思想,是认识,不是公式和文字。现在很多的工程师比较容易忽视理论,认为把东西做出来了就可以。我们的目的是要把东西做出来,但我们最终的目的是要掌握前沿的尖端的技术,推动中国科学技术的发展。IT技术发展迅速,理论的发展也非常迅速。在洪水般的新观念和新技术,电子工程师必须要有坚实的理论作为基础。电子工程技术是多学科交叉的产物,在我所参与的多个项目中涉及多个行业和学科。所以你要掌握更多更全面的知识是非常必要的。搞硬件的往往容易忽略软件方面的东西。现在做项目软硬都不分家(注意我说的是项目而不是工程),故电子工程师不仅要懂得本专业的知识,还要有广泛的自然科学知识和懂得其他行业的一些基础的常识,只有这样才能成为出色的工程技术人员。 培养自己的学习方法是工程师的必修课。现在已经进入终身学习的时代了,如果靠学校里学的一点皮毛想成为一个优秀的工程师那是不可能想象的事情,绝大多数的知识都要靠自己去学习。学习要有计划有步骤。统筹、计划、逻辑并不只是体现在工作中,更要体现在学习和生活中,做事要有科学的安排,工程师做事要严谨求实。工程师的字典里没有“还行” “差不多”等这样的词汇。行就是行,不行就是不行。还要注重积累,不仅收藏自己的智慧结晶更要收藏别人的智慧结晶。要不断的积累好的做法和前人的思想。了解技术精英和科学巨匠对事物的看法。就像佛教上所说的“见山是山见水是水;见山不是山,见水不是是水;见山还是山,见水还是水”那样认识事物。 要学会独立的猎取信息和知识,得出自己判断。每个人在生活、工作中都会遇到很多问题,在学校时有老师同学帮你解答,在工作上又有谁会帮你?如果你做的是最前沿最尖端的技术时!故工程师要有独立解决问题的能力。不但要做行动上的巨人更要做思想上的巨人。
作者:July 二零一一年一月二十九日 一、蒙特卡罗算法1946年,美国拉斯阿莫斯国家实验室的三位科学家John von Neumann,Stan Ulam 和 Nick Metropolis 共同发明了,蒙特卡罗方法。 此算法被评为20世纪最伟大的十大算法之一,详情,请参见我的博文:http://blog.csdn.net/v_JULY_v/archive/2011/01/10/6127953.aspx 蒙特卡罗方法(Monte Carlo method),又称随机抽样或统计模拟方法,是一种以概率统计理论为指导 的一类非常重要的数值计算方法。此方法使用随机数(或更常见的伪随机数)来解决很多计算问题的方 法。 由于传统的经验方法由于不能逼近真实的物理过程,很难得到满意的结果,而蒙特卡罗方法由于能够真 实地模拟实际物理过程,故解决问题与实际非常符合,可以得到很圆满的结果。 蒙特卡罗方法的基本原理及思想如下: 当所求解问题是某种随机事件出现的概率,或者是某个随机变量的期望值时,通过某种“实验”的方法 ,以这种事件出现的频率估计这一随机事件的概率,或者得到这个随机变量的某些数字特征,并将其作 为问题的解。 有一个例子可以使你比较直观地了解蒙特卡洛方法: 假设我们要计算一个不规则图形的面积,那么图形的不规则程度和分析性计算(比如,积分)的复杂程 度是成正比的。蒙特卡洛方法是怎么计算的呢?假想你有一袋豆子,把豆子均匀地朝这个图形上撒,然 后数这个图形之中有多少颗豆子,这个豆子的数目就是图形的面积。当你的豆子越小,撒的越多的时候 ,结果就越精确。 在这里我们要假定豆子都在一个平面上,相互之间没有重叠。 蒙特卡罗方法通过抓住事物运动的几何数量和几何特征,利用数学方法来加以模拟,即进行一种数字模 拟实验。它是以一个概率模型为基础,按照这个模型所描绘的过程,通过模拟实验的结果,作为问题的 近似解。 蒙特卡罗方法与一般计算方法有很大区别,一般计算方法对于解决多维或因素复杂的问题非常困难,而 蒙特卡罗方法对于解决这方面的问题却比较简单。其特点如下: I、 直接追踪粒子,物理思路清晰,易于理解。 II、 采用随机抽样的方法,较真切的模拟粒子输运的过程,反映了统计涨落的规律。 III、不受系统多维、多因素等复杂性的限制,是解决复杂系统粒子输运问题的好方法。 等等。 此算法,日后还会在本BLOG 内详细阐述。 二、数据拟合、参数估计、插值等数据处理算法我们通常会遇到大量的数据需要处理, 而处理数据的关键就在于这些算法,通常使用Matlab作为工具。 数据拟合在数学建模比赛中中有应用,与图形处理有关的问题很多与拟合有关系,一个例子就是98年数 学建模美国赛A题,生物组织切片的三维插值处理,94年A题逢山开路,山体海拔高度的插值计算,还有 吵的沸沸扬扬可能会考的“非典”问题也要用到数据拟合算法,观察数据的走向进行处理。 此类问题在 MATLAB 中有很多现成的函数可以调用,熟悉MATLAB,这些方法都能游刃有余的用好。 三、线性规划、整数规划、多元规划、二次规划等规划类问题数学建模竞赛中很多问题都和数学规划有关,可以说不少的模型都可以归结为一组不等式作为约束条件 、几个函数表达式作为目标函数的问题,遇到这类问题,求解就是关键了,比如98年B题,用很多不等式 完全可以把问题刻画清楚,因此列举出规划后用 Lindo 、 Lingo 等软件来进行解决比较方便,所以还 需要熟悉这两个软件。 四、图论算法这类问题算法有很多, 包括: Dijkstra 、 Floyd 、 Prim 、 Bellman-Ford ,最大流,二分匹配等问题。 关于此类图论算法,可参考Introduction to Algorithms--算法导论,关于图算法的第22章-第26章。 同时,本BLOG内经典算法研究系列,对Dijkstra算法有所简单描述, ----------- 经典算法研究系列:二、Dijkstra 算法初探http://blog.csdn.net/v_JULY_v/archive/2010/12/24/6096981.aspx 更多,请关注本BLOG 日后更新的博文。 五、动态规划、回溯搜索、分治算法、分支定界等计算机算法在数学建模竞赛中,如:92 年B题用分枝定界法, 97年B题是典型的动态规划问题, 此外 98 年 B 题体现了分治算法。 这方面问题和 ACM 程序设计竞赛中的问题类似, 推荐看一下算法导论,与《计算机算法设计与分析》(电子工业出版社)等与计算机算法有关的书。 六、最优化理论的三大经典算法:模拟退火法、神经网络、遗传算法 这十几年来最优化理论有了飞速发展,模拟退火法、神经网络、遗传算法这三类算法发展很快。 在数学建模竞赛中:比如97年A题的模拟退火算法,00年B题的神经网络分类算法,01年B题这种难题也可 以使用神经网络,还有美国竞赛89年A题也和 BP 算法有关系,当时是86年刚提出BP算法,89年就考了, 说明赛题可能是当今前沿科技的抽象体现。 03 年 B 题伽马刀问题也是目前研究的课题,目前算法最佳的是遗传算法。 另,本人对人工智能非常感兴趣,遗传算法已在本BLOG内有所阐述,敬请参见。 ---------- 经典算法研究系列:七、深入浅出遗传算法,透析GA本质http://blog.csdn.net/v_JULY_v/archive/2011/01/12/6132775.aspx 其它俩大算法,模拟退火法,与神经网络,也定会在本BLOG内日后的博文更新中,详细阐述。 七、网格算法和穷举法网格算法和穷举法一样,只是网格法是连续问题的穷举。 比如要求在 N 个变量情况下的最优化问题,那么对这些变量可取的空间进行采点, 比如在 [ a; b ] 区间内取 M +1 个点,就是 a; a +( b ? a ) =M; a +2 ¢ ( b ? a ) =M ; …;b 那么这样循环就需要进行 ( M + 1) N 次运算,所以计算量很大。 在数学建模竞赛中:比如 97 年 A 题、 99 年 B 题都可以用网格法搜索,这种方法最好在运算速度较 快的计算机中进行,还有要用高级语言来做,最好不要用 MATLAB 做网格,否则会算很久。 穷举法大家都熟悉,自不用多说了。 八、一些连续离散化方法大部分物理问题的编程解决,都和这种方法有一定的联系。物理问题是反映我们生活在一个连续的世界 中,计算机只能处理离散的量,所以需要对连续量进行离散处理。 这种方法应用很广,而且和上面的很多算法有关。 事实上,网格算法、蒙特卡罗算法、模拟退火都用了这个思想。 九、数值分析算法数值分析(numerical analysis),是数学的一个分支,主要研究连续数学(区别于离散数学)问题的 算法。 如果在比赛中采用高级语言进行编程的话,那一些数值分析中常用的算法比 如方程组求解、矩阵运算、 函数积分等算法就需要额外编写库函数进行调用。 这类算法是针对高级语言而专门设的,如果你用的是 MATLAB 、 Mathematica ,大可不必准备, 因为像数值分析中有很多函数一般的数学软件是具备的。 十、图象处理算法在数学建模竞赛中:比如01 年 A 题中需要你会读 BMP 图象、美国赛 98 年 A 题需要你知道三维插值 计算, 03 年 B 题要求更高,不但需要编程计算还要进行处理,而数模论文中也有很多图片需要展示, 因此图象处理就是关键。做好这类问题,重要的是把MATLAB 学好,特别是图象处理的部分。 此数学建模十大算法的程序源码打包,请于此处下载:http://download.csdn.net/source/3007336 本人对算法,尤其感兴趣,且日渐愈浓, 日后,更多的、好的、经典实用算法将会在本BLOG内有所详细而细致入微的阐述与深入研究。 完。 作者声明: 本人July对本博客所有任何文章、内容和资料享有版权, 转载请注明作者本人July及出处。谢谢。二零一一年一月二十九日。
刻苦的训练我打算最后稍微提一下。主要说后者:什么是有效地训练? 我想说下我的理解。 很多ACMer入门的时候,都被告知:要多做题,做个500多道就变牛了。其实,这既不是充分条件、也不会是必要条件。 我觉得一般情况下,对于我们普通学校的大学生,各方面能力的差距不会太大,在这种情况下,训练和学习的方法尤为重要。 其实,500题仅仅是一个标志,而且仅仅表示你做ACM-ICPC有一定的时间, 我们训练的目的是什么?我觉得有四点 1、提高编程能力 2、学习算法,(读书,读论文,包括做一些题目验证) 3、准备好面临将到来的挑战(熟悉题型,调整心态) 4、启发思维。 这里四个目的,从训练的角度上,重要性逐次递减;为什么呢? 因为前面的因素是后面的基础。而是后面的目的,想达成越为不易。我觉得前3者能保证你ac掉你能做的题,即使难题始终不会做,也可以ac掉中等偏难的题目。 而需要一定思维难度的题,要以前三者为基础而且属于训练的后期,中期只能作为偶尔调节。当然,我思维也烂得要死,对这点没什么发言权,大家可以鄙视我。 我这里想主要说下第2点。 对于算法,我发现,很多我们这样的弱校ACMer选手没有侧重好算法的学习。 下面要讲的几点,可能都很老套,但我想以035对比我自己的例子给大家做说明。 <1>算法学习是ACM比赛所要推广或者要提倡的一个方面 记得曾经路过某人的blog,上面说他作比赛的时候遇到了一个dijkstra,他没做出来,然后评论到(大意):我才不会花时间去搞明白“这种”算法。 “这种”也许有可能是指:没什么实用性,对吧,这样我就不想评论了(又是有关科学和工程的讨论)。但起码有一点需要明确的:ACM-ICPC比赛时关于计算机科学的比赛,计算机科学是算法的科学,计算机算法中dijkstra有着重要的实际和启发意义,所以比赛一定要考。 你参加这个比赛,要拿奖,就必须学习这种算法。你也许觉得你智商很高,但ACM-ICPC比赛本身不是智力比赛,比赛就是要让你去学习这些东西,所以,如果你不想学的话,我觉得也没有必要参加。说道这,可能偏题有点远,但是希望以上的分析能得出这样一个基础结论:不想学好算法,那没有必要来比赛。 <2>用模板是不好的 现在很多我们弱校的ACM-ICPC选手比较依赖模板,说实话,我也很依赖,但是我起码知道一点,这样是不对的,某种意义上说,这是你没有把算法学明白的一种表现。而且也严重影响编码速度。在我见过的huicpc035参加过的比赛中,他从来没有看过模板,全部现场敲,有一次比赛有个图强连通分量+缩点+染色+什么的题去了,我在他们机房做,我则抄模板,结果总共敲了1个半小时,而035明确算法之后,啪啦啪啦,估计30多分钟就敲完了。这里顺便八卦一下他:我和kevin以前去湖大集训队玩的时候,给他取了个外号——打字猛男(他应该还不知道)。因为他敲键盘的声音特别大特别快,呵呵。 我觉得他敲代码的时间没有浪费,某牛曾说:因为每次敲都有可能有不同的错误,所以不用模板是好习惯。我最开始学dancing link的的时候,自己敲出了代码,然后接下来的几道题部分参考了以前的代码,后来基本上是直接copy。现在,当别人问我dancing link算法或有关的题目的时候,我已经是一脸茫然。 所以,用模板是不好的,有时候由于某些原因可能你用了模板,但你起码要知道这要做是不对的,并且有机会要改正。 <3>需要深入学习 像 ACRush、zzy、ahyangyi...等等国家队的天才们,本身难以说我们与他们之间有什么可比性。但是他们的学习方法应该还是值得借鉴的,他们的学习方法当然我们得不到言传身教,但是从他们在国家队集训的论文中和他们搞完ACM-ICPC以后的轨迹中,可以有所体现。那就是:深入学习。 其实这点我来讲可能还是不够有力,因为我这方面也很欠缺,我尽量说下我的想法。 首先,觉得ACMer学算法不应停留在看看代码实现这个层面,在算法思想上要有清醒的认识,在正确性分析上要也应该要有较好的逻辑。因为网上的代码的实现上的一些细枝末节很可能掩盖了算法本身有的简洁性、美感和思想。因而丧失了对算法整体上的一些认识。还拿dijkstra算法打比方,有些算法不是基于 dijskstra的直接建模,而是需要你修改这个算法,这时你对算法没有真正理解的话,也就一筹莫展了。 我为什么老说Dijkstra算法,因为确实很多人都只知道用模板,而且模板还不好,在我看到的Dijkstra实现中,只有czyuan_acm的代码写得好。不是说其他的不对,但确实是有问题,投机取巧了的。 所以,要阅读论文和书籍,尤其与英文书籍,窥到它的本质。另一方面,只有这样,你学的的东西才能在ACM-ICPC以外,给你一定的启发——否则你会迅速忘掉它的。 据我所知,035起码阅读了几十篇集训队论文,orzorzorz,而且切掉了例题。 <4>独立思考 这点我也很惭愧,因为我也是缺乏独立思考的。很多题我不会了就去搜解题报告,所以反而我的搜资料能力变得特别强。035和许多大牛在这点上做的比我好多了,他们遇到题不会的时候,也不会很急于把题目做出来,可能每隔一段时间又拿出来想一次,总有一天想通了,之后这一类型的题目基本上也就没有什么问题了。 而我恰恰比较“虚荣”,做到的题目不会不太愿意想太久,就想尽量快些AC,于是急于看解题报告,这样导致的一个问题就是有些重要的东西解题报告中没有提到,而我也没去想就把他们忽略了,这样,我还是不会做。我和035讨论问题的时候,我不会一般就直接找他要代码,但是他不懂的时候,顶多问我大体的思路,而绝对不会要代码的。 在去年ACM赛区尾声的时候,我发现035做中难题的能力已经明显超过我一个档次。看他现在做的题目,已然是相当变态,几乎是都100以下人ac,这些题目我看了基本上没什么想法,更要命的时,解题报告也搜不到。035目前的状态让我想起一个人,不知道大家知道不:wangfangbob,他切bt题的能力也是令人汗颜的。 <5>做有意义的题 1.是不要做水题,这里的水题定义为:一眼就能看出做法,而且中途的实现可以预计没有太多问题的题目。 2.是做能够强化你最近学到的东西的题目 3.你不会但你应该会的题目。 这同时也是在说,某些没太多代表性的题目可以少做,因为对比赛帮助不大。(当然我这个参加比赛的目的很功利,非功利主义者另当别论)刚才,我把我在poj上的号和他的号对比了下,他ac而我没ac的基本上是难题,我ac他没ac的一般是水题,看得我想哭,5555。 补充一点:ac的人多的并不一定代表着水题,有些几千人ac的题目,在现场赛中ac的人很少,这样的题目往往是有一定思维难度且编码不难的好题,这种题目要认真做,某个学长说:经典的题目啊,只有那么多,做一道,就少一道。 <6>估算好某种训练所需要的时间 我觉得我学网络流就是一个例子,我在大概赛区赛之前2个月开始学习网络流,1个月前开始学习费用流,但是对于我来讲,这两个月培养出来的网络流思维还是不够(虽然也做了不少题),特别是,这种题目往往作为中难的题目出现,不会让你随便水的,于是,北京赛区的那道网络流当时就没有想出来——功利地说,学习网络流没有得到好的效果。 所以,现在来看,当时其实我可以不搞网络流。如果要学一种比较有难度的东西,并且还必须把他搞好,应该较早地,全面地学习,必须长期的训练以培养这种思维。打个比方,如果你微积分平时不学,仅仅考试前一周狂做题目,我觉得上90分是很困难的。 当然,这要根据个人情况而定,我的理解能力应该说是中等水平,如果牛的话应该可以更快地学好。 <7>有关训练的度 我有时候通宵刷体,这里我不知道huicpc035有没有这个习惯,不过我通宵的时候没见到他通宵。 我觉得其实通宵刷体,或者太长时间地做题,还是不好的。我们为什么会这样有热情的做题呢,因为我们有兴趣;但是一个人的成功不仅仅依赖于兴趣,还要依赖于自控。这和打游戏是一个道理,游戏太有趣以至于我们常常通宵——ICPC题目也太有趣,所以有时候通宵。而且很多时候是,由于一道题AC不掉,所以赌气一定要搞定才睡觉,这样一不小心,就通宵了。 其实我明白,通宵不一定效果好,这仅仅说明了你兴趣很高涨而已。通宵往往会打乱你的时间安排,打乱你的生物钟,进而影响你短期或是中期的训练计划。而且,疲惫的状态下做题,你往往只有ac题目的欲望,而完全丧失了ac题目的灵气。所以,我建议,ACMer一定要合理安排作息,能够自控,这样不仅仅对你做 ACM-ICPC有好处。 总之,有效训练是很重要,只有通过有效的训练你才能获得你参加这个比赛应得的东西。 还有就是,除了035以外,另一个值得大家学习的就是richardxx——我也很佩服,我并不觉得他是天才,我觉得他以全方位的努力让他自己变得优秀,大家看他的blog可以看到他的学习历程。 最后要说下刻苦训练这一点,这个我主要想说给我们学校的acm队员: 客观的说,我们学校很多名校落榜生(我相比而言是水进的)。确实都蛮聪明的,但再聪明也比不上ACRush吧?人家可是SGU都切满了!ACM不是智力测试,不是你什么都不做就可以天上掉馅饼的。当然我不是说题目一定要做多少多少道,但如果你觉得你可以一心二用,从概率上来讲,你百分之九十地错了,我是个工科生,我相信概率而非奇迹。 我觉得035这方面也是值得我们学习的,我比较喜欢扯淡,有时候聊题目的时候也经常不小心就去扯其他话题去了,在学习的时候,035是坚决不多聊乱七八糟的东西的,除了讨论上QQ,平时据我观察都是残酷地训练。现在回想起来,我有点后悔,QQ上和网上花掉的时间用来学习新的东西,也许结果会更好。 ACM-ICPC绝不是大学生活的全部,也不是搞算法的全部,你大可以花时间去做其他研究,做项目,或者参加学生工作(我更欣赏那些对人生和职业有良好规划的ACMer);但是,如果你搞ICPC的那段时间你不是全部投入,那的在ACM-ICPC生涯中,将只有后悔。
初级: 一.基本算法: (1)枚举. (poj1018,poj1753,poj2965) (2)贪心(poj1328,poj2109,poj2586) (3)递归和分治法. (4)递推. (5)构造法.(poj3295,poj3239) (6.1)模拟法.(poj1008,poj1068,poj2632,poj1573,poj2993,poj2996,poj3087) (6.2)模拟法(高精度算法)(poj1001,poj1503,poj2389,poj2602,poj3982,21位大数的水仙花数)二.图算法: (1)图的深度优先遍历和广度优先遍历. (2)最短路径算法(dijkstra,bellman-ford,floyd,heap+dijkstra) (poj1860,poj3259,poj1062,poj2253,poj1125,poj2240) (3)最小生成树算法(prim,kruskal) (poj1789,poj2485,poj1258,poj3026) (4)拓扑排序 (poj1094) (5)二分图的最大匹配 (匈牙利算法) (poj3041,poj3020) (6)最大流的增广路算法(压入重标法,KM算法). (poj1459,poj3436)三.数据结构. (1)串 (poj1016,poj1035,poj3080,poj1936) (2)排序(快排、归并排(与逆序数有关)、堆排) (poj1007,poj2388,poj1804,poj2299) (3)简单并查集的应用. (4)哈希表和二分查找等高效查找法(数的Hash,串的Hash) (poj1002,poj3349,poj3274,poj1840,poj2002,poj3432,poj2503) (5)优先队列(poj3253) (6)堆 (7)trie树(静态建树、动态建树) (poj2513)四.简单搜索 (1)深度优先搜索 (poj2488,poj3083,poj3009,poj1321) (2)广度优先搜索(poj3278,poj1426,poj3126,poj3414,poj2251) (3)简单搜索技巧和剪枝(poj1010,poj2362,poj1011,poj1416,poj2676,poj1129)五.动态规划 (1)背包问题. (poj1837,poj1276,poj1014) (2)型如下表的简单DP(可参考lrj的书 page149): 1.E[j]=opt{D+w(i,j)} (poj3267,poj1836,poj1260,poj2533) 2.E[i,j]=opt{D[i-1,j]+xi,D[i,j-1]+yj,D[i-1][j-1]+zij} (最长公共子序列) (poj1015,poj3176,poj1163,poj1080,poj1159) 3.C[i,j]=w[i,j]+opt{C[i,k-1]+C[k,j]}.(最优二分检索树问题) 六.数学 (1)组合数学: 1.加法原理和乘法原理. 2.排列组合. 3.递推关系. (poj1012,poj3252,poj1850,poj1496,poj1019,poj1942) (2)数论. 1.素数与整除问题 2.进制位. 3.同余模运算. (poj2305,poj2635,poj3292,poj1845,poj2115) 4.逻辑推理. (poj1013,poj1017) 4.中国余数定理(poj1006) (3)计算方法. 1.二分法求解单调函数相关知识.(poj3273,poj3258,poj1905,poj3122) (4)随机化算法(poj2531) (5)概率(poj2151)七.计算几何学. (1)几何公式. (2)叉积和点积的运用(如线段相交的判定,点到线段的距离等). (poj2031,poj1039) (3)多边型的简单算法(求面积)和相关判定(点在多边型内,多边型是否相交) (poj1408,poj1584) (4)凸包. (poj1696,poj2187,poj1113) 转载请注明出处:優YoUhttp://blog.csdn.net/lyy289065406/article/details/6642573 中级: 一.基本算法: (1)C++的标准模版库的应用. (poj3096,poj3007) (2)较为复杂的模拟题的训练(poj3393,poj1472,poj3371,poj1027,poj2706,poj1009)二.图算法: (1)差分约束系统的建立和求解. (poj1716,poj1201,poj2983) (2)最小费用最大流(poj2516,poj2195) (3)双连通分量(poj2942) (4)强连通分支及其缩点.(poj2186) (5)图的割边和割点(poj1523,poj3352,poj3177) (6)最小割模型、网络流规约(poj3308 )三.数据结构. (1)线段树. (poj2528,poj2828,poj2777,poj2886,poj2750) (2)静态二叉检索树. (poj2482,poj2352) (3)树状树组(poj1195,poj3321) (4)RMQ. (poj3264,poj3368) (5)并查集的高级应用. (poj1703,2492) (6)KMP算法. (poj1961,poj2406) 四.搜索 (1)最优化剪枝和可行性剪枝 (2)搜索的技巧和优化 (poj1020,poj3411,poj1724) (3)记忆化搜索(poj3373,poj1691) (4)搜索与状态压缩(poj1184)五.动态规划 (1)较为复杂的动态规划(如动态规划解特别的施行商问题等) (poj1191,poj1054,poj3280,poj2029,poj2948,poj1925,poj3034) (2)记录状态的动态规划. (POJ3254,poj2411,poj1185) (3)树型动态规划(poj2057,poj1947,poj2486,poj3140) 六.数学 (1)组合数学: 1.容斥原理. 2.抽屉原理. 3.置换群与Polya定理(poj1286,poj2409,poj3270,poj1026). 4.递推关系和母函数. (2)数学. 1.高斯消元法(poj2947,poj1487, poj2065,poj1166,poj1222) 2.概率问题. (poj3071,poj3440) 3.GCD、扩展的欧几里德(中国剩余定理) (poj3101) (3)计算方法. 1.0/1分数规划. (poj2976) 2.三分法求解单峰(单谷)的极值. 3.矩阵法(poj3150,poj3422,poj3070) 4.迭代逼近(poj3301) (4)随机化算法(poj3318,poj2454) (5)杂题. (poj1870,poj3296,poj3286,poj1095) 七.计算几何学. (1)坐标离散化. (2)扫描线算法(例如求矩形的面积和周长并,常和线段树或堆一起使用). (poj1765,poj1177,poj1151,poj3277,poj2280,poj3004) (3)多边形的内核(半平面交)(poj3130,poj3335) (4)几何工具的综合应用.(poj1819,poj1066,poj2043,poj3227,poj2165,poj3429) 转载请注明出处:優YoU http://blog.csdn.net/lyy289065406/article/details/6642573 高级: 一.基本算法要求: (1)代码快速写成,精简但不失风格 (poj2525,poj1684,poj1421,poj1048,poj2050,poj3306) (2)保证正确性和高效性. poj3434 二.图算法: (1)度限制最小生成树和第K最短路. (poj1639) (2)最短路,最小生成树,二分图,最大流问题的相关理论(主要是模型建立和求解) (poj3155, poj2112,poj1966,poj3281,poj1087,poj2289,poj3216,poj2446 (3)最优比率生成树. (poj2728) (4)最小树形图(poj3164) (5)次小生成树. (6)无向图、有向图的最小环 三.数据结构. (1)trie图的建立和应用. (poj2778) (2)LCA和RMQ问题(LCA(最近公共祖先问题) 有离线算法(并查集+dfs) 和 在线算法 (RMQ+dfs)).(poj1330) (3)双端队列和它的应用(维护一个单调的队列,常常在动态规划中起到优化状态转移的 目的). (poj2823) (4)左偏树(可合并堆). (5)后缀树(非常有用的数据结构,也是赛区考题的热点). (poj3415,poj3294) 四.搜索 (1)较麻烦的搜索题目训练(poj1069,poj3322,poj1475,poj1924,poj2049,poj3426) (2)广搜的状态优化:利用M进制数存储状态、转化为串用hash表判重、按位压缩存储状态、双向广搜、A*算法. (poj1768,poj1184,poj1872,poj1324,poj2046,poj1482) (3)深搜的优化:尽量用位运算、一定要加剪枝、函数参数尽可能少、层数不易过大、可以考虑双向搜索或者是轮换搜索、IDA*算法. (poj3131,poj2870,poj2286)五.动态规划 (1)需要用数据结构优化的动态规划. (poj2754,poj3378,poj3017) (2)四边形不等式理论. (3)较难的状态DP(poj3133) 六.数学 (1)组合数学. 1.MoBius反演(poj2888,poj2154) 2.偏序关系理论. (2)博奕论. 1.极大极小过程(poj3317,poj1085) 2.Nim问题. 七.计算几何学. (1)半平面求交(poj3384,poj2540) (2)可视图的建立(poj2966) (3)点集最小圆覆盖. (4)对踵点(poj2079) 八.综合题. (poj3109,poj1478,poj1462,poj2729,poj2048,poj3336,poj3315,poj2148,poj1263) 转载请注明出处:優YoU http://blog.csdn.net/lyy289065406/article/details/6642573
求高精度幂 Time Limit: 500MS Memory Limit: 10000K Total Submissions: 87719 Accepted: 20833 Description 对数值很大、精度很高的数进行高精度计算是一类十分常见的问题。比如,对国债进行计算就是属于这类问题。 现在要你解决的问题是:对一个实数R( 0.0 < R < 99.999),要求写程序精确计算 R 的 n 次方(Rn),其中n 是整数并且 0 < n<= 25。 Input T输入包括多组 R 和 n。 R 的值占第 1 到第 6 列,n 的值占第 8 和第 9列。 Output 对于每组输入,要求输出一行,该行包含精确的 R 的 n 次方。输出需要去掉前导的 0 后不要的 0。如果输出是整数,不要输出小数点。 Sample Input 95.123 12 0.4321 20 5.1234 15 6.7592 9 98.999 10 1.0100 12 Sample Output 548815620517731830194541.899025343415715973535967221869852721 .00000005148554641076956121994511276767154838481760200726351203835429763013462401 43992025569.928573701266488041146654993318703707511666295476720493953024 29448126.764121021618164430206909037173276672 90429072743629540498.107596019456651774561044010001 1.126825030131969720661201 分析: 在计算机上进行高精度计算,首先要处理好以下几个基本问题: 1、数据的接收与存储; 2、计算结果位数的确定; 3、进位处理和借位处理; 4、商和余数的求法; 输入和存储 运算因子超出了整型、实型能表示的范围,肯定不能直接用一个数的形式来表示。能表示多个数的数据类型有两种:数组和字符串。 (1)数组:每个数组元素存储1位(在优化时,这里是一个重点!),有多少位就需要多少个数组元素;优点:每一位都是数的形式,可以直接加减;运算时非常方便缺点:数组不能直接输入;输入时每两位数之间必须有分隔符,不符合数值的输入习惯; (2)字符串:字符串的最大长度是255,可以表示255位。优点:能直接输入输出,输入时,每两位数之间不必分隔符,符合数值的输入习惯;缺点:字符串中的每一位是一个字符,不能直接进行运算,必须先将它转化为数值再进行运算;运算时非常不方便(注意‘0’的问题)。 优化: 一个数组元素存放四位数 注意:是加减法时可以用interger,但是当是乘法的时候,就要用int64,否则会出现越界的情况。 还有就是:输出时对非最高位的补零处理。 另一个问题: 存储顺序 正序?? 逆序?? 还有就是一定不要忘了初始化!! 计算结果位数的确定 两数之和的位数最大为较大的数的位数加1。 乘积的位数最大为两个因子的位数之和。 阶乘:lgn!=lgn+lg(n-1)+lg(n-2)...................+lg3+lg2+lg1 =lnn/ln10+ln(n-1)/ln10+ln(n-2)/ln10+................+ln3/ln10+ ln2/ln10+ln1/ln10 =trunc(1/ln10*(lnn+ln(n-1)+ln(n-2)+...........+ln3+ln2+ln1) 乘方:lg(a?^b)=trunc(lg(a^b))+1 =trunc(b*lga)+1 =trunc(b*lna/ln10)+1 高精度的加法 ncarry=0; for (i=0;i<=len;i++) { k=a[i]+b[i]+ncarry; a[i+1]+=k/N; ncarry=k%N; } 当最后ncarry>0时,len会变化!! 高精度的减法 先比较大小,大的为a,用一个变量记录符号。 ncarry=0; for (i=0;i<=len;i++) { if (a[i]-b[i]-ncarry>=0) a[i]=a[i]-b[i]-ncarry,ncarry=0; else a[i]=a[i]+N-b[i]-ncarry,ncarry=1; } 高精度的乘法 For (i=0;i<=lena;i++) for (j=0;j<=lenb;j++) c[i+j]+=a[i]*b[j]; For (i=0;i<=lena+lenb;i++) { c[i+j+1]+=c[i+j]/N; c[i+j]=c[i+j]/N; } 高精度的除法 A÷B精确值有两种情况:①、A能被B整除,没有余数。②、A不能被B整除,对余数进行处理。首先,我们知道,在做除法运算时,有一个不变的量和三个变化的量,不变的量是除数,三个变化的量分别是:被除数、商和余数。 可以用减法代替除法运算:不断比较A[1..n]与B[1..n]的大小,如果A[1..n]>=B[1..n]则商C[1..n]+1→C[1..n],然后就是一个减法过程:A[1..n]-B[1..n]→A[1..n]。 由于简单的减法速度太慢,故必须进行优化。 设置一个位置值J,当A[1..n]>B[1..n]时,B[1..n]左移→B[0..n],j:=j+1,即令B[1..n]增大10倍。这样就减少了减法的次数。当j>0且A[1..n]<B[1..n]时,B[0..n]右移 求n! 一个例子:计算5*6*7*8 方法一:顺序连乘 5*6=30,1*1=1次乘法 30*7=210,2*1=2次乘法 210*8=1680,3*1=3次乘法 方法二:非顺序连乘 5*6=30,1*1=1次乘法 7*8=56,1*1= 1次乘法 30*56=1680,2*2=4次乘法 若“n位数*m位数=n+m位数”,则n个单精度数。 无论以何种顺序相乘,乘法次数一定为n(n-1)/2次。(n为积的位数) 证明: 设F(n)表示乘法次数,则F(1)=0,满足题设 设k<n时,F(k)=k(k-1)/2,现在计算F(n) 设最后一次乘法计算为“k位数*(n-k)位数”,则 F(n)=F(k)+F(n-k)+k (n-k)=n(n-1)/2(与k的选择无关) 考虑k+t个单精度数相乘 a1*a2*…*ak *ak+1*…*ak+t 设a1*a2*…*ak结果为m位高进制数(假设已经算出) ak+1*…*ak+t结果为1位高进制数 若顺序相乘,需要t次“m位数*1位数” ,共mt次乘法 可以先计算ak+1*…*ak+t,再一起乘,只需要m+t次乘法 在设置了缓存的前提下,计算m个单精度数的积,如果结果为n位数,则乘法次数约为n(n–1)/2次,与m关系不大 设S=a1*a2*…*am,S是n位高进制数 可以把乘法的过程近似看做,先将这m个数分为n组,每组的积仍然是一个单精度数,最后计算后面这n个数的积。时间主要集中在求最后n个数的积上,这时基本上满足“n位数*m位数=n+m位数”,故乘法次数可近似的看做n(n-1)/2次 10!=28*34*52*7 n!分解质因数的复杂度远小于nlogn,可以忽略不计 与普通算法相比,分解质因数后,虽然因子个数m变多了,但结果的位数n没有变,只要使用了缓存,乘法次数还是约为n(n-1)/2次 因此,分解质因数不会变慢(这也可以通过实践来说明) 分解质因数之后,出现了大量求乘幂的运算,我们可以优化求乘幂的算法。这样,分解质因数的好处就体现出来 二分法求乘幂 a2n+1=a2n*a a2n=(an)2 其中,a是单精度数 二分法求乘幂之优化平方算法 怎样优化 (a+b)2=a2+2ab+b2 例:123452=1232*10000+452+2*123*45*100 把一个n位数分为一个t位数和一个n-t位数,再求平方 怎样分 设求n位数的平方需要F(n)次乘法 F(n)=F(t)+F(n-t)+t(n-t),F(1)=1 用数学归纳法,可证明F(n)恒等于n(n+1)/2 所以,无论怎样分,效率都是一样 将n位数分为一个1位数和n–1位数,这样处理比较方便 优化: 计算S=ax+kbx=(ab)xak 当k<xlogab时,采用(ab)xak比较好,否则采用ax+kbx更快 可以先计算两种算法的乘法次数,再解不等式,就可以得到结论 也可以换一个角度来分析。其实,两种算法主要差别在最后一步求积上。由于两种方法,积的位数都是一样的,所以两个因数的差越大,乘法次数就越小 ∴当axbx–ak>ax+k–bx时,选用(ab)xak,反之,则采用ax+kbx。 ∴axbx–ak>ax+k–bx ∴(bx–ak)(ax+1)>0 ∴bx>ak 这时k<xlogab 这道题: 1.处理小数点问题,以及反序。 2.进行n次乘法。 3.进行输出,并加上小数点。 代码: #include <stdio.h> #include <iostream> #include <string> using namespace std; int main() { string mlp; //乘数 int power; //乘数的幂 int r[151]; //保存结果 int hdot; while(cin>>mlp>>power) { hdot=0; for(int t=0;t<150;t++) { r[t]=-1; } if(mlp.find(".")!=string::npos) hdot=mlp.length()-mlp.find(".")-1; string::iterator itr=mlp.end()-1; while(hdot>0&&itr>=mlp.begin()) { if(*itr!='0') {break;} hdot--; itr--; } int cn=0; while(itr>=mlp.begin()) { if(*itr!='.') { r[cn]=*itr-'0'; cn++; } itr--; } int k=cn-1; int m=0; //保存临时数; while(k>-1) { m=m*10+r[k]; k--; } for(int i=1;i<power;i++) { int j=0; while(r[j]>-1) { r[j]=r[j]*m; j++; } j=0; while(r[j]>-1) { if(r[j+1]==-1&&r[j]>=10) r[j+1]=r[j]/10; else r[j+1]+=r[j]/10; r[j]=r[j]; j++; } } hdot=hdot*power; int cnt=0; while(r[cnt]>-1) { cnt++; } if(hdot>=cnt) { cout<<"."; while(hdot>cnt) { cout<<"0"; hdot--; } hdot=0; } for(k=cnt-1;k>=0;k--) { if((k+1)==hdot&&hdot!=0) cout<<"."; cout<<r[k]; } cout<<endl; } return 0; }
(一) 先声明一个概念,裸编程,我创造的名词,指的是在裸机上编写程序,裸机,在单片机领域就是指带着硬件的单片机控制系统,不要想歪咯。 在裸机上编程,就犹如在一片荒地上开垦,任何一锄头下去,都会碰到硬生生的石头,要说做这有什么味?拓荒者追求的是来年的绿洲。而我们这些开垦裸机的所谓的工程师们追求的是什么?我们当然追求的是完成一个任务。 我们一般都自称是高级知识分子,那么我们在拓荒的过程中应该想些什么?当然不是想着如何把任务完成,而应该首先想着我们在想些什么。绕了是不?绕了就对了,这一绕就绕出了思想。思想是一个简单的人在一个复杂的环境里做任何事情的统帅,它影响着一个拓荒者人生的每一个细节,当然也包括裸编程本身。 当一个人拿着锄头,一锄又一锄,汗滴脚下土的时候,我们能知道他们在想什么吗?当然这不好说,如果自己去锄就知道了。但是大抵也差不多,随便举几个吧:这太阳他娘的怎么这么毒?这石头他娘的咋这么多?这地种什么最好?这还有多少天能搞完?这样干太慢了,要是有台机械搞多好。当然这只是一部分,任何人可以想出很多想法来。 那么当我们在裸机上拓荒的时候,我们该想些什么?也许我们一般的想法是:先把一个简单的功能做了,先把一个重要的功能做了,今天终于把这个功能调试好了明天可以做下一个功能了,这个为什么不是我想象的那样的结果?真是莫名其妙!也等等一下吧。 如果拿来一个任务,搭好测试平台就开始做程序,想着一个功能一个功能的凑完,然后就自我陶醉着成功的喜悦,那这样做程序,基本就叫做没思想。有思想的做程序,是不能一下去就堆积源码的,因为那样只会让一堆生硬的数字怯生生的挤在一起,不管他们有没有多余,有没有矛盾。所以写源码之前,是要想想如何写的。也许很多人在写之前都想过类似的问题,比如把任务模块化后再组织程序。但是这样的想法只是任务上的事情,而并不是裸编程时的思想,裸编程的思想,应该是在组织任务模块过程中及编写裸程序时影响源码组织的指导思想,它直接决定着源码的质量。 一个数据结构,一个模块形成,一个单片机的指令,一个硬指令的运行机制,一个口线的驱动方式,一个中断的顺序,一个跳变的延迟,一个代码的位置,一个逻辑的组织,一个模块与模块之间的生(运行时的状态)死(不运行时的状态)关系等等,都是裸程序思想的组成部分。 这似乎很琐碎,但是裸程序原本就如此,它不同于上位机程序,有一个强大完善的操作系统支持。单片机里不可能植入操作系统,那样做就变味了,可不要有人跳出来说,某某某单片机就有操作系统了。裸程序就应该是建立在赤裸裸的硬件基础上的程序,只有有用的功能才有代码,裸程序的质量也许经常在应用中感觉不出来,也许你做和他做都能实现功能,但是好的裸程序有良好的可扩充性、可维护性,系统具有高稳定性和高性能。 而追求这种高品位的技术境界,就必须要有好的思想来指导。是不是看着有些迷糊?别说看得迷糊,我说都说迷糊了,总的来说,就是把一个优秀的灵魂,植入你的源码中,让你的源码具有一个优良的思想。 (二) 前面发帖说裸编程要有思想,也许还不够具体,本帖就是要具体说裸编程的思想的具体做法。 没有思想的裸程序就如一副人体骨架,有个人形,但没有人样,骨骼之间的关节都是靠胶水或拉线连接起来的,生硬而呆板。如果给骨架包上皮肉,加上灵魂,我们就会惊叹:啊!这是帅哥,这是美女!因为骨架活了。 裸程序也一样,如果按传统的思维方式说这样就足够了,那么裸程序就形如骨架,通常只是一些功能的粗糙堆砌,也只会叫后人看了说这程序垃圾,而后人再做也未必能跳出这个圈子,那么后后人看了又叫这程序垃圾,如此下去,代代相传,传了什么?传了一个总被叫垃圾的东西:无思想的裸程序。 我做了程序好多年,也思考了编程好多年,不断的经验积累告诉我:写好的程序不是如何去完成代码,而是如何去组织代码,是如何组织,不是组织。上位机中面向对象的编程思想,就是一个非常可取的思想。 面向对象的编程思想在上位机中是有一个非常丰富的开发包和功能强大的操作系统支持的,裸编程如何引入这样的思想呢?也许很多人会觉得不可能。 其实,没有什么是不可能的。再复杂的思想,最终都会归结到汇编,归结到裸程序,我们的单片机程序,正是一种裸程序。只是在单片机编程时和微机编程时我们站在开发平台上的高度不一样,而已! 对这个高度的理解,也许很多人很困惑,因为我们平时很少注意它们,那么这里我就举个其他的例子来说明,尽管和裸编程好象不很相关,但是这个例子里的高度概念十分清晰。我们知道网络传输标准层次有七层:应用层、表示层、会话层、传输层、网络层、链路层、物理层,这么多层做什么用?也许理解这样分层的概念也十分辛苦,但是理解这样分层的思想,就容易多了,而且这也是我们硬件工程师们最应该借鉴的思想,让我们的硬件设计更具有标准性和前瞻性。这个七层的思想从根本上讲就是将一个网络传输产品细化,让不同的制造商选择一个适合自己的层次开发自己的产品,层次不一样,他们所选择的开发基础和开发内容就不一样,高一层开发者继承低层开发者的成果,从而节省社会资源,提高社会生产力。对这个指导思想我就不赘述了,各位自己去理解,这里要说的是,微机上的面向对象编程思想就是如同在应用层上实现的思想,而裸程序的面向对象思想则如同在链路层上实现的思想,他下面没有软件开发包,只有物理构架。但是在应用层上实现的思想,最终都要翻译到物理构架上。 看懂了上面的例子,就一定明白,裸程序的面向对象思想,是可以实现的,只是难度要大得多,理解要难得多。但是这不要紧,这正是软件水平的表现,你喜欢技术,又何惧之?其实也不会难到哪里去,只是把做事情的方式稍微改变一下而已。 传统上我们都喜欢用功能来划分模块,细分任务,面向对象思想不这样。面向对象思想则是先从一个任务中找出对象,在对象中搀杂些模块等来实现功能的。这就是两种风格截然不同的地方。比如我们要让我们的单片机把显示信息输出到显示器,那么传统的分析方法是信息格式化、格式化数据送显示器显示,似乎这样也就足够了,不同的显示器用不同的送显示程序或者程序段,配置不同的变量,能共的共起来,不能共的分开。但是面向对象的思想不是这样做的,而是首先把显示器当作一个对象,该对象具有一些功能和一些变量属性,不同的显示器在对象中使用相同的代码标识,如函数指针(C语言中),这样对于任何一个不同的显示器,在调用时都使用同样的代码。也许有人说,传统的做法这样也可以做呀,为什么要弄得罗里吧唆的呢?其实不然,使用了正确的思想的好处在前头已经说了好多了,如果还模糊就上去再看一次。 说了那么多理论,现在就说些具体的做法吧。以Keil C为编译环境来说说一个对象具体组织的一些做法。首先是找出对象,如显示器,这就是一个典型的对象。其次是分析一个活对象所应具有的基本特征,即属性与动作。显示器的属性如:类型代号、亮度、对比度、显存等,动作如:初始化、内容刷新和显示、开启和关闭、内容闪烁等花样显示等。这样分也比较容易理解,下面是对于代码的组织上,要注意对象的独立性与完整性,首先把显示器对象单独放在一个文档上,属于对象特有的变量与对象的定义放在一起,要区分公有变量与私有变量的定义方式,对于私有变量要考虑临时变量与永久变量的安排,这些安排都是对变量生命期的严格确定,这样可以节省内存,避免混乱。如某一个函数要使用一个变量,函数在调用完了就退出了,而有一个变量只有它使用,却要保存每一次调用函数所产生的结果,这样的变量怎么定义呢?很多人会直接定义一个全局变量,但是一个好的做法是把这个变量定义成该函数的局部变量,但是定义成静态的,那么这样这个变量对其他代码就是透明的,完全不可能会被误修改,而且代码分类性好,便于将来的维护。用函数指针来统一不同类型的显示器不同的处理方式,也是一个很好的处理办法,那样可以让具体处理方式千差万别的显示器都能用一个统一的对象,但是函数指针要慎重使用。 好了,说长了我就头晕,不说了,思想的精华,不必有一样的形态,不同的人会有不同的理解,我只希望能给大家的程序生涯抛砖引玉,我就觉得很有成就感了。 (三) 既然大家对例子要求那么强烈,那么我这里就引入一个例子。说实在的,我是想偷偷懒,说说理论就算了的,因为写一个程序,里头的理论太多,我害怕把帖子搞得老长,更害怕空想一个程序做例子。 在引入例子之前,我们要做一些准备工作,然后一步一步地走向例子里去。就以前面帖子提到过的显示器控制为例。 显示器就是一个对象。无论它是功能多么复杂的显示器,或者功能多么简单的显示器,对于需要显示信息的调用者来说,都并不重要,也就是说对于需要使用显示器的主体来讲,他只管显示信息,不管显示器的千差万别,只要显示器提供了某功能,它可以调用就行,调用前当然要遵守显示器的数据传递规则,但是不必考虑不同的显示器所产生的传递规则的差异。也就是说,对于调用者来说,永远不会希望有多条规则来让自己的调用代码变复杂。 因此,我们首先需要构造一个相对独立的代码段,也就是显示器对象,以下都以Keil C作为裸程序的编译环境。正如很多人说的,Keil C并不是OOP语言,那怎么做?正是因为我们认为Keil C不能做,所以我才把这种思想拿出来与大家探讨,让我们的程序变得更精彩,更有技术含量。 形成一个独立代码段,最好的办法就是在主工程目录下建立一个子目录,如DISPLAY,然后再在DISPLAY目录下建立一个文档,如DISPLAY.H,然后把DISPLAY\DISPLAY.H文档#include到一个恰当的位置上,这样,一个独立的面向对象的代码段就初步形成了,以后维护代码的时候,你永远不要考虑调用者是什么感受,只要维护这个文档,就可以保证你对显示器的不断更新了。 很多人也许会说,这算什么OOP?大家先别着急,这才是个开始,下面才是组织代码的具体过程。 对于一个显示器,我们必须要有显示要求,我们才会去定制它,如果连使用要求都提不出来,就不要去让人为你做显示器。所以我们首先要明确我们要的显示器必须要做什么。由于是单片机控制的显示器,我们不能想象成微机显示器那样,一个大的显存,可以显示多少页,显示多少色,满屏满屏的传递数据,如果这样想了,就是犯了盲目例比的错误,说明对问题没研究透。对于单片机控制的显示器,我们考虑能显示单个字符、单行显示,就基本足够了。所以我们可以定义下列两个对象功能: dispShowAChar(); // 显示一个字符 dispShowALine(); // 显示一行字符 由于是单片机的裸系统,所以我们作为一个软件设计者,我们一定要清楚,我们所面对的显示器,经常是没有CPU的,所以我们一定要明白,我们这两个函数,实质上都做些什么。很显然,这两个函数是不能长期占有CPU的,否则我们的程序将什么都不能做,专去显示了,成了显示器的一个处理芯片,所以这两个函数运行完后是肯定要退出来的,而显示不能中断呀,所以必须要有一个代码段一直存在于活动代码中而且不能影响其他的功能。做过上位机程序的人应该能看出来,这段代码就是线程。裸编程中我们也用这个概念。 我们的显示器对象正需要一个一直活动的线程,来完成单片机系统对显示功能的解释和执行,因此dispShowAChar()和dispShowALine()实质上是不能直接去做显示工作的,它俩最合适的工作,就是去按指定的格式去设置显示内容,这样我们在使用的时候就不必在这两个函数里设置复杂的代码和嵌套调用关系,因为那样一定会浪费很多的代码,调用多了也会让单片机运行效率降低,硬件资源消耗增加,严重的可能会造成堆栈溢出最后还不晓得为什么。让我们也为这个活动线程也先命个名吧: dispMainThread(); // 按指定的要求执行显示功能 // 指定的要求包括颜色信息、闪烁、游动等等 程序分析下去,引出的概念也就会越来越多,这里所说的多线程概念以后有机会再说,单片机里的多线程也是一个复杂罗嗦的处理问题,现在介绍还为时过早。只是我感觉一不小心又说长了,这帖就到这里吧。 (四) 对于对象能力的定义,我们一般可以从重要的入手,然后慢慢地展开,把所需要的其他能力逐渐归纳为函数,从而把面向对象的思想发展下去。上帖我们提到了三个函数是怎么来的,还没有涉及到函数的任何实质,那么本帖就探讨一下这三个函数的实质性规划与设计。 有了功能要求,我们就要实现它,在裸程序中,实现它的一个首要任务,就是要进行数据传递方式的设计。很显然我们必须要有一个显示区域,来存放我们所要显示的内容,以及显示内容的显示属性,我们还要规划这个显示区域到底要显示多少多少字符或者是点阵。但是由于我们事先并不知道我们的显示设备一次会提供多少显示量,所以我们可以把显示区域的内存,也就是显存,定义得大一点,以至任何一款符合设计要求的显示器都能得到满足,这样的做法在裸编程中其实还是比较实用的,因为裸编程中我们很少去申请动态的空间,程序设计完,所有的变量位置皆已确定,行就行,不行编译就过不去,所以我们可以通常选择一些内存资源比较丰富的新款单片机。 但是这样的做法也有一个弊端,比如当我们预先估计不足而导致数据空间不够的时候,我们就得从头来改这个显存的大小,从而导致整个显示程序都要相应的产生一些变动,这还不是最糟糕的,最糟糕的是当一款新的显示器因为新的功能需求而导致数据结构需要发生变化的时候,我们就崩溃了,前期的工作可能改动就非常大,甚至于都要重新过一遍,也就是重写重调,这么痛苦的事情,我是最讨厌的了。 所以我们要尽量避免这类事情发生,这里对面向对象的思想,就颇为需求了。这个时候,我们就要引入一个新的概念,那就是对象的儿子,子对象。前面讨论的,其实都只是一个抽象的对象,没有任何具体的样子,而只是笼统的规划了所有的显示器必须具有什么能力,而对于每一个具体的显示器来说,还没有任何具体的设计,在这里,每一个具体的显示器,就是显示器对象的子对象,他们形态各异,但是都必须能完成规定的功能。以传统的OOP语言理论来说,这里就产生了一个继承的关系,但是在裸程序思想里,我并不赞成引入这个概念,因为传统的OOP语言里的继承,纯粹是一个语法上的逻辑关系,继承关系明确,而裸程序中的这个思想,并没有任何语法支持,继承关系就非常微弱了,还不如说是归类与概括。但无论是什么关系,我还是不想就这种一目了然的关系弄个新名词来,让看的人费解。 既然引入了子对象,我们能看出这种做法有什么实际意义吗?也许有经验的资深程序员能看出来。我们在做父对象数据设计的时候,我们并不规定具体的数据格式和显存大小,而是一股脑儿地全推给子对象自己去搞,父对象什么都不管。哈哈!这样做事情真是很简单吧?不是我的事情我不管,不要说我偷懒,因为站在父对象的角度讲,这是最明智的做法,因为管不了,所以不管。 到这里也许就会产生更多的疑问了,一个对象什么都不管,那作为调用者怎么使用这个对象呢?你想用它,它什么都不管,这怎么行呀?别着急,父对象不管的,是那些具体的事情,抽象的事情,还是管的,要不然它就没有理由存在了。你抱怨了,说明你在思考,既然思考了,就把思考的问题提出来,提出来的,就是我们设计父对象的依据。提问题,我想这比搞程序要简单得多,比如:显示器能显示多少乘多少的字符?颜色是多色还是单色?显示模式是否支持预定的方式(如移动、闪烁等)?工作模式是图象还是字符?等等,这里附加说明一下,对于显示模式,我们这里都以字符显示为例,既然是面向对象的思想,相信扩充出图象显示模式,还是很容易的事情。 有问题出来了,我们就继续为它添加代码好了。 dispGetMaxCol(); // 取一行最多有多少列 dispGetMaxRow(); // 取显示器一共有多少行 dispGetMaxColors(); // 取显示器最多有多少色 dispSetShowMode(); // 设置显示的方式,对于不支持的显示方式就自动转为正常显示 dispSetWorkMode(); // 设置工作模式,如果没有的模式就返回0,支持的就返回1 对于这些函数的定义,各人可以根据自己的习惯来设置,我只是临时弄了这个例子,未必就是最好的,我的目的是重在说明思想。我也害怕把程序弄得庞大了,出本书都嫌厚。 似乎加了这些函数之后,我们根本就没看到显示数据的具体形式,和前面的函数一起,都并没有什么明确的说法。这种感觉很正确,我们确实没有对显存做任何定义,但是似乎功能却都已经定义了,其实也确实是定义了,而且将来我们就这样用,而且也不用怕,程序一定会写完的。 (五) 继续上面帖子讨论的问题。前面我们提到,为了使用dispShowAChar()、dispShowALine()、dispMainThread()这三个函数,我们又引出五个新的函数来,这些新的函数最主要的目标,就是要实现调用者与被调用者之间数据的传递。 对于程序设计来讲,数据传递与程序逻辑有着同样重要的地位,前者经常在最后会形成一种协议,后者则经常表现为各种算法。 在裸程序中,我们的思想应该主要是表现为一种灵魂,而不能如C++那样,去追求语法的完美,所以对于参数的传递,我们不能去追求语法上的完美,而是不拘一格用传递。除了函数可以传递数据外,直接调用值也是一种很快捷的方式,但是调用不能随便说调就调,而是也要学习C++上语法的习惯,尽量不能让一些专用的变量名称,出现在与专用变量无关的程序体中。例如,我们的设计中规定,我们这套裸系统对显示器最多支持65536色,那么我们就会用一个16位的无符号整数来保存这个指标。为了简化以后的说明,我们先定义两个数据类型: typedef unsigned int UINT; typedef unsigned char UCH; 如果我们用函数来传递这项数据,我们可以用如下的方式: #define Monitor01_MaxColors 0xFFFF 对于颜色调用函数则定义如下: UINT dispGetMaxColors() { return Monitor01_MaxColors; } 很显然,如果另一个显示器是个单色显示器,则颜色调用函数只需要改为下列形式就可以了: #define Monitor02_MaxColors 0x0001 UINT dispGetMaxColors() { return Monitor02_MaxColors; } 前面已经有朋友在帖子里提到过了,用数组,这可以解决很多问题。说得一点没错!上面的例子我们忽略了一个问题,那就是同一个函数名要去做很多不同函数所做的事情,而我们却没有在函数体内使用switch(),这显然是不对的。要真正实现不同显示器的共同属性MaxColors的传递,我们必须要添加switch()以区分不同的显示器类型。那么这里我们就需要引入一个新的父对象属性以指代它的第几号儿子: UCH MonitorType = 0; // 显示器类型,最多支持256种显示器 并在初始化的时候,为该属性初始化为0,以作为缺省类型显示器的代号。以下命名我们就说一个约定,以让代码更具有规范的模样:父对象的接口函数用小写的disp打头,变量用Monitor打头,宏数据用Monitor开头并且内部至少有一个下划线,宏函数则用全大写字母组成。那么不用数组的情况下,上面的代码将会变成如下形式: #define Monitor_00 0 #define Monitor_01 1 #define Monitor_02 2 UINT dispGetMaxColors() { // 以下用多出口,但这并不会破坏什么,为节约代码,完全可以使用 switch(MonitorType) { case Monitor_01: return Monitor01_MaxColors; case Monitor_02: return Monitor02_MaxColors; } return Monitor00_MaxColors; // 缺省则返回默认显示器 } 这样的形式很显然是太冗长了,尽管非常结构化,但是一般在优化程序的时候我们还是可能会废弃它的,所以这里就提到了数组的使用。既然是数组,那么它自然不能属于某一个子对象,而是应该在父对象中定义的,尽管这样做我们每次在添加新显示器的时候我们比如在父对象中添加难以理解的新的数据,但是为了节省代码,我们还是能忍受这样的痛苦。如果改用数组,则上面的代码将改变为如下形式: #define Max_Monitor_Types 3 *** #define Monitor00_MaxColors 1 UINT code MonitorMaxColorsArray[Max_Monitor_Types] = { Monitor00_MaxColors, // 缺省为单色 Monitor01_MaxColors, Monitor02_MaxColors, }; *** 打***的语句将是未来扩充时不断需要修改的句子。那么上面的函数就简单了 UINT dispGetMaxColors() { return MonitorMaxColorsArray[MonitorType]; } 甚至有人还可以用宏函数来节省运行时间,只要修改一下调用规则就可以了: #define DISPGETMAXCOLORS(c) c=MonitorMaxColorsArray[MonitorType]; 也许当我们写成如上代码的时候,我们的每一次改进,都会让我们欣喜,我们的代码又优化了。但是可惜的是,这种没有思想的优化会在不远的将来,给我们带来麻烦。我觉得我的记忆力很不好,也许一分钟前的事情我都会想不起来,这种在将来扩充中的上窜下跳地修改会让我觉得晕眩! 所以,在工程化的工作中,我们需要把父对象与子对象尽量隔离开来,减少关联性的修改量,这也是面向对象思想的重要意义之所在,对于这一改动,我将在下帖中阐述。 (六) 上帖我们说到dispGetMaxColors()的一些设计思路,我们有很多很好的办法来实现它,但是我们有没有更好的管理办法来实现它,这才是我们要站在更高层次看问题的焦点,是更重要的。这也就是一个从传统思维到面向对象思维的一个重要的转折。 要想把这个函数转变为面向对象的逻辑结构,我们也要先做些准备工作。 首先说参数传递的思想。尽量减少参数传递,这是尊重C51系列8位单片机硬件现状的一项重要措施,记着,不要抱怨C51档次低,资源少,而是要在尊重和热爱C51的前提下,我们才有热情来发展我们的裸程序面向对象思想的,也就是说,无论我们面临的系统有多简陋,我们都有策略,来实现复杂的功能,而且从发展的眼光来看,产品的升级,并不是盲目的升级我的CPU,因为那样只会让产品设计者智商下降,所以我觉得C51的特色就是应该在简洁,越来越简洁,而不是越来越复杂。所以我希望我们把思想升级作为升级产品的一个发展方向。传递参数要减少指针、浮点等类型的数据传递,尽量以UCH与UINT为主,而且参数的数量不要太多,最理想的上限是2个,如果实在多了,就使用共享缓冲区,或者全局变量。最好是不要传递参数,呵呵。 本函数就利用了MonitorType省略了一个参数传递。 其次是我们要让父对象的接口函数与具体的子对象的多种可能性的功能实现剥离,这里我们就需要使用函数指针。函数指针也许我们一般用得少,但是其实并不是很复杂。先看我们函数的形式: UINT dispGetMaxColors(void); 为该函数定义一个指针类型,只需做如下定义,就可以了: typedef UINT (*dGMC)(void); 那么对于父对象中的dispGetMaxColors()函数,我们就只需要转换定义一个函数指针,在创建父对象的时候为它提供一个子对象对应功能调用的入口地址,就足够了。所以对于这个函数的实体将只在子对象中出现,而在父对象中只会出现一个变量的定义: dGMC dispGetMaxColors; 为了给它赋初值,我们也可以定义一个空指针,作为一个未使用的判断标志: #define NIL 0 那么初始化dispGetMaxColors的时候只需要写条如下语句就可以了: dispGetMaxColors = NIL; 而且功能调用也很简单,与实质的函数是一样的: if(dispGetMaxColors!=NIL) vMaxColors = dispGetMaxColors(); 如果再加上约定,连前面的判断语句完全可以省略。因为我们的裸程序的程序空间实际上也是运行空间,不存在代码调入内存和移出内存的事情发生,所以我们不需要考虑程序内存的优化问题,只要我们规定对象一定是先创建后使用,判断语句就会变得没有意义,而且我们创建后即使不再使用,函数体我们也不会释放,因为它被放在程序空间内是固定死的,你想移出去,还不能实现呢。 第三,尽量让程序所使用的语法简单化,以减少编译器可能带来的差别而产生的理解上的误区。有人说C51是C的子集,这说法我认为不科学,只能说二者继承了C的基本精神,而在实质上它们针对的是不同的对象,所以也出现了一些不同的结果,所以我看到有些高手或者一些面试题弄出一些题目来让我望而生畏,也许我做一辈子的裸程序也做不出他们的题目,但是我并不觉得我不如他们。他们只不过是在编译器上花了很多时间研究他们的一些约定,而我并不想花时间去研究那些将来可能发生变化的东西,我希望我能用一个更简单的途径把我的事情做出色,我只关心我的项目的速度、代码的大小、运行的效率、维护的复杂度,所以我也建议和我交流的人能用一种通俗的方法来实现我们的功能,也不必过多的考虑我的程序在8位单片机上可以用16位单片机上也可以用,我们的系统要经常升级,但不要轻易去增加硬件成本。当然如果你有精力去研究那些约定,也没什么,只要你乐意。 好了,上面三条,只有第二条是我要说的关键,其他两条各人可以根据自己的具体情况来寻找一些快捷实用的方法。其实说来我们把父对象用函数指针来代替实体函数,这完全是一种思想,我们并没有使用复杂的语法,而是从管理上把对象分离开来,让不同的人做不同的事情,比较容易。但是同时我们又不能让这种中间环节影响我们程序的效率,所以我们完全可以自己寻求一些方法,而不必去循规蹈矩。我这样说也许会遭到一些非议,但是我可以告诉大家,计算机语言这门学科,本身就是一门人造科学,没有最好只有更好,我们没必要完全按照别人的思路去发展设计思想,所以也不要拿着人家的一些约定来引以为荣,那些东西,只有先学后学的问题,没有水平的差异;我们应该更注意的是,人家创造了工具,我们就用工具再来创造世界!如果你停留在欣赏工具的层次上,那就是无所鸟事了! 本帖实质上只说了一个转换,两条建议,这都不是具体的程序,而是思想。我想强调的,也不是格式,而是思想。下帖将再回到对象上,和大家探讨一下对象本身的组织问题,比如对象的层次关系、对象的创建、对象的书写等等,我也希望有人能有一些更好的方法回到帖子里,我们互相学习,共同提高。 (七) 前面的思想衍变过程已经说了很久了,面向对象的思想也就到了瓜熟蒂落呼之欲出的境界了。下面我就先图示一下裸程序设计中面向对象思想的层次关系: 相信这张图已经足够说清楚我们在Keil C中如何用语言来组织我们的显示器对象disp了。disp是一个抽象的对象,它只是一种联系,完成对所有子对象d000、d001、d002到最多d255的归纳概括并提供一组被调用者所使用的功能接口。这些功能接口正是上贴所提到的函数指针。而具体的功能实现及不同显示对象对数据结构的要求,我们都可以交给子对象设计工程师自己去决定。 很显然,大家在这套方案具体的程序设计过程中,最主要的精力还是要放在自己做自己的问题上,多思考如何把事情做得更漂亮,而不必在代码编写时黏糊不清。父对象设计者必须要完成总体方案的设计,抽象思维多而具体工作量少,子对象的设计者则恰恰相反,只需要多考虑考虑具体的设计,而不必去担心别人是怎么用自己的东西。 很显然,作为总体设计者,必须要严格考虑这中间的数据交换关系,因为我们没有操作系统,所以对于可用的内存资源的使用法则,直接关系到我们整个系统的成败,混乱使用常常会导致系统崩溃,相对独立的代码则在编译过程中由Keil C直接安排好了,我们不需要去考虑它们对程序的影响。 例子中的显存大小及显存的位置都是我们方案成败的关键。我们都知道Keil C对单片机内存划分了四种,即data、idata、pdata、xdata四种,各种存储器的大小与特点都决定着我们代码的运行效果,我们既要考虑信息所需要的空间,又要考虑单片机处理时足够达到我们的视觉要求。在这个例子中,我觉得我们可以选择xdata来作为显存。为什么呢?因为我觉得只要我们处理得当,我们的单片机完全可以克服处理速度上的缺陷,所以我们可以优先满足信息量的要求,提供足够多的空间来实现我们想要的功能。 提速的方式有很多,比如:选择一些性能优越的新型单片机,传统的是12T的,现在有6T的,也有1T的,这让很多指令都会有更高的效率;适当的提高晶振频率;选择更科学的算法;等等。 好了,本帖先到这里吧,又觉得写多了。到目前为止,基本上可以去构造我们的对象了,如果你有兴趣,你可以使用#define进行一些伪码定义,把我们的对象定义得更美观一点,更接近C++一些,不过我要说的是:我们这里没有严格的类定义,所以定义时类与对象经常是没有界限的。
从11岁时,我就一直在编程,并且一直都很喜欢技术和编程。这些年来,我积累了一些艰难又容易的经验。作为一名程序员,你或许还没这些经验,但我会把它们献给那些想从中学到更多的朋友。 我会持续更新这些经验,我可能还会有更多的感想,但就我这20年来看,我想下面这个列表中基本不需要增添额外的东西了。下面就是我至今最难忘的经验。 1. 估算解决问题所需要的时间。不要怕,承认吧!我曾见过一些程序员为了解决一个特殊问题而坐在显示器前面8小时。为自己定一个时间限制吧,1小时、30分钟或甚至15分钟。如果在这期间你不能解决问题,那就去寻求帮助,或到网上找答案,而不是尝试去做“超级堆码员”。 2. 编程语言是一种语言,只是一种语言。随着时光推移,只要你理解了一种语言的原理,你会发现各种语言之间的相似之处 。你所选择的语言,你应该觉得“舒服”,并且能够写出有效(而且简洁)的代码。最重要的,让语言去适应项目,反之亦然。 3. 不要过于注重程序的“设计模式”。有时候,写一个简单的算法,要比引入某种模式更容易。在多数情况下,程序代码应是简单易懂,甚至清洁工也能看懂。 4. 经常备份代码。在我年轻时,我就有过因硬盘故障而丢了大量代码的经历,这经历很恐怖的。只要你一次没有备份,就应当像有着严格的期限,客户明天就需要。此时就该源码/版本控制软件大显身手了。 5. 承认自己并不是最顶尖的程序员 – 知不足。我常想,我对编程了解已足够多,但是总有其他人比你优秀。正所谓,“一山总比一山高”。所以,向他们看齐吧! 6. 学习再学习。正如第5点所说,我经常会在手里拿一本计算机或编程相关的杂志或书(不信,可以问我的朋友)。诚然,总有很多你不知道的技术,你可以从中学习以保持不落后。如果你有一种灵巧的方式来获取你需要的新技术,那你每天都应该坚持学习。 7. 永恒的变化。你对待技术/编程知识,就应像你对待股票一样:多样化。不要在某一特定技术上自我感觉良好。如果那种技术或语言已经没有足够支持,那你还不如现在就开始更新你的简历,并启动培训新计划。我能保持前行的主要原则是什么呢?至少了解两到三种语言,所以,如果某种语言过时了,你在学习新技术的时候还可以依靠另一种语言。 8. 提携新人。协助并且培养初级/入门的开发人员学习优秀的编程方法和技巧。也许你还不知道,在帮助他们向更高一层前进时,你自己也在向更高一层提升,你会更加自信。 9. 简化算法。代码如恶魔,在你完成编码后,应回头并且优化它。从长远来看,这里或那里一些的改进,会让后来的支持人员更加轻松。 10. 编写文档。无论是Web服务的API,还是一个简单的类,你尽量编写相应文档。我曾经引以为豪的代码注释,因过度注释而有人指责。给三行代码加一行注释,只需要你几秒时间。如果那是一个比较难以理解的技术,千万别担心过多注释。如果你能很好做好自己的工作,大多数架构师、后备程序员、支持组都会感激你。 11. 测试、测试再测试。我是一名黑盒测试粉丝。当你完成编码后,你“被认可”的时候就开始了。如果你们公司有QA部门,如果你的代码中有错误,那你得到的评论,会比项目经理还多。如果你不彻底测试自己的代码,那恐怕你开发的就不只是代码,可能还会声名狼藉。 12. 庆祝每一次成功。我见过很多程序员在解决编程技术难题后,会和同伴握手、击掌或甚至手舞足蹈。每个人在生命中都会碰到“顿悟”。如果一个程序员高兴地跑来叫你去看他的非凡代码,也许你已经看过这样的代码100遍了,但你也应该为了这个家伙而庆祝第101次。 13. 经常检查代码。 在公司,你的代码要经常检查(包括自查和其他同事检查)。不要把别人的检查,看成是对代码风格的苛求。应该把它们看作是有建设性的批评。对个人来说,经常检查你的代码并且自问,“我怎样才能写得更好呢?” 这会让你加速你的成长,让你成为一个更优秀的程序员。 14. 回顾你的代码。在看到自己以前的代码时,通常会有两种方式:“难以至信,这代码是我写的”和“难以至信,这代码是我写的”。第一种往往是厌恶的语气,并在想如何改进它。你也许会惊叹,旧代码也能复活成为一种更好的程序,甚至是一个完整的产品。第二种通常带着惊奇和成就感。开发人员应该一到两个自己完成的项目成果,能让众人不禁而立并注目而观的项目。同样,基于你优越的编程能力,你可以把过去的程序或项目拿出来,把它们更新为更加优秀的产品或想法。 15. 幽默是不可缺的。在我20年的开发生涯中,我还没有碰到哪位程序员是没有幽默感的。实际上,干我们这行,幽默是一项必备品。 16. 谨防那些无所不知的程序员,不愿分享的程序员,还有经验不足的程序员。当你遇到这几种程序员时,你自己要谦虚。无所不知的程序员,更想当一个英雄而不是团队成员;保守的程序员则是在编写着他们独享的代码;而经验不足的程序员则会每十分钟就来问你一下,当代码完成后,代码已经是你的,而不是他们。 17. 任何项目都不会那么简单。朋友、家人和同事曾请求我仓促做一些事情,仓促做一个程序或者网站。对于这样的事,应该从双方做计划,才能做出令两方都会满意的东西。如果某人起初只是需要一个使用Microsoft Access的、只有有3个页面的网站,但来就很可能变成一个有15个页面的网站,并使用SQL Server,有一个论坛,还有一个定制的CMS(内容管理系统)。 18. 任何时候不要想当然。假如你承接一个简单的项目,你可能会认为某个部分可以轻松完成。千万别这样想!除非你有一个类、组件、或者一段已经写好的代码,并且在现有的项目已经测试通过。不要认为这将是很容易的。 19. 没有已经完成的软件。曾经有一位程序员告诉我,没有软件是已经完成的,它只是“暂时完成了”。这是明智的忠告。如果客户还在使用你写的程序,并经受了时间的考验。如果有机会,你仍在更新它,这并不是什么坏事,这让你不断地前行。 20. 耐心是一种美德。当客户、朋友或家庭成员用电脑的时候,他们也许会受挫,进而想砸电脑,或气冲冲地离开。我一直在告诉他们,“是你掌控电脑,不是电脑掌控你。”对于用作编程的电脑,你要有一定的耐心。一旦程序员知道问题所在后,他们就会站在电脑的角度看问题,并且说“哦,这就是为什么它是这样做。”
谷歌了一下网上编程网站,找到了北京大学的poj.又详细了解了一下,觉得挺好,是提高自己编程能力的一个很好的网站,以后一定要坚持刷题。 ACM1000 a加b Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 338156 Accepted: 187541 Description 计算a加b。 Input 两个整数a和b,其中0≤a, b≤10。 Output 输出a与b的和。 Sample Input 1 2 Sample Output 3 #include "stdio.h" void main() { int a,b; scanf("%d%d",&a,&b); printf("%d",a+b); return 0; }
Google的面试题在刁钻古怪方面相当出名,甚至已经有些被神化的味道。这个话题已经探讨过很多次,这里贴出15道Google面试题并一一给出了答案,其中不少都是流传很广的。怎么样?下边来热热身,看看你有没有可能去Google工作吧! 第一题:多少只高尔夫球才能填满一辆校车?(职位:产品经理) 解析:通过这道题,Google希望测试出求职者是否有能力判断出解决问题的关键。 网友的答案:我想,一辆标准大小的校车约有8英尺宽、6英尺高、20英尺长——我能知道这些数字完全是因为我曾经无数次被堵在校车后面。 据此估算,一辆校车的容积约为960立方英尺,也就是160万立方英寸。一个高尔夫球的半径约为0.85英寸,我认为一个高尔夫球的体积约为2.6立方英寸。 用校车的容积除以高尔夫球的体积,得到的结果是66万。不过,由于校车里面还有座位等等各种东西,而且高尔夫球的形状使得不同的球之间会有不少空隙。我的最终估算结果是50万。这听起来有些荒唐。如果我直接猜的话,我给出的答案肯定是10万以下,不过我相信我的数学水平。 当然,如果这里的校车是小布什当年坐过的那种,结果还要除以2,差不多是25万个。 第二题:让你清洗西雅图所有的玻璃窗,你的报价是多少?(职位:产品经理) 答案:这一题我们可以玩点花招,我们的答案是“每扇窗10美元”。 第三题:有一个人们只想生男孩子的国家,他们在有儿子之前都会继续生育。如果第一胎是女儿,他们就会继续生育直到有一个儿子。这个国家的男女儿童比例是多少?(职位:产品经理) 答案:这一题引发了不少争议,不过我们发现,这一题的解答步骤如下: 1、假设一共用10对夫妻,每对夫妻有一个孩子,男女比例相等。(共有10个孩子,5男5女); 2、生女孩的5对夫妻又生了5个孩子,男女比例相等。(共有15个孩子,男女儿童都是7.5个); 3、生女孩的2.5对夫妻又生了2.5个孩子,男女比例相等。(共有17.5个孩子,男女儿童都是8.75个); 4、因此,男女比例是1:1。 第四题:全世界共有多少名钢琴调音师?(职位:产品经理) 答案:我们的回答是“要看市场情况。如果钢琴需要每周调音一次,每次调音需要1个小时,且每个调音师每周工作40个小时。我们认为每40台钢琴就需要一名调音师。” 这个问题又被称为“费米问题”(Fermi problem)。费米提出的问题是“在芝加哥有多少钢琴调音师”。一个典型的答案是包括一系列估算数据的乘法。如果估计正确,就能得到正确答案。比如我们采用如下假设: 芝加哥约有500万人居住; 平均每个家庭有2人; 大约有1/20的家庭有定期调音的钢琴; 平均每台钢琴每年调音一次; 每个调音师调整一台钢琴需要2小时; 每个调音师每天工作8小时、每周5天、每年50周。 通过这些假设我们可以计算出每年在芝加哥需要调音的钢琴数量是: (芝加哥的500万人口)/(2人/家)×(1架钢琴/20家)×(1架钢琴调整/1年)=125000 平均每个调音师每年能调整的钢琴数量是: (50周/年)×(5天/周)×(8小时/天)/(1架钢琴/2小时)=1000 芝加哥的调音师数量是: (芝加哥需要调音的钢琴数量125,000)/(每个调音师每年能调整的钢琴数量1000)=125 第五题:马路上的井盖为什么是圆的?(职位:软件工程师) 答案:圆形的井盖在任何角度都不会掉下去。 第六题:为旧金山市设计一个紧急撤离方案(职位:产品经理) 答案:这又是一个考察求职者是否能够发现问题核心的题目。我们在回答之前首先要问的是,“撤离方案应对的是什么样的灾难”。 第七题:一天之中,时钟的时针和分钟会重合几次?(职位:产品经理) 答案:22次。 重合的时间点分别是:上午,12:00、1:05、2:11、3:16、4:22、5:27、6:33、7:38、8:44、9:49、10:55;下午12:00、1:05、2:11、3:16、4:22、5:27、6:33、7:38、8:44、9:49、10:55。 第八题:请阐述“Dead beef”的意义。(职位:软件工程师) 答案:网友给出的正确答案是,在大型机和汇编语言的时代,“DEADBEEF”是调试计算机时所使用的一个十六进制值,以便于在大量的十六进制中断信息中标记和查找特定的内存数据。大多数计算机科学专业毕业生都应该会在汇编语言的课程上见过这个概念。 第九题:有人把车停在旅馆外,丢失了他的财物,他接下来会干什么?(职位:软件工程师) 答案:下车踏到人行道上。 第十题:你需要确认朋友鲍勃是否有你正确的电话号码,但不能直接问他。你须在一张卡片上写下这个问题,然后交给爱娃,由爱娃把卡片交给鲍勃,再转告你答案。除了在卡片上写下这个问题外,你还必须怎样写,才能确保鲍勃在给出答案的同时,不让爱娃知道你的电话号码?(职位:软件工程师) 答案:既然只需要核对鲍勃手中的号码是否正确,你只需要让他在某个特定的时刻给你打电话,如果他没打过来的话,就能确认他没有你的号码。 原文来源:http://news.mydrivers.com/1/247/247037.htm 第十一题:假设你是海盗船的船长,船员们即将对黄金的分配方案投票。如果赞成票不到半数的话,你会被杀死。你怎样才能在保证自己存活的情况下拿到最多的黄金?(职位:软件工程师) 答案:将黄金平均分给最有权势的51%的船员。 第十二题:有八个大小相等的球,其中有一个重量比其他球略重。如何在只用天平称两次的情况下找出那个不一样的球?(职位:产品经理) 答案:从八个球中取出六个,在天平两边各放三个。如果平衡,把剩下的两个球分别放在天平两边,就能找出较重的球。如果不平衡,较重的球就在天平下沉的一边,从这三个当中取出两个称量,若不平衡,下沉的一边较重,若平衡,剩下的就是较重的球。 第十三题:你拿着两个鸡蛋站在100层的大楼上。鸡蛋或许结实到从楼顶掉下也不会摔破,或许很易碎,在一楼摔下就破碎。最少试验多少次可以找出鸡蛋不会被摔碎的最高楼层?(职位:产品经理) 答案:14次。从14楼丢下第一颗鸡蛋,如果破碎了就逐层往下试验,共需14次。如果没有破碎,往上走13层;在27楼第二次丢下第一颗鸡蛋,如果碎了,换第二颗鸡蛋往上走12曾测试,若仍没碎,往上走12层试验第一颗鸡蛋;以此类推,直到走到第99层。如果鸡蛋要到100层高度落下才会破碎,总共需要14次尝试。 第十四题:如果用三句话向你8岁大的侄子解释什么叫数据库?(职位:产品经理) 答案:这一题考察的是求职者用简单的语言阐述复杂概念的能力。我们的答案是“数据库是一个能够记住关于很多东西的很多信息的机器。人们用它来帮助记住这些信息。出去玩吧。” 第十五题:你被缩小到只有硬币厚度那么点高,然后被扔到一个空的玻璃搅拌机中,刀片一分钟后就开始转动。你会怎么做?(职位:产品经理) 答案:这一题考察的是求职者的创造性。我们会尝试把电动机弄坏。
一直以来都对自己的学习方式和思维方式不满,深深地感到自己到现在还是那种死记硬背的学习方式,还是那种应试教育出来的死板的思维方式,为什么这么说呢?举个例子,我现在对去年学的高数已经忘得差不多了,但是对于微分和积分的思想还算理解得够透彻,虽然细节性的一些技巧和方法忘了,但是至少现在能熟练解决物理问题,然而对于解微分方程现在却没一点思路,这是为什么呢?原因是学微分方程没有学到它的骨髓,没有将它与现实问题结合起来,只是机械地记忆了一些公式。那么如何才能使自己跳出这个BUG? 传统的以及现在大部分人的学习过程是:1.制定学习计划。2.实施学习计划。3.整理和复习学习内容。从表面上来看这个很符合我们做事的习惯,但是我们反过来想一想,这个过程我们完全是主动思考的吗?答案是NO.我们只是被动地思考这我们要这样做,因为学好它可能对我未来有大的帮助,因为大家都在看,因为朋友或者老师的推荐,又或者不知道,只是内心的直觉。于是在学习过程中因为我们心中没有一个主要问题主导着我们,我们很容易被一些细节牵引,我们的大脑成了别人思维的跑马场,我们可怜地拾人牙慧,没有自己的思考,不能抓住关键性的主旨。而我们如果根据自己现实的问题,一个不断细化的具体的问题,在学习过程中始终以这个问题为“斗星”,那么我们的学习过程就会变成一个富有挑战性的过程,在这个过程中,我们筛选阅读材料,主动根据问题来做学习笔记,而不是一味地填充在我们的笔记本上。在问题引导下的学习的最大的特点是,它所希求的知识是没有边界的,为了找到问题的解,我们可能会寻访任何可能的线索,查阅任何可能的有益的资料,而不受既定的观点的束缚。一个高段位的学习者,必定是一个优秀的提问者,他从阅读观察和思考的过程中产生问题,先解答表层的,容易的那部分,留下深度的探索式的问题给自己,被问题所困扰和折磨,同时开启之后的求知之路。摘自《怎样成为一个高段位的学习者?——采铜》。{由此可看,要想成为一个高段位的学习者,首先要学会提问,而怎样提问,怎样提出一个有价值的中肯的问题,这本身又是一个问题} 案例分析:正面;结合这次程序设计大赛,我的核心问题是怎样制作一个基于51的音乐魔幻喷泉,实现音乐和LED步调一致。基于这个问题,我重0开始接触了TDA2822双运放芯片、PCF8591AD/DA转换芯片,并理解了AD转换原理,IIc总线与单片机的通信,并在这个过程中大致了解了E^2PROM,也在这个过程中加强了写函数的能力,虽然最后因为水泵被堵无缘比赛,但是回想起来,在这个以解决一个核心问题的过程中自己真的学了很多,虽然比不上那些高手,但是自己真正的收获了一些东西就足够了。 反面;借了很多书,买了很多书,因为没什么以问题为向导,导致阅读不深入,只是记录一些语录,没在心里重新构建自己的心智,因此看了等于没看,相当于又被别人跑了一圈马场,头脑混乱,越读越蠢。 总的来说以前以及现在自己的状态是:一味地贪多求全,不懂得结合自己的思考,过滤一些信息,书籍太多,无从下手,信息太杂,太过混乱,不会合和理安排时间,不能长时间专注。如果转换成问题的话,我要解决的是如何提问?如何安排时间?如何做到长时间专注?
大一上学期的九月份,因为要 参加校运会的3000米和5000米比赛,于是每天去体育场训练,跑10多圈,然后回寝室洗澡睡觉(刚开学,没多少课,闲的很)。比赛完后每天自然地会去操场跑4圈或者5圈,没有热身运动,直接上来就跑,跑完后非常累,特别是呼吸太不爽了,当时觉得这主要原因是北方的雾霾天气不适合跑步,好像每天都是重度污染,没什么好天气。我一时兴起,报了学校的田径双选课,周二和周五6点起床开始训练,先是五六圈的跑步热身,然后老师教一些扩展运动,之后就去跑完15圈。但是到最后发现自己的右腿非常疼,根本不能再跑了,我还是以为是自己压腿时的原因。大一下,我的一位同学也开始跟着我跑步,不过他每天跑的可能要长一些,每天他早上做完热身运动后就跑6圈,晚上上完自习后也是如此。过了没多久,他告诉我跑步时自己的右脚疼。没过多久,他觉得应该要去看看医生,之后的结果让人大吃一惊:膝盖部分损伤,一个月不能再跑步。(跑步百利唯伤膝 )我开始醒悟,跑步也是一项技术活,跑得少,没效果,跑得多,如果不科学,会出问题。于是去知乎看了看 :正确的慢跑姿势 纠正自己以前跑步的几个错误: 1.跑步时低着头 ,跑步时听励志歌 --> 低着头对颈部有伤害,听励志歌不能完全放松身体的肌肉。 正确做法:保持下巴在肩部和胸部正上方,头部在肩部的正上方,听一些安静的音乐,有意识地放松面部的肌肉。 2肩膀先前塌 -->不利于呼吸 正确做法:保持身体正直 3.臀部,手臂乱晃动 -->不利于速度的提高,会造成不必要的能量损失,给自己带来困意 正确做法:手臂放低并向前摆动,手臂与肩膀向后扩以展开胸部使呼吸顺畅。 保持臀部在身体的正下方。
变量类型 变量名; 元素类型 数组名[元素个数]; // 数组中保存的每一个数据,称之为元素 特点:数组只能存放同一种数据类型 数组中的每一个元素都有一个索引号,索引号从0开始部分初始化, 没有赋值的元素默认是0 数组[]中的元素个数只能写整型常量,以及返回值是整型常量的表达式。通过变量定义数组,如果没有对数组进行初始化,里面存放的是一些垃圾数据(随机值)在定义数组的同时进行初始化,可以省略元素个数,会自动的计算出数组元素的个数。要想给数组一次性赋值(给数组中所有的元素赋值)只能在定义数组的同时,如果定义完数组之后,就不能再对数组进行一次性的赋值。数组的地址就是它第0个元素的地址。数组名就是数组的地址 数组元素存放值是从小到大, 而每一个元素存值又是从大到小 因为内存寻址从大到小,所以先定义的数组的地址比后定义的地址大 基本数据类型作位函数参数是值传递 数组作为函数参数传递的是地址 当数组作为函数参数传递的时候,可以省略[]中的元素个数 当数组作为函数参数传递的时候,会自动转换成"指针类型" 而指针在当前操作系统mac 下占用8个字节 想在函数中动态计算数组的元素个数是不行的,因为指针类型占8个字节 选择排序原理:依次选择数组中过的每一个元素和其他元素进行比较 当内循环执行完一次(比较完一次), 最值出现在头角标上 冒泡排序原理: 冒泡排序是拿相邻的两个元素进行比较 特点: 内循环执行完一次(完整的比较完一次),最值出现在尾角标上
作者:[美国]卡勒德·胡塞尼 用了大约6个小时,看完了这本书,精彩书籍,推荐给大家。 故事发生在阿富汗的喀布尔。主人公阿米尔是个富家少爷,他出生时,母亲因难产而死去。他与仆人阿里的儿子哈桑友情甚好,哈桑对阿米尔十分忠心。阿米尔的爸爸对哈桑也非常好,而这却常常使阿米尔心生嫉妒。12岁那年,阿米尔与哈桑参加了阿富汗传统的斗风筝比赛。阿米尔将对手通通打败,然而要赢得最终的胜利,还必须追到被他最后割断的风筝。哈桑是当地最出色的追风筝高手,他替阿米尔去追,承诺阿米尔一定追到最后被他们割下来的蓝风筝。然而,风筝追到了,哈桑却惨遭阿塞夫(属于普什图人对例如哈桑这样的哈拉扎人十分仇恨,认为他们弄脏了阿富汗的血脉)的横祸,阿米尔了目睹一切,性格软弱的他却选择了袖手旁观,为了减轻自己面对哈桑时内心的惭愧,他对自己的爸爸撒谎称哈桑偷了自己的手表,就这样逼哈桑和他爸爸阿里离开家门。后来,俄罗斯入侵阿富汗,阿米尔和他父亲亡命出逃,背井离乡到了美国,开始了他们的新生活。父亲逝去不久,一个来自巴基斯坦的拉辛汗(阿米尔父亲的知己)的电话却把阿米尔从貌似平静的异国生活中拉了出来,拉辛汗的电话唤起了阿米尔童年的痛苦,却也为他指明了方向:“那儿有再次成为好人的路。”为了赎罪,阿米尔登上了前往巴基斯坦的飞机。然而到了巴基斯坦,却是奇峰突起,哈桑早已死于非命,遗留下幼小的儿子(索拉博),孤零零地寄居在喀布尔的孤儿院。此时拉辛汗透露了一个惊天秘密,彻底摧毁了阿米尔对童年的体验和回忆,原来阿米尔的爸爸睡了仆人阿里的老婆,并生下了儿子哈桑,原来阿桑与自己是同父异母的兄弟,一番挣扎之后,阿米尔决意冒着生命危险,重返被塔利班占领的危机四伏的喀布尔,去寻找哈桑的儿子即自己的侄子索拉博。经过重重阻碍,阿米尔成功救出来了索拉博,并将其带回了美国,以此阿米尔完成了自我救赎,追回了童年时丧失的“人格”的风筝。 看了现在豆瓣读书里对《追风筝的人》的评价排名第一的文章,不敢苟同。我个人认为:"为你,千千万万遍"的哈桑是一个勇敢纯粹的人,他对阿米尔是那么地忠心以至于在我们看来他可能有点傻,但是他身上那种傻,那种如许三多般,好兵帅克般具有“钝感力”的“傻”却每每让人忍不住热泪盈眶。从自私嫉妒懦弱,到勇敢忠诚善良,破茧重生,主人公阿米尔完成了自我救赎。追风筝的过程其实就是追逐纯净内心,纯洁人性的过程。人生在世,人性的弱点总会怪,迫使我们做出一些自私狭隘的行为,辜负了某些人,辜负了某些事,辜负了某些爱情,以致于感觉现在的自己变成了原来所讨厌的自己。但是正如老子所倡导的,面对世界我们不仅要“正思”,更要“反思”,不停止“追风筝”的脚步,完成一次次的自我救赎。《知乎》一个用户说:人生很多高贵的品质往往不是遗传的,而是一次次救赎中坚定的。我觉得此话有理。最后以哈桑对阿米尔说的那句感人肺腑的话结束此文: 为你,千千万万遍。
Web服务器的工作原理并不复杂,一般可分成如下4个步骤:连接过程、请求过程、应答过程以及关闭连接。下面对这4个步骤作一简单的介绍。 1.连接过程就是Web服务器和其浏览器之间所建立起来的一种连接。查看连接过程是否实现,用户可以找到和打开socket这个虚拟文件,这个文件的建立意味着连接过程这一步骤已经成功建立。 2.请求过程就是Web的浏览器运用socket这个文件向其服务器而提出各种请求。 3.应答过程就是运用HTTP协议把在请求过程中所提出来的请求传输到Web的服务器,进而实施任务处理,然后运用HTTP协议把任务处理的结果传输到Web的浏览器,同时在Web的浏览器上面展示上述所请求之界面。 4.关闭连接就是当上一个步骤--应答过程完成以后,Web服务器和其浏览器之间断开连接之过程。 Web服务器上述4个步骤环环相扣、紧密相联,逻辑性比较强,可以支持多个进程、多个线程以及多个进程与多个线程相混合的技术. 以下为在lpc1768平台建立http服务器的例程: void HTTPServer(void) { if (SocketStatus & SOCK_CONNECTED) // check if somebody has connected to our TCP { if (SocketStatus & SOCK_DATA_AVAILABLE) // check if remote TCP sent data TCPReleaseRxBuffer(); // and throw it away if (SocketStatus & SOCK_TX_BUF_RELEASED) // check if buffer is free for TX { if (!(HTTPStatus & HTTP_SEND_PAGE)) // init byte-counter and pointer to webside { // if called the 1st time HTTPBytesToSend = sizeof(WebSide) - 1; // get HTML length, ignore trailing zero PWebSide = (unsigned char *)WebSide; // pointer to HTML-code } if (HTTPBytesToSend > MAX_TCP_TX_DATA_SIZE) // transmit a segment of MAX_SIZE { if (!(HTTPStatus & HTTP_SEND_PAGE)) // 1st time, include HTTP-header { memcpy(TCP_TX_BUF, GetResponse, sizeof(GetResponse) - 1); memcpy(TCP_TX_BUF + sizeof(GetResponse) - 1, PWebSide, MAX_TCP_TX_DATA_SIZE - sizeof(GetResponse) + 1); HTTPBytesToSend -= MAX_TCP_TX_DATA_SIZE - sizeof(GetResponse) + 1; PWebSide += MAX_TCP_TX_DATA_SIZE - sizeof(GetResponse) + 1; } else { memcpy(TCP_TX_BUF, PWebSide, MAX_TCP_TX_DATA_SIZE); HTTPBytesToSend -= MAX_TCP_TX_DATA_SIZE; PWebSide += MAX_TCP_TX_DATA_SIZE; } TCPTxDataCount = MAX_TCP_TX_DATA_SIZE; // bytes to xfer InsertDynamicValues(); // exchange some strings.. TCPTransmitTxBuffer(); // xfer buffer } else if (HTTPBytesToSend) // transmit leftover bytes { memcpy(TCP_TX_BUF, PWebSide, HTTPBytesToSend); TCPTxDataCount = HTTPBytesToSend; // bytes to xfer InsertDynamicValues(); // exchange some strings... TCPTransmitTxBuffer(); // send last segment TCPClose(); // and close connection HTTPBytesToSend = 0; // all data sent } HTTPStatus |= HTTP_SEND_PAGE; // ok, 1st loop executed } } else HTTPStatus &= ~HTTP_SEND_PAGE; // reset help-flag if not connected }
一、HTTP协议 1、当请求的资源中含有:img link <script src=""/>浏览器都会自动发出请求 浏览器访问多图网页 在一个HTML页面中如果包含<img>标记的话,当浏览器解析到这些标记时,还会向服务器请求访问标记中指定的文件,即再次建立连接并发出HTTP请求。 *http 1.0和http 1.1的区别: a. 1.0中链接是无状态的,浏览器与WEB服务器的连接过程是短暂的,每次连接只处理一个请求和响应。对每一个页面的访问, 浏览器与WEB服务器都要建立一次单独的连接。 b.1.1中链接是有状态的。在一个TCP连接上可以传送多个HTTP请求和响应。增加了更多的请求头和响应头 2、组成 请求部分:客户端连上服务器后,向服务器请求某个web资源,称之为客户端向服务器发送了一个HTTP请求。 一个完整的HTTP请求包括 一个请求行、若干消息头、以及请求正文,其中的一些消息头和正文都是可选的,只有当请求以post方式时,才会有请求征文 常用的请求方式: GET:默认的请求方式。http://localhost:8080/hello/4.html?username=abc&password=456 如果有表单数据,会出现在地址栏中,实际上是作为请求资源路径的一部分出现的。 不安全。长度有限制,一般情况下不能超过1k POST:使用表单的method属性来指定。POSt方式的请求正文会出现 username=abc&password=456。木有长度限制。(建议使用的方式) *常用的响应码: 200 表示一切正常,返回的是正常请求结果 302、307 (临时重定向) 指出被请求的文档已被临时移动到别处,此文档的新的URL在Location响应头中给出 304 (未修改)表示客户机缓存的版本是最新的,客户机可以继续使用它,无需到服务器请求。 404 (找不到)服务器上不存在客户机所请求的资源。 500 (服务器内部错误)一般情况下是代码错误服务器端的程序发生错误 *消息头的作用:用户客户端和服务器端之间互相传递附加信息(枕边细雨)。 请求消息头: Accept:告知服务器,浏览器可接受的MIME类型 MIME类型:文件系统文件类型用文件的扩展名区别的。 MIME类型由大类型/小类型组成的。比如:text/html. text/css text/js.image/bmp;image/jpeg;(参考Tomcat\conf\web.xml) Accept-Charset: 告知服务器,浏览器通过这个头告诉服务器,它支持哪种字符集 *Accept-Encoding:告知服务器,浏览器支持的压缩格式,浏览器能够进行解码的数据编码方式,比如gzip Accept-Language:告知服务器,浏览器所希望的语言种类 *Referer:告知服务器,当前请求的URL来自哪个地址。(防盗链) ** Referer表示该页面来自哪一个页面 Referer请求头是比较有用的一个请求头,它可以用来做统计工作,也可以用来做防盗链。 统计工作 :我公司网站在百度上做了广告,但不知道在百度上做广告对我们网站的访问量是否有影响,那么可以对每个请求中的 Referer 进行分析,如果 Referer为百度的很多,那么说明用户都是通过百度找到我们公司网站的。 防盗链 :我公司网站上有一个下载链接,而其他网站盗链了这个地址,例如在我网站上的 index.html 页面中有一个链接,点击即可下载 JDK7.0 ,但有某个人的微博中盗链了这个资源,它也有一个链接指向我们网站的 JDK7.0 ,也就是说登录它的微博,点击链接就可以从我网站上下载 JDK7.0 ,这导致我们网站的广告没有看,但下载的却是我网站的资源。这时可以使用 Referer 进行防盗链,在资源被下载之前,我们对 Referer 进行判断,如果请求来自本网站,那么允许下载,如果非本网站,先跳转到本网站看广告,然后再允许下载。 *Content-Type: application/x-www-form-urlencoded:表单的数据类型,说明会使用url格式编码数据;url编码的数据都是以“%”为前 缀,后面跟随两位的16进制,例如“传智”这两个字使用UTF-8的url编码用为“%E4%BC%A0%E6%99%BA”; If-Modified-Since: Wed, 02 Feb 2011 12:04:56 GMT利用这个头与服务器的文件进行比对,如果一致,则从缓存中直接读取文件。 User-Agent:浏览器类型. Content-Length:表示请求消息正文的长度 *****Cookie:这是最重要的请求头信息之一 ,会话有关 响应消息头: *Location: http://www.it315.org/index.jsp告知浏览器,新的资源的位置(结合302响应码来用,实现请求重定向) eg: //设置状态码,302/307表示重新定向 response.setStatus(302); //设置头,Location属性,使其重定向 response.setHeader( "Location", "/Test_day07_httpWatchTest//servlet/ServletDemo2" ); *Content-Encoding: 告知浏览器,服务器发送的数据采用的编码类型gzip *Content-Length: 80 告诉浏览器正文的长度 *Content-Type: text/html; charset=GB2312告诉浏览器,服务器发送的正文内容的MIME类型 *Refresh:1 告诉浏览器,刷新频率。单位是秒 *Content-Disposition: attachment; filename=aaa.zip告诉浏览器,以下载的形式打开数据 eg: //告诉浏览器以下载的方式打开 response.setHeader("Content-Disposition", "attachment;filename=1.gif") ; *****Set-Cookie会话有关 *//告诉客户端不要缓存 Expires: -1 (有效时间,单位是毫秒值。取值只要比当前时间小) Cache-Control: no-cache (1.1) Pragma: no-cache (1.0) 二、Servlet入门 1、Servlet的编写步骤: a、建立一个标准的JavaWeb应用的目录结构 b、在WEB-INF\classes目录下建立一个HelloServlet.java,内容如下: package com.itheima.servlet; import javax.servlet.*; import java.io.*; public class HelloServlet extends GenericServlet{ public void service(ServletRequest req,ServletResponse res) throws ServletException,IOException{ res.getWriter().write("Hello Servlet"); } } c、进入WEB-INF\classes目录,编译。 注意:要把servlet-api.jar加入到构建路径中。set classpath=%classpath%;C:\apache-tomcat-6.0.37\lib\servlet-api.jar 编译,注意有包名。javac -d . HelloServlet.java d、修改应用中的web.xml,给Servlet类一个映射地址: <?xml version="1.0" encoding="GBK"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <servlet> <!--给你的Servlet类取一个名字--> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.itheima.servlet.HelloServlet</servlet-class> </servlet> <servlet-mapping> <!--给Servlet一个映射地址--> <servlet-name>HelloServlet</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app> e、部署到Tomcat中。访问http://localhost:8080/firstServlet/hello即可看到程序的输出内容 三、Servlet的生命周期 Servlet的生命周期: Servlet对象默认情况下是用户第一次访问时由容器创建。 日后就驻留Tomcat内存了。 针对用户的每次请求,Tomcat都会调用service方法为客户端服务。 当应用被卸载了,或者TOmcat挂了,会调用destory方法。 <servlet> <!--给你的Servlet类取一个名字--> <servlet-name>HelloServlet</servlet-name> <servlet-class>com.itheima.servlet.HelloServlet</servlet-class> <load-on-startup>2</load-on-startup><!--启动顺序,应用被服务器加载时就完成了初始化--> </servlet> 四、Servlet的配置 Servlet的映射<url-pattern/>可以使用通配符(*) 使用通配符有2中形式: a、以*开头,以其他什么东西结尾。如*.do b、以/开头,必须以*结尾。如 /action/* /*匹配任意的东东 注意: 使用通配符有2中形式,绝对匹配最高-------》b优先级比a高-----》从前往后找对相近的 注意:(知道) 如果一个servlet的映射路径为一个/,那么它就是默认的Servlet。该Servlet会处理没有映射路径的所有请求。 更改MyEclipse10.6生成的Servlet模板: 1、关闭MyEclipse 2、找到MyEclipse的安装目录的上级目录。 3、搜索com.genuitec.eclipse.wizards*.jar 4、打开,找到templates\Servlet.java,这就是模板文件。(做好备份,再修改) 5、替换jar包中的Servlet.java 6、重启MyEclipse,生效
平时使用中经常会碰到ping不通的情况,ping不通的原因有很多,比如路由设置问题,比如网络问题,以下列出几点原因: 1.太心急。即网线刚插到交换机上就想Ping通网关,忽略了生成树的收敛时间。当然,较新的交换机都支持快速生成树,或者有的管理员干脆把用户端口 (access port)的生成树协议关掉,问题就解决了。 2.某些路由器端口是不允许用户Ping的。 3.访问控制。不管中间跨越了多少跳,只要有节点(包括端节点)对ICMP进行了过滤,Ping不通是正常的。最常见的就是防火墙的行为。 4.多路由负载均衡场合。比如Ping远端目的主机,成功的reply和timed out交错出现,结果发现在网关路由器上存在两条到目的网段的路由,两条路由权重相等 ,但经查一条路由存在问题。 5.网络因设备间的时延太大,造成ICMPecho报文无法在缺省时间(2秒)内收到。时延的原因有若干,比如线路(卫星网时延上下星为540毫秒),香港服务器租用路由器处理时延,或路由设计不合理造成迂回路径。使用扩展Ping,增加timed out时间,可Ping通的话就属路由时延太大问题。 6.引入NAT的场合会造成单向Ping通。NAT可以起到隐蔽内部地址的作用,当由内Ping外时,可以Ping通是因为NAT表的映射关系存在,当由外发起Ping内网主机 时,就无从查找边界路由器的NAT表项了。 7.指定源地址的扩展Ping.登陆到路由器上,Ping远程主机,当ICMP echorequest从串行广域网接口发出去的时候,路由器会指定某个IP地址作为源IP,这个IP地址 可能不是此接口的IP或这个接口根本没有IP地址。而某个下游路由器可能并没有到这个IP网段的路由,导致不能Ping通。可以采用扩展Ping,指定好源IP地址。 8.IP地址分配不连续。地址规划出现问题象是在网络中埋了地雷,地址重叠或掩码划分不连续都可能在Ping时出现问题。比如一个极端情况,A、B两台主机, 经过多跳相连,A能Ping通B的网关,而且B的网关设置正确,但A、B就是Ping不通。经查,在B的网卡上还设有第二个地址,并且这个地址与A所在的网段重叠。
说说那几款查看代码的工具 今天给大家介绍几款单片机工程师所喜欢的几款查看源代码的工具,这几款工具在懒猫的日常工作中可是帮了不少忙。 一、 Source Insight Source Insight是一个面向项目开发的程序编辑器和代码浏览器,它拥有内置的对C/C++, C#和Java等程序的分析。Source Insight能分析你的源代码并在你工作的同时动态维护它自己的符号数据库,并自动为你显示有用的上下文信息。 Source Insight不仅仅是一个强大的程序编辑器,它还能显示reference trees,class inheritance diagrams和call trees。Source Insight提供了最快速的对源代码的导航和任何程序编辑器的源信 息。 Source Insight提供了快速和革新的访问源代码和源信息的能力。与众多其它编辑器产品不同,Source Insight能在你编辑的同时分析你的源代码,为你提供实用的信息并立即进行分析。 该软件最强大的就是它的强大的搜索功能,对于分析解读源代码非常有帮助,它还可以设置背景颜色,也可以高亮显示关键字,当然也可以自定义个性的配置,关于它的使用说明可以查看其自带的帮助文件,也可以在google上搜索相关文档。 如果你现在还没有安装这款软件,并且你也是经常与源代码打交道,懒猫建议你最好安装一下,它将给你带你意想不到的方便与快捷。 二、 Ultra Edit IDM Computer Solutions公司出品的著名文本编辑器。 这款功能强大的文本编辑器,可以编辑文字、Hex、ASCII码,可以取代记事本,内建英文单字检查、C++ 、Java、HTML、VB、JSP等多种语言的指令突显,可同时编辑多个文件,而且即使开启很大的文件速度也不会慢。也可以编辑其他扩展名的文件(如.dat .sav等)。最新版本的软件修正了老版本存在的一些Bug,并新增了对安全FTP(SFTP)的支持,整合了CSE HTML Validator,增添了新的宏命令等二十余项新功能。 UltraEdite也是强大的代码查看及编译工具,你甚至可以用它来编译C51,前提是你要把C51的编译器导入里面。 另外该软件内嵌进制转换软件,可以很方便的进行各个进制之间的转换。而且还有行模式及列模式,对大量数据的筛选也很有帮助。 至于该软件的强大功能,懒猫就不在这里介绍了,懒猫平时也只是用了它部分功能,至于其它的功能,可能对您会更有帮助,但愿你已经安装了这款软件,不然你真的会非常后悔。 三、 Notepad++ 一款开源、小巧、免费的纯文本编辑器。在文字编辑方面与Windows写字板功能相当。当然,更重要的是Notepad++更是程序员们编写代码的利器!它运行便携,体积小、资源占用小,支持众多程序语言,比如C++、C#、Java 等主流程序语言;支持HTML、XML、ASP,Perl、Python、JavaScript 等网页/脚本语言。而且Notepad++做为程序员们最喜爱的编辑器之一,像语法高亮,语法折叠, 宏等编辑器常用功能一个都不少。如果你发现Notepad++有不满意的地方,还可以通过安装扩展或自行开发扩展来定义一个更强大的Notepad++! Notepad++ 主要特性: 1、所见即所得功能、语法高亮、字词自动完成功能,支持同时编辑多重文档;支持自定义语言; 2、对于HTML网页编程代码,可直接选择在不同的浏览器中打开查看,以方便进行调试; 3、自动检测文件类型,根据关键字显示节点,节点可自由折叠/打开,可显示缩进引导线,使代码富有层次感; 4、可打开双窗口,在分窗口中又可打开多个子窗口,允许快捷切换全屏显示模式(F11),支持鼠标滚轮改变文档显示比例; 5、可显示选中文本的字节数,并非普通编辑器所显示的字数;提供了一些实用工具,如邻行互换位置、宏功能,等; 懒猫主要用这款软件来查看单个代码文件,感觉用起来还是蛮顺手的。 四、SVN 这款软件严格意义上不算是查看代码的工具,它是近年来崛起的版本管理工具,是cvs的接班人。目前,绝大多数开源软件都使用svn作为代码版本管理软件。 程序的版本妥善保管对每一位程序员都是非常重要,而这款软件正符合这种需求,它可以用来保存代码版本,方便多人协作开发,也可以查看上所保存软件版本与当前软件的差异,当然也可以恢复不小心修改错误的程序。 五、Microsoft Office Visio 这款软件准不是用来看代码的,而是用来设计和查看流程图的。 六、FreeCommander FreeCommander是一个免费的用于Windows(官方支持2000、XP和Vista)系统的资源管理器。它拥有内置FTP客户端、存档文件导航、文件比较/同步,及多用途重命名工具等功能。 这款软件可能用的人比较少,懒猫用它来比较两个文件,其它的功能就是文档导航,如果你的电脑现在已塞的满满的,这款软件应该很适合你^_^ 好了,今天先介绍到这吧,当然还有其它一些便捷的软件工具,不过懒猫暂时一般只用这几款,如果您有什么好的软件,不妨也给懒猫介绍介绍,懒猫在些谢过了^_^ 最后,再吼一下懒猫久违的口号: 每天进步一点点,开心多一点^_^ ---- 2013年3月25日 20:36:00
*********************************************************************************************************/ #include "LPC17xx.h" /* LPC17xx外设寄存器 */ /******************************************************************************************************** 变量与宏定义 *********************************************************************************************************/ extern uint32_t SystemFrequency; /* Clock Variable 时钟变量 */ #define UART_BPS 9600 /* 串口通信波特率 */ /********************************************************************************************************* ** Function name: delayNS ** Descriptions: 延时函数 ** input parameters: ulDly: 延时值 ** output parameters: 无 ** Returned value: 无 *********************************************************************************************************/ void delayNS (uint32_t ulDly) { uint32_t i; for (; ulDly > 0; ulDly--) { for (i = 0; i < 5000; i++); } } /********************************************************************************************************* ** Function name: uartInit ** Descriptions: 串口初始化,设置为8位数据位,1位停止位,无奇偶校验,波特率为9600 ** input parameters: 无 ** output parameters: 无 ** Returned value: 无 *********************************************************************************************************/ void uart0Init (void) { uint16_t usFdiv; LPC_PINCON->PINSEL0 |= (0x01 << 4)|(0x01 << 6); /* 设置P0.2(TXD0)为串口,P0.3(RXD0)为串口 */ LPC_SC->PCONP = LPC_SC->PCONP|0x08; /* 打开串口0功能 */ LPC_UART0->LCR = 0x83; /* 设置串口数据格式,8位字符长度,1个停止位,无校验, 使能访问除数锁存器 ,设定波特率 */ usFdiv = (SystemFrequency/4 / 16) / UART_BPS; /* 设置波特率 */ LPC_UART0->DLM = usFdiv / 256; /*除数高8位,没有小数点情况 */ LPC_UART0->DLL = usFdiv % 256; /*除数低8位,没有小数点情况 */ LPC_UART0->LCR = 0x03; /* 锁定波特率 */ LPC_UART0->FCR = 0x06; /* 复位TxFIFO和RXFIFIO */ } /********************************************************************************************************* ** Function name: uart0GetByte ** Descriptions: 从串口接收1字节数据,使用查询方式接收 ** input para meters: 无 ** output parameters: 无 ** Returned value: ucRcvData: 接收到的数据 *********************************************************************************************************/ uint8_t uart0GetByte (void) //查询法 { uint8_t ucRcvData; while ((LPC_UART0->LSR & 0x01) == 0); //UnLSR[0] 该位是否置一,决定能否从FIFO中读取数据 //UnLSR[0]为0 :RBR为空 ; UnLSR[0]为1:RBR中包含有效数据,从FIFO中读走所有数据后,恢复为0 ucRcvData = LPC_UART0->RBR; //UnRBR为接收缓存寄存器 /* 读取数据 */ return (ucRcvData); } /********************************************************************************************************* ** Function name: uart0GetStr ** Descriptions: 串口接收字符串 ** input parameters: puiStr: 指向接收数据数组的指针 ** ulNum: 接收数据的个数 ** output parameters: 无 ** Returned value: 无 *********************************************************************************************************/ void uart0GetStr (uint8_t *puiStr, uint32_t ulNum) { for (; ulNum > 0; ulNum--) { *puiStr++ = uart0GetByte (); } } /********************************************************************************************************* ** Function name: uart0SendByte ** Descriptions: 向串口发送子节数据,并等待数据发送完成,使用查询方式 ** input parameters: ucDat: 要发送的数据 ** output parameters: 无 ** Returned value: 无 *********************************************************************************************************/ void uart0SendByte (uint8_t ucDat) { LPC_UART0->THR = ucDat; //UnTHR为发送缓存寄存器 /* 写入数据 * / while ((LPC_UART0->LSR & 0x40) == 0); // UnLSR[6] 为0 时,表明UnTHR包含数据; } // UnLSR[6]为1时,则UnTHR已空 /* 等待数据发送完毕*/ /********************************************************************************************************* ** Function name: uart0SendStr ** Descriptions: 向串口发送字符串 ** input parameters: puiStr: 要发送的字符 串指针 ** output parameters: 无 ** Returned value: 无 *********************************************************************************************************/ void uart0SendStr (uint8_t const *puiStr, uint32_t len) { while (len -- > 0) { uart0SendByte (*puiStr++); } } /********************************************************************************************************* ** Function name: main ** Descriptions: 串口查询方式接收发送数据。 ** 全速运行程序用串口调试软件发送字符串,并在接收窗口查看返回数据,波特率为9600, ** 上位机一次需要发送20个字符。 ** input parameters: 无 ** output parameters: 无 ** Returned value: 无 *********************************************************************************************************/ int main (void) { uint8_t ucBuf[30]; SystemInit(); /* 初始化目标板 */ uart0Init (); /* 串口初始化 */ while (1){ uart0GetStr(ucBuf, 20); /* 从串口接收字符串 */ delayNS(1); uart0SendStr(ucBuf, 20); /* 向串口发送字符串 */ delayNS(1); } } /********************************************************************************************************* End Of File *********************************************************************************************************/