您的位置:首页 > 其它

《程序设计实践》读书笔记第一至二章

2008-08-20 17:20 239 查看

第1章 风格

1.1 名字

1) 全局变量使用具有说明性的名字,局部变量用短名字

2) 函数采用动作性的名字

1.2 表达式和语句

1) 使用表达式的自然形式(应该写得能够大声念出来)

2) 用加括号的方式排除二义性

3) 用缩行显示程序的结构

4) 分解复杂的表达式

5) 要清晰

6) 当心副作用。像++ 这一类运算符具有副作用,它们除了返回一个值外,还将隐含地改变变量的值

1.3 一致性和习惯用法

1) 使用一致的缩排和加括号风格

2) 为了一致性,使用习惯用法

3) 用else-if表达多路选择

1.4 函数宏

避免函数宏,某些时候会使命令执行多次,产生预料之外的结果

1.5 神秘的数

1) 给神秘的数起个名字。作为一个指导原则,除了0和1之外,程序里出现的任何数大概都可以算是神秘的数,它们应该有自己的名字。在程序源代码里,一个具有原本形式的数对其本身的重要性或作用没提供任何指示性信息,它们也导致程序难以理解和修改。

2) 把数定义为常数,不要定义为宏。const int SUCCESS = 1 NOT #define SUCCESS 1,C中使用enum来定义。

3) 使用字符形式的常量,不要用整数。

4) 利用语言去计算对象的大小。不要对任何数据类型使用显式写出来的大小。

1.6 注释

1) 不要大谈明显的东西。注释不要去说明明白白的事,比如i++能够将i值加1等等。

2) 给函数和全局数据加注释。注释当然可以有价值。对于函数、全局变量、常数定义、结构和类的域等,以及任何其他加上简短说明就能够帮助理解的内容,我们都应该为之提供注释。

3) 不要注释差的代码,重写它。应该注释所有不寻常的或者可能迷惑人的内容。但是如果注释的长度超过了代码本身,可能就说明这个代码应该修改了。

4) 不要与代码矛盾。许多注释在写的时候与代码是一致的。但是后来由于修正错误,程序改变了,可是注释常常还保持着原来的样子,从而导致注释与代码的脱节。

5) 澄清情况,不要添乱。注释应该在困难的地方尽量帮助读者,而不是给他们设置障碍。

第2章 算法与数据结构

2.1 检索

超过某个界限后(这个界限因实现不同可能有差别)二分检索一定会比线性检索更快

2.2 排序

某一种快速排序:

/* quicksort: sort v[0]..v[n-1] into increasing order */

void quicksort(int v[], int n)

{

int i, last;

if (n <= 1) /* nothing to do */

return;

swap(v, 0, rand() % n); /* move pivot elem to v[0] */

last = 0;

for (i = 1; i < n; i++) /* partition */

if (v[i] < v[0])

swap(v, ++last, i);

swap(v, 0, last); /* restore pivot */

quicksort(v, last); /* recursively sort */

quicksort(v+last+1, n-last-1); /* each part */

}

/* swap: interchange v[i] and v[j] */

void swap(int v[], int i, int j)

{

int temp;

temp = v[i];

v[i] = v[j];

v[j] = temp;

}

排序算法花费的时间正比于nlogn

2.3 库

标准C++库里有一个名字为sort的类属算法,它保证O(nlogn)的执行性质。使用它的代码非常简单,因为这个函数不需要强制转换,也不需要知道元素的大小。对于已知排序关系的类型,它甚至也不要求显式的比较函数。

2.4 一个Java 快速排序

2.5 大O记法

记法

名字

例子

O(1)

常数

下标数组访问

O(logn)

对数

二分检索

O(n)

线性

字符串比较

O(nlogn)

nlogn

快速排序

O(n2)

平方

简单排序算法

O(n3)

立方

矩阵乘法

O(2n)

指数

集合划分

2.6 可增长数组

typedef struct Nameval Nameval;

struct Nameval {

char *name;

int value;

};

struct Nvtab {

int nval; /* current number of values */

int max; /* allocated number of values */

Nameval *nameval; /* array of name-value pairs */

};

enum { NVINIT = 1, NVGROW = 2 };

/* addname: add new name and value to nvtab */

int addname(Nameval newname)

{

Nameval *nvp;

if (nvtab.nameval == NULL) { /* first time */

nvtab.nameval = (Nameval *) malloc(NVINIT * sizeof(Nameval));

if (nvtab.nameval == NULL)

return -1;

nvtab.max = NVINIT;

nvtab.nval = 0;

} else if (nvtab.nval >= nvtab.max) { /* grow */

nvp = (Nameval *) realloc(nvtab.nameval,

(NVGROW*nvtab.max) * sizeof(Nameval));

if (nvp == NULL)

return -1;

nvtab.max *= NVGROW;

nvtab.nameval = nvp;

}

nvtab.nameval[nvtab.nval] = newname;

return nvtab.nval++;

}

/* delname: remove first matching nameval from nvtab */

int delname(char *name)

{

int i;

for (i = 0; i < nvtab.nval; i++)

if (strcmp(nvtab.nameval[i].name, name) == 0) {

memmove(nvtab.nameval+i, nvtab.nameval+i+1,

(nvtab.nval-(i+1)) * sizeof(Nameval));

nvtab.nval--;

return 1;

}

}

在ANSI C的标准库里定义了两个相关的函数:memcpy的速度快,但是如果源位置和目标位置重叠,它有可能覆盖掉存储区中的某些部分;memmove函数的速度可能慢些,但总能保证复制的正确完成。

2.7 表

我们无法在编译时初始化一个非空的表,这点也与数组不同。表应该完全是动态构造起来的。

二分检索完全不能适用于表。

/* apply: execute fn for each element of listp */

void apply(Nameval *listp, void (*fn)(Nameval*, void*), void *arg)

{

for ( ; listp != NULL; listp = listp->next)

(*fn)(listp, arg); /* call the function */

}

要使用apply,例如打印一个表的元素,我们可以写一个简单的函数,其参数包括一个格式描述串:

/* printnv: print name and value using format in arg */

void printnv(Nameval *p, void *arg)

{

char *fmt;

fmt = (char *) arg;

printf(fmt, p->name, p->value);

}

它的调用形式是:

apply(nvlist, printnv, “%s: %x"n”);

要销毁一个表就必须特别小心:

/* freeall: free all elements of listp */

void freeall(Nameval *listp)

{

Nameval *next;

for ( ; listp != NULL; listp = next) {

next = listp->next;

/* assumes name is freed elsewhere */

free(listp);

}

}

2.8 树

2.9 散列表

散列函数hash应该计算出什么东西。这个函数必须是确定性的,应该能算得很快,应该把数据均匀地散布到数组里。对于字符串,最常见的散列算法之一就是:逐个把字节加到已经构造的部分散列值的一个倍数上。乘法能把新字节在已有的值中散开来。这样,最后结果将是所有输入字节的一种彻底混合。根据经验,在对ASCII串的散列函数中,选择31和37作为乘数是很好的。

2.10 小结

选择算法有几个步骤。首先,应参考所有可能的算法和数据结构,考虑程序将要处理的数据大概有多少。如果被处理数据的量不大,那么就选择最简单的技术。如果数据可能增长,请删掉那些不能对付大数据集合的东西。然后,如果有库或者语言本身的特征可以使用,就应该使用。如果没有,那么就写或者借用一个短的、简单的和容易理解的实现。如果实际测试说明它太慢,那么就需要改用某种更高级的技术。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: