|| 返回 || 本站首页 ||奥赛信息||计算机基础||pascal基础||数据结构||经典算法||试题汇编||校本教程||自主练习||

|| pascal基础>> 过程和函数(子程序)

双击自动滚屏 

   

过程和函数(子程序)


一、子程序设计的需要:

    前面我们曾经学习了程序设计中的三种基本控制结构(顺序、分支、循环)。用它们可以组成任何程序。但在应用中,还经常用到子程序结构。
  通常,在程序设计中,我们会发现一些程序段在程序的不同地方反复出现,此时可以将这些程序段作为相对独立的整体,用一个标识符给它起一个名字,凡是程序中出现该程序段的地方,只要简单地写上其标识符即可。这样的程序段,我们称之为子程序。
  子程序的使用不仅缩短了程序,节省了内存空间及减少了程序的编译时间,而且有利于结构化程序设计。因为一个复杂的问题总可将其分解成若干个子问题来解决,如果子问题依然很复杂,还可以将它继续分解,直到每个子问题都是一个具有独立任务的模块。这样编制的程序结构清晰,逻辑关系明确,无论是编写、阅读、调试还是修改,都会带来极大的好处。
  在一个程序中可以只有主程序而没有子程序(本章以前都是如此),但不能没有主程序,也就是说不能单独执行子程序。pascal中子程序有两种形式:函数和过程。
    1.细化算法的过程,可以将每一个子问题运用一段相对独立的小程序来解决;
    2.一些具有相同或功能相似的程序段在程序中的不同位置反复出现,可以将这样的程序段做成一个整体,用一个标识符给它起一个名字,凡是需要这个程序段的地方只要简单地引用其标识符即可。
    3.子程序包括过程和函数两种形式。

二、函数
1.标准函数 :由Pascal定义的函数。如我们熟悉的ord,chr等,程序员编程时直接引用就行了。
2.自定义函数:由程序员在程序中定义后再使用。
(1)自定义函数的定义
function 函数名(形参表):函数类型; { ————函数首部}
var {————局部变量说明部分}
begin {————函数体}
... {————函数语句}
...
函数名:=表达式
end;

说明:
  ①函数由首部与函数体两部分组成。
  ②函数首部以关键字function开头。
  ③函数名是用户自定义的标识符。
  ④函数的类型也就是函数值的类型,所求得的函数值通过函数名传回调用它的程序。可见,函数的作用一般是为了求得一个值。
  ⑤形式参数简称形参,形参即函数的自变量。自变量的初值来源于函数调用。在函数中,形参一般格式如下:
  变量名表1:类型标识符1;变量名表2:类型标识符2;…;变量名表n:类型标识符n
  可见形参表相当于变量说明,对函数自变量进行说明,但应特别注意:此处只能使用类型标识符,而不能直接使用类型。
  ⑥当缺省形参表(当然要同时省去一对括号)时,称为无参函数。
  ⑦函数体与程序体基本相似,由说明部分和执行部分组成。
  ⑧函数体中的说明部分用来对本函数使用的标号、常量、类型、变量、子程序加以说明,这些量只在本函数内有效。
  ⑨函数体的执行部分由begin开头,end结束,中间有若干用分号隔开的语句,只是end后应跟分号,不能像程序那样用句号"."。
  ⑩在函数体的执行部分,至少应该给函数名赋一次值,以使在函数执行结束后把函数值带回调用程序。

(2)函数的调用:

    我们可以在任何与函数值类型兼容的表达式中调用函数,或者说,函数调用只能出现在允许表达式出现的地方,或作为表达式的一个因子。
  函数调用方式与标准函数的调用方式相同。
  函数调用的一般格式:
    <函数名>
    或
    <函数名>(实在参数表)
 
  说明:①实在参数简称实参。实参的个数必须与函数说明中形参的个数一致,实参的类型与形参的类型应当一一对应。
  ②调用函数时,一般的,实参必须有确定的值。
  ③函数调用的步骤为:计算实参的值,"赋给"对应的形参;

    函数在语法上相当于一个表达式,所以,调用时,函数不能独立成为一个语句;它可以出现在任何表达式可以出现的地方。
例如赋值语句的右边:
X:=函数名(实在参数表); {————X的类型与函数类型必须一致}
又,如果函数类型是boolean,则还可以出现在条件语句中,充当条件表达式:
if 函数名(实在参数表) then ……
(3)函数的应用举例
  例1 求正整数A和B之间的完全数(A   分析:所谓完全数是指它的小于该数本身的因子之和等于它本身,如6=1+2+3,6即是一个完全数。因此我们可定义一个布尔型函数perfect(x),若x是完全数,其值为TURE,否则为FALSE。整个程序算法如下:
  1 for i:=A to B do
  2 if perfect(i) then writeln(i);
  源程序如下:
  program ex7_1;
  var
    i,a,b : integer;
  function perfect(x:integer):boolean;
  var
    k,sum : integer;
  begin
   {累加x所有小于本身的因数}
   sum:=1;
   for k:=2 to x div 2 do
    if x mod k=0 then sum:=sum+k;
   {判断x是否是完全数}
   perfect:=x=sum; {将结果赋值给函数名}
  end;{end of perfect}
  begin{主程序开始}
   write('Input a,b:');
   repeat {输入0     readln(a,b);
   until (a>0)and(b>0)and(a    writeln('List of all perfect numbers:');
   {从a到b逐个判断,是完全数则打印出来
   for i:=a to b do

if perfect(i) then writeln(i);
  end.

  自定义函数只是主程序的说明部分,若主程序中没有调用函数,则系统不会执行函数子程序。当主程序调用一次函数时,则将实在参数的值传给函数的形式参数,控制转向函数子程序去执行,子程序执行完毕后自动返回调用处。

三、过程
1.标准过程:由Pascal定义的过程。如我们熟悉的read,write等,程序员编程时直接引用就行了。
2.自定义过程:由程序员在程序中定义后再使用。
(1) 过程的定义
procedure 过程名(形式参数表); {————过程首部}
var {————说明部分}
begin {————过程体}
...
...
end;

说明: ①过程首部以关键字procedure开头。
  ②过程名是用户自定义的标识符,只用来标识一个过程,不能代表任何数据,因此不能说明"过程的类型"。
  ③形参表缺省(当然要同时省去一对括号)时,称为无参过程。
  ④形参表的一般格式形式如下:
    [var] 变量名表:类型;…;[var] 变量名表:类型。
  其中带var的称为变量形参,不带var的称为值形参。在函数中,形参一般都是值形参,很少用变量形参(但可以使用)。例如,下列形参表中:
    (x,y:real;n:integer;var w:real;var k:integer;b:real)
  x、y、n、b为值形参,而w、k为变量形参。
  调用过程时,通过值形参给过程提供原始数据,通过变量形参将值带回调用程序。因此,可以说,值形参是过程的输入参数,变量形参是过程的输出参数。有关变参,这在后面内容具体叙述。
  ⑤过程体与程序、函数体类似。与函数体不同的是:函数体的执行部分至少有一个语句给函数名赋值,而过程体的执行部分不能给过程名赋值,因为过程名不能代表任何数据。 
  ⑥过程体的说明部分可以定义只在本过程有效的标号、常量、类型、变量、子程序等。
(2)过程的调用:

过程调用是通过一条独立的过程调用语句来实现的,它与函数调用完全不同。过程调用与调与标准过程(如write,read等)的方式相同。调用的一般格式为:
  <过程名> 
  或
  <过程名>(实在参数表)

  说明: ①实参的个数、类型必须与形参一一对应。
  ②对应于值形参的实参可以是表达式,对应于变量形参的实参只能是变量。
  ③过程调用的步骤为:计算实参的值;将值或变量的"地址"传送给对应的形参;执行过程体;返回调用处。
  过程与函数有下列主要区别:
  ①过程的首部与函数的首部不同;
  ②函数通常是为了求一个函数值,而过程可以得到若干个运算结果,也可用来完成一系列的数据处理,或用来完成与计算无关的各种操作;
  ③调用方式不同。函数的调用出现在表达式中,而过程调用是一个独立的语句。


(3)过程的应用举例
  例1  输出以下一个图形:
    *
    **
    ***
    ****
    *****
    ******
  分析:我们前面学习可用的二重循环打印出上图形, 现我们设置一个过程打印出N个连续的"*"号。
  源程序如下:
  program ex7_2;
   var i:integer;
   procedure draw_a_line(n:integer); {该过程打印出连续n 个星号,并换行}
    var j:integer;
    begin
     for j:=1 to n do
      write('*');
     writeln;
    end;
  begin
   for i:=1 to 6 do
    draw_a_line(i);{调用过程,第I行打印i个连续星号}
  end.

例2:某部队举行一次军事演习,A、B两队约好在同一时间从相距100公里的各自驻地出发相向运动。A队的速度为10公里/小时,B队的速度为8公里/小时。一通讯员骑马从A地同时出发为行进中的两队传递消息,速度为60公里/小时。每遇一队立即折回驶向另一队,当两队距离小于0.5公里时,停下来不再传递消息。求此时通讯员跑了多少趟(从一队到另一队为一趟)?
例3:已知二个合数A=18和B=96,键入N个[10,400]之间的自然数,求这N个数中所有合数与A、B的最大公约数。

过程、函数的数据传递
  在程序调用子程序时,调用程序将数据传递给被调用的过程或函数,而当子程序运行结束后,结果又可以通过函数名、变参。当然也可以用全局变量等形式实现数据的传递。这一节我们,就来研究参数传递与局部变量、全局变量等问题。

  (一)数值参数和变量参数
  前面已经讲过,pascal子程序中形式参数有数值形参(简称值参)和变量形参(变参)两种。事实上,还有函数形参和过程形参两种,只是应用并不太多,我们不作深入地研究。

  1、值形参
  值参的一般格式如§7.1.1所示。应该强调的是:
  ①形参表中只能使用类型标识符,而不能使用类型。
  ②值形参和对应的实参必须一一对应,包括个数和类型。
  ③实参和值形参之间数据传递是单向的,只能由实参传送给形参,相当赋值运算。
  ④一个特殊情况是,当值形参是实型变量名时,对应的实参可以是整型表达式。
  ⑤值形参作为子程序的局部量,当控制返回程序后,值形参的存储单元释放。

  2、变量形参
  变量形参的一般格式如§7.2.1所示,必须在形参前加关键字var。
  应该注意的是:
  ①与变量形参对应的实参只能是变量名,而不能是表达式。
  ②与变量形参对应的实参可以根据需要决定是否事先有值。
  ③变量形参与对应的实参的类型必须完全相同。
  ④对变量形参,运行时不另外开辟存储单元,而是与对应的实参使用相同的存储单元。也就是说,调用子程序时,是将实参的地址传送给对应的变量形参。
  ⑤当控制返回到调用程序后,变量形参的存储单元不释放,但变量形参本身无定义,即不得再使用。
  ⑥选用形式参时,到底是使用值形参还是变量形参,应慎重考虑。值形参需要另开辟存储空间,而变量形参会带来一些副作用。一般在函数中使用值形参,而在过程中才使用变量形参,但也有例外。

  例3 写出下列两个程序的运行结果。
  program ex1;            program ex2;
   var a,b:integer;          var a,b:integer;
   procedure swap(x,y:integer);    procedure swap(Var x,y:integer) ;
    var t:integer;            var t:integer;
    begin                 begin
     t:=x;x:=y;y:=t;            t:=x;x:=y;y:=t;
    end;                  end;
   begin                  begin
    a:=1;b:=2;               a:=1;b:=2;
    writeln(a:3,b:3);            writeln(a:3,b:3);
    swap(a,b);                swap(a,b);
    writeln(a:3,b:3);             writeln(a:3,b:3);
   end.                   end.

  分析:这两个程序唯一的区别是ex1中将x,y作为值形参,而 ex2中将x,y作为变量形参,因此在ex2中对x,y的修改实际上是对调用该过程时与它们对应的变量a,b的修改,故最后,a,b的值为2,1。而ex1中调用swap过程时,只是将a,b的值传递给x,y,之后在过程中的操作与a,b无关。
  答:ex1的运行结果为: ex2的运行结果为:
      1 2         1 2
      1 2         2 1

  (二)全程变量、局部变量及它们的作用域
  在主程序的说明部分和子程序的说明部分均可以说明变量,但它们的作用范围是特定的。

  1、局部量及其作用域
  在介绍过程和函数的说明时,我们曾指出,凡是在子程序内部作用的变量,应该在本子程序内加以说明。这种在子程序内部说明的变量称为局部变量。形式参数也只是在该子程序中有效,因此也属于局部变量。
  一个变量的作用域是指在程序中能对此变量进行存取的程序范围。因此,局部变量的作用域就是其所在的子程序。实际上,局部变量只是当其所在的子程序被调用时才具有确定的存储单元,当控制从子程序返回到调用程序后,局部变量的存储单元就被释放,从而变得无定义。
  事实上,在子程序内定义的标号、符号常量、类型、子程序也与局部变量具有相同的作用域。

  2、全程量及其作用域
全程量是指在主程序的说明部分中说明的量。全程量的作用域分两种情况:
  ①当全程量和局部量不同名时,其作用域是整个程序范围(自定义起直到主程序结束)。
  ②当全程量和局部量同名时,全程量的作用域不包含局部量的作用域。

  例4 写出下列程序的运行结果:
  program ex7_4;
   var x,y:integer;
   procedure a;
    var x:integer;
    begin
     x:=2;
     writeln('#',x,'#');
     writeln('#',y,'#');
    end;{of a}
   begin{main program}
    x:=1;y:=2;
    writeln('*',x,'*',y);
    a;
    writeln('***',x,'***',y);
   end.
  分析:程序中x,y是全局变量,但在过程a中也有变量x,故全程变量x的作用域为除过程a外的任何地方。而y的作用域包含了子程序a,即整个程序。
  答:运行结果如下:
    *1*2
    #2#
    #2#
    ***1***2
  评注:变量作用域内对变量的操作都是对同一存储单元中的量进行的。

四、过程和函数的嵌套
  Pascal语言中,使用过程和函数,能使程序设计简短,便于阅读,节省存贮单元和编译时间。程序往往设计成分层结构,由一个主程序和若干个过程及函数组成。在过程或函数中,还可以说明另一些过程或函数,即过程或函数可以分层嵌套。在同一层中亦可说明几个并列的过程或函数。例如:

  上例过程的分层嵌套关系如下:0层主程序sample内并列两个1层过程P1a和P1b。过程P1a又嵌套两个2层过程p2a和p2b,2层的第二过程p2b又嵌套过程p3,p3就是第3层。其中p1b,p2a和p3不再嵌套别的过程,称为基本过程。这种分层结构的程序设计,特别要注意局部变量的使用范围和过程调用的要求。
  在主程序sample中定义的变量,可以在所有的过程中使用,主程序可调用p1a和p1b两个过程。过程p1a中定义的变量,只能在p2a,p2b 和p3中使用。它能调用p2a,p2b两个过程,而不能调用p3和p1b。 在过程p1b中定义的变量,只能在p1b中使用,它只能调用过程p1a。过程p2a不能调用任何过程。过程p2b可以调用并列过程p2a和p3,而过程p3可以调用p2a过程。
  过程调用是有条件的,过程定义在先,调用在后。同一层过程,后说明的过程可以调用先说明的过程。如果要调用在它后面定义的过程(或函数),可使用<向前引用>FORWARD这个扩充标识符。 要注意的是<向前引用>过程(或函数)首部中形式参数表写一次即可, 不必重复。如:
  procedure extend(var a,b:integer);
  forward;
  表示过程extend<向前引用>。因此,过程extend 的说明部分只须如下书写:
  procedure extend;
  <说明部分>
  begin
  :
  end;

五、子程序(模块化)结构的程序设计

  例 对6到60的偶数验证哥德巴赫猜想:不小于6的偶数可分解成两个素数之和。
  分析:用布尔型函数prime(x)判断x是否是素数,若是, 函数值为真,否则,函数值为假。算法如下所示。
  1. t:=6
  2. while t≤60 do
  3. t11;
  4. repeat
  5. t11+2; /* 找下一个素数a */
  6. until prime(t1)and prime(t-t1); /*直到a,b都是素数*/
  7. writeln(i,'=',t1,'+',t-t1);
  8. tt+2;
  9. endwhile

  源程序如下:
  program ex9_7;
   var t,t1:integer;
   function prime(x:integer):boolean;
    var i:integer;
    begin
     if x=1
      then prime:=false
      else if x=2
          then prime:=true
          else begin
              prime:=true;
              i:=2;
              while (i<=round(sqrt(x)))and(x mod i<>0) do
               i:=i+1;
               if i<=round(sqrt(x)) then prime:=false;
             end;
     end;{of prime}

    begin
     t:=6;
     while t<=60 do
      begin
       t1:=1;
       repeat
        t1:=t1+2;
       until prime(t1) and prime(t-t1);
       writeln(t,'=',t1,'+',t-t1);
       t:=t+2;
      end;
   end.

  例 编写一个给一个分数约分的程序。
  源程序如下:
  program ex7_6;   ┌──变量参数
    var a,b:integer; ↓
    procedure common(var x,y:integer);
     var i,j,k:integer;
     begin
      {求x,y的最大公约数}
      i:=x;j:=y;
      repeat
       k:=i mod j;
       i:=j;j:=k;
      until k=0;
       {对x,y进行约分}
        x:=x div i;y:=y div i;
      end;
     begin
       write('Input a,b=');readln(a,b);
       common(a,b);
       writeln(a,b:5);
      end.
  如输入:
  Input a,b=12 8
  则输出:
  3 2

六、子程序中的参数
1.局部变量和全局变量
在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。

2.变量的作用域
(1)变量的作用域和它被定义的位置有关:
在哪里被定义,它的作用范围就在那里:全局变量作用域是整个程序;局部变量作用域是定义该变量的子程序
(2)全局变量与局部变量同名时:
在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用。

3.值形参和变量形参
值形参——传值
变量形参——传地址

4.例3:作用域的示例ppro4.pas和ppro5.pas

七、综合练习 (ppro8.pas)
前提知识:计算任意三角形(三条边的长度分别为a、b、c)的面积S可以用海伦公式:
S=sqrt((x-a)(x-b)(x-c))
其中:x=(a+b+c)/2
题目描述:从键盘输入三个数,判断以这三个数为边能否组成一个三角形,若不能,则给出适当信息;若能,则输出是否为等边、等腰或直角三角形,并输出其面积。
编程要求:
1. 将从键盘输入三个数设计成一个过程,过程名为input,带三个变量参数a,b,c;
2. 将判断三个数能否组成三角形(包括是什么三角形)设计成一个函数,函数为ang,参数为在input中接收到的三个数,返回的值表明了能否组成三角形或三角形的类型;
3. 将计算三角形面积设计成一个函数,函数名area,带三个值参数,返回的值为三角形的面积;
4. 要求使程序的主程序的结构明快些,并要求在程序中的适当的说明。

3. 输入5个正整数求它们的最大公约数。(提示:可用一个数组将5个数存放起来,然后求第一个数和第二个数的公约数,再求第三个数与前两个数公约数的公约数,这样求得前三个整数最大公约数……如此类推可求出5个整数的最大公约数)

4. 给一维数组输入任意6个整数,假设为:
      7 4 8 9 1 5
    请建立一个具有以下内容的方阵:
      7 4 8 9 1 5
      4 8 9 1 5 7
      8 9 1 5 7 4
      9 1 5 7 4 8
      1 5 7 4 8 9
      5 7 4 8 9 1
    (请用子程序编写)。

5. 求两个正整数的最小公倍数。

6. 输入一个任意位的正整数,将其反向输出。

7. 有五位同学,其各科成绩如下:
    学号 数学 语文 英语 总分 名次
     1  108  97 90
     2  98  88 100
     3  100  43 89
     4  84   63 50
     5  97   87 100
  (1)编写一个过程enter,输入每个学生成绩并计算各人的总分。
  (2)编写过程minci,用以排出每个人的名次。
  (3)按学号顺序输出。

 

 
 

 

 
 
 

制作与维护:重庆市忠县中学 谭海