-
Notifications
You must be signed in to change notification settings - Fork 0
/
ch03s03.html
41 lines (37 loc) · 12.9 KB
/
ch03s03.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
<?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>3. 形参和实参</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="ch03.html" title="第 3 章 简单函数" /><link rel="prev" href="ch03s02.html" title="2. 自定义函数" /><link rel="next" href="ch03s04.html" title="4. 全局变量、局部变量和作用域" /></head><body><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">3. 形参和实参</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch03s02.html">上一页</a> </td><th width="60%" align="center">第 3 章 简单函数</th><td width="20%" align="right"> <a accesskey="n" href="ch03s04.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="id2713785"></a>3. 形参和实参</h2></div></div></div><p>下面我们定义一个带参数的函数,我们需要在函数定义中指明参数的个数和每个参数的类型,定义参数就像定义变量一样,需要为每个参数指明类型,参数的命名也要遵循标识符命名规则。例如:</p><div class="example"><a id="id2713799"></a><p class="title"><b>例 3.4. 带参数的自定义函数</b></p><div class="example-contents"><pre class="programlisting">#include <stdio.h>
void print_time(int hour, int minute)
{
printf("%d:%d\n", hour, minute);
}
int main(void)
{
print_time(23, 59);
return 0;
}</pre></div></div><br class="example-break" /><p>需要注意的是,定义变量时可以把相同类型的变量列在一起,而定义参数却不可以,例如下面这样的定义是错的:</p><pre class="programlisting">void print_time(int hour, minute)
{
printf("%d:%d\n", hour, minute);
}</pre><p>学习C语言的人肯定都乐意看到这句话:“<span class="quote">变量是这样定义的,参数也是这样定义的,一模一样</span>”,这意味着不用专门去记住参数应该怎么定义了。谁也不愿意看到这句话:“<span class="quote">定义变量可以这样写,而定义参数却不可以</span>”。C语言的设计者也不希望自己设计的语法规则里到处都是例外,一个容易被用户接受的设计应该遵循最少例外原则(Rule of Least Surprise)<a id="id2713837" class="indexterm"></a>。其实关于参数的这条规定也不算十分例外,也是可以理解的,请读者想想为什么要这么规定。学习编程语言不应该死记各种语法规定,如果能够想清楚设计者这么规定的原因(Rationale)<a id="id2713847" class="indexterm"></a>,不仅有助于记忆,而且会有更多收获。本书在必要的地方会解释一些Rationale,或者启发读者自己去思考,例如上一节在脚注中解释了<code class="literal">void</code>关键字的Rationale。<a class="xref" href="bi01.html#bibli.rationale" title="Rationale for International Standard - Programming Languages - C">[<abbr class="abbrev">C99 Rationale</abbr>]</a>是随C99标准一起发布的,值得参考。</p><p>总的来说,C语言的设计是非常优美的,只要理解了少数基本概念和基本原则就可以根据组合规则写出任意复杂的程序,很少有例外的规定说这样组合是不允许的,或者那样类推是错误的。相反,C++的设计就非常复杂,充满了例外,全世界没几个人能把C++的所有规则都牢记于心,因而C++的设计一直饱受争议,这个观点在<a class="xref" href="bi01.html#bibli.taoup" title="The Art of UNIX Programming">[<abbr class="abbrev">UNIX编程艺术</abbr>]</a>中有详细阐述。</p><p>在本书中,凡是提醒读者注意的地方都是多少有些Surprise的地方,初学者如果按常理来想很可能要想错,所以需要特别提醒一下。而初学者容易犯的另外一些错误,完全是因为没有掌握好基本概念和基本原理,或者根本无视组合规则而全凭自己主观臆断所致,对这一类问题本书不会做特别的提醒,例如有的初学者看完<a class="xref" href="ch02.html#expr">第 2 章 <i>常量、变量和表达式</i></a>之后会这样打印π的值:</p><pre class="programlisting">double pi=3.1416;
printf("pi\n");</pre><p>之所以会犯这种错误,一是不理解Literal的含义,二是自己想当然地把变量名组合到字符串里去,而事实上根本没有这条语法规则。如果连这样的错误都需要在书上专门提醒,就好比提醒小孩吃饭一定要吃到嘴里,不要吃到鼻子里,更不要吃到耳朵里一样。</p><p>回到正题。我们调用<code class="literal">print_time(23, 59)</code>时,函数中的参数<code class="literal">hour</code>就代表<code class="literal">23</code>,参数<code class="literal">minute</code>就代表<code class="literal">59</code>。确切地说,当我们讨论函数中的<code class="literal">hour</code>这个参数时,我们所说的“<span class="quote">参数</span>”是指形参(Parameter)<a id="id2713967" class="indexterm"></a>,当我们讨论传一个参数<code class="literal">23</code>给函数时,我们所说的“<span class="quote">参数</span>”是指实参(Argument)<a id="id2713984" class="indexterm"></a>,但我习惯都叫参数而不习惯总把形参、实参这两个文绉绉的词挂在嘴边(事实上大多数人都不习惯),读者可以根据上下文判断我说的到底是形参还是实参。记住这条基本原理:<span class="emphasis"><em>形参相当于函数中定义的变量,调用函数传递参数的过程相当于定义形参变量并且用实参的值来初始化</em></span>。例如这样调用:</p><pre class="programlisting">void print_time(int hour, int minute)
{
printf("%d:%d\n", hour, minute);
}
int main(void)
{
int h = 23, m = 59;
print_time(h, m);
return 0;
}</pre><p>相当于在函数<code class="literal">print_time</code>中执行了这样一些语句:</p><pre class="programlisting">int hour = h;
int minute = m;
printf("%d:%d\n", hour, minute);</pre><p><code class="literal">main</code>函数的变量<code class="literal">h</code>和<code class="literal">print_time</code>函数的参数<code class="literal">hour</code>是两个不同的变量,只不过它们的存储空间中都保存了相同的值23,因为变量<code class="literal">h</code>的值赋给了参数<code class="literal">hour</code>。同理,变量<code class="literal">m</code>的值赋给了参数<code class="literal">minute</code>。C语言的这种传递参数的方式称为Call by Value<a id="id2714067" class="indexterm"></a>。在调用函数时,每个参数都需要得到一个值,函数定义中有几个形参,在调用时就要传几个实参,不能多也不能少,每个参数的类型也必须对应上。</p><p>肯定有读者注意到了,为什么我们每次调用<code class="literal">printf</code>传的实参个数都不一样呢?因为C语言规定了一种特殊的参数列表格式,用命令<code class="literal">man 3 printf</code>可以查看到<code class="literal">printf</code>函数的原型:</p><pre class="programlisting">int printf(const char *format, ...);</pre><p>第一个参数是<code class="literal">const char *</code>类型的,后面的...可以代表0个或任意多个参数,这些参数的类型也是不确定的,这称为可变参数(Variable Argument)<a id="id2714114" class="indexterm"></a>,<a class="xref" href="ch24s06.html#interface.va">第 6 节 “可变参数”</a>将会详细讨论这种格式。总之,每个函数的原型都明确规定了返回值类型以及参数的类型和个数,即使像<code class="literal">printf</code>这样规定为“<span class="quote">不确定</span>”也是一种明确的规定,调用函数时要严格遵守这些规定,有时候我们把函数叫做接口(Interface)<a id="id2714138" class="indexterm"></a>,调用函数就是使用这个接口,使用接口的前提是必须和接口保持一致。</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Man Page</h3><p>Man Page是Linux开发最常用的参考手册,由很多页面组成,每个页面描述一个主题,这些页面被组织成若干个Section。FHS(Filesystem Hierarchy Standard)<a id="id2714160" class="indexterm"></a>标准规定了Man Page各Section的含义如下:</p><div class="table"><a id="id2714168"></a><p class="title"><b>表 3.1. Man Page的Section</b></p><div class="table-contents"><table summary="Man Page的Section" border="1"><colgroup><col /><col /></colgroup><thead><tr><th>Section</th><th>描述</th></tr></thead><tbody><tr><td>1</td><td>用户命令,例如<code class="literal">ls(1)</code></td></tr><tr><td>2</td><td>系统调用,例如<code class="literal">_exit(2)</code></td></tr><tr><td>3</td><td>库函数,例如<code class="literal">printf(3)</code></td></tr><tr><td>4</td><td>特殊文件,例如<code class="literal">null(4)</code>描述了设备文件<code class="literal">/dev/null</code>、<code class="literal">/dev/zero</code>的作用</td></tr><tr><td>5</td><td>系统配置文件的格式,例如<code class="literal">passwd(5)</code>描述了系统配置文件<code class="literal">/etc/passwd</code>的格式</td></tr><tr><td>6</td><td>游戏</td></tr><tr><td>7</td><td>其它杂项,例如<code class="literal">bash-builtins(7)</code>描述了<code class="literal">bash</code>的各种内建命令</td></tr><tr><td>8</td><td>系统管理命令,例如<code class="literal">ifconfig(8)</code></td></tr></tbody></table></div></div><br class="table-break" /><p>注意区分用户命令和系统管理命令,用户命令通常位于<code class="literal">/bin</code>和<code class="literal">/usr/bin</code>目录,系统管理命令通常位于<code class="literal">/sbin</code>和<code class="literal">/usr/sbin</code>目录,一般用户可以执行用户命令,而执行系统管理命令经常需要<code class="literal">root</code>权限。系统调用和库函数的区别将在<a class="xref" href="ch19s02.html#asmc.main">第 2 节 “<code class="literal">main</code>函数和启动例程”</a>说明。</p><p>Man Page中有些页面有重名,比如敲<code class="literal">man printf</code>命令看到的并不是C函数<code class="literal">printf</code>,而是位于第1个Section的系统命令<code class="literal">printf</code>,要查看位于第3个Section的<code class="literal">printf</code>函数应该敲<code class="literal">man 3 printf</code>,也可以敲<code class="literal">man -k printf</code>命令搜索哪些页面的主题包含<code class="literal">printf</code>关键字。本书会经常出现类似<code class="literal">printf(3)</code>这样的写法,括号中的3表示Man Page的第3个Section,或者表示“<span class="quote">我这里想说的是<code class="literal">printf</code>库函数而不是<code class="literal">printf</code>命令</span>”。</p></div><div class="simplesect" lang="zh-cn" xml:lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a id="id2714399"></a>习题</h3></div></div></div><p>1、定义一个函数<code class="literal">increment</code>,它的作用是把传进来的参数加1。例如:</p><pre class="programlisting">void increment(int x)
{
x = x + 1;
}
int main(void)
{
int i = 1, j = 2;
increment(i); /* i now becomes 2 */
increment(j); /* j now becomes 3 */
return 0;
}</pre><p>我们在<code class="literal">main</code>函数中调用<code class="literal">increment</code>增加变量<code class="literal">i</code>和<code class="literal">j</code>的值,这样能奏效吗?为什么?</p><p>2、如果在一个程序中调用了<code class="literal">printf</code>函数却不包含头文件,例如<code class="literal">int main(void) { printf("\n"); }</code>,编译时会报警告:<code class="literal">warning: incompatible implicit declaration of built-in function ‘printf’</code>。请分析错误原因。</p></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="ch03s02.html">上一页</a> </td><td width="20%" align="center"><a accesskey="u" href="ch03.html">上一级</a></td><td width="40%" align="right"> <a accesskey="n" href="ch03s04.html">下一页</a></td></tr><tr><td width="40%" align="left" valign="top">2. 自定义函数 </td><td width="20%" align="center"><a accesskey="h" href="index.html">起始页</a></td><td width="40%" align="right" valign="top"> 4. 全局变量、局部变量和作用域</td></tr></table></div></body></html>