## 1.3 什么是調試
編程本身是一個復雜的過程,并且由人類而不是機器完成,所以經常會發生一些錯誤。由于一些奇怪的原因,程序中的錯誤稱為bug,而追蹤定位bug并且將其修正的過程則稱為調試(Debug)。
程序中發生的錯誤有不同的種類,知道如何分辨不同的錯誤可以更快速地定位bug的位置。
### 1.3.1 編譯時錯誤
編譯器只能編譯語法正確的程序,否則會導致編譯過程失敗,無法運行程序。__語法__指的是程序結構以及與該結構相關的規則。
以英語語法為例,一個句子必須以大寫字母開頭,句號結尾。諸如“this sentence contains a syntax error.”和“So does this one”這樣的兩個句子都包含語法錯誤。
對大多數讀者來說,少量語法錯誤并不是什么大問題。這就是為什么我們可以毫無障礙地閱讀E.E.卡明斯的詩歌。
但是編譯器并不是如此的寬容。如果你的程序中出現一處語法錯誤,編譯器就會輸出錯誤消息并且退出,而你就無法再運行自己的程序。
更糟糕的是,C++中具有比英語更多的語法規則,并且大多數時候你從編譯器得到的錯誤消息都沒有太大幫助。在你剛開始學習編程的時候,你很可能會花費大量的時間查找語法錯誤。不過隨著你經驗日益豐富,發生和查找錯誤需要的時間都會越來越少。
### 1.3.2 運行時錯誤
第二種錯誤是運行時錯誤。將其稱為運行時錯誤,是因為該錯誤只有在程序運行時才會發生。
接下來的幾周我們要寫的各種程序中,運行時錯誤很少發生。所以你可能需要一段時間才會遇到1。
> 注釋:1這不是個好事情嗎?——譯者注
### 1.3.3 邏輯和語義錯誤
第三種錯誤是__邏輯__和__語義__錯誤。如果程序中出現邏輯和語義錯誤,計算機不會產生任何錯誤消息,編譯和運行過程都會成功。但是程序并沒有做它應該做的,而是做了其他的事。只有在極少情況下它才會做你讓它做的。
問題在于你寫的程序不是你本意想寫的程序,程序的意義(語義)有錯誤。識別邏輯錯誤是一件很棘手的事情,因為它需要你回頭查看程序的輸出并且嘗試發現哪里出錯了。
### 1.3.4 實驗調試
在本書的學習過程中,你應當獲得的最重要的技能之一就是調試。盡管錯誤的出現讓你沮喪,但是調試是編程過程中最需要腦力、最富有挑戰性而且最有趣的部分了。
在某些方面調試就像偵察。你需要面對線索,推斷出具體過程和事件,這些過程和事件能夠得到你所看到的結果。
同時,調試又像是科學實驗。一旦你想出來哪里可能出錯了,你就會修改你的程序再次嘗試。如果你的假設成立,你可以預測到修改后的結果并離可工作的程序更近一步。如果假設失敗了,你需要提出一個新的假設。正如福爾摩斯所說,“當你排除了一切不可能的因素之后,剩下的無論看起來有多么不合理,也一定是事實。”(來自柯南道爾的《四個人的簽名》)。
對一些人來說,編程和調試是同一件事情。也就是說,編程的過程就是逐步調試直到程序完成你想要的功能的過程。這種觀點表明,任何時候你都應該從一個可以正常運行的程序入手,然后進行小的改動并調試通過,這樣你的程序可以一直工作。
比如,Linux操作系統包含成千上萬行代碼,但是它最開始也只是 Linux Torvalds用于探索英特爾 80386芯片的簡單程序。Larry Greenfield說:“Linus早期的工程之一就是一段在輸出AAAA和BBBB之間切換的程序。然后進化成了Linux。”(來自Linux用戶指導測試版1)。
在稍后的章節里,我會提出關于調試和編程練習的更多建議。
- 譯者簡介
- 作者簡介
- 第1章 編程方式
- 1.1 什么是編程語言
- 1.2 什么是程序
- 1.3 什么是調試
- 1.4 形式語言和自然語言
- 1.5 第一個程序
- 1.6 術語
- 第2章 變量和類型
- 2.1 輸出更多
- 2.2 值
- 2.3 變量
- 2.4 賦值
- 2.5 輸出變量
- 2.6 關鍵字
- 2.7 運算符
- 2.8 計算順序
- 2.9 字符類型的運算符
- 2.10 組合
- 2.11 術語
- 第3章 函數
- 3.1 浮點數
- 3.2 從double轉換為int
- 3.3 數學函數
- 3.4 復合表達式
- 3.5 添加新的函數
- 3.6 定義和用法
- 3.7 多函數程序
- 3.8 形參和實參
- 3.9 形參和局部變量
- 3.10 多參數函數
- 3.11 帶返回值的函數
- 3.12 術語
- 第4章 條件和遞歸
- 4.1 模運算符
- 4.2 條件執行
- 4.3 選擇執行
- 4.4 鏈式條件
- 4.5 嵌套條件
- 4.6 return語句
- 4.7 遞歸
- 4.8 無限遞歸
- 4.9 遞歸函數的調用棧圖
- 4.10 術語
- 第5章 帶返回值的函數
- 5.1 返回值
- 5.2 程序開發
- 5.3 復合用法
- 5.4 重載
- 5.5 布爾值
- 5.6 布爾型變量
- 5.7 邏輯操作符
- 5.8 布爾函數
- 5.9 main函數返回值
- 5.10 多重遞歸
- 5.11信心的跳躍
- 5.12 更多的例子
- 5.13 術語
- 第6章 迭代
- 6.1 多次賦值
- 6.2 迭代
- 6.3 while語句
- 6.4 表格
- 6.5 二維表
- 6.6 封裝和廣義化
- 6.7 函數
- 6.8 更多封裝
- 6.9 局部變量
- 6.10 更多廣義化
- 6.11 術語
- 第7章 字符串和其他
- 7.1 字符串容器
- 7.2 apstring變量
- 7.3 字符串中的字符
- 7.4 長度
- 7.5 遍歷
- 7.6 運行時錯誤
- 7.7 find函數
- 7.8 自定義find函數
- 7.9 循環和計數
- 7.10 遞增和遞減操作符
- 7.11 字符串拼接
- 7.12 apstring的可變性
- 7.13 apstrings的可比較性
- 7.14 字符分類
- 7.15 其他apstring函數
- 7.16 術語
- 第8章 結構體
- 8.1 復合值
- 8.2 Point對象
- 8.3 訪問實例變量
- 8.4 操作結構體
- 8.5 將結構體作為參數
- 8.6 值傳遞
- 8.7 引用傳遞
- 8.8 矩形
- 8.9 返回結構體類型
- 8.10 將其他類型按引用傳遞
- 8.11 獲取用戶輸入
- 8.12 術語
- 第9章 更多結構體
- 9.1 Time
- 9.2 printTime
- 9.3 對象函數
- 9.4 純函數
- 9.5 const參數
- 9.6 修改器
- 9.7 填寫函數
- 9.8 哪個最好
- 9.9 增量式開發VS規劃
- 9.10 普遍化
- 9.11 算法
- 9.12 術語
- 第10章 vector
- 10.1 訪問元素
- 10.2 復制vector
- 10.3 for循環
- 10.4 vector的長度
- 10.5 隨機數
- 10.6 統計
- 10.7 隨機數的vector
- 10.8 計數
- 10.9 檢查其他值
- 10.10 直方圖
- 10.11 單次遍歷的解決方案
- 10.12 隨機種子
- 10.13 術語
- 第11章 成員函數
- 11.1 對象和方法
- 11.2 print
- 11.3 隱式變量訪問
- 11.4 另一個例子
- 11.5 第三個例子
- 11.6 更復雜的例子
- 11.7 結構體
- 11.8 初始化還是構造
- 11.9 最后一個例子
- 11.10 頭文件
- 11.11 術語
- 第12章 包含對象的vector
- 12.1 復合形式
- 12.2 Card對象
- 12.3 printCard函數
- 12.4 equals函數
- 12.5 isGreater函數
- 12.6 包含Card對象的vector
- 12.7 printDeck函數
- 12.8 搜索
- 12.9 二分查找
- 12.10 vector和子 vector
- 12.11 術語
- 第13章 向量對象
- 13.1 枚舉類型
- 13.2 switch語句
- 13.3 Deck
- 13.4 另一個構造函數
- 13.5 Deck成員函數
- 13.6 洗牌
- 13.7 排序
- 13.8 subdeck
- 13.9 洗牌和處理
- 13.10 合并排序
- 13.11 術語
- 第14章 類和不變式
- 14.1 私有數據和私有類
- 14.2 什么是類
- 14.3 復數
- 14.4 訪問器函數
- 14.5 輸出
- 14.6 支持復數運算的函數
- 14.7 支持復數運算的其他函數
- 14.8 不變式
- 14.9 先驗條件
- 14.10 私有函數
- 14.11 術語
- 第15章 文件輸入/輸出和apmatrix
- 15.1 流
- 15.2 文件輸入
- 15.3 文件輸出
- 15.4 輸入解析
- 15.5 數字解析
- 15.6 Set數據結構
- 15.7 apmatrix
- 15.8 距離矩陣
- 15.9 合適的距離矩陣
- 15.10 術語
- 附錄A AP類的快速參考
- 版權聲明
- 版權