1、函数原型符合设计要求,函数定义符合认知规律,做到见名知义,最少词汇量包含最大的信息量。
2、合理运用空格使提高代码的可读性。从框架上来说有:
变量定义 初始化变量 处理 输出 返回值
3、采用伪码的方式简化问题,降低编程难度,如打印最长文本行的算法框架:
while(还有未处理的行) if(该行比已处理的最长行还要长) { 保存该行为最长行 保存该行的长度 } 打印最长的行
4、状态变量辅助字符串处理,如统计单词数中的
#define IN 1 #define OUT 0
初始化state = OUT;
5、注释:在函数定义处写上函数的功能,对变量范围有要求的注明范围;常量定义处写明常量用途;变量声明处写明变量用途;注释单行时,前后留有空格。注意不要画蛇添足了,一些显而易见的变量用途是不需要说明的,如最常用的循环变量i。
6、应该搞清楚字符常量与仅包含一个字符的字符串之间的区别:
'x' 与"x" 是不同的。前者是一个整数,其值是字母x 在机器字符集中对应的数值(内部表示值); 后者是一个包含一个字符(即字母x)以及一个结束符'\0' 的字符数组。
7、默认情况下,外部变量与静态变量将被初始化为零。未经显示初始化的自动变量的值为未定义值(即无效值)。
8、const 限定符指定变量的值不能被修改。对数组而言,const 限定符指定数组所有元素的值都不能被修改。
9、整数除法会截断结果中的小数部分。
10、如果某一年的年份能被4 整除但不能被100 整除,那么这一年就是闰年,此外,能被400 整除的年份也是闰年。
if((year % 4 == 0 && year % 100 !=0) || year % 400 == 0) printf("%d is a leap year.\n", year); else printf("%d is not a leap year.\n", year);
取模运算符% 不能应用于float 或double 类型。
在有负操作数的情况下,整数除法截取的方向以及取模运算结果的符号取决于具体机器的实现,这和处理上溢或下溢的情况是一样的。
11、逻辑! 运算符的作用是将非0 操作数转换为0,将操作数0 转换为1。
12、C语言没有指定char 类型的变量是无符号变量还是带符号变量,但是int 是 signed int。
13、
// atoi: convert s to integer int atoi(char s[]) { int i, n; n = 0; for(i = 0; s[i] >= '0' && s[i] <= '9'; ++i) n = 10*n + (s[i]-'0'); return n; }
14、
// lower: convert c to lower case; ASCII only int lower(int c) { if(c >= 'A' && c <= 'Z') return c + 'a' - 'A'; else return c; }
注意这个函数是专门为ASCII字符集设计的,因为A~Z 之间只有字母,但对EBCDIC字符集不成立,因此该函数作用于EBCDIC字符集就不仅限于转换字母的大小写了。
此外,还需要注意的一点是,为什么形参是int?考虑到lower 的一个可能的应用场景:char c = lower(getchar()); 如果getchar() 返回EOF(即-1),而lower 接收char,如果该系统的char 是无符号的话,就会出现转换问题,这也是为什么C标准库(ctype.h)中的tolower 函数签名是 int tolower(int c) 而不是char tolower(char c) 的原因。
15、某些函数(如isdigit)在结果为真时可能返回任意的非零值,在if、while、for 等语句的测试部分中,"真"就意味着"非零"。
16、隐式算术类型转换:一般地,如果二元运算符的两个操作数具有不同的类型,那么在进行运算之前先要把"较低"的类型提升为"较高"的类型,运算结果为较高的类型。
-1 < 1U,因为unsigned int 的1U 提升为signed long 类型
-1 > 1UL,因为-1L 将被提升为unsigned long 类型
此外,赋值时也要进行类型转换。
17、表达式++n 先将n 的值递增1,然后再使用变量n 的值,而表达式n++ 则是先使用变量n 的值,然后再将n 的值递增1。
20、
// squeeze: delete all c from s void squeeze(char s[], int c) { int i, j; for(i = j = 0; s[i] != '\0'; i++) if(s[i] != c) s[j++] = s[i]; s[j] = '\0'; }
21、区分& 与&&:x = 1,y = 2,则x & y == 0, x && y == 1。
22、返回x 中从右边数第p 位开始向右数n 位的字段(假定最右边的一位是第0 位,n 与p 都是合理的正值)。例如:getbits(x, 4, 3) 返回x 中第4、3、2 三位的值。
// getbits: get n bits from position p unsigned getbits(unsigned x, int p, int n) { return (x >> (p+1-n)) & ~(~0 << n); // x >> (p+1-n)将期望获得的字段移位到字的最右端,~(~0 << n)建立最右边n位全为1的屏蔽码 }
23、x *= y + 1 <-> x = x * (y+1)
这是因为在C语言中有:expr1 op= expr2 <-> (expr1) op (expr2)
24、统计整型参数的二进制表示中值为1 的个数
// bitcount: count 1 bits in x int bitcount(unsigned x) // 这里将x声明为无符号类型是为了保证将x右移时,无论该程序在什么机器上运行,左边空出的位都用0(而不是符号位)填补 { int b; for(b = 0; x != 0; x >> 1) if(x & 01) b++; return b; }
25、条件表达式结果的类型由转换规则决定。
例如:如果f 为float 类型,n 为int 类型,那么表达式 (n > 0) ? f : n 是float 类型,与n 是否为正值无关。
利用条件表达式可以编写出很简洁的代码:
for(i = 0; i < n; i++) printf("%6d%c", a[i], (i%10 == 9 || i == n-1) ? '\n' : ' ');
这段代码用来打印一个数组的n 个元素,每行打印10 个,每列用一个空格隔开,每行用一个换行符结束(包括最后一行)。
另一个很好的例子是:
printf("You have %d item%s.", n, n == 1 ? "" : "s");
26、优先级:一元运算符+、-、&、* 比相应的二元运算符+、-、&、* 的优先级高。
位运算符&、^、| 的优先级比==、!= 的低,这意味着位测试表达式如 if((x & MASK) == 0) 必须用圆括号括起来才能得到正确结果。
27、同大多数语言一样,C语言没有指定同一运算符中多个操作数的计算顺序(&&、||、?:、, 运算符除外)。例如:在形如x = f() + g(); 的语句中,f() 可以在g() 之前计算,也可以在g() 之后计算。因此,如果函数f 或g 改变了另一个函数使用的变量,那么x 的结果可能依赖于这两个函数的计算顺序。为了保证特定的计算顺序,可以把中间结果保存在临时变量中。
类似地,C语言也没有指定函数各参数的求值顺序。因此,语句printf("%d %d\n", ++n, power(2, n)); 在不同的编译器中可能会产生不同的结果,这取决于n 的自增运算在power 调用之前还是之后执行。解决办法是把该语句改写成:++n; printf("%d %d\n", n, power(2, n));
函数调用、嵌套赋值语句、自增与自减运算符都有可能产生"副作用"——在对表达式求值的同时,修改了某些变量的值。在有副作用影响的表达式中,其执行结果同表达式中的变量被修改的顺序之间存在着微妙的依赖关系,语句a[i] = i++; 就是一个典型的令人不愉快的情况,问题是:数组下标i 是引用旧值还是引用新值?对这种情况编译器的解释可能不同,并因此产生不同的结果。C语言标准对大多数这类问题有意未作具体规定。表达式何时会产生这种副作用(对变量赋值),将由编译器决定,因为最佳的求值顺序同机器结构有很大关系(ANSI C 标准明确规定了所有对参数的副作用都必须在函数调用之前生效,但是这对前面介绍的printf 函数调用没有什么帮助)。
在任何一种编程语言中,如果代码的执行结果与求值顺序有关,则都是不好的程序设计风格。很自然,有必要了解哪些问题需要避免,但是,如果不知道这些问题在各种机器上是如何解决的,就最好不要尝试运用某种特殊的实现方式。
28、
// binsearch: find x in v[0] <= v[1] <= ... <= v[n-1] int binsearch(int x, int v[], int n) { int low, high, mid; low = 0; high = n - 1; while(low <= high) { mid = (low+high) / 2; if(x < v[mid]) high = mid - 1; else if(x > v[mid]) low = mid + 1; else // found match return mid; } return -1; // no match }
29、用switch 语句统计数字、空白符以及其它所有字符出现的次数。
#include<stdio.h> int main(void) { int c, i, nwhite, nother, ndigit[10]; nwhite = nother = 0; for(i = 0; i < 10; i++) ndigit[i] = 0; while((c = getchar()) != EOF) { switch(c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ndigit[c-'0']++; break; case ' ': case '\n': case '\t': nwhite++; break; default: nother++; break; } } printf("digits = "); for(i = 0; i < 10; i++) printf(" %d", ndigit[i]); printf(", white space = %d, other = %d\n", nwhite, nother); return 0; }
30、前面已经写了一个atoi 了,下面我们写一个更通用的版本:如有空白符,则跳过;如有符号,则处理符号;取整数部分,并执行转换。
#include<ctype.h> // atoi: convert s to integer; version 2 int atoi(char s[]) { int i, n, sign; for(i = 0; isspace(s[i]); i++) // skip white space ; sign = (s[i] == '-') ? -1 : 1; if(s[i] == '+' || s[i] == '-') // skip sign i++; for(n = 0; isdigit(s[i]); i++) n = 10*n + (s[i]-'0'); return sign * n; }
31、原始希尔排序:
// shellsort: sort v[0] ... v[n-1] into increasing order void shellsort(int v[], int n) { int gap, i, j, temp; for(gap = n/2; gap > 0; gap /= 2) { for(i = gap; i < n; i++) { for(j = i - gap; j >= 0 && v[j] > v[j+gap]; j -= gap) { temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp; } } } }
32、合理运用逗号运算符的实例:
for(i = 0, j = strlen(s) - 1; i < j; i++, j--) c= s[i], s[i] = s[j], s[j] = c;
33、
// itoa: convert n to characters in s void itoa(int n, char s[]) { int i, sign; if((sign = n) < 0) // record sign n = -n; // make n positive i = 0; do // generate digits in reverse order { s[i++] = n % 10 + '0'; // get next digit }while((n / 10) > 0); // delete it if(sign < 0) s[i++] = '-'; s[i] = '\0'; reverse(s); }
34、
// trim: remove trailing blanks, tabs, newlines int trim(char s[]) { int n; for(n = strlen(s) - 1; n >= 0; n--) if(s[n] != ' ' && s[n] != '\t' && s[n] != '\n') break; s[n+1] = '\0'; return n; }
35、合理应用goto 语句的场合:跳出深度嵌套循环(两层或多层)
例子:使用goto 语句判定两数组a 与 b 是否具有相同元素
for(i = 0; i < n; i++) for(j = 0; j < m; j++) if(a[i] == b[j]) goto found; // didn't find any common element found: // got one: a[i] == b[j]
不使用goto 的话,当然也可以,不过略繁
found = 0; for(i = 0; i < n && !found; i++) for(j = 0; j < m && !found; j++) if(a[i] == b[j]) found = 1; if(found) // got one: a[i-1] == b[j-1] else // didn't find any common element
未完待续...
(Author_XPJIANG)