最近在南邮华为俱乐部做了一次小小的交流,讲的内容是“浅谈ACM盲区”,这里我把主要内容整理出来,如果有不正确的地方欢迎指正。
首先我们来谈谈ACM练习的必要性,正如大家所知,ACM可以:
1.提高编程能力(递归、指针、函数、结构……)
2.学习算法(分治、动态规划、回溯……)
3.锻炼思维
……
但是,不得不对ACMer提醒的是,ACM作为一个竞赛,其具有相当的挑战性,亦具有局限性。如若想全面发展,以下的几个ACM盲区是我们所不能忽视的。
(一)界面友好
即使是在Linux中的开发,也是需要界面友好的,更不用提Windows的界面开发(MFC、Java GUI等)。而在ACM中,一般很少有人去考虑界面的设计以及人机交互。在这里,我做一个简单的提示,可以利用main()函数的参数扩展程序功能。
例如:
void main (int arg0, char** arg1)
{
if (arg0 == 2 && strcmp (arg1[1], "-help") == 0)
{
showHelpInformation();
}
//……
}
(二)编程规范
变量和函数的命名规范,一定要用有意义的字符,而且尽量用英文。不能用无意义的字符,或者用拼音。
例如:
double getVariance(int *array,int length);
void encrypt(char *plain,char *key,char cipher);
double stdIcValue,minIcValue;
这里我再举三个例子,来说明变量命名的规范性。例子所实现的功能是对一个三位数的百位、十位、个位进行分解。
int x;
int v=x%10;
int vv=x/10%10;
int vvv=x/100;
printf("%d %d %d ",v,vv,vvv);
int x;
int gw=x%10;
int sw=x/10%10;
int bw=x/100;
printf("%d %d %d ",gw,sw,bw);
int x;
int single=x%10;
int decade=x/10%10;
int hundred=x/100;
printf("%d %d %d ", single,decade,hundred);
显然,第三种写法更符合规范,也让人一目了然。
在这里,我在讲一下变量和函数的命名格式,目前主流的写法是采用驼峰式命名。
//变量:开头字母小写
stdIcValue,textField
//函数:开头字母小写
double getVariance(int *array,int length)
View findViewById(int id)
void setOnClickListener(OnClickListener l)
//类、结构: 开头字母大写
SQLiteOpenHelper LocationListener
//常量: 全大写
#define INFTY 99999
const int INFTY=99999;
final int INFTY=99999;
还有一点非常重要的规范是关于缩进,从逻辑上来讲,我们把相互对应的代码块进行相同的缩进。下面是一个典型的错误范例,其功能是判断一个考分是否及格。
if(score>=0 && score<=100)
if(score>=60) printf("pass\n");
else printf("failed\n")
很显然,下面的else应该与第二个if配对,在这里缩进,虽然代码是正确无误的,但是不符合规范。正确的写法应该如下所示:
if(score>=0 && score<=100)
{
if(score>=60) printf("pass\n");
else printf("failed\n");
}
再下面我讲讲关于注释,一般地,我们尽量用英文来保证兼容性。例如下面的递归的例子:
void solve(int *answer,int step,int currentTotal,int *change,int length,int money)
{
//answer is a stack array, step is the level of recursion
//currentTotal is the current change numbers
//change is a const array ([11,17,5,1]), length is the length of const array (4)
//money is a const of total money (20)
int cnt,i;
if(step>length) return;
if(currentTotal>currentMin) return;
……
}
同时,我们可以使用/* */来进行小规模或大规模注释,例如在下面的例子中,我们仅仅希望去除函数中最后一个参数的声明:
void recursion(int *answer,int step/*,int *pcnt*/)
{
// ……
recursion(answer,step+1/*,int *pcnt*/);
// (*pcnt)++;
}
void main()
{
int *answer=(int*)malloc(20*sizeof(int))/*,cnt=0*/;
recursion(answer,0/*,&cnt*/);
// printf("cnt=%d\n",cnt);
}
最后,关于编程规范,我讲讲程序的可移植性、函数封装与模块耦合,一般函数的封装要保证其逻辑性,并将使用者的权限降至最低。
举个简单的例子吧。
void outputArray(int *array,int length)
{
int i;
for(i=0;i<length;i++)
printf("%d ",array[i]);
printf("\n");
}
void main()
{
int array[]={1,2,3,4,5};
int length=sizeof(array)/sizeof(int);
outputArray(array,length);
}
这是一个正确的例子,outputArray()函数实现了对数组的输出,其符合逻辑性。而有些人在实现其输出时,将回车符的输出printf(“\n”)写在main()函数中。虽然这样同样能得到正确的结果,但是输出功能应该属于函数outputArray(),放在main()函数中实为不妥,不符合逻辑性。
(三)非实用性方法
ACM中一些“独特”的方法,例如打表法、O(n)排序法等,在今后的工作岗位中,几乎不可能用到。我的建议是,ACM中用用就好,别太当回事即可。
举个打表法的例子:
NOJ 1025 请在从1到某个整数范围中打印出所有的完数来,所谓“完数”是指一个数恰好等于它的所有不同因子之和。其中输入的数字n(1<n<10000)
样例输入
100
5000
样例输出
100: 6 28
5000: 6 28 496
如果不用打表法,很难在规定时间内得出结果。但是,如果打表,则相当简单,下面我给出核心代码。
if(n>=6) printf(" 6");
if(n>=28) printf(" 28");
if(n>=496) printf(" 496");
if(n>=8128) printf(" 8128");
printf("\n");
(四)实用性编程
关于实用性编程,我的建议是,在熟练玩转ACM的基础上,至少掌握一个。常见的实用性编程包括但不局限于以下:
Linux下的进程、通信、数据库、GNOME(或Qt)编程
Android 开发
C++ MFC
LAMP Web开发体系(Linux、Apache、MySQL、PHP)
(五)面向对象编程
在ACM中,很少接触到面向对象编程。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。对面向对象的介绍,我会在下一个博客中进行简介。同时会举出一个生动的例子,供大家参考。