前面我們用過的一些內置函數(如數學函數)都會生成結果值。也就是說,調用函數的效果是產生一個新值,一般我們會把這個值賦給變量,或用作表達式的一部分。例如:
~~~
double e = exp(1.0);
double height = radius * sin(angle);
~~~
但到目前為止,我們編寫的所有函數都是**void**函數,它們不返回任何值。調用void函數時,常見的是函數調用語句本身占一行,沒有賦值操作:
~~~
nLines(3);
countdown(n-1);
~~~
本章我們將學習編寫帶有返回值的函數,因為沒有更好的名字,我索性直接稱之為“**有返回值的函數**”。第一個例子是area函數,它以一個double值為參數,返回以給定參數值為半徑的圓的面積:
~~~
double area(double radius) {
double pi = acos(-1.0);
double area = pi * radius * radius;
return area;
}
~~~
首先要注意到,該函數定義的開始部分與void函數(如果以“void”開始,則說明這是void函數)不同,這里使用了double,說明函數返回double類型的值。
再就是注意最后一行,這是return語句的一種可選形式,它帶了一個返回值。這句話的意思是,”以其后的表達式為返回值,立即從函數返回。“表達式可以非常復雜,所以area函數可以簡化為:
~~~
double area(double radius) {
return acos(-1.0) * radius * radius;
}
~~~
另一方面,像area這樣的**臨時變量**會使調試更容易。不管哪種情況,return語句中表達式的類型必須與函數的返回類型匹配。換句話說,當把函數的返回類型聲明為double時,就要保證函數最終會得到一個double值。如果不返回任何表達式,或者返回了類型不匹配的表達式,編譯器都會報錯。
有時包含多個返回語句是有用的,比如每個分支一個:
~~~
double absoluteValue(double x){
if (x < 0) {
return -x;
} else {
reurn x;
}
}
~~~
這些return語句分布在不同的條件分支中,只有一個能執行。雖然函數可以有多個return語句,但是只要其中一個執行,函數也就隨之結束了,不會再執行后面的語句。
return語句后面的代碼,或任何不可能執行到的代碼,稱為“**死代碼**”。如果存在死代碼,有的編譯器會給出警告。
如果return語句在一個條件分支中,必須保證每個可能的路徑都能碰到return語句。例如:
~~~
double absoluteValue(double x) {
if(x < 0) {
return –x;
} else if(x > 0) {
return x;
} // 錯誤
}
~~~
這個函數是錯誤的,因為當x為0的時候,所有條件都不滿足,最終函數找不到相應的return語句。非常不幸,很多C++編譯器并不捕捉此類錯誤,程序可以通過編譯并運行,但是當x==0時返回值可能是任意值,而且在不同環境下也可能有不同表現。
現在你可能還是很討厭看到編譯錯誤信息,但是隨著經驗的增長,你會意識到:當程序有錯誤時,不出現編譯錯誤會比出現更糟糕。
有時會有這樣的事情,你用一些值測試了absoluteValue函數,而且該函數看起來是可以正常工作的,可是當你把程序交給別人在其他環境下測試時,卻出現了不可思議的bug,經過幾天的調試你才發現absoluteValue的實現有問題。要是編譯器能早發現問題并警告你該多好啊!
從現在開始,如果編譯器指出了程序中的錯誤,請不要抱怨編譯器。相反,你應該感謝編譯器幫你找出錯誤,而且節約了你數天的調試時間。有的編譯器可以通過選項指定更嚴格的編譯檢查并報告所有錯誤。你要一直開著這些選項。
說句題外話,math庫中的fabs函數能夠正確計算double變量的絕對值。
- 第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 apstring是可比較的
- 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章 向量
- 10.1 元素訪問
- 10.2 向量的復制
- 10.3 for循環
- 10.4 向量的長度
- 10.5 隨機數
- 10.6 統計
- 10.7 隨機數的向量
- 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.8 初始化還是構造?
- 11.7 構造函數
- 11.9 最后一個例子
- 11.10 頭文件
- 11.11 術語表
- 第12章 對象的向量
- 12.1 組合
- 12.2 紙牌對象(Card)
- 12.3 printCard函數
- 12.4 equals函數
- 12.5 isGreater函數
- 12.6 紙牌的向量
- 12.7 printDeck函數
- 12.8 查找
- 12.9 二分查找
- 12.10 牌堆與子牌堆
- 12.11 術語表
- 第13章 基于向量的對象
- 13.1 枚舉類型
- 13.2 switch語句
- 13.3 牌堆
- 13.4 另一個構造函數
- 13.5 Deck成員函數
- 13.6 洗牌
- 13.7 排序
- 13.8 子牌堆
- 13.9 洗牌與發牌
- 13.10 歸并排序
- 13.11 術語表
- 第14章 類與不變式
- 14.1 私有數據和私有類
- 14.2 什么是類?
- 14.3 復數
- 14.4 訪問函數(Accessor functions)
- 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 術語表