480 likes | 641 Views
程序设计实践. 程序风格. 参考教材 《 程序设计实践 》 第 1 章. 主要内容. 标识符命名 表达式和语句 一致性和习惯 函数宏 神秘的数 注释 编程规范介绍. 风格的作用. 具有良好风格的代码更容易阅读和理解,几乎可以保证其中的错误更少。 程序不仅需要给计算机读,也要给人读。 好的风格使代码更容易读,无论是对程序员本人,还是其他读程序的人。 风格是一种习惯,通过规范形式给出。. P 1 例:. if ( ( country == SING ) || ( country == BRNI ) ||
E N D
程序设计实践 程序风格 参考教材《程序设计实践》第1章
主要内容 • 标识符命名 • 表达式和语句 • 一致性和习惯 • 函数宏 • 神秘的数 • 注释 • 编程规范介绍
风格的作用 • 具有良好风格的代码更容易阅读和理解,几乎可以保证其中的错误更少。 • 程序不仅需要给计算机读,也要给人读。 • 好的风格使代码更容易读,无论是对程序员本人,还是其他读程序的人。 • 风格是一种习惯,通过规范形式给出。
P1例: if ( ( country == SING ) || ( country == BRNI ) || ( country == POL ) || ( country == ITALY ) { /* * if the country is Singapore, Brunei or Poland * then the current time is the answer time * rather than off hook time. * Reset answer time and set day of week. */ …… }
P2例: #define ONE 1 #define TEN 10 #define TWENTY 20 #define INPUT_MODEL 1 #define INPUT_BUFSIZE 10 #define OUTPUT_BUFSIZE 20
程序设计语言的风格不相同: • 编程风格因人而异。 • 每种程序设计语言都有自己特殊的风格。 • 有一些规则是所有程序设计语言都应遵守的。
1.1 名字 • 名字,又可以称为标识符,它可以是变量名、函数名、标号、宏名、常量名,甚至文件名。 • 对名字的一般要求: • 简洁、容易记忆 • 符合大多数人的习惯 • 最好能够拼读 • 最好具有一定的含义
名字的规则: • 全局变量使用具有说明性的名字 • 足够长,足够的说明性,加注释 • 局部变量用短名字 • 保持一致; • 函数采用动作性名字(含有动词); • 更准确(携带有助读程序的信息)
1.1名字(续1) • 全局(说明性) vs. 局部(短名字) eg.1 记录队列长度 int npending = 0; //current length of queue 反例 eg.2 局部计数器变量 int n vs. int npoints / numberofpoints • 通常,i、j作为循环变量,p、q作为指针,s、t表示字符串等,过长了反而有害
典型的命名法 • 匈牙利命名法--Simonyi • prefix + basename • basename使用大小写混排 • 例如: char chType; char *pszName; • 其他命名法参考: • http://www.cppblog.com/issayandfaye/archive/2010/03/11/109424.html
1.1名字(续2) • 改进: • struct UserQueue • { • int nitems, front, capacity; • int (*nusers)(); • } • queue.capacity++; • n = (*queue.nusers)(); • C中可以使用函数指针成员; • 需要进行合适的初始化; • 保持一致性 • 统一名字指示同类对象=> • eg.4 struct UserQueue { int noOfItemsInQ, frontOfTheQueue, queueCapacity; int (*noOfUsersInQueue)(); } len = queue.queueCapacity;
名字(续3) • 函数使用动作性名字---动词+名词 • 易于理解 eg.5 获取当前时间 now = date.getTime(); putchar('\n'); eg.6 判断字符是否8进制数 if (checkoctal(c)) ... vs. if (isoctal(c)) ..√
名字(续4) • 准确---符合表达的信息 eg.7 判断是否为8进制数组成字符 #define isoctal(c) ((c) >= '0' && (c) <= '8') vs. #define isoctal(c) ((c) >= '0' && (c) <= '7') eg.8 名字与实现一致 boolean inTable(Object *this, Object obj) { int j = this->getIndex(obj); return (j == nTable); }
1.2 表达式和语句 • 表达式具有运算功能,简单形式可以只有一个常量或一个变量,复杂时可能超过几行。 • 表达式的好风格关键是让读者明白操作符与操作数的关系,清晰是最重要的。 • 语句最关键的是缩行,体现出语句的层次关系。
表达式的规则 • 用缩行显示程序的结构 • 使用表达式的自然形式 • 用加括号的方式排除二义性 • 分解复杂的表达式 • 更清晰,写出最清晰的代码,而不是最巧妙的代码。 • 当心副作用
表达式和语句(续一) • 一致的缩行风格 • 进行必要缩行,同级代码起始于相同列 • 符合语句的一般形式要求
表达式和语句(续二) eg.1 初始化字符数组 for(n++;n<100;field[n++]='\0'); *i='\0';return('\n'); vs. for (n++; n < 100; n++) field[n] = '\0'; *i = '\0'; return '\n';
表达式和语句(续三) • 自然的表达式 eg.2 if (!(block_id < actblks) || !(block_id >= unblocks)) ... vs. if ((block_id >= actblks) || (block_id < unblocks)) ...
表达式和语句(续四) • 充分使用括号---消除二义性 • 控制运算的顺序 eg.3 赋值 vs. 逻辑运算 while ((c = getchar()) != EOF) ... eg.4 位运算 vs. 逻辑运算 if (x&MASK == BITS) // <=> if (x && (MASK ==BITS)) vs. if ((x & MASK) == BITS)
表达式和语句(续五) • 容易理解 eg.5 判断是否为闰年 leap_year = y % 4 == 0 && y % 100 != 0 || y % 400 == 0; vs. leap_year = ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0);
Oh my god! 表达式和语句(续六) • 分解复杂的表达式 --- 不要太长 eg.6 *x += (*xp=(2*k < (n-m)? c[k+1]: d[k--])); vs. if (2*k < (n-m)) *xp = c[k+1]; else *xp = d[k--]; *x += *xp;
Oh my god! 表达式和语句(续七) • 清晰---最巧妙未必最清晰 eg.7 获得移位数目 subkey = subkey >> (bitoff - ((bitoff >>3) << 3)) vs. subkey >>= bitoff & 0x7 显示内层表达式右移三位,然后又重新移回来。目的是后三位置0,而再用原值bitoff减去它,目的是取bitoff的最低的三位。 再由此值判断subkey的右移位数
Oh my god! 表达式和语句(续八) eg.8 反对滥用?: --- 源代码少 执行码少 child = (!LC &&!RC)? 0 : (!LC?RC:LC); vs. if (LC == 0 && RC == 0) child = 0; else if (LC == 0) child = RC; else child = LC; 运算符?: 适用于短的表达式, 不应该成为条件语句的一般替代品
Oh! NO! 表达式和语句(续九) • 注意副作用 • 考虑两方面:符合习惯 vs 不确定性 • 保证单语句中的副作用与计算顺序无关 eg.9 向str中连续写入两个空格 str[i++] = str[i++] = ' '; vs str[i++] = ' '; str[i++] = ' ';
表达式和语句(续十) eg.10 scanf的参数 scanf("%d %d", &yr, &profit[yr]); vs scanf("%d", &yr); scanf("%d", &profit[yr]);
练习1-4 改进下面各个程序片段 if ((c != 'y') && (c != 'Y’)) return; if ( !( c == 'y' || c == 'Y' ) ) return; length = ( length < BUFSIZE ) ? length : BUFSIZE; flag = flag ? 0:1; quote = ( *line == '"') ? 1 : 0; if ( val & 1 ) bit = 1; else bit = 0; if ( length >= BUFSIZE ) length =BUFSIZE; quote = ( *line == toascii(34)) ? 1 : 0; bit = ( val & 1 ) ? 1 : 0;
1.3 一致性和习惯用法 • 一致性带来的将是更好的程序。 • 如果相同的计算的每次出现总是采用相同的方式,任何变化就预示着是经过深思熟虑,要求读程序的人注意。 • 如果你工作在一个不是自己写的程序上,请注意保留程序原有的风格。当你需要做修改时,不要使用你自己的风格,即使你特别喜欢它。
一致性的一般要求: • 使用一致性的缩排和加括号风格 • 为一致性,使用习惯用法 • 用else-if表达多路选择
一些习惯用法:(1/4) • 给n个元素的数组赋初值 for( i = 0; i<100; i++ ) array[i] = 1.0; • 无穷循环 • 写法1: for ( ; ; ) …… • 写法2: whlie( 1 ) …... while (i <= n-1) array[i++] = 1.0; for(i = 0; i < n; ) array[i++] = 1.0
一些习惯用法:(2/4) • 赋值放在循环里 while ( ( c = getchar( ) ) != EOF ) putchar( c ); • 字符串分配空间及操作的习惯 p=malloc( strlen( buf ) + 1 ) ; strcpy( p, buf );
一些习惯用法:(3/4) • 用else-if表达多路选择 if ( condition1 ) statement1 else if ( condition2 ) statement2 …… else if ( condition ) statementn else default-statement
例:处理命令行参数 (差) • if (argc == 3) • if ((fin = fopen(argv[1], "r")) != NULL) • if ((fout = fopen(argv[2], "w")) != NULL) • { • while((c = getc(fin)) != EOF) • putc(c, fout); • fclose(fin); fclose(fout); • } • else • printf("Can't open output file \n"); • else • printf("Can't open input file \n"); • else • printf("Usage: cp inputfile outputfile \n");
if (argc != 3) • printf("Usage: cp inputfile outputfile \n"); • else if ((fin = fopen(argv[1], "r")) == NULL) • printf("Can't open input file \n"); • else if ((fout = fopen(argv[2], "w")) == NULL) • { • printf("Can't open output file \n"); • fclose(fin); • } • else • { • while((c = getc(fin)) != EOF) • putc(c, fout); • fclose(fin); • fclose(fout); • }
一些习惯用法:(4/4) • switch-case语句中break特别重要 switch ( c ) { case '-': sign = -1; /* fall through */ case '+': c = getchar( ); case '.': break; default: …… }
练习1-7 if ( istty( stdin ) ) ; else if (istty( stdout ) ); else if ( istty( stderr ) ); else return( 0 ); if ( reval != SUCCESS ) { return ( reval ); } /* All went well! */ return SUCCESS; for ( k=0; k++<5; x+=dx ) scanf( "%lf", &dfx ); if ( !istty( stdin ) || !istty( stdout ) || ( !istty( stderr ) ) return( 0 ); if ( reval != SUCCESS ) return ( reval ); else return SUCCESS; for ( k=0; k<5; k++, x+=dx ) scanf( "%lf", &dx );
找出下面程序段中的错误并按习惯修改循环 int count = 0; while (count < total) { count++; if (!strcmp(getName(count), nametable.userName)) { return (true); } }
1.4 函数宏 • 函数宏的主要问题是,它的副作用,如: #define isupper(c) ( (c) >= 'A' && (c) <='Z' ) …… while( isupper( c = getchar( ) ) ) 上述程序改写成: #define isupper(c) ( (c) >= 'A' && (c) <='Z' ) …… while(( c = getchar( )) != EOF && isupper( c ))
减少函数宏副作用的方法: • 给宏的体和参数都加上括号。如下写法: #define square( x ) ( x ) * ( x ) …... y = 1 / square( x ); ↓ y = 1 / ( x ) * ( x ); 给宏的体和参数都加上括号: # define square( x ) ( ( x ) * ( x ) )
解决宏函数的一般方法: • 给宏的体和参数都加上括号 • 避免使用宏函数,要用函数或inline函数,替换宏函数。 • 在目前的计算机处理能力下,函数宏的缺点远远超过它能带来的好处。
1.5 神密的数 • 神密的数是这样一些数字,读程序的人很难从数字本身得到它所表示的含义。 • 神密的数包含: • 各种常数 • 数组的大小 • 字符位置 • 系数 • ……。 • 解决神密数的好方法是给它们起名字。
P15例:(1/3) fac = lim / 20; /* set scale factor */ if ( fac < 1 ) fac = 1; /* generate histogram */ for ( i = 0, col = 0; i < 27; i++, j++ ) { col += 3; k = 21 - ( let[i] / fac ); star = ( let[i] == 0 ) ? ' ' : '*'; for ( j = k; j < 22; j++ ) draw( j, col, star ); } draw( 23, 2, ' ' ); /* label x axis */ for ( i = 'A'; i <= 'Z'; i++ ) printf( "%c ", i );
改写后的程序:(2/3) enum { MINROW = 1, /* top edge */ MINCOL = 1, /* left edge */ MAXROW = 24, /* bottom edge */ MAXCOL = 80, /* right edge (<=) */ LABELROW= 1, /* position of labels */ NLET = 26, /* size of alphabet */ HEIGHT = MAXROW - 4, /* height of bars */ WIDTH = ( MAXCOL - 1 ) / NLET /* width of bars */ };
改写后的程序:(3/3) fac = ( lim+HEIGHT-1) / HEIGHT; /* set scale factor */ if ( fac < 1 ) fac = 1; for (i=0; i<NLET; i++ ) { /* generate histogram */ if ( let[i] == 0 ) continue; for ( j=HEIGHT - let[i] / fac; j<HEIGHT; j++ ) draw( j+1+LABELROW, ( i+1 )*WIDTH, '*' ); } draw( MAXROW-1, MINCOL + 1); /* label x axis */ for ( i = 'A'; i <= 'Z'; i++ ) printf( "%c ", i );
解决神密数的一般方法 • 把数定义为常数,不要定义为宏 • 使用字符形式的常数,不要用对应的整数 • 利用语言去计算对象的大小,如sizeof
1.6 注释 • 注释是帮助程序读者的一种手段。 • 最好的注释是简洁地点明程序的突出特征,或者提供概括说明,帮助别人理解程序。 • 注释包括: • 序言注解 • 功能注解 • 语句注解
练习1-11 评论下面的注释: if ( n > MAX || n % 2 > 0 ) //test for even number //Write a message //Add to line counter for each line written void write_message( ) { // increment line counter line_number = line_number + 1; fprintf( fout, "%d %s\n%d %s\n%d %s\n", line_number, HEADER, line_number + 1, BODY, line_number + 2, TRAILER ); // increment line counter line_number = line_number + 2 ; }
注释的一般原则 • 不要大谈明显的东西。 • 给函数和全局数据加注释。 • 不要注释差的代码,重写它。 • 不要与代码矛盾。 • 澄清情况,不要添乱。
程序风格的关键: 好风格应该成为一种习惯。 如果你在开始写代码时就关心风格问题,如果你花时间去审视和改进它,你将会逐渐养成一种好的编程习惯。一旦这种习惯变成自动的东西,你的潜意识就会帮你照料许多细节问题,甚至你在工作压力下写出的代码也会更好。