博客首页 注册 建议与交流 排行榜 加入友情链接
推荐 投诉 搜索: 帮助

G.C--YANG

  guoyangyang.cublog.cn

关于作者
姓名:G.C--YANG
职业:学生
年龄:20
位置:辽宁省丹东
个性介绍:
|| << >> ||
我的分类


函数(C语言)(参考E Balagurusamy著标准C程序设计)
                            概括一下
   一个C程序由一个或多个函数组成,甚至一个小程序也由几个函数组成,实际上C语言鼓励把一个程序分解成多个函数。其中必包括一个main函数。程序的执行从main函数开始,执行完main函数就意味着整个函数执行完闭(注:在程序的任一个地方都可能通过调用某些库函数来结束程序,但在本章不会用到这些特殊函数)。如果把一个程序视为某个实际问题的一种解决方案,那么每个函数解决该问题的一部分。正如一个问题可以分解成多个部分,一个C程序可分解成多个组成函数。
下面来看一个函数调用的简单例子:
#include<stdio.h>
void main()
{
 void printstar();                     /*对printstar函数进行声明*/
 void print_message();                 /对print_message函数进行声明*/
 printstar();                          /*调用printstar函数*/
 print_message();                      /*调用print_message函数*/
 printstar();                          /*调用printstar函数*/
}
 
void printstar()                       /*定义printstar函数*/
{
 printf("*****************************");
}
void printmessage()                    /*定义print_message函数*/
{
 printf("    HOW DO YOU DO !     ");
}
 
运行情况如下:
*******************************
     HOW DO YOU DO !
*******************************
 
相信许多初学者和我一样,看到这样的函数一头雾水,再仔细审理一下吧,还可以看出一些规律性的东东,但是对于一些术语是一点都不懂,不过没有关系,一后会慢慢讲到的。
 
自定义函数的元素:
为了使用自定义函数,我们需要创建与函数有关系的三个元素:
(1)函数的定义;
(2)函数调用;
(3)函数声明。
下面我们来详细的介绍这三个元素。
 
函数声明:
与变量一样,C程序中的所有函数在使用之前都必须进行声明。函数声明由4部分组成。
  • 函数类型(返回值)
  • 函数名
  • 参数列表
  • 终止分号
函数的编码形式为:function-type  function-name(parameter list);
例:int mul(int m,int n);
也可以这样声明:int mul(int,int);
mul(int a,int b);
mul(int,int);
注意要点:
  1. 参数列表必须用逗号隔开。
  2. 函数原型声明与函数定义中的参数名不必相同。
  3. 参数的类型、数量和顺序必须与函数定义中的一样。
  4. 声明中的参数名是可以自已定义的。
  5. 如果函数没有形参,那么参数列表可写作(void)。
  6. 如果函数返回的是int类型的数据,那么返回类型是可以不用标明的。
  7. 如果不返回值,那么返回类型必须是void。
  8. 当声明类型与函数定义中的类型不一样时,编译器将发生错误。
当函数无参数,也不返回任何值时,其原型可写作:
void display(void);
函数原型声明可以放在程序的如下两个地方:
(1)所有函数(包括main函数)
(2)函数定义之中。
当函数声明位于所有函数之前,那么该函数原型为全局原型。这种声明是对程序中所有的函数都是有用的。
当函数声明位于函数定义中,该函数原型为局部原型。这种声明函数主要是被包含它们的函数使用。
 
 
函数调用:
使用函数名后跟实参列表就可以实现函数调用。
main()
{
 int y;
 y=mul(10,4);  /*函数调用*/
 printf("%d\n",y);
 
当编译器遇到函数调用时,控制权转移到函数mul().然后逐行运行函数,遇到return语句时返回一个值。该值赋给y.
返回一个值的函数可以像其它变量一样使用在表达式中。如:
printf("%d\n",mul(p,q));
y=mul(p,q);
if(mul(m,n)>total) printf("Large");
函数不能用在赋值语句的左边。下面的语句是不合法的:mul(a,b)=15;
 
如果实参比形参多,那么多余的实参将被丢弃掉。
如果实参比形参少,那么那些没有实参参与的形参将被初始化为垃圾数据。
 
 
对被调用的数数作声明。
#include<stdio.h>
void main(0
{
 float add(float x,float y);   /*对被调用函数的声明*/
 float a,b,c;
 scanf("%f%f",&a,&b);
 c=add(a,b);
printf("sum is %f\n",c);
float add(float x,float y)      /*函数首部*/
{                               /*函数体*/
 float z;
 z=x+y;
 return(z);
}
 
如果被调用函数的定义出现在主调函数之前,可以不必加以声明。因为编译器系统已经先知道了已定义函数的有关情况,会根据函数首部提供的信息对函数的调用作正确性检查。
#include<stdio.h>
float add(float x,float y)         /*函数定义*/          
{
 float z;
 z=x+y;
 return(z);
}
void main()
{                                     /*不用再对add函数进行声明*/
float a,b,c;
scanf("%f%f",&a,&b);
c=add(a,b);
printf("%f\n",c);
}
 
 
 
如果已在文件的开头(在所有函数之前),已对本文件所调用的函数进行了声明,则在各函数中不必对其所调用的函数进行声明。
char letter(char,char);  /*以下三行在所有的函数之前,且在函数外部*/
float f(float,float);
int i(float,float);
 
void main()             /*在main函数中要调用letter、f和i函数*/
{                       /*不必对它所调用的这3个函数进行声明*/
 。
 。
 。
}
char letter(char c1,char c2)     /*定义letter函数*/
{
 。
 。
 。
}
float f(float x,float y)        /*定义f函数*/
{
 。
 。
 。
}
int i(float j,float k)          /*定义i函数*/
{
 。
 。
 。
}
由于在文件的开头已对要调用的函数进行了声明,因此编译系统从声明中已知道了函数的有关情况,所以不必在主调函数中进行声明。
 
 
如果被调用函数类型为整型,C语言允许在调用函数前不必作函数原型声明。
但是使用这种方法,系统无法对函数的个数和类型进行检查。若调用函数时参数使用不当,在编译时也不会报错,而在运行时出错。而且,用Turbo C和Visual C++时,要求对所有被调用函数进行声明,因此,为了程序清晰和安全及通过性,编写程序时最好都加上函数原型声明。
 
函数定义:
 
函数定义,又称为函数实现,应包括以下元素:
  1. 函数名
  2. 函数类型
  3. 参数列表
  4. 局部变量声明
  5. 函数语句
  6. 返回语句
 
所以这6个元素又可分组为两部分,即:
  • 函数头(前三个元素)
  • 函数体(后三个元素)
实现这两部分的函数定义的一般格式:
 
 function_type  function_name(parameter)
 {
  local variable  declaration;
  executable statement1;
  executable statement2;
  ...
  return statement;
 }
第一行
 function_type  function_name(parameter)
称为函数头,开括号与闭括号之间的语句组成称为函数体,它是一个复合语句。
 
函数头:由三部分组成:函数类型(也称返回类型)、函数名和形参列表。注意,在函数头的末尾不要使用分号。
函数名与类型:函数类型指定希望返回程序调用函数的值的类型(如float和double)。如果没有显示地指定返回类型,C假设为整型。如果函数不返回任何值,则需要将返回类型指定为void。函数名是任意合法的C标志符,因此,必须遵守C的变量名命令规则。函数名应与函数要完成的任务相适应。但是,应注意避免与库函数名或操作系统的命令重名。
 
 
 
 
实际参数和形式参数:
 
 
 函数的形参和实参具有以下特点:
1.形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。

2.实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。

3.实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。
函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。例可以说明这个问题。
void main()
{
int n;
printf("input number\n");
scanf("%d",&n);
s(n);
printf("n=%d\n",n);
}
int s(int n)
{
int i;
for(i=n-1;i>=1;i--)
n=n+i;
printf("n=%d\n",n);
}
本程序中定义了一个函数s,该函数的功能是求∑ni=1i 的值。在主函数中输入n值,并作为实参,在调用时传送给s 函数的形参量n( 注意,本例的形参变量和实参变量的标识符都为n, 但这是两个不同的量,各自的作用域不同)。 在主函数中用printf 语句输出一次n值,这个n值是实参n的值。在函数s中也用printf 语句输出了一次n值,这个n值是形参最后取得的n值0。从运行情况看,输入n值为100。即实参n的值为100。把此值传给函数s时,形参 n 的初值也为100,在执行函数过程中,形参n的值变为5050。 返回主函数之后,输出实参n的值仍为100。可见实参的值不随形参的变化而变化。
 
 
特别注意的是,在进行函数调用时,实参一定要有一个确定的值
 
 
main()
{
int biggest ,a,b;
int max(int m,int s);
biggest=max(a,b);
printf("%d\n",biggest);
}
int max(int m,int s)
{
int i;int a[60];
printf("Enter m:");
scanf("%d",&m);
for(i=o;i<m;i++)
scanf("%d",&a[i]);
for(i=1;i<=m;i++);
if(a[i]>s)s=a[i];
return(s);
}
这是一个我当时写的一个错误的程序。出错信息为Possible use of 'a'before definttion in function main。
这里没有给a,b赋值就使用了,应改为:
main()
{
 int biggest;
 int max();
 biggest=max();
 printf("%d\n",biggest);
}
 int max()
{
 int a[69];int m,s,i;
 printf("Enter m:");
 scanf("%d",&m);
 printf("\n");
 printf("Enter m nums:");
 for(i=0;i<m;i++)
 scanf("%d",&a[i]);
 s=a[0];
 for(i=1;i<=m;i++)
 if(a[i]>s)s=a[i];
 return(s);
}这样就行了。
 
返回值及其类型:
 
   函数可以返回任意值给调用函数。如果要返回值,则通过return语句来实现。尽管可以给被调用函数传递任意数量的值,但被调用函数在每次调用时最多只能返回一个值。
 
return语句可以是如下形式:
 return;
return(expression);
前一种形式不返回任意值,它只起函数的闭区间的作用。当遇到return语句时,控制权理解返回调用函数。
 
这种形式的一个示例如下:
if(error)
   return;
后一种形式是return语句加一个表达式,可以返回该表达式的值。例:
int mul(int a,int b)
{
 int p;
 p=x*y;
 return(p);
}
 
将返回P的值,它是X和Y的乘积。最后两条可以组成一条:
return(x*y);
函数可以有多条return语句。当要返回的值是基于某种条件时就会出现这种情况:
if(x<=0)
 return(0);
else
 return(1);
默认条件下,所有函数返回int类型的值。但是,如果函数要返回其它类型的值,该怎么办?通过在函数头中使用类型指示符,就可以强制函数返回特定的函数的值。
int product(void)
{
 return(2.5*3.0);
}
将返回结果值的整数部分7。
 
 
函数的类型:
 
  • 无参数无返回值的函数;
  • 有参数无返回值的函数;
  • 有参数有返回值的函数;
  • 无参数但有一个返回值的函数;
 
 
无参数无返回值的函数:
 
当函数没有参数时,不用从调用函数接收任何数据。同样,它也不返回值,调用函数不会从被调用函数中接收任意值。在调用函数和被调用函数间没有任意数据的传递,只有控制权的转移。
 
文章的开头就是一个典型的无参数无返回值的函数。
来看下面的这一个程序:
void printline(void);
void value(viod);         /*function declaration*/
  main()
 {
   printline();
   value();
   printline();
 }
 
 void printline(void)
{
 int i;
 for(i=1;i<=35;i++)
 printf("%c",'*');
printf("\n");
}
 
void value(void)
{
 int year,period;
 float inrate,sum,principal;
 printf("Principle amount?");
 scanf("%f",&principal);
 printf("Interest rate?");
 scanf("%f",&inrate);
 printf("Period?");
 scanf("%d",&period);
 
 
 sum=principal;
year=1;
while(year<=period)
{
  sum=sum*(1+inrate);
  year++;
}
 printf("\n%8.2f %5.2f %5d %12.2f\n",principal,inrate,period,sum);
}
 
需要重点注意的是,函数value直接从终端接收数据。输入数据包括本金数额、利率和要计算的期限。while循环计算最终值,结果由库函数printf显示出来。当运行到value()的闭括号时,控制权转移到调用函数main。由于数据本身已经完成了所有的事情,因此不需要给调用函数返回任何东西,printline和value的返回类型都声明为void.
注意这里没有使用return语句。当不返回任何值时,return语句是可选的。
 
 
 
有参数无返回值的函数:
 
在上一个程序中,函数printline在每一次调用时都将显示同一行。对value函数也是如此。其实,我们也可以让调用函数从终端读取数据,然后将它传递给被调用函数。这种方法看起来更时智些,因为如果有必要,在将数据传递给被调用函数之前,调用函数可以检查该数据的有效性。
我们可以如下修改这两个被调用函数,使之包含参数:
void printline(char ch);
void value(float f,float r,int n);
参数ch,p,r和n称为形参,现在,调用函数就可以使用带参数的函数调用来将值传递给参数了。
例如:函数调用:
value(500,0.12,5)
就可以把值500,0.12,5传递给函数
void value(float f,float r,int n);
并把500赋给p,0.12赋给r,5赋给n。值500,0.12和n就是实参,它们是被调用函数中形参的值。
 
下面我们来合修改上面介绍过的函数:
void printline(char c);
void value(float,float,int);
 
main()
{
  float principal,inrate;
  int period;
 
  printf("Enter principal amount,interest");
  printf("rate,and period\n");
  scanf("%f %f %d",&principal,&inrate,&period);
  printline('z');
  value(principal,inrate,period);
  printline('c');
}
 
void printline(char ch)
{
 int i;
 for(i=1;i<=52;i++)
  printf("%c",ch);
printf("\n");
}
 
 
void value(float p,float r,int n)
{
 int year;
 float sum;
 sum=p;
 year=1;
while(year<=n)
{
 sum=sum*(1+r);
 year++;
}
 printf("%f\t%f\t%d\t%f\n\n",p,r,n,sum);
}
 
声明在函数中的变量称为局部变量,因为它们仅限于函数内部,不能被其它函数访问。
函数value计算给定期限的最终数量,并显示出结果。运行到该函数的闭括号时,控制权转移回调用函数。value并没有返回值。
函数printline被调用了两次,第一次调用传递的是字符‘z‘,而第二次调用传用传递的是字符’c‘。这些字符都被赋值给形参ch用于显示输出行。
 
 
 
有参数有返回值的函数:
 
上一个函数value通过参数从调用函数中接收数据,但不返回任意值。而且,它在终端显示计算结果。但是,我们并不总是希望显示函数的结果。我们可能要在调用函数中使用它作进一步的处理。而且为了确保程序间的可移植性,函数往往编码为不含任何I/O操作。例如,不同程序可能要求不同的输入格式以显示结果。这些缺点可以这样来克服:把函数的结果传递给调用函数,在调用函数中,返回值可以被程序按要求处理。
 
按要求修改后的程序:一个主要修改是把printf语句从value函数移到了main函数中。
void printline(char ch,int len);
       value(float,float,int);
 
main()
{
 float principal,inrate,amount;
 int period;
 printf("Enter principal amount,interest");
 printf("rate,and period\n");
scanf("%f%f%d",&principal,&inrate,&interest");
printline('*',52);
amount=value(principal,inrate,period);
printf("\n%f\t%f\t%d\t%f\n\n",principal,inrate,period,amount);
printline('=',52);
}
void printline(char ch,int len)
{
 int i;
 for(i=1;i<=len;i++)
printf("%c",ch);
printf("\n");
}
 
value(float p,float r,int n)
{
int year;
float sum;
sum=p;year=1;
while(year<=n)
{
 sum=sum*(1+r);
 year++;
}
return(sum);
}
计算后的值通过以下的语句传递给main函数:
return(sum);
 
由于在缺省情况下value函数的返回值为int .sum的整型值返回给main函数,并通过如下的函数调用语句把值赋给变量amount:
amount=value(printcipal,inrate,period);
 
当上面的函数调用语句运行时,将依次发生以下事件:
(1)函数调用语句把控制权连同实参principal,inrate和period的值副本传递给value函数,该函数把实参值赋给形参p,r和n;
(2)被调用函数按正常方式逐行运行,直到遇到return(sum)语句。此时,sum的整型值返回给main中的调用函数,并进行下面的间接赋值语句:
value(principal,inrate,period)=sum;
(3)调用函数正常运行,因此返回值赋给浮点变量amount。
(4)由于amount是浮点变量,返回值sum的整数部分转换为浮点值。
    另一个重要的修改是给printline函数加了第二个函数,以接收来自调用函数的显示行的长度值。
因此,调用函数:printline('*'52);将把控制权转移给函数printline,然后把下面的值赋给形参ch和len:
ch='*';
len=52;
 
 
无参数但有一个返回值的函数:
 
有时,我们可能需要这样的函数:无参数但返回一个值给调用函数。声明在关文件<stdio.h>中的getchar函数就是一个典型的例子。
int get_numuber(void);
main()
{
 int m=get_number();
printf("%d",m);
}
int get_number(void)
{
 int number;
 scanf("%d",&number);
 return(number);
}
 
 
 
 
函数的嵌套:
 
 
 C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。 但是C语言允许在一个函数的定义中出现对另一个函数的调用。 这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。 这与其它语言的子程序嵌套的情形是类似的。
其执行过程是:执行main函数中调用a函数的语句时,即转去执行a函数,在a函数中调用b 函数时,又转去执行b函数,b函数执行完毕返回a函数的断点继续执行,a 函数执行完毕返回main函数的断点继续执行。
float ratio(int,int,int);
int difference(int,int);
main()
{
 int a,b,c;
 scanf("%d%d%d",&a,&b,&c);
 printf("%f \n",ratio(a,b,c));
}
float ratio(int x,int y,int z)
{
 if(difference(y,z))
 return(x/(y-z));
 else
 return(0.0);
}
int difference(int p,int q)
{
 if(p!=q)
 return(1);
 else
 return(0);
}
main函数读取a,b和c的值,并调用ratio函数,该函数计算a/(b-c)的值。如果(b-c)=0,那么ratio就不能计算。因此,ratio函数调用了另外一个函数difference,以测试(b-c)的差是否为0。
 
 
 
 
函数的递归调用

 
 
 一个函数在它的函数体内调用它自身称为递归调用。 这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中, 主调函数又是被调函数。执行递归函数将反复调用其自身。 每调用一次就进入新的一层。例如有函数f如下:
int f (int x)
{
int y;
z=f(y);
return z;
}
  这个函数是一个递归函数。 但是运行该函数将无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行, 必须在函数内有终止递归调用的手段。常用的办法是加条件判断, 满足某种条件后就不再作递归调用,然后逐层返回。
#include<stdio.h>
void main()
{
 printf("%d\n",age(5));
}
int age(int n)
{
 int c;
 if(n==1)
 c=10;
 else
 c=age(n-1)+2;
 return(c);
}
 
 
age函数共被调用了5次,即age(5),age(4),age(3),age(2),age(1).其中age(5)是main函数调用的,其余四次是在age函数中调用的,即递归调用了4次。注意:在某一次调用age函数地并不是立即得到age(n)的值,而是一次又一次地进行递归调用,到age(1)时才有确定的值,然后再递推出age(2),age(3),age(4),age(5)。
 
再来看一个例题:
用递归方法求n!.
#include<stdio.h>
void main()
{
 flaot fac(int n);
 int n;
float y;
printf("input an integer number:");
scanf("%d",&n);
y=fac(n);
printf("%d!=%10.0f\n",n,y);
}
 
float fac(int n)
{
 float f;
 if(n<0)
 {
 printf("n<0,dataerror!");
 }
else
  if(n==0||n==1)
  f=1;
else
 f=fac(n-1)*n;
return(f);
}
 
或:
#include<stdio.h>
#include<stdlib.h>
 int fact(int num);
main()
{
 int num;
printf("\nPlease input a number:");
scanf("%d",&sum);
 
if(num<0)
  printf("\NERROR-number must be >=0\n");
else
printf("\n\Factorial of %d is %d.\n",sum,fact(num));
return EXIT_SUCCESS;
}
 
int fact(int num)
{
 if(num<=1)
  return 1;
 else
  return num*fact(num-1);
}
 
 
 
 
数组作为函数参数:
 
一个是用数组元素作函数实参其用法与变量相同。此外,数组名也可以作实参和形参,传递的数据是数组(首元素的地址)。
用数组名作函数参数与用数组元素作实参有几点不同:
1. 用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此, 并不要求函数的形参也是下标变量。 换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时, 则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。

2. 在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢? 数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送, 也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。存单元(整型数组每个元素占二字节)。例如a[0]和b[0]都占用2000和2001单元,当然a[0]等于b[0]。类推则有a[i]等于b[i]。
有一个一维数组score,内放10个学生成绩,求平均成绩。
#include<stdio.h>
void main()
{
 float average(float array[10]);
 float score[10],aver;
 int i;
printf("input 10 score:\n");
for(i=0;i<10;i++)
  scanf("%f",&score);
printf("\n");
aver=average(score);
printf("average score is %5.2f\n",aver);
}
 
float average(float array[10])
{
 int i;
 float aver,sum=array[0];
for(i=1;i<10;i++)
sum=sum+array[i];
aver=sum/10;
return(aver);
}
 
 
变量的存储方式可分为“静态存储”和“动态存储”两种。

  静态存储变量通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。5.5.1节中介绍的全局变量即属于此类存储方式。动态存储变量是在程序执行过程中,使用它时才分配存储单元, 使用完毕立即释放。 典型的例子是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时,才予以分配, 调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、 释放形参变量的存储单元。从以上分析可知, 静态存储变量是一直存在的, 而动态存储变量则时而存在时而消失。我们又把这种由于变量存储方式不同而产生的特性称变量的生存期。 生存期表示了变量存在的时间。 生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系,又有区别。 一个变量究竟属于哪一种存储方式, 并不能仅从其作用域来判断,还应有明确的存储类型说明。

  在C语言中,对变量的存储类型说明有以下四种:
auto     自动变量
register   寄存器变量
extern    外部变量
static    静态变量
  自动变量和寄存器变量属于动态存储方式, 外部变量和静态变量属于静态存储方式。在介绍了变量的存储类型之后, 可以知道对一个变量的说明不仅应说明其数据类型,还应说明其存储类型。 因此变量说明的完整形式应为: 存储类型说明符 数据类型说明符 变量名,变量名…; 例如:
static int a,b;           说明a,b为静态类型变量
auto char c1,c2;          说明c1,c2为自动字符变量
static int a[5]={1,2,3,4,5};    说明a为静整型数组
extern int x,y;           说明x,y为外部整型变量
下面分别介绍以上四种存储类型:

一、自动变量的类型说明符为auto。
  这种存储类型是C语言程序中使用最广泛的一种类型。C语言规定, 函数内凡未加存储类型说明的变量均视为自动变量, 也就是说自动变量可省去说明符auto。 在前面各章的程序中所定义的变量凡未加存储类型说明符的都是自动变量。例如:
{ int i,j,k;
char c;
……
}等价于: { auto int i,j,k;
auto char c;
……
}
  自动变量具有以下特点:
1. 自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量只在该复合语句中有效。 例如:
int kv(int a)
{
auto int x,y;
{ auto char c;
} /*c的作用域*/
……
} /*a,x,y的作用域*/

2. 自动变量属于动态存储方式,只有在使用它,即定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。例如以下程序:
main()
{ auto int a,s,p;
printf("\ninput a number:\n");
scanf("%d",&a);
if(a>0){
s=a+a;
p=a*a;
}
printf("s=%d p=%d\n",s,p);
}

s,p是在复合语句内定义的自动变量,只能在该复合语句内有效。而程序的第9行却是退出复合语句之后用printf语句输出s,p的值,这显然会引起错误。

3. 由于自动变量的作用域和生存期都局限于定义它的个体内( 函数或复合语句内), 因此不同的个体中允许使用同名的变量而不会混淆。 即使在函数内定义的自动变量也可与该函数内部的复合语句中定义的自动变量同名。例5.14表明了这种情况。
[例5.14]
main()
{
auto int a,s=100,p=100;
printf("\ninput a number:\n");
scanf("%d",&a);
if(a>0)
{
auto int s,p;
s=a+a;
p=a*a;
printf("s=%d p=%d\n",s,p);
}
printf("s=%d p=%d\n",s,p);
}
  本程序在main函数中和复合语句内两次定义了变量s,p为自动变量。按照C语言的规定,在复合语句内,应由复合语句中定义的s,p起作用,故s的值应为a+ a,p的值为a*a。退出复合语句后的s,p 应为main所定义的s,p,其值在初始化时给定,均为100。从输出结果可以分析出两个s和两个p虽变量名相同, 但却是两个不同的变量。

4. 对构造类型的自动变量如数组等,不可作初始化赋值。

二、外部变量外部变量的类型说明符为extern。

在前面介绍全局变量时已介绍过外部变量。这里再补充说明外部变量的几个特点:
1. 外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生存期。

2. 当一个源程序由若干个源文件组成时, 在一个源文件中定义的外部变量在其它的源文件中也有效。例如有一个源程序由源文件F1.C和F2.C组成: F1.C
int a,b; /*外部变量定义*/
char c; /*外部变量定义*/
main()
{
……
}
F2.C
extern int a,b; /*外部变量说明*/
extern char c; /*外部变量说明*/
func (int x,y)
{
……
}
在F1.C和F2.C两个文件中都要使用a,b,c三个变量。在F1.C文件中把a,b,c都定义为外部变量。在F2.C文件中用extern把三个变量说明为外部变量,表示这些变量已在其它文件中定义,并把这些变量的类型和变量名,编译系统不再为它们分配内存空间。 对构造类型的外部变量, 如数组等可以在说明时作初始化赋值,若不赋初值,则系统自动定义它们的初值为0。
静态变量

  静态变量的类型说明符是static。 静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量, 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态外部变量,或称静态全局变量。 对于自动变量,前面已经介绍它属于动态存储方式。 但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。
由此看来, 一个变量可由static进行再说明,并改变其原有的存储方式。

1. 静态局部变量
  在局部变量的说明前再加上static说明符就构成静态局部变量。
例如:
static int a,b;
static float array[5]={1,2,3,4,5};
  
  静态局部变量属于静态存储方式,它具有以下特点:
(1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。

(2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。

(3)允许对构造类静态局部量赋初值。在数组一章中,介绍数组初始化时已作过说明。若未赋以初值,则由系统自动赋以0值。

(4)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。 根据静态局部变量的特点, 可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。
[例5.15]main()
{
int i;
void f(); /*函数说明*/
for(i=1;i<=5;i++)
f(); /*函数调用*/
}
void f() /*函数定义*/
{
auto int j=0;
++j;
printf("%d\n",j);
}
  程序中定义了函数f,其中的变量j 说明为自动变量并赋予初始值为0。当main中多次调用f时,j均赋初值为0,故每次输出值均为1。现在把j改为静态局部变量,程序如下:
main()
{
int i;
void f();
for (i=1;i<=5;i++)
f();
}
void f()
{
static int j=0;
++j;
printf("%d\n",j);
}
void f()
{
static int j=0;
++j;
printf("%d/n",j);
}

由于j为静态变量,能在每次调用后保留其值并在下一次调用时继续使用,所以输出值成为累加的结果。读者可自行分析其执行过程。

2.静态全局变量
  全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它
的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。

四、寄存器变量

  上述各类变量都存放在存储器内, 因此当对一个变量频繁读写时,必须要反复访问内存储器,从而花费大量的存取时间。 为此,C语言提供了另一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写, 这样可提高效率。寄存器变量的说明符是register。 对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量。
[例5.16]求∑200i=1imain()
{
register i,s=0;
for(i=1;i<=200;i++)
s=s+i;
printf("s=%d\n",s);
}
本程序循环200次,i和s都将频繁使用,因此可定义为寄存器变量。
对寄存器变量还要说明以下几点:

1. 只有局部自动变量和形式参数才可以定义为寄存器变量。因为寄存器变量属于动态存储方式。凡需要采用静态存储方式的量不能定义为寄存器变量。

2. 在Turbo C,MS C等微机上使用的C语言中, 实际上是把寄存器变量当成自动变量处理的。因此速度并不能提高。 而在程序中允许使用寄存器变量只是为了与标准C保持一致。3. 即使能真正使用寄存器变量的机器,由于CPU 中寄存器的个数是有限的,因此使用寄存器变量的个数也是有限的。
 

发表于: 2007-12-12,修改于: 2008-01-06 15:06,已浏览194次,有评论0条 推荐 投诉


网友评论
 发表评论