1. 编程是一项综合技能
1.1 编程和语言
之前的系列中,我们重点关注的是如何学会一门计算机语言。而在这个系列中,我们会重点关注如何编程。编程是一项综合技能,它主要包括了以下几个方面:
- 计算机语言的掌握
- 编程工具的使用
- 程序设计思想
- 调试技巧
- 代码分析能力
很显然,仅仅是对语言的掌握还远远不够。很多人学习编程都扎进了语言学习的泥潭中难以自拔。计算机语言无疑是编程中最枯燥的一部分,如果不把它与其他的技能联系起来学习很容易让人失去兴趣。
我们这个天花板编程手把手计划就是要带着大家从这几个方面入手,以做练习的方式多角度地学习编程。也许作为初学者的你只掌握了最基础的计算机语言,但你会发现用最简单的语法也能写出相对复杂的功能。通过成就感引领你继续探索才是学习编程最好的方法。
1.2 IDE之争
在微信群里,总有人问我能不能不用VS,能不能用某某IDE。在这个系列中,我强行规定了大家必须使用VS,而且是VS2010以上的版本。原因主要有以下两点:
第一,我们的练习过程中,会涉及到一些代码调试的技巧,不同IDE的具体操作方法是不同的,如果不统一就很难通过文字的方式进行手把手的教授。
第二,在Windows平台上,VS是目前业内使用最广泛的IDE,从某种意义上讲,VS的使用是一个必备的技能点。如果你不会,那么找工作时可能会减分。
1.3 手把手方式
由于没办法拿出整块的时间做辅导,因此我的线下辅导主要是以微信群的方式进行。所有报名的同学目前都已经在群里,在完成作业的过程中有任何问题可以随时在微信群中沟通,我会尽可能在第一时间给予回答。
对于一些调试技巧和工具的使用问题,我也会在群里做一对一的辅导。
2. 报名作业
今天先说一下报名作业。麻雀虽小,五脏俱全,这个作业看似容易,做好可不容易。题目是编程实现如下功能:依次打印出1~100,遇到素数折行。效果如下:
我们先简单分析一下。
2.1 功能拆分
遇到一个题目,我们首先要了解它要实现什么功能,之后再把这个目标功能分解成若干个子功能。如果这些子功能都是我们实现过的,那这个问题就解决了。
这道题可以分为两个子功能:
- 打印从1到100这100个数字
- 判断每个数字是否是素数
2.2 寻找解决方案
有了这两个子功能之后,我们需要找到对应的解决方案。
打印1~100很容易,每个同学都能想到下面这段代码:
int i;
for (i = 1; i <= 100; i++)
{
printf("%d ", i);
}
接下来,判断素数成为了这道题的一个小小的难点。
3. 判断素数的方法
3.1 什么是素数
素数又称质数,是指除了1和它本身以外,不能被任何整数整除的数。比如5就是素数,因为它不能被2~4之间的任何一个整数整除。
3.2 思路一 : 通过定义实现
判断一个整数n是否是素数,只需把n被 2 ~ n-1 之间的每一个整数去除,如果都不能被整除,那么n就是一个素数。 代码如下:
int i;
for (i = 2; i < n; i++)
{
if (n % i == 0)
{
// n不是素数
break;
}
}
if (i >= n) // 完成循环
{
// n是素数
}
else // 循环被中断
{
// n不是素数
}
这个方法是我目前最推荐的,因为对于初学者通过把自然语言描述的算法直接转换成计算机语言去解决问题的能力非常重要。掌握了这个能力,完成功能性代码就再也难不倒你了。
3.3 思路二 : 算法优化
这是一个进阶的解决方案。在我们解决问题时,需要在完成了基本功能之后思考一下是否有优化的可能。程序优化主要有两个点:
- 代码优化
按照“高内聚,低耦合”的思想,修改自己的代码,使它拥有更高的可复用性和可读性。这方面的优化是为了降低今后的维护成本。
- 算法优化
通过算法的优化提高程序的执行效率和空间开销。
对于这个具体的问题,我们可以通过数学方法优化算法。
n 并不用被 2 ~ n-1 之间的每一个整数去除,只需被 2 ~ 根号n 之间的每一个整数去除就可以了。如果n不能被 2 ~ 根号n 间任一整数整除,n必定是素数。
例如 : 判别17是是否为素数,只需使17被2~4之间的每一个整数去除,由于都不能整除,所以17是素数。
原因 : 因为如果n能被 2 ~ n-1 之间任一整数整除,它的两个因子必定有一个小于或等于根号n,另一个大于或等于根号n。
代码如下:
// 求平方根,注意sqrt()的参数为 double 类型,这里要强制转换m的类型
int i;
k = (int)sqrt((double)n);
for (i = 2; i <= k; i++)
{
if (m % i == 0)
{
break;
}
}
if (i >= n) // 完成循环
{
// n是素数
}
else // 循环被中断
{
// n不是素数
}
到这里,有人会说我怎么能够想到这么一个优化算法呢。你可以用万能的互联网啊。
4. 寻找资料也是重要技能
在入门阶段,我们会用一些非常基础的题目做练习。比如简单排序,各种基础数据结构什么的。但当你真正进入软件开发行业时,你首先要学会的思维方式就是不要重复发明轮子。每当你遇到一个具体的问题,你首先要考虑的是有没有现有的东西可以用,比如各种开源项目代码、各种经典的库Boost、STL、MFC等等。对于这些现有的“轮子”,使用它们是你最好的选择。
因此,从现在起,你做的每一个小项目都要在自己思考解决方案的同时试着在网上找找是否有可以利用的现成资源。对于这些知识的积累才是更有价值的项目经验。
另外,在编程时遇到的很多工具报错的问题都可以在搜索引擎中找到解决方法。要学会利用这些资源。举个简单的例子,很多人在使用高版本的VS时都会遇到这样的报错信息。
只要你把这串报错信息粘贴到搜索引擎中就能找到解决方法,在代码最前面添加这句话即可:
#define _CRT_SECURE_NO_WARNINGS
如果有好奇心查看相关资料,你会学到更多关于VS代码安全机制方面的知识。千万不要给自己切断了这么重要的一个学习途径。
5. 功能整合
无论是自己实现,还是利用网络资源,我们现在都已经找到了全部子功能的解决方案。这时候你的心里应该有底了,最后只剩下把这些子功能整合成我们最终需要的完整程序了。
在这个阶段,我们最需要考虑的就是代码的复用性。说简单点就是让自己后续修改任何一块子功能的时候都尽可能小的改动代码。一般我们有两种方法。
- 方法一:代码逻辑分隔法
- 方法二:函数划分法
5.1 代码逻辑划分
- 第一步,我们按照子功能构建代码框架
int main()
{
int num;
// 循环得到100个数
for (num = 1; num <= 100; num++)
{
/**** 判断素数 ****/
/**** 判断结束 ****/
/**** 打 印 ****/
/**** 打印结束 ****/
}
}
这是一个最基本的代码框架,把每个子功能的代码位置留好。
- 第二步:填空
像填空一样把每部分的代码填在相应的位置上。这样可以最大程度的保持每部分代码的独立性。
int main()
{
int num, i;
// 循环得到100个数
for (num = 1; num <= 100; num++)
{
/**** 判断素数 ****/
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
break;
}
}
/**** 判断结束 ****/
/**** 打 印 ****/
if (i >= num && num != 1)
{
printf("%d\n", num);
}
else
{
printf("%d ", num);
}
/**** 打印结束 ****/
}
printf("\n");
}
- 第三歩:代码整理
细心的同学会发现,打印部分的if语句其实是在判断之前程序计算的结果,因此应该放在判断部分。但如果把这行划分在前面会破坏if语句的完整性,因此我们利用标记变量来让它们的耦合度更低。
int main()
{
int num, i;
int isPrimeNum;
// 循环得到100个数
for (num = 1; num <= 100; num++)
{
/**** 判断素数 ****/
if (num == 1)
{
isPrimeNum = 0;
}
else
{
isPrimeNum = 1;
}
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
isPrimeNum = 0;
break;
}
}
/**** 判断结束 ****/
/**** 打 印 ****/
if (isPrimeNum == 1)
{
printf("%d\n", num);
}
else
{
printf("%d ", num);
}
/**** 打印结束 ****/
}
printf("\n");
}
这段代码中,我们引入了一个标记变量isPrimeNum,它作为第一部分向第二部分传递判断结果的一个变量。正因为有了它,才让这两个部分在逻辑上完全分开了。虽然在判断1这个特殊情况时,我们多写了几行代码,但从逻辑上更加清晰了,代码的可读性也就更强了。
这样做的好处是什么呢?如果我们现在需要修改判断素数的算法,或者需要修改打印方式,都只需要修改一部分的代码即可。代码维护中有个永恒的真理就是:修改的代码越少,出错的机会就越少。
5.2 函数划分法
这个方法是在上面方法的基础上把重要的部分提取成一个独立的函数,从逻辑上说耦合性更小。
int IsPrimeNum1(int num)
{
int i;
if (num == 1)
{
return 0;
}
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
return 0;
}
}
return 1;
}
int IsPrimeNum2(int num)
{
int i, k;
if (num == 1)
{
return 0;
}
k = (int)sqrt((double)num);
for (i = 2; i <= k; i++)
{
if (num % i == 0)
{
return 0;
}
}
return 1;
}
int main()
{
int num, i;
int isPrimeNum;
for (num = 1; num <= 100; num++)
{
if (IsPrimeNum1(num) == 1)
//if (IsPrimeNum2(num) == 1)
{
printf("%d\n", num);
}
else
{
printf("%d ", num);
}
}
printf("\n");
}
两种判断素数的方法被写成两个独立的函数IsPrimeNum1()和IsPrimeNum2(),如果需要相互替换,可以随时在main函数中修改一下调用代码。修改一行代码就能替换一个完整的功能。是不是很神奇。
在实际的项目中,这两种方法要结合使用。究竟在什么时候选择什么样的方式,这就是属于你自己的编程风格和项目经验。
6. 完成情况
漫天星星55 | a627892820 这两位同学都用到了函数划分的方法,值得表扬。需要注意的是:
- 上传代码要使用代码框,否则不方便别人阅读
- 上传源码图片的方式也不可取
- 做练习时最好把源码文件认真归档,方便以后复习。不要在VS的默认目录中。(别问我怎么发现的)
- 代码风格要统一,该有的空格不要省略
- 变量命名要规范,a b c这样的变量名不要用
7. 课后作业
给出任意一个N*N的矩阵,将里面的数字按照从左上到右下有小到大排序,之后计算出新矩阵对角线上的数字总和(每个位置只参与一次计算)。例如:
给出左边这个矩阵,先把它转换成右边的矩阵,之后计算对角线上的数字之和:1 + 5 + 9 + 3 + 7 = 25
请在2017年4月21日23:00之前完成。完成方式请参考:天花板编程手把手计划
我是天花板,让我们一起在软件开发中自我迭代。
如有任何问题,欢迎与我联系。
网友评论
http://www.jianshu.com/p/10c5a473b485第1天
和_q_p_一个人
Barry,我又重写了一遍,你看这个可以不。