-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathch05s01.html
49 lines (48 loc) · 11.2 KB
/
ch05s01.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>1. return语句</title><link rel="stylesheet" href="styles.css" type="text/css" /><meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /><link rel="start" href="index.html" title="Linux C编程一站式学习" /><link rel="up" href="ch05.html" title="第 5 章 深入理解函数" /><link rel="prev" href="ch05.html" title="第 5 章 深入理解函数" /><link rel="next" href="ch05s02.html" title="2. 增量式开发" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">1. return语句</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch05.html">上一页</a> </td><th width="60%" align="center">第 5 章 深入理解函数</th><td width="20%" align="right"> <a accesskey="n" href="ch05s02.html">下一页</a></td></tr></table><hr /></div><div class="sect1" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="id2721598"></a>1. return语句</h2></div></div></div><p>之前我们一直在<code class="literal">main</code>函数中使用<code class="literal">return</code>语句,现在是时候全面深入地学习一下了。在有返回值的函数中,<code class="literal">return</code>语句的作用是提供整个函数的返回值,并结束当前函数返回到调用它的地方。在没有返回值的函数中也可以使用<code class="literal">return</code>语句,例如当检查到一个错误时提前结束当前函数的执行并返回:</p><pre class="programlisting">#include <math.h>
void print_logarithm(double x)
{
if (x <= 0.0) {
printf("Positive numbers only, please.\n");
return;
}
printf("The log of x is %f", log(x));
}</pre><p>这个函数首先检查参数<code class="literal">x</code>是否大于0,如果<code class="literal">x</code>不大于0就打印错误提示,然后提前结束函数的执行返回到调用者,只有当<code class="literal">x</code>大于0时才能求对数,在打印了对数结果之后到达函数体的末尾,自然地结束执行并返回。注意,使用数学函数<code class="literal">log</code>需要包含头文件<code class="literal">math.h</code>,由于<code class="literal">x</code>是浮点数,应该与同类型的数做比较,所以写成0.0。</p><p>在<a class="xref" href="ch04s02.html#cond.ifelse">第 2 节 “if/else语句”</a>中我们定义了一个检查奇偶性的函数,如果是奇数就打印<code class="literal">x is odd.</code>,如果是偶数就打印<code class="literal">x is even.</code>。事实上这个函数并不十分好用,我们定义一个检查奇偶性的函数往往不是为了打印两个字符串就完了,而是为了根据奇偶性的不同分别执行不同的后续动作。我们可以把它改成一个返回布尔值的函数:</p><pre class="programlisting">int is_even(int x)
{
if (x % 2 == 0)
return 1;
else
return 0;
}</pre><p>有些人喜欢写成<code class="literal">return(1);</code>这种形式也可以,表达式外面套括号表示改变运算符优先级,在这里不起任何作用。我们可以这样调用这个函数:</p><pre class="programlisting">int i = 19;
if (is_even(i)) {
/* do something */
} else {
/* do some other thing */
}</pre><p>返回布尔值的函数是一类非常有用的函数,在程序中通常充当控制表达式,函数名通常带有<code class="literal">is</code>或<code class="literal">if</code>等表示判断的词,这类函数也叫做谓词(Predicate)<a id="id2721727" class="indexterm"></a>。<code class="literal">is_even</code>这个函数写得有点啰嗦,<code class="literal">x % 2</code>这个表达式本来就有0值或非0值,直接把这个值当作布尔值返回就可以了:</p><pre class="programlisting">int is_even(int x)
{
return !(x % 2);
}</pre><p>函数的返回值应该这样理解:<span class="emphasis"><em>函数返回一个值相当于定义一个和返回值类型相同的临时变量并用<code class="literal">return</code>后面的表达式来初始化</em></span>。例如上面的函数调用相当于这样的过程:</p><pre class="programlisting">int 临时变量 = !(x % 2);
函数退出,局部变量x的存储空间释放;
if (临时变量) { /* 临时变量用完就释放 */
/* do something */
} else {
/* do some other thing */
}</pre><p>当<code class="literal">if</code>语句对函数的返回值做判断时,函数已经退出,局部变量<code class="literal">x</code>已经释放,所以不可能在这时候才计算表达式<code class="literal">!(x % 2)</code>的值,表达式的值必然是事先计算好了存在一个临时变量里的,然后函数退出,局部变量释放,<code class="literal">if</code>语句对这个临时变量的值做判断。注意,虽然函数的返回值可以看作是一个临时变量,但我们只是读一下它的值,读完值就释放它,而不能往它里面存新的值,换句话说,<span class="emphasis"><em>函数的返回值不是左值,或者说函数调用表达式不能做左值</em></span>,因此下面的赋值语句是非法的:</p><pre class="programlisting">is_even(20) = 1;</pre><p>在<a class="xref" href="ch03s03.html#func.paraarg">第 3 节 “形参和实参”</a>中讲过,C语言的传参规则是Call by Value,按值传递,现在我们知道返回值也是按值传递的,即便返回语句写成<code class="literal">return x;</code>,返回的也是变量<code class="literal">x</code>的值,而非变量<code class="literal">x</code>本身,因为变量<code class="literal">x</code>马上就要被释放了。</p><p>在写带有<code class="literal">return</code>语句的函数时要小心检查所有的代码路径(Code Path)<a id="id2721866" class="indexterm"></a>。有些代码路径在任何条件下都执行不到,这称为Dead Code<a id="id2721874" class="indexterm"></a>,例如把&&和||运算符记混了(据我了解初学者犯这个低级错误的不在少数),写出如下代码:</p><pre class="programlisting">void foo(int x, int y)
{
if (x >= 0 || y >= 0) {
printf("both x and y are positive.\n");
return;
} else if (x < 0 || y < 0) {
printf("both x and y are negetive.\n");
return;
}
printf("x has a different sign from y.\n");
}</pre><p>最后一行<code class="literal">printf</code>永远都没机会被执行到,是一行Dead Code。有Dead Code就一定有Bug,你写的每一行代码都是想让程序在某种情况下去执行的,你不可能故意写出一行永远不会被执行的代码,如果程序在任何情况下都不会去执行它,说明跟你预想的不一样,要么是你对所有可能的情况分析得不正确,也就是逻辑错误,要么就是像上例这样的笔误,语义错误。还有一些时候,对程序中所有可能的情况分析得不够全面将导致漏掉一些代码路径,例如:</p><pre class="programlisting">int absolute_value(int x)
{
if (x < 0) {
return -x;
} else if (x > 0) {
return x;
}
}</pre><p>这个函数被定义为返回<code class="literal">int</code>,就应该在任何情况下都返回<code class="literal">int</code>,但是上面这个程序在<code class="literal">x==0</code>时安静地退出函数,什么也不返回,C语言对于这种情况会返回什么结果是未定义的,通常返回不确定的值,等学到<a class="xref" href="ch19s01.html#asmc.funccall">第 1 节 “函数调用”</a>你就知道为什么了。另外注意这个例子中把-号当负号用而不是当减号用,事实上+号也可以这么用。正负号是单目运算符,而加减号是双目运算符,正负号的优先级和逻辑非运算符相同,比加减的优先级要高。</p><p>以上两段代码都不会产生编译错误,编译器只做语法检查和最简单的语义检查,而不检查程序的逻辑<sup>[<a id="id2721968" href="#ftn.id2721968" class="footnote">7</a>]</sup>。虽然到现在为止你见到了各种各样的编译器错误提示,也许你已经十分讨厌编译器报错了,但很快你就会认识到,如果程序中有错误编译器还不报错,那一定比报错更糟糕。比如上面的绝对值函数,在你测试的时候运行得很好,也许是你没有测到<code class="literal">x==0</code>的情况,也许刚好在你的环境中<code class="literal">x==0</code>时返回的不确定值就是0,然后你放心地把它集成到一个数万行的程序之中。然后你把这个程序交给用户,起初的几天里相安无事,之后每过几个星期就有用户报告说程序出错,但每次出错的现象都不一样,而且这个错误很难复现,你想让它出现时它就不出现,在你毫无防备时它又突然冒出来了。然后你花了大量的时间在数万行的程序中排查哪里错了,几天之后终于幸运地找到了这个函数的Bug,这时候你就会想,如果当初编译器能报个错多好啊!所以,如果编译器报错了,不要责怪编译器太过于挑剔,它帮你节省了大量的调试时间。另外,在<code class="literal">math.h</code>中有一个<code class="literal">fabs</code>函数就是求绝对值的,我们通常不必自己写绝对值函数。</p><div class="simplesect" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2721483"></a>习题</h3></div></div></div><p>1、编写一个布尔函数<code class="literal">int is_leap_year(int year)</code>,判断参数<code class="literal">year</code>是不是闰年。如果某年份能被4整除,但不能被100整除,那么这一年就是闰年,此外,能被400整除的年份也是闰年。</p><p>2、编写一个函数<code class="literal">double myround(double x)</code>,输入一个小数,将它四舍五入。例如<code class="literal">myround(-3.51)</code>的值是-4.0,<code class="literal">myround(4.49)</code>的值是4.0。可以调用<code class="literal">math.h</code>中的库函数<code class="literal">ceil</code>和<code class="literal">floor</code>实现这个函数。</p></div><div class="footnotes"><br /><hr width="100" align="left" /><div class="footnote"><p><sup>[<a id="ftn.id2721968" href="#id2721968" class="para">7</a>] </sup>有的代码路径没有返回值的问题编译器是可以检查出来的,如果编译时加<code class="literal">-Wall</code>选项会报警告。</p></div></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch05.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch05.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch05s02.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">第 5 章 深入理解函数 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 2. 增量式开发</td></tr></table></div></body></html>