分类: C/C++
2012-09-19 10:18:48
《C语言的科学与艺术》学习笔记
一、该书中各种编译器环境配置问题
vc++6.0下
第一步,先用库的c源文件编译成.lib静态库文件。在vc中建立"win32 static library"工程,然后新建一个"c++ source file"注意文件名要加上扩展名.c。然后将所有standard文件夹内扩展名为.c的文件内的代码,全部复制到新建的c源代码文件里。注意一定要把全部复制进来,各个源文件不是独立的,不然就要一个一个的弄很麻烦。然后编译链接,生成.lib文件。
把生成的这个文件复制到,vc下的lib文件夹内。
第二步,复制所有standard文件夹下的.h文件到vc下的include文件夹内。这样里面所用到的自定义库就做好了,下面介绍如何在程序中使用。建立一个win32控制台程序,然后选择“工具->选择”在“目录”选项卡中加入你头文件的文件夹路径,如果你放在include文件夹内(与标准库在一起)这一步可省略。因为路径本来会在里面。
选择“工程->设置”在“link”选项卡 中的 “对象/库模块”中加入你刚刚编译的.lib文件的文件名即可。
devc++下
因为devc++是基于MinGW开发的(意思就是编译系统是linux下的编译系统(我自己的理解,可能不太确切)),所以它的静态链接库文件的扩展名为.a。步骤和vc下差不多
第一步,生成.a静态库文件。先建立一个工程“static library”选择c工程,工程名一定要注意一定要以“lib”三个字母开头,因为linux默认的库文件都以lib开头这样才可以用 -l 命令。这里的目的是保证生成的库文件名为lib开头,不然复制时需要重命名。新建一个源代码文件,然后将所有standard文件夹内扩展名为.c的文件内的代码,全部复制到新建的c源代码文件里。原因同vc。然后编译生成lib<库文件名>.a文件,把它复制到devc++的lib文件夹里。
第二步,复制所有standard文件夹下的.h文件到devc+下的include文件夹内。 在devc++中的使用自定义库建立一个win32控制台程序,选择“工程->工程属性”在“参数”选项卡中写入命令 -l<库文件名> 注意后不要输入.a。如你的.a文件名为 libhello.a 在此处写 -lhello就可以。然后编译运行就好了。如果不愿在每个工程都加入此参数,可以在“工具->编译选项”的“编译器”选项卡中 勾选 “在连接器命令行加入一下命令”并写入-lhello就可以
二、该书中自定义头文件的作用
(1)genlib.h
1.几个新的“原始”类型声明(最重要的是bool和字符串)在整个其他库和基本类型的应用。
2.一种新的内存分配函数集。
3.用于错误处理功能。
4.一个用于内部退出循环重复声明。
(2)simpio.h
该接口定义了访问功能,简化了输入和读取数据。
(3)strlib.h
该strlib.h文件定义了一个简单的字符串库接口。在这个包的情况下,字符串被认为是一个抽象的数据类型,这意味着客户端只能在该类型中定义的操作,而不是依赖于底层的代表。
(4)random.h
该接口提供了生成伪随机数的函数。
三、自顶向下的设计(逐步精化)原则:
一旦你有了某个程序的概要描述,你就应该在此结束,并把它写下来,用过程或函数来表示那些还要继续细化的程序。
四、C语言输出格式:
%d 十进制整数 %f 浮点数(包括double型)%.2f表示保留两位小数
%e 指数型 输出格式为d.ddddde±xx对应于数学中表示d.ddddde×
%g 普通型 数值用%f和%e格式中较短的一个显示,在不能是钱确定输出值有多大的情
况下,%g可能是最好的输出格式
%s 字符串 %% 并不是输出格式,而是为了输出%
五、万年历算法
根据具体的某一天的真实日期及星期为基点,往前往后运算。
六、改善循环的策略
检查循环条件中有没有一些放在循环开始前执行更好的计算。这样就不必在循环的每一个周期都进行这样的计算。
七、接口相关
1.接口是指客户(使用接口的人)和实现者(编写接口的人)的机会点与临界点。C语言中的接口常用头文件来表示,作为接口的头文件包含大量的文档注释以及由库导出的函数原型。也就是说客户只需要阅读头文件,了解头文件中的函数原型和功能,即可应用。而函数的实现过程是实现者即编写接口的人来完成的。
2.在解决比较大的问题的时候,一种非常有效的策略是试图在一个大问题的不同部分中找出共同的部分,使得可以对这些共同的部分应用一个解决方案(利用已有的函数编写自己的函数,根据不同的参数实现不同的功能),也即是根据已有的库函数和功能来定义新的函数达到扩展该库的功能。
3.自下而上设计法是比较传统的方法。在自下而上设计中,先生成零件并将之插入装配体,然后根据设计要求配合零件。自下而上设计法的意义在于将复杂的大问题分解为相对简单的小问题,找出每个问题的关键、重点所在,然后用精确的思维定性、定量地去描述问题。其核心本质是"分解"。而书中介绍的是自底向上的实现,其方法是先实现低层次的工具使它更易于调试程序的每个模块,这通常比一次性调试所有的程序更容易。具体来说,对于大型程序开发的一个很有用的策略是考虑可用于当前问题的通用工具,如果你构建了这些工具,就能更容易的解决当前问题以及其他包含类似操作的问题。当你写一个程序时,最好先构建这些工具,这样可以随时测试你的程序。
4.构造接口的挑战在于如何设计一个好的接口,而不是编写一个接口。一个良好的自定义接口必须具备同一性、简单性、充分性、通用性和稳定性。有时这些标准之间可能会产生冲突,你必须学会如何在接口设计中达到适当的平衡,其中:
同一性是指一个接口应该定义一个某一明确的主题一直的抽象。一个良好的接口的最重要的特征是他表现的同一的和一致的抽象。如sqrt函数不应出现在标准的I/O库中。
简单性是指基本的实现本身很复杂,接口必须对客户隐藏这个复杂性。对使用接口的客户来说,获取太多的信息和获取信息不够一样糟糕,接口的真正价值不在于它揭示了某些信息,而在于它隐藏了某些信息(具体的实现)。
充分性是指接口必须为客户提供足够的功能来满足他们的要求,简单性是一个要求,但不能简单到毫无价值。在接口设计中,学会在简单性和完备性之间达到合理的平衡是程序设计最基本的挑战之一。
通用性是指一个良好的接口应该足够灵活以满足许多不同客户的需求。只针对某一客户需求需求的接口对其他客户可能是毫无用处的。
稳定性是指不管接口的实现方法如何改变,在一个接口中定义的函数必须保持完全相同的结构和作用。修改接口经常会产生全局性的巨变,导致使用该接口的每一个程序都要修改,这往往是客户难以接受的,因此在接口必须变化时,需要客户的参与。如果你发现需要在接口的生命周期中对接口做修改,最好的办法是通过扩展(使已有的程序不需要修改就可以继续运行的修改接口的方式)对他进行修改。
5.大多数情况下,除了文件类型外(.h和.c),接口和它的实现的文件名是相同的。
6.接口文件的语法结构,即模版文件
#ifndef _name_h
#define _name_h
任何所需的#include行
接口项
#endif
其中:name是库的名字
#include行部分仅当接口本身需要其他库时才使用
接口项表示库输出的函数原型、常量和类型
在整个接口中,应该出现注释,该接口为客户提供使用这个库时所需的信息。
每一个实现都需要包含他自己的接口,使编译器能针对真正的定义检查原型。如在name.c中的#include行里必须包括#include “name.h”。
每个接口必须包含编译器理解接口本身所需要的任何头文件,然而接口中不包括仅在实现中用到的头文件,这些头文件应该包含在该接口的实现.C文件中。
.c和.h中的注释对应的是不同的读者,实现的注释是为另一个实现者而写的,目的是方便另一个实现者按某种方式修改实现,因此编写者必须解释这个实现是如何工作的,并提供所有以后维护时应该知道的任何细节。接口中的注释是为客户而写的,目的是告诉客户该接口的功能以及如何使用该接口的输出函数。
八、伪随机数是指由计算机内部的算法产生的“随机”数(强调它并不是真正的随机活动)。当将rand函数的结果转换为更有限的整数范围时,不要尝试用取余运算(因为其结果对奇偶数的随机分布是不能保证的),它返回的结果的位置在数组上具有随机特性。
(1)每次Rand函数调用都回产生一个不同的值,如果要保存一个特定值,必须把函数的结果保存在一个变量中。
(2)int RandomInteger(int low,int high)的实现:
1. 规范化:将rand函数的整数结果转换为0<=d<1之间的浮点数。d=(double)rand()/((double)RAND_MAX+1)
2. 换算:将d乘以所希望的范围的大小,使它可以包含正确的整形数个数。d*(high-low+1)
3. 截断:丢弃小数部分,将此数截断为整形数。(int)d*(high-low+1)
4. 转化:将此整形数转换,使之以所期望的下界开始。Return (low+k)
随机浮点数和概率的实现也类似于以上算法。
(3)rand函数每次调用产生的随机数序列都是相同的,这是因为ANSI的库实现将初
始种子(随机数生成是由一个初始的数即种子经过计算而得出的)设为一个常量,这样做的目的是为了方便调试,因为如果第二次运行的结果和第一次不相同的话,那么在第一次中出现的错误可能在第二次不出现,这样很难检测出引起第一次产生错误的条件。而这种重复的行为在程序设计中又是不允许的,为了保证新的种子值在每次程序运行时都变化,标准的做法是用内部系统时钟值作为初始种子,因为时间一直在变。但应注意在调试时最好不要调用Randomize函数,当程序运行似乎正常时,可以在主程序中插入对Randomize函数的调用。