分类: 奇技
密码保护:毕业后的新生活
密码保护:春节假期短思
OO + Design Patterns
本文主要干两件事情,推荐几本书,以及安利一点莫名其妙的笔记。
前面提到我被自己写的词法分析程序深深地伤害了,因为它竟然如此之丑,于是决心学习设计模式。
貌似一般很少能找到某个领域,是有一本公认的最权威的参考书籍的,然而设计模式就是这样的“异类”。GoF的<Design Patterns>简直是该领域的至尊。但是我要推另一本<Head First 设计模式>,适合完全没有什么OO经验的人去读。前者基于c++和少量smalltalk,后者基于Java。个人感受是第一本更像是一本字典,要理解各种模式的内涵可能会比较吃力,第二本则是绝对的神书,轻松爆笑不管多困都看的下,并且里面的例子更基础更具体,更适合入门。
因为最近刚刚开始学java的OO(Object Oriented),所以<Head First 设计模式>给了我很多的灵感。读到有不懂的地方我就去翻<Head First Java>,一周过去涨了很高的姿势。
有一些关于OO的想法要分享。
关于继承。以前不懂得继承是为了什么,做作业为了花样炫技就强行继承。后来这学期老师说继承不为什么,也只不过是为了复用一些代码,让你少码一点(当时我暗想:这理由好蠢)。后来过了好久,直到我在看设计模式的时候才重新想到这句话。我觉得其实复用的意义更在于它更支持“维护”,说的土一点是修改的时候你不用改得很崩溃。这是个很关键的点。
在c面向过程编程时也提到过类似的概念,比如要代码重构包装出一些子函数来,就是为了在出错时便于修改。打ACM的时候这也是很重要的,比如那些大模拟题,如果相同的代码段只是不断地ctrl v而不装在一个子函数里,当发现出错的时候一个一个改是要崩的,思路很容易就飞了。
继承所支持的“可维护”包括但不局限于此。但我感觉继承并不是OO的主角。
可能是因为刚学所以正热乎,我感觉多态才是OO的精髓,当然多态离不开继承等概念的配合,只是说多态这一概念本身最能体现OO的强大。
OO的基础就是抽象、封装、多态、继承。
OO原则:
封装变化(把可能发生变化的地方封装起来,如各种需求变更)。找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。建立可维护的OO系统,要诀就在于随时想到系统以后可能需要的变化以及应对变化的原则。
多用组合,少用继承。使用组合来建立系统具有更大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。对应“策略模式”
组合(composition)和委托(delegation)可以在运行时具有继承行为的效果。利用一般继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地扩展。我们可以利用此技巧把多个新职责,甚至是设计超类时没有想到的职责加在对象身上。而且,可以不用修改原来的代码。通过动态地组合对象,可以写新的代码、添加新的功能,而无须修改现有代码。既然没有改变现有代码,那么引进BUG或产生意外副作用的机会将大幅度减少。
针对接口编程,不针对实现编程(“接口”是一个概念,并不特指java里面的interface,因为有时类也可以作“接口”。另外因为JAVA不支持多重继承,所以它要变相靠“接口”来实现;我看GoF那本书中,C++中貌似就是直接用类来实现“接口”的)
为交互对象之间的松耦合设计而努力。这里针对有“交互”行为的对象间,对应“观察者模式”。
类应该对扩展开放,对修改关闭。对应“装饰者模式”。这一规则一般不可能被百分之百遵守,但是这个意识会帮助你权衡要遇到什么样的困境才值得去打破原则。
依赖抽象,而不要依赖具体类。“依赖倒置原则”(Dependency Inversion Principle)。大概是强调一个多态,即把抽象的方法放到抽象类中,而不是在基类中分类实现子类的函数(否则每次增删一个子类都得改动抽象类的函数实现)。
OO模式
策略模式:定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
观察者模式:在对象之间定义一对多的以来,这样一来,当一个对象改变状态,依赖它的对象都会收到通知并自动更新。
装饰者模式:动态地将责任附加到对象身上。想要扩展功能,装饰者模式提供了有别于继承的另一种选择。
工厂方法模式:定义了一个创建对象的接口(比如new的过程),但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。(大概是通过一个接口来控制一批接口生产出一个系列的产品。对于这个理解我暂时不敢确定)
后面还有适配器模式、模板模式、组合模式和MVC很著名,不知道还有没有时间好好看。
大概只学到这里,最近这一周的code世界观天翻地覆,以前以为懂得了那些基本概念就是懂了C++,现在想来真是管中窥豹。
毕竟纸上得来终觉浅,最近要用c++写一个电商,我还得花点功夫看看java的这些奇技淫巧在c++里是怎么做的(看来不得不啃GoF那本神书了)。这学期压力太大,没有太多自由学习的时间,先分享到这里,等电商写到告一段落再来更新一些心得。
0 等你来赞java的“值传递” + 异常抛出
方法调用(call by)(相当于c++中的“函数调用”),根据参数传递的情况分为值调用( call by reference ) 和引用调用( call by value ) 。
Java的对象参数传递仍然是值调用,这里强调的是“对象”的参数传递。
Java中除了primitive type以外一切都是对象。primitive type只有以下几种:
注意,就连我们熟知的数组或者String也都是对象。Java没有指针,所以对primitive type实现著名的swap函数就显得有些困难,一种可行的做法是先特地把它们包装成对象。
Java中对象变量名实际上代表的是对象在堆中的地址(专业术语叫做对象引用 )。在Java方法调用的时候,参数传递的是对象的引用。重要的是,形参和实参所占的内存地址并不一样,形参中的内容只是实参中存储的对象引用的一份拷贝。这正是“值传递”的表现形式。
通过传递对象引用,形参和实参指向了同一个地址。所以修改形参的成员变量,相当于修改了实参的成员变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Employee { public String name=null; public Employee(String n){ this.name=n; } public static void Change(Employee e1,Employee e2){ e2.name = "王五"; System.out.println(e1.name+" "+e2.name); //打印结果:张三 王五 } //主函数 public static void main(String[] args) { Employee worker=new Employee("张三"); Employee manager=new Employee("李四"); Change(worker,manager); System.out.println(worker.name+" "+manager.name); //打印结果仍然是: 张三 王五 } } |
试着对一个对象实现swap。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class Employee { public String name=null; public Employee(String n){ this.name=n; } //将两个Employee对象交换 public static void swap(Employee e1,Employee e2){ Employee temp=e1; e1=e2; e2=temp; System.out.println(e1.name+" "+e2.name); //打印结果:李四 张三 e2.name = "王五"; System.out.println(e1.name+" "+e2.name); //打印结果:李四 王五 } //主函数 public static void main(String[] args) { Employee worker=new Employee("张三"); Employee manager=new Employee("李四"); swap(worker,manager); System.out.println(worker.name+" "+manager.name); //打印结果仍然是: 张三 李四 } } |
交换失败。前面说到形参和实参都是对象引用,本质上是引用,那么上面那段代码只是把形参的两个引用交换了一下(相当于c++中,只是交换了指针,而不是交换指针所指向的内容),对实参的情况毫无影响。下面用图给出直观的描述:
由此可进一步理解,为什么说JAVA本质上仍然是值传递。
下面讲讲异常抛出。
若要实现以下功能:
第一种方法,使用异常抛出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class student{ ... public static void main(){ while(markForMaths < 0 || markForMaths > 100 || markForEnglish < 0 || markForEnglish > 100 || markForScience < 0 || markForScience > 100){ System.out.println("请输入学生三门课成绩(数学,英语,科学):"); String tmp1 = in.next(); //输入三个数字,以逗号分隔 String []tmp2 = tmp1.split(","); try{ markForMaths = Integer.parseInt(tmp2[0]); markForEnglish = Integer.parseInt(tmp2[1]); markForScience = Integer.parseInt(tmp2[2]); if(markForMaths < 0 || markForMaths > 100 || markForEnglish < 0 || markForEnglish > 100 || markForScience < 0 || markForScience > 100) throw new ScoreException(); } catch(ScoreException e){ System.out.println(e); } catch(IOException e){ System.out.println(e); } } } ... } |
1 2 3 4 5 |
public class ScoreException extends IOException { public ScoreException(){ super("成绩数据有误,请重新输入"); } } |
第二种写法,不使用异常抛出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class student{ ... public static void main(){ while(true){ System.out.println("请输入学生三门课成绩(数学,英语,科学):"); String tmp1 = in.next(); //输入三个数字,以逗号分隔 String []tmp2 = tmp1.split(","); markForMaths = Integer.parseInt(tmp2[0]); markForEnglish = Integer.parseInt(tmp2[1]); markForScience = Integer.parseInt(tmp2[2]); if(markForMaths < 0 || markForMaths > 100 || markForEnglish < 0 || markForEnglish > 100 || markForScience < 0 || markForScience > 100) break; else System.out.println("成绩数据有误,请重新输入"); } } ... } |
显然第二种写法更简明清晰,也是我在学异常抛出之前做错误处理一贯的方式。这难道说明异常抛出反而是累赘吗?不是的,这实际上是一个失败的例子。
可以看到,在第一种写法中,既没有用到JAVA的“重新抛出异常”(就是在这个类的方法内部不处理异常,而是交给调用它的那个类的方法去处理,从里面一直往外抛, 简单说就是谁调用谁处理异常,我不管),而且也只有一层代码,事实上这种情况下很少强行使用异常抛出。
异常是有层级关系的错误处理机制,是专门抽象出来的一个用来处理错误的语法。子函数在出现异常的时候,可以自己处理掉,也可以抛给调用者处理,还可以自己处理一部分调用者处理一部分。处理异常的代码可以非常清晰地与逻辑主体隔绝开来。并且,Exception被捕获的话就不会终止程序,相比之下error则会使之宕掉。
可以设想这么一个场景,你在某个地方调用一个函数,这个函数的作用,是接受学生成绩并做相应的处理。但是,如题所说,输入的学生成绩可能是错误的。那么你在调用的地方怎么获取这个信息呢?两种办法,一种是用返回值表征状态,一种是用异常处理机制。此例中返回值可以有两个枚举值,0表示正常,1表示成绩不在0到100的闭区间内。
然而对于更一般的情况来说,返回值能表示的信息很有限,也就是,只能表示错误状态,却难以将更多的错误信息表达出来。比如如果接受的是一个数组,那么你可能还需要知道脱离0到100范围的数的下标是多少。出现别的错误时,你可能又想获取别的信息,这种情况下返回值就很难做到了。
所以后来我把代码改成了这个样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class student{ ... public static void main(){ int score[] = new int[3]; illegalFlag = true; while(illegalFlag){ illegalFlag = false; System.out.println("请输入学生三门课成绩(数学,英语,科学):"); try{ getScore(score); } catch(_2013212130_zz_2_ScoreException e){ System.out.println(e); } catch(Exception e){ System.out.println("输入格式不符合要求,请重新输入"); } } ... } |
1 2 3 4 5 6 7 8 9 10 11 |
public static void getScore(int score[]) throws Exception{ Scanner in = new Scanner(new BufferedInputStream(System.in)); String tmp1 = in.next(); String []tmp2 = tmp1.split(","); int markForMaths = score[0] = Integer.parseInt(tmp2[0]); int markForEnglish = score[1] = Integer.parseInt(tmp2[1]); int markForScience = score[2] = Integer.parseInt(tmp2[2]); if(markForMaths < 0 || markForMaths > 100 || markForEnglish < 0 || markForEnglish > 100 || markForScience < 0 || markForScience > 100) throw new _2013212130_zz_2_ScoreException(); } |
1 2 3 4 5 |
public class ScoreException extends IOException { public ScoreException(){ super("成绩数据有误,请重新输入"); } } |
这样才不会背离“异常处理”的原则太远。
初初接触异常抛出,除了明白基本概念,我感觉自己还需要知道什么时候该用以及该怎么用,在网上找到了相关有营养的建议。
1.只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程。谨慎地使用异常,异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。
2.切忌使用空catch块。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。
3.不要将提供给用户看的信息放在异常信息里。因为业务逻辑跟用户界面要解耦合,展示给用户的错误提示信息若写死在里面了,万一要改成英文版的,不是又得改下程序;万一你觉得这个promt不合适,想换句好听点的话,又得改下程序;万一你要支持多语言,你还得在代码里面敲进N多种语言,再用一个swtich去拿…
其中一种做法是拿一个 txt 文件或者 ini 文件之类的,把错误编码跟错误提示文本存起来。然后主程序再跑去文件里面根据错误编码找到具体的对应提示文本,这就是一种配置文件。如下图:
4.避免多次在日志信息中记录同一个异常。只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源。
5.异常处理尽量放在高层进行。尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。
6.在finally中释放资源。如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。
reference:
以及,与@erueat的聊天
0 等你来赞linux + github + notes + habit
大家可以看到我上一篇文章加了密码,事情是这样的,听我解释。这是我们课内的作业,贴上来的东西分分钟就是我自己的实验报告,并且deadline还远着。然后我是很相信我同学的“正确使用搜索引擎”的能力的,并且我之前装了一个插件可以帮助google搜索抓取我的页面使之更易被检索到,所以,这只是为了避免不必要的悲剧发生,等到deadline过去以后自然会解放权限。密码是,我的高中校名的拼音。如果不知道是众多校名称呼的哪一个,多试几次就好了,毕竟我只读过一个高中。
孟祥武孟爷爷老是说:“你们不能只用一个操作系统啊,除了windows,还应该多试试别的。如果只用一个操作系统,你就会觉得windows就是操作系统,操作系统就是windows——这是不对的。”
类似的,孟爷爷还有“不能只学一门语言啊”,“不能只用一种浏览器啊”的谆谆教导。
于是下定决心在自己电脑上装了个虚拟机,先装了ubuntu kylin 15.04,但是觉得不流畅,网上评价也很一般,于是转ubuntu kylin 14.04,使用至今感觉不错。之所以不用原版ubuntu,是因为麒麟版本对于中文更加友好,并且跟原版也没什么差别。网上有人在撕麒麟是不是国产的,其实明显它并不是国产,说是联合开发,实际上kylin只是ubuntu一个子分支,用起来不用心存芥蒂。
顺手贴个很著名的很洗脑的文章,《完全用Linux工作》。书架上还有一本《鸟哥私房菜》嗷嗷待操。
转型的过程总体还算顺利,装codeblocks写c++,然后g++和gcc系统是没有自带的,所以顺手装了好多东西。换语言的时候出了问题,出了一个“源引用”错误(提示信息: “ubuntu kylin It is impossible to install or remove any software”),需要打开某个属性栏然后勾选或者是删除某个选项才行,具体的记不清了(翻了一下历史记录没找到),似乎没什么遇到跟我一样的情况;再就是选择简体中文的时候,如下图
一开始那个“汉语(中国)”的选项是在下面,而且是浅色的,常识告诉我它仍不能选。google了以后发现正确的打开方式是:点住他、拖动到语言栏的最上方。太魔性,别人不跟我说的话真是一辈子都找不出来毛病。
eclipse至今没有建起来,原因是我搜的那个办法有一步是在shell内使用vim修改一个源文件,当场我就萌比了。还有github理论上是可以直接用shell去push和pull的然而也还没学会(因为暂时还不知道怎么用shell往git直接提交,所以我是开了github的网页然后直接粘上去的)。shell命令也基本不懂,而这才是linux的精髓。以上,列入TODOLIST了。
linux部分大概就这些,使用了半个月,用户体验非常好。因为里面没有qq没有qq音乐也没有全家桶们没有弹窗们,没有任何可以打扰我的东西,做事效率高一点,并且是不是能骗取路人们的仰慕,无形装逼。
之前在windows上配好了github没用过,这次在linux上也配了一下(但是还不懂怎么push和pull),然后写作业的时候就真正用github做版本管理了。放几张效果图
github的界面简洁明了
上图为各个版本
版本管理非常方便,点开以后可以看到当前版本与上一版本相比做了什么修改,红色行为原来的写法,其中深红色(此图没有)是具体的修改或删除的地方;绿色行为该版本的修改,深绿色标明了修改或添加的地方。没有改动的代码段自动折叠。最左有新旧两版本的行号对照。支持多人管理同一文档,每一个改动都会标明修改者id、修改内容的题目、修改内容的简要说明等关键信息。
实在很强大,难怪田队年初的时候决心在ACM校队内普及git的用法。今年北邮的校赛的出题验题工作就都是在git上完成的。
总之,github是一个强大的工具。
最后做一点个人的读书笔记。
关于如何写注释。
1.不要为那些从代码本身就能快速推断的事实写注释。eg:构造函数就不用再写 //constructor了
2.不要给不好的名字加注释,即注释不应用于粉饰不好的名字。一个好的名字比一个好的注释更重要,因为在任何用到这个函数的地方都能看见它。好代码 > 坏代码 + 好注释。
3.不要写废话。如果一定要解释一个函数,请尝试给出它的逻辑细节。
4.记录你的思想,克服“作者心理阻值”。例如,假如你正在写一个函数,然后心想:“哦,天啊,如果一旦这东西在列表中有重复的话会变得很难处理的”,不如把它写下来:
1 |
//哦,天啊,如果一旦这东西在列表中有重复的话会变得很难处理的 |
It does help. 并且最后review的时候你可以尝试改一下这句话,把具体问题解释得更清楚一点。
5.在代码中加入注释记录你对代码有价值的见解。例如,
1 2 3 4 5 |
//出乎意料的是,对于这些数据用二叉树比哈希表快40% //作为整体可能会丢掉几个词。但这不是问题,实际上要100%解决太难了 ... |
6.为代码中的瑕疵写注释。
1 2 3 4 5 |
//TODO: 采用更快的算法 //TODO:处理除JPEG以外的图像格式 ... |
尝试使用标记。书里给出一种流行的写法。“TODO”:我还没有处理的事情。“FIXME”:已知的无法运行的代码。“HACK”:对于一个问题不得不采用的比较粗糙的解决方案。“XXX”:危险!这里有重要的问题。
7.当定义常量时,通常背后有一个关于它是什么或者为什么它是这个值的“故事”。
1 2 3 4 5 |
NUM_THREADS = 8 # as long as it's >= 2 * num_processors, that's good enough image_quality = 0.72; // users thought 0.72 gave the best size/quality tradeoff ... |
这可以让读代码的人有了调整这个值的指南。经验告诉我们,很多常量可以通过加注释得到改进,而这只不过是匆匆记下你在决定这个常量值时的想法而已。
8.可能需要一些高级别的注释,比如文件级别的:
1 2 |
//这个文件包含了一些辅助函数,为我们的文件系统提供了更便利的接口 //它处理了文件权限及其他基本的细节 |
——以上摘自DB & TF 的<The Art of Readable Code>
45个习惯。
7.懂得丢弃。敏捷的根本之一就是拥抱变化。在学习一门新技术的时候,多问问自己,是否把太多旧的态度和方法用在了新技术上。例如,学习一门新的编程语言时,应该使用推荐的集成开发环境,而不是你过去开发时用的工具插件。转换的时候,完全不要使用过去的语言开发工具。只有更少被旧习惯牵绊,才更容易养成新习惯。对旧习惯不是完全摒弃,如果环境合适,可以举一反三地灵活应用。
9.把我开发节奏。以固定、有规律的长度运行迭代。
14.提早集成,频繁集成。在产品的开发过程中,集成是一个主要的风险区域。让你的子系统不停地增长,不去做系统集成,就等于一步一步把自己置于越来越大的风险中。而若在早期就进行集成,你会更清晰地看到子系统之间的交互和影响,就可以估算它们之间的通信和共享的数据信息。如果落实得好,集成就不再会是一个繁重的任务。它只是编写代码周期中的一部分,集成时产生的问题都会是小问题并且容易解决
——以上摘自VS & AH 的 <Practices of an Agile Developer>
0 等你来赞
密码保护:something
linux系统下的fork函数,wait函数和exit函数
fork()函数通过系统调用创建一个新进程(子进程),系统为它分配资源,并把其父进程的当前的情况复制到子进程中,从整体上看相当于有两份数据(所以简单改动一个进程内的变量的数据完全不会影响到另一个进程)。
fork()函数调用一次,返回两次。看下面一段代码
1 2 |
pid_t = fpid; fpid = fork(); |
这里fork()函数只出现了一次,即只被调用了一次,但此时它已经创建了另一个进程,所以共两个进程。可以理解为,父进程调用了fork()函数,创建子进程以后,fork()返回了一个值给父进程,同时还返回了一个值给新创建的子进程。
fork()函数有三种返回值:
1)在父进程中,fork()返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
上面提到fork()函数只复制所谓“当前的情况”,可以理解为“此时已经执行过的语句所呈现的情况”,所以时间上在fork()以后才执行的语句会在两个进程中执行。若有这样的语句:
1 2 |
fpid1 = fork(); fpid2 = fork(); |
则执行完后系统将会有4个进程。具体的,一开始只有进程1,执行完第一个fork()以后进程1创建了进程2,fork()分别给他们返回了一个值。之后,对于进程1和进程2,都是从得到一个返回值以后继续运行的,也就是分别调用了一次fork()。这样进程1就创建了进程3,进程2创建了进程4(此处数字不表示先后创建顺序,见下一段)。面试题中可能会有这样的考点,给出的例子是最简单最基本的情形。
创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
如果要控制fork()以后,等子进程执行完并返回了,再执行父进程,可以调用wait()函数。讲wait()函数之前,先简单讲一个概念:僵尸进程。
在UNIX系统下,若一个子进程结束了,但它的父进程没有等待(调用wait() /waitpid())它,那它将会变成一个僵尸进程。僵尸进程的危害在于:若不及时释放,它会一直占用着它的进程号,而系统所能使用的进程号是有限的,所以大量的僵尸进程可能导致系统无法创建新进程。
讲回wait()函数。进程一旦调用了wait(),就立即阻塞自己,并由wait自动分析是否有某个当前进程的子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
exit()函数可用于终结一个进程。exit的返回值是返回给操作系统的,但如果是多进程,则是返回给父进程的。
写了一个简单的小程序:创建子进程,在子进程中计算斐波那契数列并把父进程挂起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <stdio.h> #include <sys/types.h> #include <sys/wait.h> int main() { pid_t pid; //process id int i, a, b, fib; int n; scanf("%d", &n); pid = fork(); if(pid < 0) //说明当前进程是父进程且创建子进程失败 { fprintf(stderr, "Fork Failed\n"); exit(-1); } else if(pid == 0) //说明当前进程是子进程 { if(n == 1) printf("0\n"); else if(n == 2) printf("0 1\n"); else if(n > 2) { a = 0; b = 1; printf("0 1 "); for(i = 3; i <= n; i++) { fib = a + b; printf("%d%c", fib, " \n"[i == n]); a = b; b = fib; } } } else //说明当前进程是父进程 { wait(NULL); exit(0); } return 0; } |
文中提到的相关概念只做了很粗浅的解释,深入学习的话请移步下面链接。
reference:
0 等你来赞
博客重建
之前用BAE3+wordpress建的站有些页面打不开 似乎是数据库传得不好
于是把整个数据库删掉,然后选了一个更老的版本重新上传、安装,再更新,终于弄好了:)
调整了半天调出了自己喜欢的配色,摸索着装上了各种插件,试验了各种功能,总算把工作做得七七八八了~
开心开心~~~
域名和服务器均在gegehost购买
建站参考资料:
1 个人赞过辣戈戈主机客户中心自助购买主机教程图解
http://www.gegehost.com/2010/05/21/buy-gegehost/
Gegehost域名注册与管理教程
http://www.gegehost.com/2011/02/28/register-and-manage-domain-with-gegehost/
cPanel安装WordPress中文教程
http://www.gegehost.com/2009/03/16/cpanel-wordpress-chinese/