返回列表 发帖

C语言初学者入门讲座 第七讲 循环结构

循环结构是程序中一种很重要的结构。其特点是, 在给定条件成立时,反复执行某程序段,直到条件不成立为止。 给定的条件称为循环条件,反复执行的程序段称为循环体。 C语言提供了多种循环语句,可以组成各种不同形式的循环结构。

  while语句

  while语句的一般形式为: while(表达式)语句; 其中表达式是循环条件,语句为循环体。

  while语句的语义是:计算表达式的值,当值为真(非0)时, 执行循环体语句。其执行过程可用图3—4表示。 统计从键盘输入一行字符的个数。

#include <stdio.h>
void main(){
int n=0;
printf("input a string:\n");
while(getchar()!='\n') n++;
printf("%d",n);
} int n=0;
printf("input a string:\n");
while(getchar()!='\n')
n++;
printf("%d",n);  

  本例程序中的循环条件为getchar()!='\n',其意义是, 只要从键盘输入的字符不是回车就继续循环。循环体n++完成对输入字符个数计数。从而程序实现了对输入一行字符的字符个数计数。

  使用while语句应注意以下几点:

  1.while语句中的表达式一般是关系表达或逻辑表达式,只要表达式的值为真(非0)即可继续循环。

void main(){
 int a=0,n;
 printf("\n input n: ");
 scanf("%d",&n);
 while (n--)
 printf("%d ",a++*2);
} int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
while (n--)
printf("%d ",a++*2);  

  本例程序将执行n次循环,每执行一次,n值减1。循环体输出表达式a++*2的值。该表达式等效于(a*2;a++)

  2.循环体如包括有一个以上的语句,则必须用{}括起来, 组成复合语句。

  3.应注意循环条件的选择以避免死循环。

void main(){
 int a,n=0;
 while(a=5)
  printf("%d ",n++);
} int a,n=0;
while(a=5)
printf("%d ",n++);  

  本例中while语句的循环条件为赋值表达式a=5, 因此该表达式的值永远为真,而循环体中又没有其它中止循环的手段, 因此该循环将无休止地进行下去,形成死循环。4.允许while语句的循环体又是while语句,从而形成双重循环。

  do-while语句

  do-while语句的一般形式为:

do
 语句;
while(表达式);

  其中语句是循环体,表达式是循环条件。

  do-while语句的语义是:

  先执行循环体语句一次, 再判别表达式的值,若为真(非0)则继续循环,否则终止循环。

  do-while语句和while语句的区别在于do-while是先执行后判断,因此do-while至少要执行一次循环体。而while是先判断后执行,如果条件不满足,则一次循环体语句也不执行。

  while语句和do-while语句一般都可以相互改写。

void main(){
 int a=0,n;
 printf("\n input n: ");
 scanf("%d",&n);
 do printf("%d ",a++*2);
 while (--n);
}
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
do printf("%d ",a++*2);
while (--n);

  在本例中,循环条件改为--n,否则将多执行一次循环。这是由于先执行后判断而造成的。

  对于do-while语句还应注意以下几点:

  1.在if语句,while语句中, 表达式后面都不能加分号, 而在 do-while语句的表达式后面则必须加分号。

  2.do-while语句也可以组成多重循环,而且也可以和while语句相互嵌套。

  3.在do和while之间的循环体由多个语句组成时,也必须用{}括起来组成一个复合语句。

  4.do-while和while语句相互替换时,要注意修改循环控制条件。

  for语句

  for语句是C语言所提供的功能更强,使用更广泛的一种循环语句。其一般形式为:

for(表达式1;表达式2;表达3)  

  语句:

  表达式1 通常用来给循环变量赋初值,一般是赋值表达式。也允许在for语句外给循环变量赋初值,此时可以省略该表达式。

  表达式2 通常是循环条件,一般为关系表达式或逻辑表达式。

  表达式3 通常可用来修改循环变量的值,一般是赋值语句。

  这三个表达式都可以是逗号表达式, 即每个表达式都可由多个表达式组成。三个表达式都是任选项,都可以省略。

  一般形式中的“语句”即为循环体语句。for语句的语义是:

  1.首先计算表达式1的值。

  2.再计算表达式2的值,若值为真(非0)则执行循环体一次, 否则跳出循环。

  3.然后再计算表达式3的值,转回第2步重复执行。在整个for循环过程中,表达式1只计算一次,表达式2和表达式,3则可能计算多次。循环体可能多次执行,也可能一次都不执行。for 语句的执行过程如图所示。

void main(){
int n,s=0;
for(n=1;n<=100;n++)
s=s+n;
printf("s=%d\n",s);
}  

  用for语句计算s=1+2+3+...+99+100

int n,s=0;
for(n=1;n<=100;n++)
s=s+n;
printf("s=%d\n",s);  

  本例for语句中的表达式3为n++,实际上也是一种赋值语句,相当于n=n+1,以改变循环变量的值。

void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;n>0;a++,n--)
printf("%d ",a*2);
}

  用for语句修改例题。从0开始,输出n个连续的偶数。

int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;n>0;a++,n--)
printf("%d ",a*2);

  本例的for语句中,表达式1已省去,循环变量的初值在for语句之前由scanf语句取得,表达式3是一个逗号表达式,由a++,n-- 两个表达式组成。每循环一次a自增1,n自减1。a的变化使输出的偶数递增,n的变化控制循次数。

  在使用for语句中要注意以下几点

  1.for语句中的各表达式都可省略,但分号间隔符不能少。如:for(;表达式;表达式)省去了表达式1。for(表达式;;表达式)省去了表达式2。

  for(表达式;表达式;)省去了表达式3。for(;;)省去了全部表达式。

  2.在循环变量已赋初值时,可省去表达式1,如例3.27即属于这种情形。如省去表达式2或表达式3则将造成无限循环, 这时应在循环体内设法结束循环。例题即属于此情况。

void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;n>0;)
{ a++;n--;
 printf("%d ",a*2);
}
} int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;n>0;)
{ a++;n--;
printf("%d ",a*2);
}


  本例中省略了表达式1和表达式3,由循环体内的n--语句进行循环变量n的递减,以控制循环次数。

void main(){
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;;){
a++;n--;
printf("%d ",a*2);
if(n==0)break;
}
}
int a=0,n;
printf("\n input n: ");
scanf("%d",&n);
for(;;){
a++;n--;
printf("%d ",a*2);
if(n==0)break;
}

  本例中for语句的表达式全部省去。由循环体中的语句实现循环变量的递减和循环条件的判断。当n值为0时,由break语句中止循环,转去执行for以后的程序。在此情况下,for语句已等效于while( 1)语句。如在循环体中没有相应的控制手段,则造成死循环。

  3.循环体可以是空语句。

#include"stdio.h"
void main(){
 int n=0;
 printf("input a string:\n");
 for(;getchar()!='\n';n++);
  printf("%d",n);
}

  本例中,省去了for语句的表达式1,表达式3也不是用来修改循环变量,而是用作输入字符的计数。这样, 就把本应在循环体中完成的计数放在表达式中完成了。因此循环体是空语句。应注意的是,空语句后的分号不可少,如缺少此分号,则把后面的printf 语句当成循环体来执行。反过来说,如循环体不为空语句时, 决不能在表达式的括号后加分号, 这样又会认为循环体是空语句而不能反复执行。这些都是编程中常见的错误,要十分注意。

  4.for语句也可与while,do-while语句相互嵌套,构成多重循环。以下形成都合法的嵌套。

(1)for(){…
  while()
   {…}
  …
    }
(2)do{
   …
  for()
   {…}
  …
  }while();
(3)while(){
      …
      for()
       {…}
      …
     }
(4)for(){
    …
    for(){
    …
     }
    }
void main(){
int i,j,k;
for(i=1;i<=3;i++)
{
 for(j=1;j<=3-i+5;j++)
  printf(" ");
  for(k=1;k<=2*i-1+5;k++)
  {
   if(k<=5) printf(" ");
   else printf("*");
  }
  printf("\n");
 }
}

TOP

C语言初学者入门讲座 第八讲 转移语句

程序中的语句通常总是按顺序方向, 或按语句功能所定义的方向执行的。如果需要改变程序的正常流向, 可以使用本小节介绍的转移语句。在C语言中提供了4种转移语句:

  goto,break, continue和return。

  其中的return语句只能出现在被调函数中, 用于返回主调函数,我们将在函数一章中具体介绍。 本小节介绍前三种转移语句。

  1.goto语句

  goto语句也称为无条件转移语句,其一般格式如下: goto 语句标号; 其中语句标号是按标识符规定书写的符号, 放在某一语句行的
前面,标号后加冒号(:)。语句标号起标识语句的作用,与goto 语句配合使用。

  如: label: i++;
    loop: while(x<7);

  C语言不限制程序中使用标号的次数,但各标号不得重名。goto语句的语义是改变程序流向, 转去执行语句标号所标识的语句。

  goto语句通常与条件语句配合使用。可用来实现条件转移, 构成循环,跳出循环体等功能。

  但是,在结构化程序设计中一般不主张使用goto语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难。

  统计从键盘输入一行字符的个数。

#include"stdio.h"
void main(){
 int n=0;
 printf("input a string\n");
 loop: if(getchar()!='\n')
 {
  n++;
  goto loop;
 }
 printf("%d",n);
} int n=0;
printf("input a string\n");
loop: if(getchar()!='\n')
{
 n++;
 goto loop;
}
printf("%d",n);  

  本例用if语句和goto语句构成循环结构。当输入字符不为'\n'时即执行n++进行计数,然后转移至if语句循环执行。直至输入字符为'\n'才停止循环。

  break语句

  break语句只能用在switch 语句或循环语句中, 其作用是跳出switch语句或跳出本层循环,转去执行后面的程序。由于break语句的转移方向是明确的,所以不需要语句标号与之配合。break语句的一般形式为: break; 上面例题中分别在switch语句和for语句中使用了break 语句作为跳转。使用break语句可以使循环语句有多个出口,在一些场合下使编程更加灵活、方便。

  continue语句

  continue语句只能用在循环体中,其一般格式是:

continue;

  其语义是:结束本次循环,即不再执行循环体中continue 语句之后的语句,转入下一次循环条件的判断与执行。应注意的是, 本语句只结束本层本次的循环,并不跳出循环。

void main(){
 int n;
 for(n=7;n<=100;n++)
 {
  if (n%7!=0)
   continue;
  printf("%d ",n);
 }
}

  输出100以内能被7整除的数。

int n;
for(n=7;n<=100;n++)
{
 if (n%7!=0)
  continue;
 printf("%d ",n);
}

  本例中,对7~100的每一个数进行测试,如该数不能被7整除,即模运算不为0,则由continus语句转去下一次循环。只有模运算为0时,才能执行后面的printf语句,输出能被7整除的数。

#include"stdio.h"
void main(){
 char a,b;
 printf("input a string:\n");
 b=getchar();
 while((a=getchar())!='\n'){
  if(a==b){
   printf("same character\n");
   break;
  }b=a;
 }
}

  检查输入的一行中有无相邻两字符相同。

char a,b;
printf("input a string:\n");
b=getchar();
while((a=getchar())!='\n'){
 if(a==b){
  printf("same character\n");
  break;
 }b=a;
}

  本例程序中,把第一个读入的字符送入b。然后进入循环,把下一字符读入a,比较a,b是否相等,若相等则输出提示串并中止循环,若不相等则把a中的字符赋予b,输入下一次循环。

  输出100以内的素数。素数是只能被1 和本身整除的数。可用穷举法来判断一个数是否是素数。

void main(){
 int n,i;
 for(n=2;n<=100;n++){
  for(i=2;i<n;i++)
   if(n%i==0) break;
   if(i>=n) printf("\t%d",n);
  }
 } int n,i;
 for(n=2;n<=100;n++){
  for(i=2;i<n;i++)
   if(n%i==0) break;
   if(i>=n) printf("\t%d",n);
}

  本例程序中,第一层循环表示对1~100这100个数逐个判断是否是素数,共循环100次,在第二层循环中则对数n用2~n-1逐个去除,若某次除尽则跳出该层循环,说明不是素数。 如果在所有的数都是未除尽的情况下结束循环,则为素数,此时有i>=n, 故可经此判断后输出素数。然后转入下一次大循环。实际上,2以上的所有偶数均不是素数,因此可以使循环变量的步长值改为2,即每次增加2,此外只需对数n用2~n去除就可判断该数是否素数。这样将大大减少循环次数,减少程序运行时间。

#include"math.h"
void main(){
 int n,i,k;
 for(n=2;n<=100;n+=2){
  k=sqrt(n);
  for(i=2;i<k;i++)
   if(n%i==0) break;
   if(i>=k) printf("\t%2d",n);
 }
}

TOP

C语言初学者入门讲座 第九讲 数组(1)

数组在程序设计中,为了处理方便, 把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称为数组。在C语言中, 数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。

  本章介绍数值数组和字符数组,其余的在以后各章陆续介绍。数组类型说明 在C语言中使用数组必须先进行类型说明。 数组说明的一般形式为:

  类型说明符 数组名 [常量表达式],……;

  其中,类型说明符是任一种基本数据类型或构造数据类型。 数组名是用户定义的数组标识符。 方括号中的常量表达式表示数据元素的个数,也称为数组的长度。

  例如:

int a[10]; 说明整型数组a,有10个元素。
float b[10],c[20]; 说明实型数组b,有10个元素,实型数组c,有20个元素。
char ch[20]; 说明字符数组ch,有20个元素。

  对于数组类型说明应注意以下几点:

  1.数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素的数据类型都是相同的。

  2.数组名的书写规则应符合标识符的书写规定。

  3.数组名不能与其它变量名相同,例如:

void main()
{
 int a;
 float a[10];
 ……
}

  是错误的。

  4.方括号中常量表达式表示数组元素的个数,如a[5]表示数组a有5个元素。但是其下标从0开始计算。因此5个元素分别为a[0],a[1],a[2],a[3],a[4]。

  5.不能在方括号中用变量来表示元素的个数, 但是可以是符号常数或常量表达式。例如:

#define FD 5
void main()
{
 int a[3+2],b[7+FD];
 ……
}

  是合法的。但是下述说明方式是错误的。

void main()
{
 int n=5;
 int a[n];
 ……
}

  6.允许在同一个类型说明中,说明多个数组和多个变量。

  例如: int a,b,c,d,k1[10],k2[20];

  数组元素的表示方法

  数组元素是组成数组的基本单元。数组元素也是一种变量, 其标识方法为数组名后跟一个下标。 下标表示了元素在数组中的顺序号。数组元素的一般形式为: 数组名[下标] 其中的下标只能为整型常量或整型表达式。如为小数时,C编译将自动取整。例如,a[5],a[i+j],a[i++]都是合法的数组元素。 数组元素通常也称为下标变量。必须先定义数组, 才能使用下标变量。在C语言中只能逐个地使用下标变量, 而不能一次引用整个数组。 例如,输出有10 个元素的数组必须使用循环语句逐个输出各下标变量:

for(i=0; i<10; i++)  printf("%d",a);  

  而不能用一个语句输出整个数组,下面的写法是错误的:

printf("%d",a);

void main()
{
 int i,a[10];
 for(i=0;i<10;)
  a[i++]=2*i+1;
 for(i=9;i>=0;i--)
  printf("%d",a);
 printf("\n%d %d\n",a[5.2],a[5.8]);
}
for(i=0;i<10;)
 a[i++]=2*i+1;
for(i=9;i>=0;i--)
 printf("%d",a);
printf("\n%d %d\n",a[5.2],a[5.8]);  

  本例中用一个循环语句给a数组各元素送入奇数值,然后用第二个循环语句从大到小输出各个奇数。在第一个 for语句中,表达式3省略了。在下标变量中使用了表达式i++,用以修改循环变量。当然第二个for语句也可以这样作, C语言允许用表达式表示下标。 程序中最后一个printf语句输出了两次a[5]的值, 可以看出当下标不为整数时将自动取整。数组的赋值给数组赋值的方法除了用赋值语句对数组元素逐个赋值外, 还可采用初始化赋值和动态赋值的方法。数组初始化赋值数组初始化赋值是指在数组说明时给数组元素赋予初值。 数组初始化是在编译阶段进行的。这样将减少运行时间,提高效率。

  初始化赋值的一般形式为: static 类型说明符 数组名[常量表达式]={值,值……值}; 其中static表示是静态存储类型, C语言规定只有静态存储数组和外部存储数组才可作初始化赋值(有关静态存储,外部存储的概念在第五章中介绍)。在{ }中的各数据值即为各元素的初值, 各值之间用逗号间隔。例如: static int a[10]={ 0,1,2,3,4,5,6,7,8,9 }; 相当于a[0]=0;a[1]=1...a[9]=9;

  C语言对数组的初始赋值还有以下几点规定:

  1.可以只给部分元素赋初值。当{ }中值的个数少于元素个数时,只给前面部分元素赋值。例如: static int a[10]={0,1,2,3,4};表示只给a[0]~a[4]5个元素赋值,而后5个元素自动赋0值。

  2.只能给元素逐个赋值,不能给数组整体赋值。 例如给十个元素全部赋1值,只能写为:

static int a[10]={1,1,1,1,1,1,1,1,1,1};

  而不能写为:

static int a[10]=1;

  3.如不给可初始化的数组赋初值,则全部元素均为0值。

  4.如给全部元素赋值,则在数组说明中, 可以不给出数组元素的个数。例如:
  
static int a[5]={1,2,3,4,5};
  
  可写为:

static int a[]={1,2,3,4,5};
  
  动态赋值可以在程序执行过程中,对数组作动态赋值。 这时可用循环语句配合scanf函数逐个对数组元素赋值。

void main()
{
 int i,max,a[10];
 printf("input 10 numbers:\n");
 for(i=0;i<10;i++)
  scanf("%d",&a);
 max=a[0];
 for(i=1;i<10;i++)
  if(a>max) max=a;
   printf("maxmum=%d\n",max);
}
for(i=0;i<10;i++)
 scanf("%d",&a);
max=a[0];
for(i=1;i<10;i++)
 if(a>max) max=a;
  printf("maxmum=%d\n",max);

  本例程序中第一个for语句逐个输入10个数到数组a中。 然后把a[0]送入max中。在第二个for语句中,从a[1]到a[9]逐个与max中的内容比较,若比max的值大,则把该下标变量送入max中,因此max总是在已比较过的下标变量中为最大者。比较结束,输出max的值。

void main()
{
 int i,j,p,q,s,a[10];
 printf("\n input 10 numbers:\n");
 for(i=0;i<10;i++)
  scanf("%d",&a);
 for(i=0;i<10;i++){
  p=i;q=a;
  for(j=i+1;j<10;j++)
  if(q<a[j]) { p=j;q=a[j]; }
  if(i!=p)
  {
   s=a;
   a=a[p];
   a[p]=s;
  }
  printf("%d",a);
 }
}
for(i=0;i<10;i++)
 scanf("%d",&a);
for(i=0;i<10;i++){
 p=i;q=a;
 for(j=i+1;j<10;j++)
 if(q<a[j]) { p=j;q=a[j]; }
 if(i!=p)
 {
  s=a;
  a=a[p];
  a[p]=s;
 }
 printf("%d",a);
}

  本例程序中用了两个并列的for循环语句,在第二个for 语句中又嵌套了一个循环语句。第一个for语句用于输入10个元素的初值。第二个for语句用于排序。本程序的排序采用逐个比较的方法进行。在i次循环时,把第一个元素的下标i赋于p,而把该下标变量值a赋于q。然后进入小循环,从a[i+1]起到最后一个元素止逐个与a作比较,有比a大者则将其下标送p,元素值送q。 一次循环结束后,p即为最大元素的下标,q则为该元素值。若此时i≠p,说明p,q值均已不是进入小循环之前所赋之值,则交换a和a[p]之值。 此时a为已排序完毕的元素。输出该值之后转入下一次循环。对i+1以后各个元素排序。

TOP

C语言初学者入门讲座 第九讲 数组(2)

二维数组

  前面介绍的数组只有一个下标,称为一维数组, 其数组元素也称为单下标变量。在实际问题中有很多量是二维的或多维的, 因此C语言允许构造多维数组。多维数组元素有多个下标, 以标识它在数组中的位置,所以也称为多下标变量。 本小节只介绍二维数组,多维数组可由二维数组类推而得到。二维数组类型说明二维数组类型说明的一般形式是:

类型说明符 数组名[常量表达式1][常量表达式2]…;

  其中常量表达式1表示第一维下标的长度,常量表达式2 表示第二维下标的长度。例如:

int a[3][4];

  说明了一个三行四列的数组,数组名为a,其下标变量的类型为整型。该数组的下标变量共有3×4个,即:

a[0][0],a[0][1],a[0][2],a[0][3]
a[1][0],a[1][1],a[1][2],a[1][3]
a[2][0],a[2][1],a[2][2],a[2][3]

  二维数组在概念上是二维的,即是说其下标在两个方向上变化, 下标变量在数组中的位置也处于一个平面之中, 而不是象一维数组只是一个向量。但是,实际的硬件存储器却是连续编址的, 也就是说存储器单元是按一维线性排列的。 如何在一维存储器中存放二维数组,可有两种方式:一种是按行排列, 即放完一行之后顺次放入第二行。另一种是按列排列, 即放完一列之后再顺次放入第二列。在C语言中,二维数组是按行排列的。 在图4.1中,按行顺次存放,先存放a[0]行,再存放a[1]行,最后存放a[2]行。每行中有四个元素也是依次存放。由于数组a说明为

  int类型,该类型占两个字节的内存空间,所以每个元素均占有两个 字节(图中每一格为一字节)。

  二维数组元素的表示方法

  二维数组的元素也称为双下标变量,其表示的形式为: 数组名[下标][下标] 其中下标应为整型常量或整型表达式。例如: a[3][4] 表示a数组三行四列的元素。下标变量和数组说明在形式中有些相似,但这两者具有完全不同的含义。 数组说明的方括号中给出的是某一维的长度,即可取下标的最大值; 而数组元素中的下标是该元素在数组中的位置标识。前者只能是常量, 后者可以是常量,变量或表达式。

  一个学习小组有5个人,每个人有三门课的考试成绩。求全组分科的平均成绩和各科总平均成绩。

课程 成绩姓名 Math C DBASE
张      80  75 92
王      61  65 71
李      59  63 70
赵      85  87 90
周      76  77 85

  可设一个二维数组a[5][3]存放五个人三门课的成绩。再设一个一维数组v[3]存放所求得各分科平均成绩,设变量l为全组各科总平均成绩。编程如下:

void main()
{
int i,j,s=0,l,v[3],a[5][3];
printf("input score\n");
for(i=0;i<3;i++){
for(j=0;j<5;j++)
{ scanf("%d",&a[j]);
s=s+a[j];}
v=s/5;
s=0;
}
l=(v[0]+v[1]+v[2])/3;
printf("math:%d\nc languag:%d\ndbase:%d\n",v[0],v[1],v[2]);
printf("total:%d\n",l);
} for(i=0;j<3;i++)
for(j=0;j<5;j++)
{ scanf("%d",&a[j]);
s=s+a[j];}
v=s/5;
s=0;
}
l=(v[0]+v[1]+v[2])/3;

  程序中首先用了一个双重循环。 在内循环中依次读入某一门课程的各个学生的成绩,并把这些成绩累加起来, 退出内循环后再把该累加成绩除以5送入v之中,这就是该门课程的平均成绩。外循环共循环三次,分别求出三门课各自的平均成绩并存放在v数组之中。退出外循环之后,把v[0],v[1],v[2]相加除以3即得到各科总平均成绩。最后按题意输出各个成绩。

  二维数组的初始化

  二维数组初始化也是在类型说明时给各下标变量赋以初值。 二维数组可按行分段赋值,也可按行连续赋值。 例如对数组a[5][3]:

  1.按行分段赋值可写为static int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},{85,87,90},{76,77,85} };

  2.按行连续赋值可写为static int a[5][3]={ 80,75,92,61,65,71,59,63,70,85,87,90,76,77,85 };

  这两种赋初值的结果是完全相同的。

void main()
{
int i,j,s=0,l,v[3];
static int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},
{85,87,90},{76,77,85} };
for(i=0;i<3;i++)
{ for(j=0;j<5;j++)
s=s+a[j];
v=s/5;
s=0;
}
l=(v[0]+v[1]+v[2])/3;
printf("math:%d\nc languag:%d\ndbase:%d\n",v[0],v[1],v[2]);
printf("total:%d\n",l);
}

  对于二维数组初始化赋值还有以下说明:

  1.可以只对部分元素赋初值,未赋初值的元素自动取0值。

  例如:

static int a[3][3]={{1},{2},{3}};  

  是对每一行的第一列元素赋值,未赋值的元素取0值。 赋值后各元素的值为: 1 0 02 0 03 0 0

static int a [3][3]={{0,1},{0,0,2},{3}};  

  赋值后的元素值为 0 1 00 0 23 0 0

  2.如对全部元素赋初值,则第一维的长度可以不给出。

  例如:

static int a[3][3]={1,2,3,4,5,6,7,8,9};  

  可以写为:

static int a[][3]={1,2,3,4,5,6,7,8,9};
  
  数组是一种构造类型的数据。 二维数组可以看作是由一维数组的嵌套而构成的。设一维数组的每个元素都又是一个数组, 就组成了二维数组。当然,前提是各元素类型必须相同。根据这样的分析,一个二维数组也可以分解为多个一维数组。 C语言允许这种分解有二维数组a[3][4],可分解为三个一维数组,其数组名分别为a[0],a[1],a[2]。对这三个一维数组不需另作说明即可使用。这三个一维数组都有4个元素,例如:一维数组a[0]的元素为a[0][0],a[0][1],a[0][2],a[0][3]。必须强调的是,a[0],a[1],a[2]不能当作下标变量使用,它们是数组名,不是一个单纯的下标变量。


  字符数组

  用来存放字符量的数组称为字符数组。 字符数组类型说明的形式与前面介绍的数值数组相同。例如: char c[10]; 由于字符型和整型通用,也可以定义为int c[10]但这时每个数组元素占2个字节的内存单元。字符数组也可以是二维或多维数组,例如: char c[5][10];即为二维字符数组。 字符数组也允许在类型说明时作初始化赋值。例如: static char c[10]={`c`,` `,`p`,`r`,o`,g`,r`,`a`,`m`};赋值后各元素的值为: 数组C c[0]c[1]c[2]c[3]c[4]c [5]c[6]c[7]c[8]c[9]其中c[9]未赋值,由系统自动赋予0值。 当对全体元素赋初值时也可以省去长度说明。例如: static char c[]={`c`,` `,`p`,`r`,`o`,`g`,`r`,`a`,`m`};这时C数组的长度自动定为9。

main()
{
 int i,j;
 char a[][5]={{'B','A','S','I','C',},{'d','B','A','S','E'}};
 for(i=0;i<=1;i++)
 {
  for(j=0;j<=4;j++)
   printf("%c",a[j]);
  printf("\n");
 }
}

  本例的二维字符数组由于在初始化时全部元素都赋以初值, 因此一维下标的长度可以不加以说明。字符串在C语言中没有专门的字符串变量, 通常用一个字符数组来存放一个字符串。在2.1.4节介绍字符串常量时,已说明字符串总是以'\0'作为串的结束符。因此当把一个字符串存入一个数组时, 也把结束符'\0'存入数组,并以此作为该字符串是否结束的标志。 有了'\0'标志后,就不必再用字符数组的长度来判断字符串的长度了。

  C语言允许用字符串的方式对数组作初始化赋值。例如:

static char c[]={'c', ' ','p','r','o','g','r','a','m'}; 可写为:
static char c[]={"C program"}; 或去掉{}写为:
sratic char c[]="C program";

  用字符串方式赋值比用字符逐个赋值要多占一个字节, 用于存放字符串结束标志'\0'。上面的数组c在内存中的实际存放情况为: C program\0`\0'是由C编译系统自动加上的。由于采用了`\0'标志,所以在用字符串赋初值时一般无须指定数组的长度, 而由系统自行处理。在采用字符串方式后,字符数组的输入输出将变得简单方便。 除了上述用字符串赋初值的办法外,还可用printf函数和scanf函数一次性输出输入一个字符数组中的字符串, 而不必使用循环语句逐个地输入输出每个字符。

void main()
{
 static char c[]="BASIC\ndBASE";
 printf("%s\n",c);
} printf("%s\n",c);

  注意在本例的printf函数中,使用的格式字符串为“%s”, 表示输出的是一个字符串。而在输出表列中给出数组名则可。 不能写为:

printf("%s",c[]);
void main()
{
 char st[15];
 printf("input string:\n");
 scanf("%s",st);
 printf("%s\n",st);
} char st[15];  

  本例中由于定义数组长度为15, 因此输入的字符串长度必须小于15,以留出一个字节用于存放字符串结束标志`\0`。 应该说明的是,对一个字符数组,如果不作初始化赋值,则必须说明数组长度。还应该特别注意的是,当用scanf函数输入字符串时,字符串中不能含有空格,否则将以空格作为串的结束符。例如运行例4.8,当输入的字符串中含有空格时,运行情况为: input string:this is a book this 从输出结果可以看出空格以后的字符都未能输出。 为了避免这种情况, 可多设几个字符数组分段存放含空格的串。程序可改写如下:

  Lesson

void main()
{
 char st1[6],st2[6],st3[6],st4[6];
 printf("input string:\n");
 scanf("%s%s%s%s",st1,st2,st3,st4);
 printf("%s %s %s %s\n",st1,st2,st3,st4);
}

  本程序分别设了四个数组, 输入的一行字符的空格分段分别装入四个数组。然后分别输出这四个数组中的字符串。在前面介绍过,scanf的各输入项必须以地址方式出现,如 &a,&b等。但在例4.8中却是以数组名方式出现的,这是为什么呢?这是由于在C语言中规定,数组名就代表了该数组的首地址。 整个数组是以首地址开头的一块连续的内存单元。如有字符数组char c[10],在内存可表示如图4.2。设数组c的首地址为2000,也就是说c[0]单元地址为2000。则数组名c就代表这个首地址。因此在c前面不能再加地址运算符&。如写作scanf("%s",&c);则是错误的。 在执行函数printf("%s",c) 时,按数组名c找到首地址,然后逐个输出数组中各个字符直到遇到字符串终止标志'\0'为止。

  字符串常用函数

  C语言提供了丰富的字符串处理函数, 大致可分为字符串的输入、输出、合并、修改、比较、转换、复制、搜索几类。 使用这些函数可大大减轻编程的负担。用于输入输出的字符串函数, 在使用前应包含头文件"stdio.h" ; 使用其它字符串函数则应包含头文件"string.h"。 下面介绍几个最常用的字符串函数。

  1.字符串输出函数 puts 格式: puts (字符数组名) 功能:把字符数组中的字符串输出到显示器。 即在屏幕上显示该字符串

#include"stdio.h"
main()
{
static char c[]="BASIC\ndBASE";
puts(c);
}
static char c[]="BASIC\ndBASE";
puts(c);

  从程序中可以看出puts函数中可以使用转义字符, 因此输出结果成为两行。puts函数完全可以由printf函数取代。 当需要按一定格式输出时,通常使用printf函数。

  2.字符串输入函数gets 格式: gets (字符数组名) 功能:从标准输入设备键盘上输入一个字符串。 本函数得到一个函数值,即为该字符数组的首地址。

#include"stdio.h"
main()
{
char st[15];
printf("input string:\n");
gets(st);
puts(st);
}

  可以看出当输入的字符串中含有空格时,输出仍为全部字符串。说明gets函数并不以空格作为字符串输入结束的标志, 而只以回车作为输入结束。这是与scanf函数不同的。

  3.字符串连接函数strcat 格式: strcat (字符数组名1,字符数组名2) 功能:把字符数组2中的字符串连接到字符数组1 中字符串的后面,并删去字符串1后的串标志“\0”。本函数返回值是字符数组1的首地址。

#include"string.h"
main()
{
 static char st1[30]="My name is ";
 int st2[10];
 printf("input your name:\n");
 gets(st2);
 strcat(st1,st2);
 puts(st1);
}
static char st1[30]="My name is ";
int st2[10];
printf("input your name:\n");
gets(st2);
strcat(st1,st2);  

  本程序把初始化赋值的字符数组与动态赋值的字符串连接起来。 要注意的是,字符数组1应定义足够的长度,否则不能全部装入被连接的字符串

  4.字符串拷贝函数strcpy 格式: strcpy (字符数组名1,字符数组名2) 功能:把字符数组2中的字符串拷贝到字符数组1中。串结束标志“\0”也一同拷贝。字符数名2, 也可以是一个字符串常量。这时相当于把一个字符串赋予一个字符数组。

#include"string.h"
main()
{
 static char st1[15],st2[]="C Language";
 strcpy(st1,st2);
 puts(st1);printf("\n");
}
static char st1[15],st2[]="C Language";
strcpy(st1,st2);

  本函数要求字符数组1应有足够的长度,否则不能全部装入所拷贝的字符串。

  5.字符串比较函数strcmp 格式: strcmp(字符数组名1,字符数组名2) 功能:按照ASCII码顺序比较两个数组中的字符串,并由函数返回值返回比较结果。

  字符串1=字符串2,返回值=0;
  字符串2〉字符串2,返回值〉0;
  字符串1〈字符串2,返回值〈0。
  本函数也可用于比较两个字符串常量,或比较数组和字符串常量。

#include"string.h"
main()
{
 int k;
 static char st1[15],st2[]="C Language";
 printf("input a string:\n");
 gets(st1);
 k=strcmp(st1,st2);
 if(k==0) printf("st1=st2\n");
 if(k>0) printf("st1>st2\n");
 if(k<0) printf("st1<st2\n");
}
{
 int k;
 static char st1[15],st2[]="C Language";
 printf("input a string:\n");
 gets(st1);
 k=strcmp(st1,st2);
 if(k==0) printf("st1=st2\n");
 if(k>0) printf("st1>st2\n");
 if(k<0) printf("st1<st2\n");
}

  本程序中把输入的字符串和数组st2中的串比较,比较结果返回到k中,根据k值再输出结果提示串。当输入为dbase时,由ASCII 码可知“dBASE”大于“C Language”故k〉0,输出结果“st1>st2”。

  6.测字符串长度函数strlen 格式: strlen(字符数组名) 功能:测字符串的实际长度(不含字符串结束标志‘\0’) 并作为函数返回值。

#include"string.h"
main()
{
 int k;
 static char st[]="C language";
 k=strlen(st);
 printf("The lenth of the string is %d\n",k);
}


  程序举例

  把一个整数按大小顺序插入已排好序的数组中。 为了把一个数按大小插入已排好序的数组中, 应首先确定排序是从大到小还是从小到大进行的。设排序是从大到小进序的, 则可把欲插入的数与数组中各数逐个比较, 当找到第一个比插入数小的元素i时,该元素之前即为插入位置。然后从数组最后一个元素开始到该元素为止,逐个后移一个单元。最后把插入数赋予元素i即可。如果被插入数比所有的元素值都小则插入最后位置。

main()
{
 int i,j,p,q,s,n,a[11]={127,3,6,28,54,68,87,105,162,18};
  for(i=0;i<10;i++)
 {
  p=i;q=a;
  for(j=i+1;j<10;j++)
   if(q<a[j]) {p=j;q=a[j];}
   if(p!=i)
   {
    s=a;
    a=a[p];
    a[p]=s;
   }
   printf("%d ",a);
 }
 printf("\ninput number:\n");
 scanf("%d",&n);
 for(i=0;i<10;i++)
  if(n>a)
  {
   for(s=9;s>=i;s--) a[s+1]=a;
   break;
  }
  a=n;
  for(i=0;i<=10;i++)
   printf("%d ",a);
   printf("\n");
 }
 scanf("%d",&n);
 for(i=0;i<10;i++)
  if(n>a)
   {
    for(s=9;s>=i;s--) a[s+1]=a;
    break;
   }
 a=n;

  本程序首先对数组a中的10个数从大到小排序并输出排序结果。然后输入要插入的整数n。再用一个for语句把n和数组元素逐个比较,如果发现有n>a时,则由一个内循环把i以下各元素值顺次后移一个单元。后移应从后向前进行(从a[9]开始到a为止)。 后移结束跳出外循环。插入点为i,把n赋予a即可。 如所有的元素均大于被插入数,则并未进行过后移工作。此时i=10,结果是把n赋于a[10]。最后一个循环输出插入数后的数组各元素值。程序运行时,输入数47。从结果中可以看出47已插入到54和 28之间。

  在二维数组a中选出各行最大的元素组成一个一维数组b。 a=3 16 87 65 4 32 11 108 10 25 12 37b=(87 108 37) 本题的编程思路是,在数组A的每一行中寻找最大的元素,找到之后把该值赋予数组B相应的元素即可。程序如下:

main()
{
 static int a[][4]={3,16,87,65,4,32,11,108,10,25,12,27};
 int b[3],i,j,l;
 for(i=0;i<=2;i++)
 {
  l=a[0];
  for(j=1;j<=3;j++)
   if(a[j]>l) l=a[j];
    b=l;
 }
 printf("\narray a:\n");
 for(i=0;i<=2;i++)
 {
  for(j=0;j<=3;j++)
   printf("%5d",a[j]);
   printf("\n");
 }
 printf("\narray b:\n");
 for(i=0;i<=2;i++)
  printf("%5d",b);
  printf("\n");
}
for(i=0;i<=2;i++){
 l=a[0];
 for(j=1;j<=3;j++)
 if(a[j]>l) l=a[j];
 b=l;
}

  程序中第一个for语句中又嵌套了一个for语句组成了双重循环。外循环控制逐行处理,并把每行的第0列元素赋予l。进入内循环后,把l与后面各列元素比较,并把比l大者赋予l。内循环结束时l 即为该行最大的元素,然后把l值赋予b。等外循环全部完成时,数组b中已装入了a各行中的最大值。后面的两个 for语句分别输出数组a和数组b。

  输入五个国家的名称按字母顺序排列输出。

  本题编程思路如下:五个国家名应由一个二维字符数组来处理。然而C语言规定可以把一个二维数组当成多个一维数组处理。 因此本题又可以按五个一维数组处理, 而每一个一维数组就是一个国家名字符串。用字符串比较函数比较各一维数组的大小,并排序, 输出结果即可。

  编程如下:

void main()
{
 char st[20],cs[5][20];
 int i,j,p;
 printf("input country's name:\n");
 for(i=0;i<5;i++)
  gets(cs);
 printf("\n");
 for(i=0;i<5;i++)
 {
  p=i;strcpy(st,cs);
  for(j=i+1;j<5;j++)
   if(strcmp(cs[j],st)<0) {p=j;strcpy(st,cs[j]);}
   if(p!=i)
   {
    strcpy(st,cs);
    strcpy(cs,cs[p]);
    strcpy(cs[p],st);
   }
   puts(cs);}printf("\n");
 }
 for(i=0;i<5;i++)
 {
  p=i;strcpy(st,cs);
  for(j=i+1;j<5;j++)
  if(strcmp(cs[j],st)<0) { p=j;strcpy(st,cs[j]);}
  if(p!=i)
  {
   strcpy(st,cs);
   strcpy(cs,cs[p]);
   strcpy(cs[p],st);
  }
 }
}  

  本程序的第一个for语句中,用gets函数输入五个国家名字符串。上面说过C语言允许把一个二维数组按多个一维数组处理, 本程序说明cs[5][20]为二维字符数组,可分为五个一维数组cs[0],cs[1],cs[2],cs[3],cs[4]。因此在gets函数中使用cs是合法的。 在第二个for语句中又嵌套了一个for语句组成双重循环。 这个双重循环完成按字母顺序排序的工作。在外层循环中把字符数组cs中的国名字符串拷贝到数组st中,并把下标i赋予P。 进入内层循环后,把st与cs以后的各字符串作比较,若有比st小者则把该字符串拷贝到st中,并把其下标赋予p。内循环完成后如p不等于 i 说明有比cs更小的字符串出现,因此交换cs和st的内容。 至此已确定了数组cs的第i号元素的排序值。然后输出该字符串。在外循环全部完成之后即完成全部排序和输出。

  本章小结

  1.数组是程序设计中最常用的数据结构。数组可分为数值数组(整数组,实数组),字符数组以及后面将要介绍的指针数组,结构数组等。

  2.数组可以是一维的,二维的或多维的。

  3.数组类型说明由类型说明符、数组名、数组长度 (数组元素个数)三部分组成。数组元素又称为下标变量。 数组的类型是指下标变量取值的类型。

  4.对数组的赋值可以用数组初始化赋值, 输入函数动态赋值和赋值语句赋值三种方法实现。 对数值数组不能用赋值语句整体赋值、输入或输出,而必须用循环语句逐个对数组元素进行操作。

TOP

C语言初学者入门讲座 第十讲 函数(1)

概述

  在第一章中已经介绍过,C源程序是由函数组成的。 虽然在前面各章的程序中都只有一个主函数main(), 但实用程序往往由多个函数组成。函数是C源程序的基本模块, 通过对函数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序。 C语言不仅提供了极为丰富的库函数(如Turbo C,MS C 都提供了三百多个库函数),还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。



  可以说C程序的全部工作都是由各式各样的函数完成的, 所以也把C语言称为函数式语言。 由于采用了函数模块式的结构, C语言易于实现结构化程序设计。使程序的层次结构清晰,便于程序的编写、阅读、调试。

  在C语言中可从不同的角度对函数分类。

  1. 从函数定义的角度看,函数可分为库函数和用户定义函数两种。

  (1)库函数

  由C系统提供,用户无须定义, 也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf 、 scanf 、 getchar 、putchar、gets、puts、strcat等函数均属此类。

  (2)用户定义函数

  由用户按需要写的函数。对于用户自定义函数, 不仅要在程序中定义函数本身, 而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。

  2. C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。

  (1)有返回值函数

  此类函数被调用执行完后将向调用者返回一个执行结果, 称为函数返回值。如数学函数即属于此类函数。 由用户定义的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型。

  (2)无返回值函数

  此类函数用于完成某项特定的处理任务, 执行完成后不向调用者返回函数值。这类函数类似于其它语言的过程。 由于函数无须返回值,用户在定义此类函数时可指定它的返回为“空类型”, 空类型的说明符为“void”。

  3. 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。

  (1)无参函数

  函数定义、函数说明及函数调用中均不带参数。 主调函数和被调函数之间不进行参数传送。 此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。

  (2)有参函数

  也称为带参函数。在函数定义及函数说明时都有参数, 称为形式参数(简称为形参)。在函数调用时也必须给出参数, 称为实际参数(简称为实参)。 进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。

  4. C语言提供了极为丰富的库函数, 这些库函数又可从功能角度作以下分类。

  (1)字符类型分类函数

  用于对字符按ASCII码分类:字母,数字,控制字符,分隔符,大小写字母等。

  (2)转换函数

  用于字符或字符串的转换;在字符量和各类数字量 (整型, 实型等)之间进行转换;在大、小写之间进行转换。

  (3)目录路径函数

  用于文件目录和路径操作。

  (4)诊断函数

  用于内部错误检测。

  (5)图形函数

  用于屏幕管理和各种图形功能。

  (6)输入输出函数

  用于完成输入输出功能。

  (7)接口函数

  用于与DOS,BIOS和硬件的接口。

  (8)字符串函数

  用于字符串操作和处理。

  (9)内存管理函数

  用于内存管理。

  (10)数学函数

  用于数学函数计算。

  (11)日期和时间函数

  用于日期,时间转换操作。

  (12)进程控制函数

  用于进程管理和控制。

  (13)其它函数

  用于其它各种功能。
  
  以上各类函数不仅数量多,而且有的还需要硬件知识才会使用,因此要想全部掌握则需要一个较长的学习过程。 应首先掌握一些最基本、 最常用的函数,再逐步深入。由于篇幅关系,本书只介绍了很少一部分库函数, 其余部分读者可根据需要查阅有关手册。

  还应该指出的是,在C语言中,所有的函数定义,包括主函数main在内,都是平行的。也就是说,在一个函数的函数体内, 不能再定义另一个函数, 即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。 函数还可以自己调用自己,称为递归调用。main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。 因此,C程序的执行总是从main函数开始, 完成对其它函数的调用后再返回到main函数,最后由main函数结束整个程序。一个C源程序必须有,也只能有一个主函数main。

  函数定义的一般形式

  1.无参函数的一般形式

  类型说明符 函数名()
  {
   类型说明
   语句
  }

  其中类型说明符和函数名称为函数头。 类型说明符指明了本函数的类型,函数的类型实际上是函数返回值的类型。 该类型说明符与第二章介绍的各种说明符相同。 函数名是由用户定义的标识符,函数名后有一个空括号,其中无参数,但括号不可少。{} 中的内容称为函数体。在函数体中也有类型说明, 这是对函数体内部所用到的变量的类型说明。在很多情况下都不要求无参函数有返回值, 此时函数类型符可以写为void。

  我们可以改为一个函数定义:

void Hello()
{
 printf ("Hello,world \n");
}

  这里,只把main改为Hello作为函数名,其余不变。Hello 函数是一个无参函数,当被其它函数调用时,输出Hello world字符串。

  2.有参函数的一般形式

  类型说明符 函数名(形式参数表)

  型式参数类型说明
  {
   类型说明
   语句
  }

  有参函数比无参函数多了两个内容,其一是形式参数表, 其二是形式参数类型说明。在形参表中给出的参数称为形式参数, 它们可以是各种类型的变量, 各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。 形参既然是变量,当然必须给以类型说明。例如,定义一个函数, 用于求两个数中的大数,可写为:

int max(a,b)
int a,b;
{
if (a>b) return a;
else return b;
}  

  第一行说明max函数是一个整型函数,其返回的函数值是一个整数。形参为a,b。第二行说明a,b均为整型量。 a,b 的具体值是由主调函数在调用时传送过来的。在{}中的函数体内, 除形参外没有使用其它变量,因此只有语句而没有变量类型说明。 上边这种定义方法称为“传统格式”。 这种格式不易于编译系统检查,从而会引起一些非常细微而且难于跟踪的错误。ANSI C 的新标准中把对形参的类型说明合并到形参表中,称为“现代格式”。

  例如max函数用现代格式可定义为:

int max(int a,int b)
{
if(a>b) return a;
else return b;
}

  现代格式在函数定义和函数说明(后面将要介绍)时, 给出了形式参数及其类型,在编译时易于对它们进行查错, 从而保证了函数说明和定义的一致性。例1.3即采用了这种现代格式。 在max函数体中的return语句是把a(或b)的值作为函数的值返回给主调函数。有返回值函数中至少应有一个return语句。 在C程序中,一个函数的定义可以放在任意位置, 既可放在主函数main之前,也可放在main之后。例如例1.3中定义了一个max 函数,其位置在main之后, 也可以把它放在main之前。

  修改后的程序如下所示。

int max(int a,int b)
{
if(a>b)return a;
else return b;
}
void main()
{
int max(int a,int b);
int x,y,z;
printf("input two numbers:\n");
scanf("%d%d",&x,&y);
z=max(x,y);
printf("maxmum=%d",z);
}

  现在我们可以从函数定义、 函数说明及函数调用的角度来分析整个程序,从中进一步了解函数的各种特点。程序的第1行至第5行为max函数定义。进入主函数后,因为准备调用max函数,故先对max函数进行说明(程序第8行)。函数定义和函数说明并不是一回事,在后面还要专门讨论。 可以看出函数说明与函数定义中的函数头部分相同,但是末尾要加分号。程序第12 行为调用max函数,并把x,y中的值传送给max的形参a,b。max函数执行的

  结果 (a或b)将返回给变量z。最后由主函数输出z的值。

  函数调用的一般形式前面已经说过,在程序中是通过对函数的调用来执行函数体的,其过程与其它语言的子程序调用相似。C语言中, 函数调用的一般形式为:

  函数名(实际参数表) 对无参函数调用时则无实际参数表。 实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式。 各实参之间用逗号分隔。'Next of Page在C语言中,可以用以下几种方式调用函数:

  1.函数表达式

  函数作表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如: z=max(x,y)是一个赋值表达式,把max的返回值赋予变量z。'Next of Page

  2.函数语句

  函数调用的一般形式加上分号即构成函数语句。例如: printf ("%D",a);scanf ("%d",&b);都是以函数语句的方式调用函数。

  3.函数实参

  函数作为另一个函数调用的实际参数出现。 这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。例如: printf("%d",max(x,y)); 即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。 所谓求值顺序是指对实参表中各量是自左至右使用呢,还是自右至左使用。 对此, 各系统的规定不一定相同。在3.1.3节介绍printf 函数时已提

  到过,这里从函数调用的角度再强调一下。 看例5.2程序。

void main()
{
int i=8;
printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--);
}

  如按照从右至左的顺序求值。例5.2的运行结果应为:

  8
  7
  7
  8

  如对printf语句中的++i,--i,i++,i--从左至右求值,结果应为:

  9
  8
  8
  9

  应特别注意的是,无论是从左至右求值, 还是自右至左求值,其输出顺序都是不变的, 即输出顺序总是和实参表中实参的顺序相同。由于Turbo C现定是自右至左求值,所以结果为8,7,7,8。上述问题如还不理解,上机一试就明白了。函数的参数和函数的值

TOP

C语言初学者入门讲座 第十讲 函数(2)

一、函数的参数

  前面已经介绍过,函数的参数分为形参和实参两种。 在本小节中,进一步介绍形参、实参的特点和两者的关系。 形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。 形参和实参的功能是作数据传送。发生函数调用时, 主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。

  函数的形参和实参具有以下特点:

  1.形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。

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

  3.实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。

  4.函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。例5.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。可见实参的值不随形参的变化而变化。

  二、函数的值

  函数的值是指函数被调用之后, 执行函数体中的程序段所取得的并返回给主调函数的值。如调用正弦函数取得正弦值,调用例5.1的max函数取得的最大数等。对函数的值(或称函数返回值)有以下一些说明:

  1. 函数的值只能通过return语句返回主调函数。return 语句的一般形式为:

  return 表达式;

  或者为:

  return (表达式);

  该语句的功能是计算表达式的值,并返回给主调函数。 在函数中允许有多个return语句,但每次调用只能有一个return 语句被执行, 因此只能返回一个函数值。

  2. 函数值的类型和函数定义中函数的类型应保持一致。 如果两者不一致,则以函数类型为准,自动进行类型转换。

  3. 如函数值为整型,在函数定义时可以省去类型说明。

  4. 不返回函数值的函数,可以明确定义为“空类型”, 类型说明符为“void”。如例5.3中函数s并不向主函数返函数值,因此可定义为:

void s(int n)
{ ……
}

  一旦函数被定义为空类型后, 就不能在主调函数中使用被调函数的函数值了。例如,在定义s为空类型后,在主函数中写下述语句 sum=s(n); 就是错误的。为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为空类型。函数说明在主调函数中调用某函数之前应对该被调函数进行说明, 这与使用变量之前要先进行变量说明是一样的。 在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型, 以便在主调函数中按此种类型对返回值作相应的处理。 对被调函数的说明也有两种格式,一种为传统格式,其一般格式为: 类型说明符 被调函数名(); 这种格式只给出函数返回值的类型,被调函数名及一个空括号。

  这种格式由于在括号中没有任何参数信息, 因此不便于编译系统进行错误检查,易于发生错误。另一种为现代格式,其一般形式为:

  类型说明符 被调函数名(类型 形参,类型 形参…);

  或为:

  类型说明符 被调函数名(类型,类型…);

  现代格式的括号内给出了形参的类型和形参名, 或只给出形参类型。这便于编译系统进行检错,以防止可能出现的错误。例5.1 main函数中对max函数的说明若

  用传统格式可写为:

int max();

  用现代格式可写为:

int max(int a,int b);

  或写为:

int max(int,int);

  C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。

  1. 如果被调函数的返回值是整型或字符型时, 可以不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。例5.3的主函数中未对函数s作说明而直接调用即属此种情形。

  2. 当被调函数的函数定义出现在主调函数之前时, 在主调函数中也可以不对被调函数再作说明而直接调用。例如例5.1中, 函数max的定义放在main 函数之前,因此可在main函数中省去对 max函数的函数说明int max(int a,int b)。

  3. 如在所有函数定义之前, 在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。例如:

char str(int a);
float f(float b);
main()
{
……
}
char str(int a)
{
……
}
float f(float b)
{
……
}

  其中第一,二行对str函数和f函数预先作了说明。 因此在以后各函数中无须对str和f函数再作说明就可直接调用。

  4. 对库函数的调用不需要再作说明, 但必须把该函数的头文件用include命令包含在源文件前部。数组作为函数参数数组可以作为函数的参数使用,进行数据传送。 数组用作函数参数有两种形式,一种是把数组元素(下标变量)作为实参使用; 另一种是把数组名作为函数的形参和实参使用。一、数组元素作函数实参数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时, 把作为实参的数组元素的值传送给形参,实现单向的值传送。例5.4说明了这种情况。[例5.4]判别一个整数数组中各元素的值,若大于0 则输出该值,若小于等于0则输出0值。编程如下:

void nzp(int v)
{
if(v>0)
printf("%d ",v);
else
printf("%d ",0);
}
main()
{
int a[5],i;
printf("input 5 numbers\n");
for(i=0;i<5;i++)
{
scanf("%d",&a);
nzp(a);
}
}void nzp(int v)
{ ……
}
main()
{
int a[5],i;
printf("input 5 numbers\n");
for(i=0;i<5;i++)
{ scanf("%d",&a);
nzp(a);
}
}  

  本程序中首先定义一个无返回值函数nzp,并说明其形参v 为整型变量。在函数体中根据v值输出相应的结果。在main函数中用一个for 语句输入数组各元素, 每输入一个就以该元素作实参调用一次nzp函数,即把a的值传送给形参v,供nzp函数使用。

  二、数组名作为函数参数

  用数组名作函数参数与用数组元素作实参有几点不同:

  1. 用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此, 并不要求函数的形参也是下标变量。 换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时, 则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。

  2. 在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢? 在第四章中我们曾介绍过,数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送, 也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。图5.1说明了这种情形。图中设a为实参数组,类型为整型。a占有以2000 为首地址的一块内存区。b为形参数组名。当发生函数调用时,进行地址传送, 把实参数 组a的首地址传送给形参数组名b,于是b也取得该地址2000。 于是a,b两数组共同占有以2000 为首地址的一段连续内存单元。从图中还可以看出a和b下标相同的元素实际上也占相同的两个内存单元(整型数组每个元素占二字节)。例如a[0]和b[0]都占用2000和2001单元,当然a[0]等于b[0]。类推则有a等于b

  [例5.5]数组a中存放了一个学生5门课程的成绩,求平均成绩。float aver(float a[5])

{
int i;
float av,s=a[0];
for(i=1;i<5;i++)
s=s+a;
av=s/5;
return av;
}
void main()
{
float sco[5],av;
int i;
printf("\ninput 5 scores:\n");
for(i=0;i<5;i++)
scanf("%f",&sco);
av=aver(sco);
printf("average score is %5.2f",av);
}
float aver(float a[5])
{ ……
}
void main()
{
……
for(i=0;i<5;i++)
scanf("%f",&sco);
av=aver(sco);
……
}  

  本程序首先定义了一个实型函数aver,有一个形参为实型数组a,长度为5。在函数aver中,把各元素值相加求出平均值,返回给主函数。主函数main 中首先完成数组sco的输入,然后以sco作为实参调用aver函数,函数返回值送av,最后输出av值。 从运行情况可以看出,程序实现了所要求的功能

  3. 前面已经讨论过,在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同, 而形参的值发生改变后,实参并不变化, 两者的终值是不同的。例5.3证实了这个结论。 而当用数组名作函数参数时,情况则不同。 由于实际上形参和实参为同一数组, 因此当形参数组发生变化时,实参数组也随之变化。 当然这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后实参数组的值将由于形参数组值的变化而变化。为了说明这种情况,把例5.4改为例5.6的形式。[例5.6]题目同5.4例。改用数组名作函数参数。

void nzp(int a[5])
{
int i;
printf("\nvalues of array a are:\n");
for(i=0;i<5;i++)
{
if(a<0) a=0;
printf("%d ",a);
}
}
main()
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b);
nzp(b);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b);
}
void nzp(int a[5])
{ ……
}
main()
{
int b[5],i;
……
nzp(b);
……
}

  本程序中函数nzp的形参为整数组a,长度为 5。 主函数中实参数组b也为整型,长度也为5。在主函数中首先输入数组b的值,然后输出数组b的初始值。 然后以数组名b为实参调用nzp函数。在nzp中,按要求把负值单元清0,并输出形参数组a的值。 返回主函数之后,再次输出数组b的值。从运行结果可以看出,数组b 的初值和终值是不同的,数组b 的终值和数组a是相同的。这说明实参形参为同一数组,它们的值同时得以改变。 用数组名作为函数参数时还应注意以下几点:

  a. 形参数组和实参数组的类型必须一致,否则将引起错误。

  b. 形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。如把例5.6修改如下:

void nzp(int a[8])
{
int i;
printf("\nvalues of array aare:\n");
for(i=0;i<8;i++)
{
if(a<0)a=0;
printf("%d",a);
}
}
main()
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d",b);
nzp(b);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d",b);
}

  本程序与例5.6程序比,nzp函数的形参数组长度改为8,函数体中,for语句的循环条件也改为i<8。因此,形参数组 a和实参数组b的长度不一致。编译能够通过,但从结果看,数组a的元素a[5],a[6],a[7]显然是无意义的。c. 在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。

  例如:可以写为:

void nzp(int a[])

  或写为

void nzp(int a[],int n)

  其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。

  由此,例5.6又可改为例5.7的形式。

  [例5.7]

void nzp(int a[],int n)
{
int i;
printf("\nvalues of array a are:\n");
for(i=0;i<n;i++)
{
if(a<0) a=0;
printf("%d ",a);
}
}
main()
{
int b[5],i;
printf("\ninput 5 numbers:\n");
for(i=0;i<5;i++)
scanf("%d",&b);
printf("initial values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b);
nzp(b,5);
printf("\nlast values of array b are:\n");
for(i=0;i<5;i++)
printf("%d ",b);
}
void nzp(int a[],int n)
{ ……
}
main()
{
……
nzp(b,5);
……
}


  本程序nzp函数形参数组a没有给出长度,由n 动态确定该长度。在main函数中,函数调用语句为nzp(b,5),其中实参5将赋予形参n作为形参数组的长度。

  d. 多维数组也可以作为函数的参数。 在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。因此,以下写法都是合法的。

int MA(int a[3][10])

  或

int MA(int a[][10])

  函数的嵌套调用

  C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。 但是C语言允许在一个函数的定义中出现对另一个函数的调用。 这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。 这与其它语言的子程序嵌套的情形是类似的。其关系可表示如图5.2。

  图5.2表示了两层嵌套的情形。其执行过程是:执行main函数中调用a函数的语句时,即转去执行a函数,在a函数中调用b 函数时,又转去执行b函数,b函数执行完毕返回a函数的断点继续执行,a 函数执行完毕返回main函数的断点继续执行。

  [例5.8]计算s=2?2!+3?2!

  本题可编写两个函数,一个是用来计算平方值的函数f1, 另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值, 再在f1中以平方值为实参,调用 f2计算其阶乘值,然后返回f1,再返回主函数,在循环程序中计算累加和。

long f1(int p)
{
int k;
long r;
long f2(int);
k=p*p;
r=f2(k);
return r;
}
long f2(int q)
{
long c=1;
int i;
for(i=1;i<=q;i++)
c=c*i;
return c;
}
main()
{
int i;
long s=0;
for (i=2;i<=3;i++)
s=s+f1(i);
printf("\ns=%ld\n",s);
}
long f1(int p)
{
……
long f2(int);
r=f2(k);
……
}
long f2(int q)
{
……
}
main()
{ ……
s=s+f1(i);
……
}

  在程序中,函数f1和f2均为长整型,都在主函数之前定义, 故不必再在主函数中对f1和f2加以说明。在主程序中, 执行循环程序依次把i值作为实参调用函数f1求i?2值。在f1中又发生对函数f2的调用,这时是把i?2的值作为实参去调f2,在f2 中完成求i?2! 的计算。f2执行完毕把C值(即i?2!)返回给f1,再由f1 返回主函数实现累加。至此,由函数的嵌套调用实现了题目的要求。 由于数值很大, 所以函数和一些变量的类型都说明为长整型,否则会造成计算错误。

TOP

C语言初学者入门讲座 第十讲 函数(3)

函数的递归调用

  一个函数在它的函数体内调用它自身称为递归调用。 这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中, 主调函数又是被调函数。执行递归函数将反复调用其自身。 每调用一次就进入新的一层。例如有函数f如下:

int f (int x)
{
int y;
z=f(y);
return z;
}

  这个函数是一个递归函数。 但是运行该函数将无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行, 必须在函数内有终止递归调用的手段。常用的办法是加条件判断, 满足某种条件后就不再作递归调用,然后逐层返回。 下面举例说明递归调用的执行过程。

  [例5.9]用递归法计算n!用递归法计算n!可用下述公式表示:

n!=1 (n=0,1)
n×(n-1)! (n>1)
按公式可编程如下:
long ff(int n)
{
long f;
if(n<0) printf("n<0,input error");
else if(n==0||n==1) f=1;
else f=ff(n-1)*n;
return(f);
}
main()
{
int n;
long y;
printf("\ninput a inteager number:\n");
scanf("%d",&n);
y=ff(n);
printf("%d!=%ld",n,y);
}
long ff(int n)
{ ……
else f=ff(n-1)*n;
……
}
main()
{ ……
y=ff(n);
……
}  

  程序中给出的函数ff是一个递归函数。主函数调用ff 后即进入函数ff执行,如果n<0,n==0或n=1时都将结束函数的执行,否则就递归调用ff函数自身。由于每次递归调用的实参为n-1,即把n-1 的值赋予形参n,最后当n-1的值为1时再作递归调用,形参n的值也为1,将使递归终止。然后可逐层退回。下面我们再举例说明该过程。 设执行本程序时输入为5, 即求 5!。在主函数中的调用语句即为y=ff(5),进入ff函数后,由于n=5,不等于0或1,故应执行f=ff(n-1)*n,即f=ff(5-1)*5。该语句对ff作递归调用即ff(4)。 逐次递归展开如图5.3所示。进行四次递归调用后,ff函数形参取得的值变为1,故不再继续递归调用而开始逐层返回主调函数。ff(1)的函数返回值为1,ff(2)的返回值为1*2=2,ff(3)的返回值为2*3=6,ff(4) 的返回值为6*4=24,最后返回值ff(5)为24*5=120。

  例5. 9也可以不用递归的方法来完成。如可以用递推法,即从1开始乘以2,再乘以3…直到n。递推法比递归法更容易理解和实现。但是有些问题则只能用递归算法才能实现。典型的问题是Hanoi塔问题。
  
  [例5.10]Hanoi塔问题

  一块板上有三根针,A,B,C。A针上套有64个大小不等的圆盘, 大的在下,小的在上。如图5.4所示。要把这64个圆盘从A针移动C针上,每次只能移动一个圆盘,移动可以借助B针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。

  本题算法分析如下,设A上有n个盘子。

  如果n=1,则将圆盘从A直接移动到C。

  如果n=2,则:

  1.将A上的n-1(等于1)个圆盘移到B上;

  2.再将A上的一个圆盘移到C上;

  3.最后将B上的n-1(等于1)个圆盘移到C上。

  如果n=3,则:

  A. 将A上的n-1(等于2,令其为n`)个圆盘移到B(借助于C),

  步骤如下:

  (1)将A上的n`-1(等于1)个圆盘移到C上,见图5.5(b)。

  (2)将A上的一个圆盘移到B,见图5.5(c)

  (3)将C上的n`-1(等于1)个圆盘移到B,见图5.5(d)

  B. 将A上的一个圆盘移到C,见图5.5(e)

  C. 将B上的n-1(等于2,令其为n`)个圆盘移到C(借助A),

  步骤如下:

  (1)将B上的n`-1(等于1)个圆盘移到A,见图5.5(f)

  (2)将B上的一个盘子移到C,见图5.5(g)

  (3)将A上的n`-1(等于1)个圆盘移到C,见图5.5(h)。

  到此,完成了三个圆盘的移动过程。

  从上面分析可以看出,当n大于等于2时, 移动的过程可分解为三个步骤:

  第一步 把A上的n-1个圆盘移到B上;

  第二步 把A上的一个圆盘移到C上;

  第三步 把B上的n-1个圆盘移到C上;其中第一步和第三步是类同的。

  当n=3时,第一步和第三步又分解为类同的三步,即把n`-1个圆盘从一个针移到另一个针上,这里的n`=n-1。 显然这是一个递归过
程,据此算法可编程如下:

move(int n,int x,int y,int z)
{
if(n==1)
printf("%c-->%c\n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c\n",x,z);
move(n-1,y,x,z);
}
}
main()
{
int h;
printf("\ninput number:\n");
scanf("%d",&h);
printf("the step to moving %2d diskes:\n",h);
move(h,'a','b','c');
}
move(int n,int x,int y,int z)
{
if(n==1)
printf("%-->%c\n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c\n",x,z);
move(n-1,y,x,z);
}
}
main()
{ ……
move(h,'a','b','c');
}

  从程序中可以看出,move函数是一个递归函数,它有四个形参n,x,y,z。n表示圆盘数,x,y,z分别表示三根针。move 函数的功能是把x上的n个圆盘移动到z 上。当n==1时,直接把x上的圆盘移至z上,输出x→z。如n!=1则分为三步:递归调用move函数,把n-1个圆盘从x移到y;输出x→z;递归调用move函数,把n-1个圆盘从y移到z。在递归调用过程中n=n-1,故n的值逐次递减,最后n=1时,终止递归,逐层返回。当n=4 时程序运行的结果为

input number:
4
the step to moving 4 diskes:
a→b
a→c
b→c
a→b
c→a
c→b
a→b
a→c
b→c
b→a
c→a
b→c
a→b
a→c
b→c

TOP

C语言初学者入门讲座 第十讲 函数(4)

变量的作用域

  在讨论函数的形参变量时曾经提到, 形参变量只在被调用期间才分配内存单元,调用结束立即释放。 这一点表明形参变量只有在函数内才是有效的, 离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量, C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。 C语言中的变量,按作用域范围可分为两种, 即局部变量和全局变量。

  一、局部变量

  局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。

  例如:

int f1(int a) /*函数f1*/
{
int b,c;
……
}a,b,c作用域
int f2(int x) /*函数f2*/
{
int y,z;
}x,y,z作用域
main()
{
int m,n;
}

  m,n作用域 在函数f1内定义了三个变量,a为形参,b,c为一般变量。在 f1的范围内a,b,c有效,或者说a,b,c变量的作用域限于f1内。同理,x,y,z的作用域限于f2内。 m,n的作用域限于main函数内。关于局部变量的作用域还要说明以下几点:

  1. 主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。

  2. 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。

  3. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。如在例5.3 中,形参和实参的变量名都为n,是完全允许的。4. 在复合语句中也可定义变量,其作用域只在复合语句范围内。例如:

main()
{
int s,a;
……
{
int b;
s=a+b;
……b作用域
}
……s,a作用域
}[例5.11]main()
{
int i=2,j=3,k;
k=i+j;
{
int k=8;
if(i==3) printf("%d\n",k);
}
printf("%d\n%d\n",i,k);
}
main()
{
int i=2,j=3,k;
k=i+j;
{
int k=8;
if(i=3) printf("%d\n",k);
}
printf("%d\n%d\n",i,k);
}


  本程序在main中定义了i,j,k三个变量,其中k未赋初值。 而在复合语句内又定义了一个变量k,并赋初值为8。应该注意这两个k不是同一个变量。在复合语句外由main定义的k起作用,而在复合语句内则由在复合语句内定义的k起作用。因此程序第4行的k为main所定义,其值应为5。第7行输出k值,该行在复合语句内,由复合语句内定义的k起作用,其初值为8,故输出值为8,第9行输出i,k值。i是在整个程序中有效的,第7行对i赋值为3,故以输出也为3。而第9行已在复合语句之外,输出的k应为main所定义的k,此k值由第4 行已获得为5,故输出也为5。

  二、全局变量

  全局变量也称为外部变量,它是在函数外部定义的变量。 它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。 例如:

int a,b; /*外部变量*/
void f1() /*函数f1*/
{
……
}
float x,y; /*外部变量*/
int fz() /*函数fz*/
{
……
}
main() /*主函数*/
{
……
}/*全局变量x,y作用域 全局变量a,b作用域*/

  从上例可以看出a、b、x、y 都是在函数外部定义的外部变量,都是全局变量。但x,y 定义在函数f1之后,而在f1内又无对x,y的说明,所以它们在f1内无效。 a,b定义在源程序最前面,因此在f1,f2及main内不加说明也可使用。

  [例5.12]输入正方体的长宽高l,w,h。求体积及三个面x*y,x*z,y*z的面积。

int s1,s2,s3;
int vs( int a,int b,int c)
{
int v;
v=a*b*c;
s1=a*b;
s2=b*c;
s3=a*c;
return v;
}
main()
{
int v,l,w,h;
printf("\ninput length,width and height\n");
scanf("%d%d%d",&l,&w,&h);
v=vs(l,w,h);
printf("v=%d s1=%d s2=%d s3=%d\n",v,s1,s2,s3);
}

  本程序中定义了三个外部变量s1,s2,s3, 用来存放三个面积,其作用域为整个程序。函数vs用来求正方体体积和三个面积, 函数的返回值为体积v。由主函数完成长宽高的输入及结果输出。由于C语言规定函数返回值只有一个, 当需要增加函数的返回数据时,用外部变量是一种很好的方式。本例中,如不使用外部变量, 在主函数中就不可能取得v,s1,s2,s3四个值。而采用了外部变量, 在函数vs中求得的s1,s2,s3值在main 中仍然有效。因此外部变量是实现函数之间数据通讯的有效手段。对于全局变量还有以下几点说明:

  1. 对于局部变量的定义和说明,可以不加区分。而对于外部变量则不然,外部变量的定义和外部变量的说明并不是一回事。外部变量定义必须在所有的函数之外,且只能定义一次。其一般形式为: [extern] 类型说明符 变量名,变量名… 其中方括号内的extern可以省去不写。

  例如: int a,b;

  等效于:

  extern int a,b;
 
  而外部变量说明出现在要使用该外部变量的各个函数内, 在整个程序内,可能出现多次,外部变量说明的一般形式为: extern 类型说明符 变量名,变量名,…; 外部变量在定义时就已分配了内存单元, 外部变量定义可作初始赋值,外部变量说明不能再赋初始值, 只是表明在函数内要使用某外部变量。

  2. 外部变量可加强函数模块之间的数据联系, 但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的, 因此在不必要时尽量不要使用全局变量。

  3. 在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。

  [例5.13]

int vs(int l,int w)
{
extern int h;
int v;
v=l*w*h;
return v;
}
main()
{
extern int w,h;
int l=5;
printf("v=%d",vs(l,w));
}
int l=3,w=4,h=5;

  本例程序中,外部变量在最后定义, 因此在前面函数中对要用的外部变量必须进行说明。外部变量l,w和vs函数的形参l,w同名。外部变量都作了初始赋值,mian函数中也对l作了初始化赋值。执行程序时,在printf语句中调用vs函数,实参l的值应为main中定义的l值,等于5,外部变量l在main内不起作用;实参w的值为外部变量w的值为4,进入vs后这两个值传送给形参l,wvs函数中使用的h 为外部变量,其值为5,因此v的计算结果为100,返回主函数后输出。变量的存储类型各种变量的作用域不同, 就其本质来说是因变量的存储类型相同。所谓存储类型是指变量占用内存空间的方式, 也称为存储方式。

  变量的存储方式可分为“静态存储”和“动态存储”两种。

  静态存储变量通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。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);
}
{ auto int a;
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);
}

  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。

TOP

C语言初学者入门讲座 第十讲 函数(5)

三、静态变量

  静态变量的类型说明符是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 中寄存器的个数是有限的,因此使用寄存器变量的个数也是有限的。

  三、静态变量

  静态变量的类型说明符是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 中寄存器的个数是有限的,因此使用寄存器变量的个数也是有限的。

TOP

C语言初学者入门讲座 第十一讲 指针的慨念(1)

指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; 能很方便地使用数组和字符串; 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环, 能否正确理解和使用指针是我们是否掌握C语言的一个标志。同时, 指针也是C语言中最为困难的一部分,在学习中除了要正确理解基本概念,还必须要多编程,上机调试。只要作到这些,指针也是不难掌握的。



  指针的基本概念 在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针, 其中存放的数据才是该单元的内容。在C语言中, 允许用一个变量来存放指针,这种变量称为指针变量。因此, 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中,设有字符变量C,其内容为“K”(ASCII码为十进制数 75),C占用了011A号单元(地址用十六进数表示)。设有指针变量P,内容为011A, 这种情况我们称为P指向变量C,或说P是指向变量C的指针。 严格地说,一个指针是一个地址, 是一个常量。而一个指针变量却可以被赋予不同的指针值,是变。 但在常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址, 是常量,“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。
 
  既然指针变量的值是一个地址, 那么这个地址不仅可以是变量的地址, 也可以是其它数据结构的地址。在一个指针变量中存放一
个数组或一个函数的首地址有何意义呢? 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址, 也就找到了该数组或函数。这样一来, 凡是出现数组,函数的地方都可以用一个指针变量来表示, 只要该指针变量中赋予数组或函数的首地址即可。这样做, 将会使程序的概念十分清楚,程序本身也精练,高效。在C语言中, 一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构, 而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址, 它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。 这也是引入“指针”概念的一个重要原因。

  指针变量的类型说明

  对指针变量的类型说明包括三个内容:

  (1)指针类型说明,即定义变量为一个指针变量;

  (2)指针变量名;

  (3)变量值(指针)所指向的变量的数据类型。

  其一般形式为: 类型说明符 *变量名;

  其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。

  例如: int *p1;表示p1是一个指针变量,它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量, 应由向p1赋予的地址来决定。

  再如:

staic int *p2; /*p2是指向静态整型变量的指针变量*/
float *p3; /*p3是指向浮点变量的指针变量*/
char *p4; /*p4是指向字符变量的指针变量*/ 应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量, 时而又指向一个字符变量。

  指针变量的赋值

  指针变量同普通变量一样,使用之前不仅要定义说明, 而且必须赋予具体的值。未经赋值的指针变量不能使用, 否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误。在C语言中, 变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。 C语言中提供了地址运算符&来表示变量的地址。其一般形式为: & 变量名; 如&a变示变量a的地址,&b表示变量b的地址。 变量本身必须预先说明。设有指向整型变量的指针变量p,如要把整型变量a 的地址赋予p可以有以下两种方式:

  (1)指针变量初始化的方法

int a;
int *p=&a;

  (2)赋值语句的方法

int a;
int *p;
p=&a;

  不允许把一个数赋予指针变量,故下面的赋值是错误的: int *p;p=1000; 被赋值的指针变量前不能再加“*”说明符,如写为*p=&a 也是错误的

  指针变量的运算

  指针变量可以进行某些运算,但其运算的种类是有限的。 它只能进行赋值运算和部分算术运算及关系运算。

  1.指针运算符

  (1)取地址运算符&

  取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中,我们已经了解并使用了&运算符。

  (2)取内容运算符*

  取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。需要注意的是指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。

main(){
 int a=5,*p=&a;
 printf ("%d",*p);
}
......

  表示指针变量p取得了整型变量a的地址。本语句表示输出变量a的值。

  2.指针变量的运算

  (1)赋值运算

  指针变量的赋值运算有以下几种形式:

  ①指针变量初始化赋值,前面已作介绍。

  ②把一个变量的地址赋予指向相同数据类型的指针变量。例如:

int a,*pa;
pa=&a; /*把整型变量a的地址赋予整型指针变量pa*/

  ③把一个指针变量的值赋予指向相同类型变量的另一个指针变量。如:

int a,*pa=&a,*pb;
pb=pa; /*把a的地址赋予指针变量pb*/

  由于pa,pb均为指向整型变量的指针变量,因此可以相互赋值。

  ④把数组的首地址赋予指向数组的指针变量。

  例如:

int a[5],*pa;
pa=a; (数组名表示数组的首地址,故可赋予指向数组的指针变量pa)

  也可写为:

pa=&a[0]; /*数组第一个元素的地址也是整个数组的首地址,

  也可赋予pa*/

  当然也可采取初始化赋值的方法:

int a[5],*pa=a;

  ⑤把字符串的首地址赋予指向字符类型的指针变量。例如: char *pc;pc="c language";或用初始化赋值的方法写为: char *pc="C Language"; 这里应说明的是并不是把整个字符串装入指针变量, 而是把存放该字符串的字符数组的首地址装入指针变量。 在后面还将详细介绍。

  ⑥把函数的入口地址赋予指向函数的指针变量。例如: int (*pf)();pf=f; /*f为函数名*/

TOP

返回列表