不要疑惑,我說的確實不是如何除錯程式,而是怎樣調戲程式。調戲程式比除錯程式更困難、更重要也更有樂趣。因為,雖然不懂得如何除錯程式就意味著無法改正程式中存在的錯誤,但是,如果不懂得怎樣調戲程式,你可能連程式中所存在的錯誤都無法發現。
好,廢話少說,這就來看題。題目是這樣的:
題目:找出乙個二維陣列中的鞍點,即該位置上的元素在該行上最大,在該列上最小。也可能沒有鞍點。
——譚浩強 ,《c程式設計(第四版)學習輔導》,清華大學出版社,2023年7月,p63
題目看起來沒有任何問題,但請不要輕信。就像不要輕易相信小廣告一樣,一旦輕信你就必然上當,所以輕信是一種很foolish的行為,在程式設計過程中也是如此,因為一旦輕信,就根本不可能調戲程式。
實際上自然語言往往是模糊的、有歧義的,這種歧義可能導致對程式功能在理解上的分歧,而一旦產生這種分歧,程式寫的再怎麼正確也必定是錯誤的。
在這個題目裡,「最大」、「最小」這兩個詞彙就非常令人起疑。究竟什麼叫「在該行上最大」,如果在某行上有這樣幾個元素:5、2、3、1、5,那麼這裡的那兩個5究竟算不算在該行上最大?或者說這行上究竟有沒有最大元素?這樣的問題,恐怕3個人中至少會產生4種見解。
然而弄清楚這兩個詞的確切含義,關係到是否真正了解了題目的要求。而真正了解題目的要求,在程式設計中的重要性是首當其衝的。連究竟要做什麼都不知道你還寫神馬**?
初學者往往意識不到這一點,他們經常輕率地認為他們了解題目的要求是什麼,而把重點放在寫**上。但優秀的程式設計師們恰恰相反,他們往往要在弄清楚究竟要做什麼上花費最多的精力。這裡就有乙個生動的例子(《如何去應付你的上司給你乙個變化無常的需求?》
你可以發現優秀的程式設計師在弄明白究竟要做什麼這件事情上承受著多麼巨大的煩惱,絕對不會像初學者們那樣「沒心沒肺」。因為他們清楚地知道:「需求錯了是你製造出的最大的bug」。與正確地理解需求相比,什麼設計啊、**啊神馬的都是浮雲。
現在回到題目上。題目中並沒有進一步明確「最大」、「最小」這兩個詞的任何線索,這讓人好生煩惱。不過好在後面有題目解答,因此我們可以帶著對「最大」、「最小」這兩個詞的確切含義的疑問繼續調戲程式。下面是這道題目的一種解題思路
乙個二維陣列最多只有乙個鞍點,也可能沒有。解題思路是:先找出一行中值最大的元素,然後檢查它是否為該列的最小值,如果是,則是鞍點(不需要再找別的鞍點了),輸出該鞍點;如果不是,則再找下一行的最大數……如果每一行的最大數都不是鞍點,則此陣列無鞍點。
——譚浩強 ,《c程式設計(第四版)學習輔導》,清華大學出版社,2023年7月,p63
思路點評:
這個解題思路首先沒頭沒腦且非常武斷地來了一句「乙個二維陣列最多只有乙個鞍點」。由於這不屬於常識範圍之列,又沒有給出有力的證明,所以不管你們信不信,反正我是不信。至少在弄清楚「最大」、「最小」這兩個詞的含義之前不信。
「先找出一行中值最大的元素,然後檢查它是否為該列的最小值」:貌似有理,然而仔細推敲一下不難發現其中的邏輯漏洞。找出最大的元素的前提是存在這樣的元素,如果某行中各個元素的值都相等,那麼算不算存在最大元素?如果算的話應該找出幾個?如果某行中各個元素的值都相等算不算是存在最大元素,如果出現了這種情況程式應該如何處理?這些問題在這個解題思路顯然根本就沒有考慮。這樣的程式不出毛病才怪呢!
經過了這些思考,現在就可以正式調戲程式了。這個程式的**如下
#include<
stdio.h
>
#define
n 4#define
m 5int
main()
flag=1
;for(k=
0;k<
n;k++
) if
(max
>
a[k][maxj])
if(flag) }
if(!flag)
printf(
"it is not exist!\n");
return0;
}
——譚浩強 ,《c程式設計(第四版)學習輔導》,清華大學出版社,2023年7月,p63~64
這個程式,當輸入
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
時的輸出為:
a[0][4]=5
當輸入1 2 3 4 11
2 4 6 8 12
3 6 9 12 15
4 8 12 16 7
時的輸出為:
it is not exist!
表現似乎倒也還算正常(雖然我沒看懂「it is not exist!」究竟是哪國英語)。
然而所謂「調戲」,就是要用各式各樣的資料騷擾程式,試驗程式都有什麼反應。簡而言之,就是逗你玩。懷著前面對「最大」「最小」的困惑,很自然會想到用這樣的輸入資料來調戲一下程式:
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
這回程式的輸出竟然是
a[0][0]=1
這個輸出結果究竟應該怎麼理解呢?若是說輸入的二維陣列沒有鞍點吧,這個程式硬給整出來乙個;若是說這陣列有鞍點吧,那麼它一共有20個鞍點(注意這和「乙個二維陣列最多只有乙個鞍點」相矛盾),但程式只算出了乙個。然而可以確定的是,無論怎樣解釋這個結果都是錯誤的。
看來,程式本無bug,調戲的次數多了,也就有了bug。
稍微地審視一下**,不難發現這一段**是錯誤的
max=a[ i][0];
maxj=0
; for(j=
0;j<
m;j++)if
(a[ i][j]
>
max)
這段**想當然地認為可以求出第i行中的最大值及其位置,但其實卻不能。這段**甚至在還沒弄清楚「最大」這個詞的確切含義的情況下,就自作多情地假設任何一行都存在著最大值。
順便要提到的是下面兩段**,
for(k=0;k<
n;k++
) if
(max
>
a[k][maxj])
和
if(!flag)
printf(
"it is not exist!\n
");
這兩行**寫得很有喜感,仔細閱讀一下就會發現。如果看了第一段沒有任何感覺說明你的c語言很菜,看了第二段沒有任何感覺則說明你的英語言很菜。
回過頭來看程式錯誤的產生原因,不難發現,雖然直接原因是**的產生的,但**的錯誤來自錯誤的解題思路,而錯誤的解題思路則是由於題目本身不清不楚不明不確或是對題目要求理解得不清不楚不明不確造成的。因此要正確地解決這個問題,首先必須明確問題本身是什麼?為此將題目更正為
題目:找出乙個二維陣列中的鞍點,即該位置上的元素在該行上最大(即大於該行的其他元素),在該列上最小(即小於該行的其他元素),輸出鞍點位置及其對應的值。如果鞍點不存在輸出「沒有鞍點」。
方法也無非是老老實實地乙個元素乙個元素地檢查。
#include<
stdio.h
>
#define
n 4#define
m 5#define
no 0
#define
yes 1
intmain(
void
) }if
( be_max
==no )
//a[i][j]在該行中不是最大
continue
;for
( m =0
; m
<
n ; m
++)
//判斷a[i][j]是否在該列中最小
}if( be_min
==no )
//a[i][j]在該列中不是最小
continue
;no_saddle_point
=no ;
//鞍點存在
printf(
"鞍點在第%d行第%d列,值為%d\n
",i,j,a[i][j] );
//return 0; }if
( no_saddle_point
==yes )
printf(
"沒有鞍點\n
");
return0;
}
在明確了最大、最小的含義後,不難分析出二維陣列確實只可能有0或1個鞍點。因此前面的**還有進一步優化的可能:找到鞍點後直接退出。實現這一點也非常容易,只要在輸出鞍點之後加上一句return 0;就可以了,此時也沒有必要定義no_saddle_point這個變數。這裡就不再另寫一次**了。
如何優雅的調戲XSS
前言 正文 如何呼叫xss shellcode?我們先來學習一下,如何去呼叫我們的xss shellcode,嘿嘿。我們來看這種方式 這裡,我們是使用script標籤的src屬性從遠端呼叫了我們的js檔案,來實現呼叫shellcode。我們除了使用script標籤來呼叫我們的shellcode,還可...
怎樣來寫程式
如果你是乙個程式設計師,想要在計算機技術領域成為高手,而目前你的情況是懂程式設計懂的很少,給你的建議是,多寫程式。怎麼來寫程式了?最開始的時候買一本關於程式設計的書,然後把上面的例子和習題都做一遍,不用管那些狗屁什麼規範什麼軟體工程的,只要能把程式除錯通,功能能達到自己預期的效果,就行了。讓自己有個...
怎樣給程式設限
不用第三方軟體也能給程式設限 以下以 qq為例 第一種方法 利用組策略執行 gpedit.msc 確定後,依次展開 本地計算機策略 使用者配置 管理模板 系統分支,然後再右視窗中用滑鼠雙擊 不要執行 windows 應用程式 在 設定 按鈕中選擇 已啟用 選項,單擊 顯示 按鈕,彈出 顯示內容 視窗...