1. 編程是一項(xiàng)綜合技能
1.1 編程和語言
之前的系列中,我們重點(diǎn)關(guān)注的是如何學(xué)會一門計(jì)算機(jī)語言。而在這個系列中,我們會重點(diǎn)關(guān)注如何編程。編程是一項(xiàng)綜合技能,它主要包括了以下幾個方面:
- 計(jì)算機(jī)語言的掌握
- 編程工具的使用
- 程序設(shè)計(jì)思想
- 調(diào)試技巧
- 代碼分析能力
很顯然,僅僅是對語言的掌握還遠(yuǎn)遠(yuǎn)不夠。很多人學(xué)習(xí)編程都扎進(jìn)了語言學(xué)習(xí)的泥潭中難以自拔。計(jì)算機(jī)語言無疑是編程中最枯燥的一部分,如果不把它與其他的技能聯(lián)系起來學(xué)習(xí)很容易讓人失去興趣。
我們這個天花板編程手把手計(jì)劃就是要帶著大家從這幾個方面入手,以做練習(xí)的方式多角度地學(xué)習(xí)編程。也許作為初學(xué)者的你只掌握了最基礎(chǔ)的計(jì)算機(jī)語言,但你會發(fā)現(xiàn)用最簡單的語法也能寫出相對復(fù)雜的功能。通過成就感引領(lǐng)你繼續(xù)探索才是學(xué)習(xí)編程最好的方法。
1.2 IDE之爭
在微信群里,總有人問我能不能不用VS,能不能用某某IDE。在這個系列中,我強(qiáng)行規(guī)定了大家必須使用VS,而且是VS2010以上的版本。原因主要有以下兩點(diǎn):
第一,我們的練習(xí)過程中,會涉及到一些代碼調(diào)試的技巧,不同IDE的具體操作方法是不同的,如果不統(tǒng)一就很難通過文字的方式進(jìn)行手把手的教授。
第二,在Windows平臺上,VS是目前業(yè)內(nèi)使用最廣泛的IDE,從某種意義上講,VS的使用是一個必備的技能點(diǎn)。如果你不會,那么找工作時可能會減分。
1.3 手把手方式
由于沒辦法拿出整塊的時間做輔導(dǎo),因此我的線下輔導(dǎo)主要是以微信群的方式進(jìn)行。所有報(bào)名的同學(xué)目前都已經(jīng)在群里,在完成作業(yè)的過程中有任何問題可以隨時在微信群中溝通,我會盡可能在第一時間給予回答。
對于一些調(diào)試技巧和工具的使用問題,我也會在群里做一對一的輔導(dǎo)。
2. 報(bào)名作業(yè)
今天先說一下報(bào)名作業(yè)。麻雀雖小,五臟俱全,這個作業(yè)看似容易,做好可不容易。題目是編程實(shí)現(xiàn)如下功能:依次打印出1~100,遇到素?cái)?shù)折行。效果如下:
我們先簡單分析一下。
2.1 功能拆分
遇到一個題目,我們首先要了解它要實(shí)現(xiàn)什么功能,之后再把這個目標(biāo)功能分解成若干個子功能。如果這些子功能都是我們實(shí)現(xiàn)過的,那這個問題就解決了。
這道題可以分為兩個子功能:
- 打印從1到100這100個數(shù)字
- 判斷每個數(shù)字是否是素?cái)?shù)
2.2 尋找解決方案
有了這兩個子功能之后,我們需要找到對應(yīng)的解決方案。
打印1~100很容易,每個同學(xué)都能想到下面這段代碼:
int i;
for (i = 1; i <= 100; i++)
{
printf("%d ", i);
}
接下來,判斷素?cái)?shù)成為了這道題的一個小小的難點(diǎn)。
3. 判斷素?cái)?shù)的方法
3.1 什么是素?cái)?shù)
素?cái)?shù)又稱質(zhì)數(shù),是指除了1和它本身以外,不能被任何整數(shù)整除的數(shù)。比如5就是素?cái)?shù),因?yàn)樗荒鼙?~4之間的任何一個整數(shù)整除。
3.2 思路一 : 通過定義實(shí)現(xiàn)
判斷一個整數(shù)n是否是素?cái)?shù),只需把n被 2 ~ n-1 之間的每一個整數(shù)去除,如果都不能被整除,那么n就是一個素?cái)?shù)。 代碼如下:
int i;
for (i = 2; i < n; i++)
{
if (n % i == 0)
{
// n不是素?cái)?shù)
break;
}
}
if (i >= n) // 完成循環(huán)
{
// n是素?cái)?shù)
}
else // 循環(huán)被中斷
{
// n不是素?cái)?shù)
}
這個方法是我目前最推薦的,因?yàn)閷τ诔鯇W(xué)者通過把自然語言描述的算法直接轉(zhuǎn)換成計(jì)算機(jī)語言去解決問題的能力非常重要。掌握了這個能力,完成功能性代碼就再也難不倒你了。
3.3 思路二 : 算法優(yōu)化
這是一個進(jìn)階的解決方案。在我們解決問題時,需要在完成了基本功能之后思考一下是否有優(yōu)化的可能。程序優(yōu)化主要有兩個點(diǎn):
- 代碼優(yōu)化
按照“高內(nèi)聚,低耦合”的思想,修改自己的代碼,使它擁有更高的可復(fù)用性和可讀性。這方面的優(yōu)化是為了降低今后的維護(hù)成本。
- 算法優(yōu)化
通過算法的優(yōu)化提高程序的執(zhí)行效率和空間開銷。
對于這個具體的問題,我們可以通過數(shù)學(xué)方法優(yōu)化算法。
n 并不用被 2 ~ n-1 之間的每一個整數(shù)去除,只需被 2 ~ 根號n 之間的每一個整數(shù)去除就可以了。如果n不能被 2 ~ 根號n 間任一整數(shù)整除,n必定是素?cái)?shù)。
例如 : 判別17是是否為素?cái)?shù),只需使17被2~4之間的每一個整數(shù)去除,由于都不能整除,所以17是素?cái)?shù)。
原因 : 因?yàn)槿绻鹡能被 2 ~ n-1 之間任一整數(shù)整除,它的兩個因子必定有一個小于或等于根號n,另一個大于或等于根號n。
代碼如下:
// 求平方根,注意sqrt()的參數(shù)為 double 類型,這里要強(qiáng)制轉(zhuǎn)換m的類型
int i;
k = (int)sqrt((double)n);
for (i = 2; i <= k; i++)
{
if (m % i == 0)
{
break;
}
}
if (i >= n) // 完成循環(huán)
{
// n是素?cái)?shù)
}
else // 循環(huán)被中斷
{
// n不是素?cái)?shù)
}
到這里,有人會說我怎么能夠想到這么一個優(yōu)化算法呢。你可以用萬能的互聯(lián)網(wǎng)啊。
4. 尋找資料也是重要技能
在入門階段,我們會用一些非常基礎(chǔ)的題目做練習(xí)。比如簡單排序,各種基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)什么的。但當(dāng)你真正進(jìn)入軟件開發(fā)行業(yè)時,你首先要學(xué)會的思維方式就是不要重復(fù)發(fā)明輪子。每當(dāng)你遇到一個具體的問題,你首先要考慮的是有沒有現(xiàn)有的東西可以用,比如各種開源項(xiàng)目代碼、各種經(jīng)典的庫Boost、STL、MFC等等。對于這些現(xiàn)有的“輪子”,使用它們是你最好的選擇。
因此,從現(xiàn)在起,你做的每一個小項(xiàng)目都要在自己思考解決方案的同時試著在網(wǎng)上找找是否有可以利用的現(xiàn)成資源。對于這些知識的積累才是更有價值的項(xiàng)目經(jīng)驗(yàn)。
另外,在編程時遇到的很多工具報(bào)錯的問題都可以在搜索引擎中找到解決方法。要學(xué)會利用這些資源。舉個簡單的例子,很多人在使用高版本的VS時都會遇到這樣的報(bào)錯信息。
只要你把這串報(bào)錯信息粘貼到搜索引擎中就能找到解決方法,在代碼最前面添加這句話即可:
#define _CRT_SECURE_NO_WARNINGS
如果有好奇心查看相關(guān)資料,你會學(xué)到更多關(guān)于VS代碼安全機(jī)制方面的知識。千萬不要給自己切斷了這么重要的一個學(xué)習(xí)途徑。
5. 功能整合
無論是自己實(shí)現(xiàn),還是利用網(wǎng)絡(luò)資源,我們現(xiàn)在都已經(jīng)找到了全部子功能的解決方案。這時候你的心里應(yīng)該有底了,最后只剩下把這些子功能整合成我們最終需要的完整程序了。
在這個階段,我們最需要考慮的就是代碼的復(fù)用性。說簡單點(diǎn)就是讓自己后續(xù)修改任何一塊子功能的時候都盡可能小的改動代碼。一般我們有兩種方法。
- 方法一:代碼邏輯分隔法
- 方法二:函數(shù)劃分法
5.1 代碼邏輯劃分
- 第一步,我們按照子功能構(gòu)建代碼框架
int main()
{
int num;
// 循環(huán)得到100個數(shù)
for (num = 1; num <= 100; num++)
{
/**** 判斷素?cái)?shù) ****/
/**** 判斷結(jié)束 ****/
/**** 打 印 ****/
/**** 打印結(jié)束 ****/
}
}
這是一個最基本的代碼框架,把每個子功能的代碼位置留好。
- 第二步:填空
像填空一樣把每部分的代碼填在相應(yīng)的位置上。這樣可以最大程度的保持每部分代碼的獨(dú)立性。
int main()
{
int num, i;
// 循環(huán)得到100個數(shù)
for (num = 1; num <= 100; num++)
{
/**** 判斷素?cái)?shù) ****/
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
break;
}
}
/**** 判斷結(jié)束 ****/
/**** 打 印 ****/
if (i >= num && num != 1)
{
printf("%d\n", num);
}
else
{
printf("%d ", num);
}
/**** 打印結(jié)束 ****/
}
printf("\n");
}
- 第三歩:代碼整理
細(xì)心的同學(xué)會發(fā)現(xiàn),打印部分的if語句其實(shí)是在判斷之前程序計(jì)算的結(jié)果,因此應(yīng)該放在判斷部分。但如果把這行劃分在前面會破壞if語句的完整性,因此我們利用標(biāo)記變量來讓它們的耦合度更低。
int main()
{
int num, i;
int isPrimeNum;
// 循環(huán)得到100個數(shù)
for (num = 1; num <= 100; num++)
{
/**** 判斷素?cái)?shù) ****/
if (num == 1)
{
isPrimeNum = 0;
}
else
{
isPrimeNum = 1;
}
for (i = 2; i < num; i++)
{
if (num % i == 0)
{
isPrimeNum = 0;
break;
}
}
/**** 判斷結(jié)束 ****/
/**** 打 印 ****/
if (isPrimeNum == 1)
{
printf("%d\n", num);
}
else
{
printf("%d ", num);
}
/**** 打印結(jié)束 ****/
}
printf("\n");
}
這段代碼中,我們引入了一個標(biāo)記變量isPrimeNum,它作為第一部分向第二部分傳遞判斷結(jié)果的一個變量。正因?yàn)橛辛怂抛屵@兩個部分在邏輯上完全分開了。雖然在判斷1這個特殊情況時,我們多寫了幾行代碼,但從邏輯上更加清晰了,代碼的可讀性也就更強(qiáng)了。
這樣做的好處是什么呢?如果我們現(xiàn)在需要修改判斷素?cái)?shù)的算法,或者需要修改打印方式,都只需要修改一部分的代碼即可。代碼維護(hù)中有個永恒的真理就是:修改的代碼越少,出錯的機(jī)會就越少。
5.2 函數(shù)劃分法
這個方法是在上面方法的基礎(chǔ)上把重要的部分提取成一個獨(dú)立的函數(shù),從邏輯上說耦合性更小。
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");
}
兩種判斷素?cái)?shù)的方法被寫成兩個獨(dú)立的函數(shù)IsPrimeNum1()和IsPrimeNum2(),如果需要相互替換,可以隨時在main函數(shù)中修改一下調(diào)用代碼。修改一行代碼就能替換一個完整的功能。是不是很神奇。
在實(shí)際的項(xiàng)目中,這兩種方法要結(jié)合使用。究竟在什么時候選擇什么樣的方式,這就是屬于你自己的編程風(fēng)格和項(xiàng)目經(jīng)驗(yàn)。
6. 完成情況
漫天星星55 | a627892820 這兩位同學(xué)都用到了函數(shù)劃分的方法,值得表揚(yáng)。需要注意的是:
- 上傳代碼要使用代碼框,否則不方便別人閱讀
- 上傳源碼圖片的方式也不可取
- 做練習(xí)時最好把源碼文件認(rèn)真歸檔,方便以后復(fù)習(xí)。不要在VS的默認(rèn)目錄中。(別問我怎么發(fā)現(xiàn)的)
- 代碼風(fēng)格要統(tǒng)一,該有的空格不要省略
- 變量命名要規(guī)范,a b c這樣的變量名不要用
7. 課后作業(yè)
給出任意一個N*N的矩陣,將里面的數(shù)字按照從左上到右下有小到大排序,之后計(jì)算出新矩陣對角線上的數(shù)字總和(每個位置只參與一次計(jì)算)。例如:
給出左邊這個矩陣,先把它轉(zhuǎn)換成右邊的矩陣,之后計(jì)算對角線上的數(shù)字之和:1 + 5 + 9 + 3 + 7 = 25
請?jiān)?017年4月21日23:00之前完成。完成方式請參考:天花板編程手把手計(jì)劃
我是天花板,讓我們一起在軟件開發(fā)中自我迭代。
如有任何問題,歡迎與我聯(lián)系。