重庆美图库!!
楼主: wjshw
打印 上一主题 下一主题
收起左侧

C语言完全教程

[复制链接]
11
 楼主| 发表于 2006-6-11 22:30 | 只看该作者
重庆商务网,重庆电子商务第一网!
<P><FONT color=#000000><FONT size=3>第五章 数组<br><br>数组在程序设计中,为了处理方便, 把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称为数组。在C语言中, 数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。<br><br>  本章介绍数值数组和字符数组,其余的在以后各章陆续介绍。数组类型说明 在C语言中使用数组必须先进行类型说明。 数组说明的一般形<br>式为: 类型说明符 数组名 [常量表达式],……; 其中,类型说明符是任一种基本数据类型或构造数据类型。 数组名是用户定义的数组标识符。 方括号中的常量表达式表示数据元素的个数,也称为数组的长度。<br>例如:<br>int a[10]; 说明整型数组a,有10个元素。<br>float b[10],c[20]; 说明实型数组b,有10个元素,实型数组c,有20个元素。<br>char ch[20]; 说明字符数组ch,有20个元素。<br><br>对于数组类型说明应注意以下几点:<br>1.数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素的数据类型都是相同的。<br>2.数组名的书写规则应符合标识符的书写规定。<br>3.数组名不能与其它变量名相同,例如:<br>void main()<br>{<br>int a;<br>float a[10];<br>……<br>}<br>是错误的。<br>4.方括号中常量表达式表示数组元素的个数,如a[5]表示数组a有5个元素。但是其下标从0开始计算。因此5个元素分别为a[0],a[1],a[2],a[3],a[4]。<br>5.不能在方括号中用变量来表示元素的个数, 但是可以是符号常数或常量表达式。例如:<br>#define FD 5<br>void main()<br>{<br>int a[3+2],b[7+FD];<br>……<br>}<br>是合法的。但是下述说明方式是错误的。<br>void main()<br>{<br>int n=5;<br>int a[n];<br>……<br>}<br>6.允许在同一个类型说明中,说明多个数组和多个变量。<br>例如: int a,b,c,d,k1[10],k2[20];<br><br>数组元素的表示方法<br><br>  </FONT><FONT face=宋体 size=3>数组元素是组成数组的基本单元。数组元素也是一种变量, 其标识方法为数组名后跟一个下标。 下标表示了元素在数组中的顺序号。数组元素的一般形式为: 数组名[下标] 其中的下标只能为整型常量或整型表达式。如为小数时,C编译将自动取整。例如,a[5],a[i+j],a[i++]都是合法的数组元素。 数组元素通常也称为下标变量。必须先定义数组, 才能使用下标变量。在C语言中只能逐个地使用下标变量, 而不能一次引用整个数组。 例如,输出有10 个元素的数组必须使用循环语句逐个输出各下标变量:<br>for(i=0; i&lt;10; i++)  printf("%d",a</FONT></FONT><FONT face=宋体 color=#000000 size=3>); 而不能用一个语句输出整个数组,下面的写法是错误的: printf("%d",a);<br>void main()<br>{<br>int i,a[10];<br>for(i=0;i&lt;10;)<br>a[i++]=2*i+1;<br>for(i=9;i&gt;=0;i--)<br>printf("%d",a</FONT><FONT face=宋体 color=#000000 size=3>);<br>printf("\n%d %d\n",a[5.2],a[5.8]);} for(i=0;i&lt;10;)<br>a[i++]=2*i+1; for(i=9;i&gt;=0;i--)<br>printf("%d",a</FONT><FONT face=宋体 color=#000000 size=3>); printf("\n%d %d\n",a[5.2],a[5.8]);<br>   本例中用一个循环语句给a数组各元素送入奇数值,然后用第二个循环语句从大到小输出各个奇数。在第一个 for语句中,表达式3省略了。在下标变量中使用了表达式i++,用以修改循环变量。当然第二个for语句也可以这样作, C语言允许用表达式表示下标。 程序中最后一个printf语句输出了两次a[5]的值, 可以看出当下标不为整数时将自动取整。数组的赋值给数组赋值的方法除了用赋值语句对数组元素逐个赋值外, 还可采用初始化赋值和动态赋值的方法。数组初始化赋值数组初始化赋值是指在数组说明时给数组元素赋予初值。 数组初始化是在编译阶段进行的。这样将减少运行时间,提高效率。<br><br>  初始化赋值的一般形式为: 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;<br><br>  C语言对数组的初始赋值还有以下几点规定:<br>1.可以只给部分元素赋初值。当{ }中值的个数少于元素个数时,只给前面部分元素赋值。例如: static int a[10]={0,1,2,3,4};表示只给a[0]~a[4]5个元素赋值,而后5个元素自动赋0值。<br>2.只能给元素逐个赋值,不能给数组整体赋值。 例如给十个元素全部赋1值,只能写为: static int a[10]={1,1,1,1,1,1,1,1,1,1};而不能写为: static int a[10]=1;<br>3.如不给可初始化的数组赋初值,则全部元素均为0值。<br>4.如给全部元素赋值,则在数组说明中, 可以不给出数组元素的个数。例如: static int a[5]={1,2,3,4,5};可写为: static int a[]={1,2,3,4,5};动态赋值可以在程序执行过程中,对数组作动态赋值。 这时可用循环语句配合scanf函数逐个对数组元素赋值。<br>void main()<br>{<br>int i,max,a[10];<br>printf("input 10 numbers:\n");<br>for(i=0;i&lt;10;i++)<br>scanf("%d",&amp;a</FONT><FONT face=宋体><FONT size=3><FONT color=#000000>);<br>max=a[0];<br>for(i=1;i&lt;10;i++)<br>if(a</FONT><FONT color=#000000>&gt;max) max=a</FONT></FONT></FONT><FONT face=宋体 color=#000000 size=3>;<br>printf("maxmum=%d\n",max);<br>}<br>for(i=0;i&lt;10;i++)<br>scanf("%d",&amp;a</FONT><FONT face=宋体><FONT size=3><FONT color=#000000>);<br>max=a[0];<br>for(i=1;i&lt;10;i++)<br>if(a</FONT><FONT color=#000000>&gt;max) max=a</FONT></FONT></FONT><FONT face=宋体 color=#000000 size=3>;<br>printf("maxmum=%d\n",max);<br>   本例程序中第一个for语句逐个输入10个数到数组a中。 然后把a[0]送入max中。在第二个for语句中,从a[1]到a[9]逐个与max中的内容比较,若比max的值大,则把该下标变量送入max中,因此max总是在已比较过的下标变量中为最大者。比较结束,输出max的值。<br>void main()<br>{<br>int i,j,p,q,s,a[10];<br>printf("\n input 10 numbers:\n");<br>for(i=0;i&lt;10;i++)<br>scanf("%d",&amp;a</FONT><FONT face=宋体 color=#000000 size=3>);<br>for(i=0;i&lt;10;i++){<br>p=i;q=a</FONT><FONT face=宋体 color=#000000 size=3>;<br>for(j=i+1;j&lt;10;j++)<br>if(q&lt;a[j]) { p=j;q=a[j]; }<br>if(i!=p)<br>{s=a</FONT><FONT face=宋体 color=#000000 size=3>;<br>a</FONT><FONT face=宋体 color=#000000 size=3>=a[p];<br>a[p]=s; }<br>printf("%d",a</FONT><FONT face=宋体 color=#000000 size=3>);<br>}<br>}<br>for(i=0;i&lt;10;i++)<br>scanf("%d",&amp;a</FONT><FONT face=宋体 color=#000000 size=3>);<br>for(i=0;i&lt;10;i++){<br>p=i;q=a</FONT><FONT face=宋体 color=#000000 size=3>;<br>for(j=i+1;j&lt;10;j++)<br>if(q&lt;a[j]) { p=j;q=a[j]; }<br>if(i!=p)<br>{ s=a</FONT><FONT face=宋体 color=#000000 size=3>;<br>a</FONT><FONT face=宋体 color=#000000 size=3>=a[p];<br>a[p]=s; }<br>printf("%d",a</FONT><FONT color=#000000><FONT face=宋体 size=3>);<br>}<br>   本例程序中用了两个并列的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以后各个元素排序。</FONT></FONT></P><br>
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
12
 楼主| 发表于 2006-6-11 22:31 | 只看该作者
重庆女性论坛
<FONT size=3>二维数组<br><br>  前面介绍的数组只有一个下标,称为一维数组, 其数组元素也称为单下标变量。在实际问题中有很多量是二维的或多维的, 因此C语言允许构造多维数组。多维数组元素有多个下标, 以标识它在数组中的位置,所以也称为多下标变量。 本小节只介绍二维数组,多维数组可由二维数组类推而得到。二维数组类型说明二维数组类型说明的一般形式是:<br>类型说明符 数组名[常量表达式1][常量表达式2]…;<br>其中常量表达式1表示第一维下标的长度,常量表达式2 表示第二维下标的长度。例如:<br>int a[3][4]; 说明了一个三行四列的数组,数组名为a,其下标变量的类型为整型。该数组的下标变量共有3×4个,即: a[0][0],a[0][1],a[0][2],a[0][3]<br>a[1][0],a[1][1],a[1][2],a[1][3]<br>a[2][0],a[2][1],a[2][2],a[2][3]<br>   二维数组在概念上是二维的,即是说其下标在两个方向上变化, 下标变量在数组中的位置也处于一个平面之中, 而不是象一维数组只是一个向量。但是,实际的硬件存储器却是连续编址的, 也就是说存储器单元是按一维线性排列的。 如何在一维存储器中存放二维数组,可有两种方式:一种是按行排列, 即放完一行之后顺次放入第二行。另一种是按列排列, 即放完一列之后再顺次放入第二列。在C语言中,二维数组是按行排列的。 在图4.1中,按行顺次存放,先存放a[0]行,再存放a[1]行,最后存放a[2]行。每行中有四个元素也是依次存放。由于数组a说明为<br>int类型,该类型占两个字节的内存空间,所以每个元素均占有两个 字节(图中每一格为一字节)。<br><br>二维数组元素的表示方法<br><br>  二维数组的元素也称为双下标变量浔硎镜男问轿?数组名[下标][下标] 其中下标应为整型常量或整型表达式。例如: a[3][4] 表示a数组三行四列的元素。下标变量和数组说明在形式中有些相似,但这两者具有完全不同的含义。 数组说明的方括号中给出的是某一维的长度,即可取下标的最大值; 而数组元素中的下标是该元素在数组中的位置标识。前者只能是常量, 后者可以是常量,变量或表达式。<br>一个学习小组有5个人,每个人有三门课的考试成绩。求全组分科的平均成绩和各科总平均成绩。<br>课程 成绩姓名 Math C DBASE<br>张      80  75 92<br>王      61  65 71<br>李      59  63 70<br>赵      85  87 90<br>周      76  77 85<br>   可设一个二维数组a[5][3]存放五个人三门课的成绩。再设一个一维数组v[3]存放所求得各分科平均成绩,设变量l为全组各科总平均成绩。编程如下:<br>void main()<br>{<br>int i,j,s=0,l,v[3],a[5][3];<br>printf("input score\n");<br>for(i=0;i&lt;3;i++){<br>for(j=0;j&lt;5;j++)<br>{ scanf("%d",&amp;a[j]);<br>s=s+a[j];}<br>v=s/5;<br>s=0;<br>}<br>l=(v[0]+v[1]+v[2])/3;<br>printf("math:%d\nc languag:%d\ndbase:%d\n",v[0],v[1],v[2]);<br>printf("total:%d\n",l);<br>} for(i=0;j&lt;3;i++)<br>for(j=0;j&lt;5;j++)<br>{ scanf("%d",&amp;a[j]);<br>s=s+a[j];}<br>v=s/5;<br>s=0;<br>}<br>l=(v[0]+v[1]+v[2])/3;<br>   程序中首先用了一个双重循环。 在内循环中依次读入某一门课程的各个学生的成绩,并把这些成绩累加起来, 退出内循环后再把该累加成绩除以5送入v之中,这就是该门课程的平均成绩。外循环共循环三次,分别求出三门课各自的平均成绩并存放在v数组之中。退出外循环之后,把v[0],v[1],v[2]相加除以3即得到各科总平均成绩。最后按题意输出各个成绩。<br><br>二维数组的初始化<br>   二维数组初始化也是在类型说明时给各下标变量赋以初值。 二维数组可按行分段赋值,也可按行连续赋值。 例如对数组a[5][3]:<br>1.按行分段赋值可写为static int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},{85,87,90},{76,77,85} };<br>2.按行连续赋值可写为static int a[5][3]={ 80,75,92,61,65,71,59,63,70,85,87,90,76,77,85 };<br>   这两种赋初值的结果是完全相同的。<br>void main()<br>{<br>int i,j,s=0,l,v[3];<br>static int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},<br>{85,87,90},{76,77,85} };<br>for(i=0;i&lt;3;i++)<br>{ for(j=0;j&lt;5;j++)<br>s=s+a[j];<br>v=s/5;<br>s=0;<br>}<br>l=(v[0]+v[1]+v[2])/3;<br>printf("math:%d\nc languag:%d\ndbase:%d\n",v[0],v[1],v[2]);<br>printf("total:%d\n",l);<br>}<br>   对于二维数组初始化赋值还有以下说明:<br>1.可以只对部分元素赋初值,未赋初值的元素自动取0值。<br>例如: static int a[3][3]={{1},{2},{3}}; 是对每一行的第一列元素赋值,未赋值的元素取0值。 赋值后各元素的值为: 1 0 02 0 03 0 0<br>static int a [3][3]={{0,1},{0,0,2},{3}}; 赋值后的元素值为 0 1 00 0 23 0 0<br>2.如对全部元素赋初值,则第一维的长度可以不给出。<br>例如: 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};<br>   数组是一种构造类型的数据。 二维数组可以看作是由一维数组的嵌套而构成的。设一维数组的每个元素都又是一个数组, 就组成了二维数组。当然,前提是各元素类型必须相同。根据这样的分析,一个二维数组也可以分解为多个一维数组。 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]不能当作下标变量使用,它们是数组名,不是一个单纯的下标变量。<br><br>字符数组<br><br>  用来存放字符量的数组称为字符数组。 字符数组类型说明的形式与前面介绍的数值数组相同。例如: 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。<br>main()<br>{<br>int i,j;<br>char a[][5]={{'B','A','S','I','C',},{'d','B','A','S','E'}};<br>for(i=0;i&lt;=1;i++)<br>{<br>for(j=0;j&lt;=4;j++)<br>printf("%c",a[j]);<br>printf("\n");<br>}<br>}<br>   本例的二维字符数组由于在初始化时全部元素都赋以初值, 因此一维下标的长度可以不加以说明。字符串在C语言中没有专门的字符串变量, 通常用一个字符数组来存放一个字符串。在2.1.4节介绍字符串常量时,已说明字符串总是以'\0'作为串的结束符。因此当把一个字符串存入一个数组时, 也把结束符'\0'存入数组,并以此作为该字符串是否结束的标志。 有了'\0'标志后,就不必再用字符数组的长度来判断字符串的长度了。<br>   C语言允许用字符串的方式对数组作初始化赋值。例如:<br>static char c[]={'c', ' ','p','r','o','g','r','a','m'}; 可写为:<br>static char c[]={"C program"}; 或去掉{}写为:<br>sratic char c[]="C program";<br>   用字符串方式赋值比用字符逐个赋值要多占一个字节, 用于存放字符串结束标志'\0'。上面的数组c在内存中的实际存放情况为: C program\0`\0'是由C编译系统自动加上的。由于采用了`\0'标志,所以在用字符串赋初值时一般无须指定数组的长度, 而由系统自行处理。在采用字符串方式后,字符数组的输入输出将变得简单方便。 除了上述用字符串赋初值的办法外,还可用printf函数和scanf函数一次性输出输入一个字符数组中的字符串, 而不必使用循环语句逐个地输入输出每个字符。<br>void main()<br>{<br>static char c[]="BASIC\ndBASE";<br>printf("%s\n",c);<br>} printf("%s\n",c);<br>注意在本例的printf函数中,使用的格式字符串为“%s”, 表示输出的是一个字符串。而在输出表列中给出数组名则可。 不能写为: printf("%s",c[]);<br>void main()<br>{<br>char st[15];<br>printf("input string:\n");<br>scanf("%s",st);<br>printf("%s\n",st);<br>} char st[15];<br>   本例中由于定义数组长度为15, 因此输入的字符串长度必须小于15,以留出一个字节用于存放字符串结束标志`\0`。 应该说明的是,对一个字符数组,如果不作初始化赋值,则必须说明数组长度。还应该特别注意的是,当用scanf函数输入字符串时,字符串中不能含有空格,否则将以空格作为串的结束符。例如运行例4.8,当输入的字符串中含有空格时,运行情况为: input string:this is a book this 从输出结果可以看出空格以后的字符都未能输出。 为了避免这种情况, 可多设几个字符数组分段存放含空格的串。程序可改写如下:<br>Lesson<br>void main()<br>{<br>char st1[6],st2[6],st3[6],st4[6];<br>printf("input string:\n");<br>scanf("%s%s%s%s",st1,st2,st3,st4);<br>printf("%s %s %s %s\n",st1,st2,st3,st4);<br>}<br>   本程序分别设了四个数组, 输入的一行字符的空格分段分别装入四个数组。然后分别输出这四个数组中的字符串。在前面介绍过,scanf的各输入项必须以地址方式出现,如 &amp;a,&amp;b等。但在例4.8中却是以数组名方式出现的,这是为什么呢?这是由于在C语言中规定,数组名就代表了该数组的首地址。 整个数组是以首地址开头的一块连续的内存单元。如有字符数组char c[10],在内存可表示如图4.2。设数组c的首地址为2000,也就是说c[0]单元地址为2000。则数组名c就代表这个首地址。因此在c前面不能再加地址运算符&amp;。如写作scanf("%s",&amp;c);则是错误的。 在执行函数printf("%s",c) 时,按数组名c找到首地址,然后逐个输出数组中各个字符直到遇到字符串终止标志'\0'为止。<br><br>字符串常用函数<br><br>  C语言提供了丰富的字符串处理函数, 大致可分为字符串的输入、输出、合并、修改、比较、转换、复制、搜索几类。 使用这些函数可大大减轻编程的负担。用于输入输出的字符串函数, 在使用前应包含头文件"stdio.h" ; 使用其它字符串函数则应包含头文件"string.h"。 下面介绍几个最常用的字符串函数。<br>1.字符串输出函数 puts 格式: puts (字符数组名) 功能:把字符数组中的字符串输出到显示器。 即在屏幕上显示该字符串<br>#include"stdio.h"<br>main()<br>{<br>static char c[]="BASIC\ndBASE";<br>puts(c);<br>}<br>static char c[]="BASIC\ndBASE";<br>puts(c);<br>   从程序中可以看出puts函数中可以使用转义字符, 因此输出结果成为两行。puts函数完全可以由printf函数取代。 当需要按一定格式输出时,通常使用printf函数。<br>2.字符串输入函数gets 格式: gets (字符数组名) 功能:从标准输入设备键盘上输入一个字符串。 本函数得到一个函数值,即为该字符数组的首地址。<br>#include"stdio.h"<br>main()<br>{<br>char st[15];<br>printf("input string:\n");<br>gets(st);<br>puts(st);<br>}<br>   可以看出当输入的字符串中含有空格时,输出仍为全部字符串。说明gets函数并不以空格作为字符串输入结束的标志, 而只以回车作为输入结束。这是与scanf函数不同的。<br>3.字符串连接函数strcat 格式: strcat (字符数组名1,字符数组名2) 功能:把字符数组2中的字符串连接到字符数组1 中字符串的后面,并删去字符串1后的串标志“\0”。本函数返回值是字符数组1的首地址。<br>#include"string.h"<br>main()<br>{<br>static char st1[30]="My name is ";<br>int st2[10];<br>printf("input your name:\n");<br>gets(st2);<br>strcat(st1,st2);<br>puts(st1);<br>}<br>static char st1[30]="My name is ";<br>int st2[10];<br>printf("input your name:\n");<br>gets(st2);<br>strcat(st1,st2);<br>本程序把初始化赋值的字符数组与动态赋值的字符串连接起来。 要注意的是,字符数组1应定义足够的长度,否则不能全部装入被连接的字符串<br>4.字符串拷贝函数strcpy 格式: strcpy (字符数组名1,字符数组名2) 功能:把字符数组2中的字符串拷贝到字符数组1中。串结束标志“\0”也一同拷贝。字符数名2, 也可以是一个字符串常量。这时相当于把一个字符串赋予一个字符数组。<br>#include"string.h"<br>main()<br>{<br>static char st1[15],st2[]="C Language";<br>strcpy(st1,st2);<br>puts(st1);printf("\n");<br>}<br>static char st1[15],st2[]="C Language";<br>strcpy(st1,st2);<br>本函数要求字符数组1应有足够的长度,否则不能全部装入所拷贝的字符串。<br>5.字符串比较函数strcmp 格式: strcmp(字符数组名1,字符数组名2) 功能:按照ASCII码顺序比较两个数组中的字符串,并由函数返回值返回比较结果。<br>字符串1=字符串2,返回值=0;<br>字符串2〉字符串2,返回值〉0;<br>字符串1〈字符串2,返回值〈0。<br>本函数也可用于比较两个字符串常量,或比较数组和字符串常量。<br>#include"string.h"<br>main()<br>{ int k;<br>static char st1[15],st2[]="C Language";<br>printf("input a string:\n");<br>gets(st1);<br>k=strcmp(st1,st2);<br>if(k==0) printf("st1=st2\n");<br>if(k&gt;0) printf("st1&gt;st2\n");<br>if(k&lt;0) printf("st1&lt;st2\n");<br>}<br>{ int k;<br>static char st1[15],st2[]="C Language";<br>printf("input a string:\n");<br>gets(st1);<br>k=strcmp(st1,st2);<br>if(k==0) printf("st1=st2\n");<br>if(k&gt;0) printf("st1&gt;st2\n");<br>if(k&lt;0) printf("st1&lt;st2\n");<br>}<br>   本程序中把输入的字符串和数组st2中的串比较,比较结果返回到k中,根据k值再输出结果提示串。当输入为dbase时,由ASCII 码可知“dBASE”大于“C Language”故k〉0,输出结果“st1&gt;st2”。<br>6.测字符串长度函数strlen 格式: strlen(字符数组名) 功能:测字符串的实际长度(不含字符串结束标志‘\0’) 并作为函数返回值。<br>#include"string.h"<br>main()<br>{ int k;<br>static char st[]="C language";<br>k=strlen(st);<br>printf("The lenth of the string is %d\n",k);<br>}<br><br>程序举例<br><br>  把一个整数按大小顺序插入已排好序的数组中。 为了把一个数按大小插入已排好序的数组中, 应首先确定排序是从大到小还是从小到大进行的。设排序是从大到小进序的, 则可把欲插入的数与数组中各数逐个比较, 当找到第一个比插入数小的元素i时,该元素之前即为插入位置。然后从数组最后一个元素开始到该元素为止,逐个后移一个单元。最后把插入数赋予元素i即可。如果被插入数比所有的元素值都小则插入最后位置。<br>main()<br>{<br>int i,j,p,q,s,n,a[11]={127,3,6,28,54,68,87,105,162,18};<br>for(i=0;i&lt;10;i++)<br>{ p=i;q=a;<br>for(j=i+1;j&lt;10;j++)<br>if(q&lt;a[j]) {p=j;q=a[j];}<br>if(p!=i)<br>{<br>s=a;<br>a=a[p];<br>a[p]=s;<br>}<br>printf("%d ",a);<br>}<br>printf("\ninput number:\n");<br>scanf("%d",&amp;n);<br>for(i=0;i&lt;10;i++)<br>if(n&gt;a)<br>{for(s=9;s&gt;=i;s--) a[s+1]=a;<br>break;}<br>a=n;<br>for(i=0;i&lt;=10;i++)<br>printf("%d ",a);<br>printf("\n");<br>}<br>scanf("%d",&amp;n);<br>for(i=0;i&lt;10;i++)<br>if(n&gt;a)<br>{ for(s=9;s&gt;=i;s--) a[s+1]=a;<br>break; }<br>a=n; 本程序首先对数组a中的10个数从大到小排序并输出排序结果。然后输入要插入的整数n。再用一个for语句把n和数组元素逐个比较,如果发现有n&gt;a时,则由一个内循环把i以下各元素值顺次后移一个单元。后移应从后向前进行(从a[9]开始到a为止)。 后移结束跳出外循环。插入点为i,把n赋予a即可。 如所有的元素均大于被插入数,则并未进行过后移工作。此时i=10,结果是把n赋于a[10]。最后一个循环输出插入数后的数组各元素值。程序运行时,输入数47。从结果中可以看出47已插入到54和 28之间。<br><br>  在二维数组a中选出各行最大的元素组成一个一维数组b。 a=3 16 87 65 4 32 11 108 10 25 12 37b=(87 108 37) 本题的编程思路是,在数组A的每一行中寻找最大的元素,找到之后把该值赋予数组B相应的元素即可。程序如下:<br>main()<br>{<br>static int a[][4]={3,16,87,65,4,32,11,108,10,25,12,27};<br>int b[3],i,j,l;<br>for(i=0;i&lt;=2;i++)<br>{ l=a[0];<br>for(j=1;j&lt;=3;j++)<br>if(a[j]&gt;l) l=a[j];<br>b=l;}<br>printf("\narray a:\n");<br>for(i=0;i&lt;=2;i++)<br>{ for(j=0;j&lt;=3;j++)<br>printf("%5d",a[j]);<br>printf("\n");}<br>printf("\narray b:\n");<br>for(i=0;i&lt;=2;i++)<br>printf("%5d",b);<br>printf("\n");<br>}<br>for(i=0;i&lt;=2;i++){<br>l=a[0];<br>for(j=1;j&lt;=3;j++)<br>if(a[j]&gt;l) l=a[j];<br>b=l;<br>}<br>   程序中第一个for语句中又嵌套了一个for语句组成了双重循环。外循环控制逐行处理,并把每行的第0列元素赋予l。进入内循环后,把l与后面各列元素比较,并把比l大者赋予l。内循环结束时l 即为该行最大的元素,然后把l值赋予b。等外循环全部完成时,数组b中已装入了a各行中的最大值。后面的两个 for语句分别输出数组a和数组b。<br><br>  输入五个国家的名称按字母顺序排列输出。<br>   本题编程思路如下:五个国家名应由一个二维字符数组来处理。然而C语言规定可以把一个二维数组当成多个一维数组处理。 因此本题又可以按五个一维数组处理, 而每一个一维数组就是一个国家名字符串。用字符串比较函数比较各一维数组的大小,并排序, 输出结果即可。<br>编程如下:<br>void main()<br>{<br>char st[20],cs[5][20];<br>int i,j,p;<br>printf("input country's name:\n");<br>for(i=0;i&lt;5;i++)<br>gets(cs);<br>printf("\n");<br>for(i=0;i&lt;5;i++)<br>{ p=i;strcpy(st,cs);<br>for(j=i+1;j&lt;5;j++)<br>if(strcmp(cs[j],st)&lt;0) {p=j;strcpy(st,cs[j]);}<br>if(p!=i)<br>{<br>strcpy(st,cs);<br>strcpy(cs,cs[p]);<br>strcpy(cs[p],st);<br>}<br>puts(cs);}printf("\n");<br>}<br>for(i=0;i&lt;5;i++)<br>{ p=i;strcpy(st,cs);<br>for(j=i+1;j&lt;5;j++)<br>if(strcmp(cs[j],st)&lt;0) { p=j;strcpy(st,cs[j]);}<br>if(p!=i)<br>{<br>strcpy(st,cs);<br>strcpy(cs,cs[p]);<br>strcpy(cs[p],st);<br>}<br>   本程序的第一个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号元素的排序值。然后输出该字符串。在外循环全部完成之后即完成全部排序和输出。<br><br>本章小结<br><br>1.数组是程序设计中最常用的数据结构。数组可分为数值数组(整数组,实数组),字符数组以及后面将要介绍的指针数组,结构数组等。<br><br>2.数组可以是一维的,二维的或多维的。<br><br>3.数组类型说明由类型说明符、数组名、数组长度 (数组元素个数)三部分组成。数组元素又称为下标变量。 数组的类型是指下标变量取值的类型。<br><br>4.对数组的赋值可以用数组初始化赋值, 输入函数动态赋值和赋值语句赋值三种方法实现。 对数值数组不能用赋值语句整体赋值、输入或输出,而必须用循环语句逐个对数组元素进行操作。</FONT>
重庆商务网,重庆电子商务第一网,欢迎入驻!
13
 楼主| 发表于 2006-6-11 22:40 | 只看该作者
说普通话,从我做起,全面提升重庆文明程度
<BR><FONT color=red>第六章 函数</FONT><BR><BR>  在第一章中已经介绍过,C源程序是由函数组成的。 虽然在前面各章的程序中都只有一个主函数main(), 但实用程序往往由多个函数组成。函数是C源程序的基本模块, 通过对函数模块的调用实现特定的功能。C语言中的函数相当于其它高级语言的子程序。 C语言不仅提供了极为丰富的库函数(如Turbo C,MS C 都提供了三百多个库函数),还允许用户建立自己定义的函数。用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用函数。<BR><BR>  可以说C程序的全部工作都是由各式各样的函数完成的, 所以也把C语言称为函数式语言。 由于采用了函数模块式的结构, C语言易于实现结构化程序设计。使程序的层次结构清晰,便于程序的编写、阅读、调试。<BR><BR>  在C语言中可从不同的角度对函数分类。<BR><BR>1. 从函数定义的角度看,函数可分为库函数和用户定义函数两种。<BR><BR>(1)库函数<BR>   由C系统提供,用户无须定义, 也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf 、 scanf 、 getchar 、putchar、gets、puts、strcat等函数均属此类。<BR><BR>(2)用户定义函数<BR>   由用户按需要写的函数。对于用户自定义函数, 不仅要在程序中定义函数本身, 而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。<BR><BR>2. C语言的函数兼有其它语言中的函数和过程两种功能,从这个角度看,又可把函数分为有返回值函数和无返回值函数两种。<BR><BR>(1)有返回值函数<BR>   此类函数被调用执行完后将向调用者返回一个执行结果, 称为函数返回值。如数学函数即属于此类函数。 由用户定义的这种要返回函数值的函数,必须在函数定义和函数说明中明确返回值的类型。<BR><BR>(2)无返回值函数<BR>   此类函数用于完成某项特定的处理任务, 执行完成后不向调用者返回函数值。这类函数类似于其它语言的过程。 由于函数无须返回值,用户在定义此类函数时可指定它的返回为“空类型”, 空类型的说明符为“void”。<BR><BR>3. 从主调函数和被调函数之间数据传送的角度看又可分为无参函数和有参函数两种。<BR><BR>(1)无参函数<BR>   函数定义、函数说明及函数调用中均不带参数。 主调函数和被调函数之间不进行参数传送。 此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。<BR><BR>(2)有参函数<BR>   也称为带参函数。在函数定义及函数说明时都有参数, 称为形式参数(简称为形参)。在函数调用时也必须给出参数, 称为实际参数(简称为实参)。 进行函数调用时,主调函数将把实参的值传送给形参,供被调函数使用。<BR><BR>4. C语言提供了极为丰富的库函数, 这些库函数又可从功能角度作以下分类。<BR>(1)字符类型分类函数<BR>   用于对字符按ASCII码分类:字母,数字,控制字符,分隔符,大小写字母等。<BR>(2)转换函数<BR>   用于字符或字符串的转换;在字符量和各类数字量 (整型, 实型等)之间进行转换;在大、小写之间进行转换。<BR>(3)目录路径函数<BR>   用于文件目录和路径操作。<BR>(4)诊断函数<BR>   用于内部错误检测。<BR>(5)图形函数<BR>   用于屏幕管理和各种图形功能。<BR>(6)输入输出函数<BR>   用于完成输入输出功能。<BR>(7)接口函数<BR>   用于与DOS,BIOS和硬件的接口。<BR>(8)字符串函数<BR>   用于字符串操作和处理。<BR>(9)内存管理函数<BR>   用于内存管理。<BR>(10)数学函数<BR>   用于数学函数计算。<BR>(11)日期和时间函数<BR>   用于日期,时间转换操作。<BR>(12)进程控制函数<BR>   用于进程管理和控制。<BR>(13)其它函数<BR>   用于其它各种功能。<BR>   <BR>   以上各类函数不仅数量多,而且有的还需要硬件知识才会使用,因此要想全部掌握则需要一个较长的学习过程。 应首先掌握一些最基本、 最常用的函数,再逐步深入。由于篇幅关系,本书只介绍了很少一部分库函数, 其余部分读者可根据需要查阅有关手册。<BR><BR>  还应该指出的是,在C语言中,所有的函数定义,包括主函数main在内,都是平行的。也就是说,在一个函数的函数体内, 不能再定义另一个函数, 即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用。习惯上把调用者称为主调函数。 函数还可以自己调用自己,称为递归调用。main 函数是主函数,它可以调用其它函数,而不允许被其它函数调用。 因此,C程序的执行总是从main函数开始, 完成对其它函数的调用后再返回到main函数,最后由main函数结束整个程序。一个C源程序必须有,也只能有一个主函数main。<BR>   <BR>函数定义的一般形式<BR><BR>1.无参函数的一般形式<BR>类型说明符 函数名()<BR>{<BR>类型说明<BR>语句<BR>}<BR>   其中类型说明符和函数名称为函数头。 类型说明符指明了本函数的类型,函数的类型实际上是函数返回值的类型。 该类型说明符与第二章介绍的各种说明符相同。 函数名是由用户定义的标识符,函数名后有一个空括号,其中无参数,但括号不可少。{} 中的内容称为函数体。在函数体中也有类型说明, 这是对函数体内部所用到的变量的类型说明。在很多情况下都不要求无参函数有返回值, 此时函数类型符可以写为void。<BR>我们可以改为一个函数定义:<BR>void Hello()<BR>{<BR>printf ("Hello,world \n");<BR>}<BR>  这里,只把main改为Hello作为函数名,其余不变。Hello 函数是一个无参函数,当被其它函数调用时,输出Hello world字符串。<BR><BR>2.有参函数的一般形式<BR>类型说明符 函数名(形式参数表)<BR>型式参数类型说明<BR>{<BR>类型说明<BR>语句<BR>}<BR>   有参函数比无参函数多了两个内容,其一是形式参数表, 其二是形式参数类型说明。在形参表中给出的参数称为形式参数, 它们可以是各种类型的变量, 各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。 形参既然是变量,当然必须给以类型说明。例如,定义一个函数, 用于求两个数中的大数,可写为:<BR>int max(a,b)<BR>int a,b;<BR>{<BR>if (a&gt;b) return a;<BR>else return b;<BR>}<BR>   第一行说明max函数是一个整型函数,其返回的函数值是一个整数。形参为a,b。第二行说明a,b均为整型量。 a,b 的具体值是由主调函数在调用时传送过来的。在{}中的函数体内, 除形参外没有使用其它变量,因此只有语句而没有变量类型说明。 上边这种定义方法称为“传统格式”。 这种格式不易于编译系统检查,从而会引起一些非常细微而且难于跟踪的错误。ANSI C 的新标准中把对形参的类型说明合并到形参表中,称为“现代格式”。<BR>   例如max函数用现代格式可定义为:<BR>int max(int a,int b)<BR>{<BR>if(a&gt;b) return a;<BR>else return b;<BR>}<BR>   现代格式在函数定义和函数说明(后面将要介绍)时, 给出了形式参数及其类型,在编译时易于对它们进行查错, 从而保证了函数说明和定义的一致性。例1.3即采用了这种现代格式。 在max函数体中的return语句是把a(或b)的值作为函数的值返回给主调函数。有返回值函数中至少应有一个return语句。 在C程序中,一个函数的定义可以放在任意位置, 既可放在主函数main之前,也可放在main之后。例如例1.3中定义了一个max 函数,其位置在main之后, 也可以把它放在main之前。<BR>修改后的程序如下所示。<BR>int max(int a,int b)<BR>{<BR>if(a&gt;b)return a;<BR>else return b;<BR>}<BR>void main()<BR>{<BR>int max(int a,int b);<BR>int x,y,z;<BR>printf("input two numbers:\n");<BR>scanf("%d%d",&amp;x,&amp;y);<BR>z=max(x,y);<BR>printf("maxmum=%d",z);<BR>}<BR>   现在我们可以从函数定义、 函数说明及函数调用的角度来分析整个程序,从中进一步了解函数的各种特点。程序的第1行至第5行为max函数定义。进入主函数后,因为准备调用max函数,故先对max函数进行说明(程序第8行)。函数定义和函数说明并不是一回事,在后面还要专门讨论。 可以看出函数说明与函数定义中的函数头部分相同,但是末尾要加分号。程序第12 行为调用max函数,并把x,y中的值传送给max的形参a,b。max函数执行的<BR>结果 (a或b)将返回给变量z。最后由主函数输出z的值。<BR><BR>  函数调用的一般形式前面已经说过,在程序中是通过对函数的调用来执行函数体的,其过程与其它语言的子程序调用相似。C语言中, 函数调用的一般形式为:<BR><BR>  函数名(实际参数表) 对无参函数调用时则无实际参数表。 实际参数表中的参数可以是常数,变量或其它构造类型数据及表达式。 各实参之间用逗号分隔。'Next of Page在C语言中,可以用以下几种方式调用函数:<BR>1.函数表达式<BR>   函数作表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。例如: z=max(x,y)是一个赋值表达式,把max的返回值赋予变量z。'Next of Page<BR>2.函数语句<BR>   函数调用的一般形式加上分号即构成函数语句。例如: printf ("%D",a);scanf ("%d",&amp;b);都是以函数语句的方式调用函数。<BR>3.函数实参<BR>   函数作为另一个函数调用的实际参数出现。 这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的。例如: printf("%d",max(x,y)); 即是把max调用的返回值又作为printf函数的实参来使用的。在函数调用中还应该注意的一个问题是求值顺序的问题。 所谓求值顺序是指对实参表中各量是自左至右使用呢,还是自右至左使用。 对此, 各系统的规定不一定相同。在3.1.3节介绍printf 函数时已提<BR>到过,这里从函数调用的角度再强调一下。 看例5.2程序。<BR>void main()<BR>{<BR>int i=8;<BR>printf("%d\n%d\n%d\n%d\n",++i,--i,i++,i--);<BR>}<BR>如按照从右至左的顺序求值。例5.2的运行结果应为:<BR>8<BR>7<BR>7<BR>8<BR>如对printf语句中的++i,--i,i++,i--从左至右求值,结果应为:<BR>9<BR>8<BR>8<BR>9<BR>   应特别注意的是,无论是从左至右求值, 还是自右至左求值,其输出顺序都是不变的, 即输出顺序总是和实参表中实参的顺序相同。由于Turbo C现定是自右至左求值,所以结果为8,7,7,8。上述问题如还不理解,上机一试就明白了。函数的参数和函数的值<BR>一、函数的参数<BR>   前面已经介绍过,函数的参数分为形参和实参两种。 在本小节中,进一步介绍形参、实参的特点和两者的关系。 形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。 形参和实参的功能是作数据传送。发生函数调用时, 主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。<BR><BR>  函数的形参和实参具有以下特点:<BR>1.形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。<BR><BR>2.实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。<BR><BR>3.实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。<BR><BR>4.函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。例5.3可以说明这个问题。<BR>void main()<BR>{<BR>int n;<BR>printf("input number\n");<BR>scanf("%d",&amp;n);<BR>s(n);<BR>printf("n=%d\n",n);<BR>}<BR>int s(int n)<BR>{<BR>int i;<BR>for(i=n-1;i&gt;=1;i--)<BR>n=n+i;<BR>printf("n=%d\n",n);<BR>}<BR>本程序中定义了一个函数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。可见实参的值不随形参的变化而变化。<BR>
14
 楼主| 发表于 2006-6-11 22:40 | 只看该作者
经典百事通,你的生活好助手~
二、函数的值<br><br>  函数的值是指函数被调用之后, 执行函数体中的程序段所取得的并返回给主调函数的值。如调用正弦函数取得正弦值,调用例5.1的max函数取得的最大数等。对函数的值(或称函数返回值)有以下一些说明:<br><br>1. 函数的值只能通过return语句返回主调函数。return 语句的一般形式为:<br>return 表达式;<br>或者为:<br>return (表达式);<br>该语句的功能是计算表达式的值,并返回给主调函数。 在函数中允许有多个return语句,但每次调用只能有一个return 语句被执行, 因此只能返回一个函数值。<br><br>2. 函数值的类型和函数定义中函数的类型应保持一致。 如果两者不一致,则以函数类型为准,自动进行类型转换。 3. 如函数值为整型,在函数定义时可以省去类型说明。<br><br>4. 不返回函数值的函数,可以明确定义为“空类型”, 类型说明符为“void”。如例5.3中函数s并不向主函数返函数值,因此可定义为:<br>void s(int n)<br>{ ……<br>}<br><br>  一旦函数被定义为空类型后, 就不能在主调函数中使用被调函数的函数值了。例如,在定义s为空类型后,在主函数中写下述语句 sum=s(n); 就是错误的。为了使程序有良好的可读性并减少出错, 凡不要求返回值的函数都应定义为空类型。函数说明在主调函数中调用某函数之前应对该被调函数进行说明, 这与使用变量之前要先进行变量说明是一样的。 在主调函数中对被调函数作说明的目的是使编译系统知道被调函数返回值的类型, 以便在主调函数中按此种类型对返回值作相应的处理。 对被调函数的说明也有两种格式,一种为传统格式,其一般格式为: 类型说明符 被调函数名(); 这种格式只给出函数返回值的类型,被调函数名及一个空括号。<br><br>  这种格式由于在括号中没有任何参数信息, 因此不便于编译系统进行错误检查,易于发生错误。另一种为现代格式,其一般形式为:<br>类型说明符 被调函数名(类型 形参,类型 形参…);<br>或为:<br>类型说明符 被调函数名(类型,类型…);<br>   现代格式的括号内给出了形参的类型和形参名, 或只给出形参类型。这便于编译系统进行检错,以防止可能出现的错误。例5.1 main函数中对max函数的说明若<br>用传统格式可写为:<br>int max();<br>用现代格式可写为:<br>int max(int a,int b);<br>或写为:<br>int max(int,int);<br>   C语言中又规定在以下几种情况时可以省去主调函数中对被调函数的函数说明。<br>1. 如果被调函数的返回值是整型或字符型时, 可以不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。例5.3的主函数中未对函数s作说明而直接调用即属此种情形。<br><br>2. 当被调函数的函数定义出现在主调函数之前时, 在主调函数中也可以不对被调函数再作说明而直接调用。例如例5.1中, 函数max的定义放在main 函数之前,因此可在main函数中省去对 max函数的函数说明int max(int a,int b)。<br><br>3. 如在所有函数定义之前, 在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。例如:<br>char str(int a);<br>float f(float b);<br>main()<br>{<br>……<br>}<br>char str(int a)<br>{<br>……<br>}<br>float f(float b)<br>{<br>……<br>}<br>其中第一,二行对str函数和f函数预先作了说明。 因此在以后各函数中无须对str和f函数再作说明就可直接调用。<br><br>4. 对库函数的调用不需要再作说明, 但必须把该函数的头文件用include命令包含在源文件前部。数组作为函数参数数组可以作为函数的参数使用,进行数据传送。 数组用作函数参数有两种形式,一种是把数组元素(下标变量)作为实参使用; 另一种是把数组名作为函数的形参和实参使用。一、数组元素作函数实参数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时, 把作为实参的数组元素的值传送给形参,实现单向的值传送。例5.4说明了这种情况。[例5.4]判别一个整数数组中各元素的值,若大于0 则输出该值,若小于等于0则输出0值。编程如下:<br>void nzp(int v)<br>{<br>if(v&gt;0)<br>printf("%d ",v);<br>else<br>printf("%d ",0);<br>}<br>main()<br>{<br>int a[5],i;<br>printf("input 5 numbers\n");<br>for(i=0;i&lt;5;i++)<br>{<br>scanf("%d",&amp;a);<br>nzp(a);<br>}<br>}void nzp(int v)<br>{ ……<br>}<br>main()<br>{<br>int a[5],i;<br>printf("input 5 numbers\n");<br>for(i=0;i&lt;5;i++)<br>{ scanf("%d",&amp;a);<br>nzp(a);<br>}<br>}<br>   本程序中首先定义一个无返回值函数nzp,并说明其形参v 为整型变量。在函数体中根据v值输出相应的结果。在main函数中用一个for 语句输入数组各元素, 每输入一个就以该元素作实参调用一次nzp函数,即把a的值传送给形参v,供nzp函数使用。<br><br>二、数组名作为函数参数<br><br>  用数组名作函数参数与用数组元素作实参有几点不同:<br>1. 用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此, 并不要求函数的形参也是下标变量。 换句话说,对数组元素的处理是按普通变量对待的。用数组名作函数参数时, 则要求形参和相对应的实参都必须是类型相同的数组,都必须有明确的数组说明。当形参和实参二者不一致时,即会发生错误。 2. 在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传送是把实参变量的值赋予形参变量。在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素。因为实际上形参数组并不存在,编译系统不为形参数组分配内存。那么,数据的传送是如何实现的呢? 在第四章中我们曾介绍过,数组名就是数组的首地址。因此在数组名作函数参数时所进行的传送只是地址的传送, 也就是说把实参数组的首地址赋予形参数组名。形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是形参数组和实参数组为同一数组,共同拥有一段内存空间。图5.1说明了这种情形。图中设a为实参数组,类型为整型。a占有以2000 为首地址的一块内存区。b为形参数组名。当发生函数调用时,进行地址传送, 把实参数 组a的首地址传送给形参数组名b,于是b也取得该地址2000。 于是a,b两数组共同占有以2000 为首地址的一段连续内存单元。从图中还可以看出a和b下标相同的元素实际上也占相同的两个内<br>存单元(整型数组每个元素占二字节)。例如a[0]和b[0]都占用2000和2001单元,当然a[0]等于b[0]。类推则有a等于b。<br>[例5.5]数组a中存放了一个学生5门课程的成绩,求平均成绩。float aver(float a[5])<br>{<br>int i;<br>float av,s=a[0];<br>for(i=1;i&lt;5;i++)<br>s=s+a;<br>av=s/5;<br>return av;<br>}<br>void main()<br>{<br>float sco[5],av;<br>int i;<br>printf("\ninput 5 scores:\n");<br>for(i=0;i&lt;5;i++)<br>scanf("%f",&amp;sco);<br>av=aver(sco);<br>printf("average score is %5.2f",av);<br>}<br>float aver(float a[5])<br>{ ……<br>}<br>void main()<br>{<br>……<br>for(i=0;i&lt;5;i++)<br>scanf("%f",&amp;sco);<br>av=aver(sco);<br>……<br>}<br>   本程序首先定义了一个实型函数aver,有一个形参为实型数组a,长度为5。在函数aver中,把各元素值相加求出平均值,返回给主函数。主函数main 中首先完成数组sco的输入,然后以sco作为实参调用aver函数,函数返回值送av,最后输出av值。 从运行情况可以看出,程序实现了所要求的功能<br><br>3. 前面已经讨论过,在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向形参,不能从形参传回实参。形参的初值和实参相同, 而形参的值发生改变后,实参并不变化, 两者的终值是不同的。例5.3证实了这个结论。 而当用数组名作函数参数时,情况则不同。 由于实际上形参和实参为同一数组, 因此当形参数组发生变化时,实参数组也随之变化。 当然这种情况不能理解为发生了“双向”的值传递。但从实际情况来看,调用函数之后实参数组的值将由于形参数组值的变化而变化。为了说明这种情况,把例5.4改为例5.6的形式。[例5.6]题目同5.4例。改用数组名作函数参数。<br>void nzp(int a[5])<br>{<br>int i;<br>printf("\nvalues of array a are:\n");<br>for(i=0;i&lt;5;i++)<br>{<br>if(a&lt;0) a=0;<br>printf("%d ",a);<br>}<br>}<br>main()<br>{<br>int b[5],i;<br>printf("\ninput 5 numbers:\n");<br>for(i=0;i&lt;5;i++)<br>scanf("%d",&amp;b);<br>printf("initial values of array b are:\n");<br>for(i=0;i&lt;5;i++)<br>printf("%d ",b);<br>nzp(b);<br>printf("\nlast values of array b are:\n");<br>for(i=0;i&lt;5;i++)<br>printf("%d ",b);<br>}<br>void nzp(int a[5])<br>{ ……<br>}<br>main()<br>{<br>int b[5],i;<br>……<br>nzp(b);<br>……<br>}<br>   本程序中函数nzp的形参为整数组a,长度为 5。 主函数中实参数组b也为整型,长度也为5。在主函数中首先输入数组b的值,然后输出数组b的初始值。 然后以数组名b为实参调用nzp函数。在nzp中,按要求把负值单元清0,并输出形参数组a的值。 返回主函数之后,再次输出数组b的值。从运行结果可以看出,数组b 的初值和终值是不同的,数组b 的终值和数组a是相同的。这说明实参形参为同一数组,它们的值同时得以改变。 用数组名作为函数参数时还应注意以下几点:<br>a. 形参数组和实参数组的类型必须一致,否则将引起错误。<br>b. 形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。如把例5.6修改如下:<br>void nzp(int a[8])<br>{<br>int i;<br>printf("\nvalues of array aare:\n");<br>for(i=0;i&lt;8;i++)<br>{<br>if(a&lt;0)a=0;<br>printf("%d",a);<br>}<br>}<br>main()<br>{<br>int b[5],i;<br>printf("\ninput 5 numbers:\n");<br>for(i=0;i&lt;5;i++)<br>scanf("%d",&amp;b);<br>printf("initial values of array b are:\n");<br>for(i=0;i&lt;5;i++)<br>printf("%d",b);<br>nzp(b);<br>printf("\nlast values of array b are:\n");<br>for(i=0;i&lt;5;i++)<br>printf("%d",b);<br>}<br>   本程序与例5.6程序比,nzp函数的形参数组长度改为8,函数体中,for语句的循环条件也改为i&lt;8。因此,形参数组 a和实参数组b的长度不一致。编译能够通过,但从结果看,数组a的元素a[5],a[6],a[7]显然是无意义的。c. 在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。<br>例如:可以写为:<br>void nzp(int a[])<br>或写为<br>void nzp(int a[],int n)<br>   其中形参数组a没有给出长度,而由n值动态地表示数组的长度。n的值由主调函数的实参进行传送。<br>由此,例5.6又可改为例5.7的形式。<br>[例5.7]void nzp(int a[],int n)<br>{<br>int i;<br>printf("\nvalues of array a are:\n");<br>for(i=0;i&lt;n;i++)<br>{<br>if(a&lt;0) a=0;<br>printf("%d ",a);<br>}<br>}<br>main()<br>{<br>int b[5],i;<br>printf("\ninput 5 numbers:\n");<br>for(i=0;i&lt;5;i++)<br>scanf("%d",&amp;b);<br>printf("initial values of array b are:\n");<br>for(i=0;i&lt;5;i++)<br>printf("%d ",b);<br>nzp(b,5);<br>printf("\nlast values of array b are:\n");<br>for(i=0;i&lt;5;i++)<br>printf("%d ",b);<br>}<br>void nzp(int a[],int n)<br>{ ……<br>}<br>main()<br>{<br>……<br>nzp(b,5);<br>……<br>}<br>   本程序nzp函数形参数组a没有给出长度,由n 动态确定该长度。在main函数中,函数调用语句为nzp(b,5),其中实参5将赋予形参n作为形参数组的长度。<br>d. 多维数组也可以作为函数的参数。 在函数定义时对形参数组可以指定每一维的长度,也可省去第一维的长度。因此,以下写法都是合法的。<br>int MA(int a[3][10])<br>或<br>int MA(int a[][10])<br><br>函数的嵌套调用<br><br>  C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。 但是C语言允许在一个函数的定义中出现对另一个函数的调用。 这样就出现了函数的嵌套调用。即在被调函数中又调用其它函数。 这与其它语言的子程序嵌套的情形是类似的。其关系可表示如图5.2。<br><br>  图5.2表示了两层嵌套的情形。其执行过程是:执行main函数中调用a函数的语句时,即转去执行a函数,在a函数中调用b 函数时,又转去执行b函数,b函数执行完毕返回a函数的断点继续执行,a 函数执行完毕返回main函数的断点继续执行。<br>[例5.8]计算s=22!+32!<br>本题可编写两个函数,一个是用来计算平方值的函数f1, 另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值, 再在f1中以平方值为实参,调用 f2计算其阶乘值,然后返回f1,再返回主函数,在循环程序中计算累加和。<br>long f1(int p)<br>{<br>int k;<br>long r;<br>long f2(int);<br>k=p*p;<br>r=f2(k);<br>return r;<br>}<br>long f2(int q)<br>{<br>long c=1;<br>int i;<br>for(i=1;i&lt;=q;i++)<br>c=c*i;<br>return c;<br>}<br>main()<br>{<br>int i;<br>long s=0;<br>for (i=2;i&lt;=3;i++)<br>s=s+f1(i);<br>printf("\ns=%ld\n",s);<br>}<br>long f1(int p)<br>{<br>……<br>long f2(int);<br>r=f2(k);<br>……<br>}<br>long f2(int q)<br>{<br>……<br>}<br>main()<br>{ ……<br>s=s+f1(i);<br>……<br>}<br>   在程序中,函数f1和f2均为长整型,都在主函数之前定义, 故不必再在主函数中对f1和f2加以说明。在主程序中, 执行循环程序依次把i值作为实参调用函数f1求i2值。在f1中又发生对函数f2的调用,这时是把i2的值作为实参去调f2,在f2 中完成求i2! 的计算。f2执行完毕把C值(即i2!)返回给f1,再由f1 返回主函数实现累加。至此,由函数的嵌套调用实现了题目的要求。 由于数值很大, 所以函数和一些变量的类型都说明为长整型,否则会造成计算错误。<br>
15
 楼主| 发表于 2006-6-11 22:49 | 只看该作者
<P>函数的递归调用<br><br>  一个函数在它的函数体内调用它自身称为递归调用。 这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中, 主调函数又是被调函数。执行递归函数将反复调用其自身。 每调用一次就进入新的一层。例如有函数f如下:<br>int f (int x)<br>{<br>int y;<br>z=f(y);<br>return z;<br>}<br>   这个函数是一个递归函数。 但是运行该函数将无休止地调用其自身,这当然是不正确的。为了防止递归调用无终止地进行, 必须在函数内有终止递归调用的手段。常用的办法是加条件判断, 满足某种条件后就不再作递归调用,然后逐层返回。 下面举例说明递归调用的执行过程。<br>[例5.9]用递归法计算n!用递归法计算n!可用下述公式表示:<br>n!=1 (n=0,1)<br>n×(n-1)! (n&gt;1)<br>按公式可编程如下:<br>long ff(int n)<br>{<br>long f;<br>if(n&lt;0) printf("n&lt;0,input error");<br>else if(n==0||n==1) f=1;<br>else f=ff(n-1)*n;<br>return(f);<br>}<br>main()<br>{<br>int n;<br>long y;<br>printf("\ninput a inteager number:\n");<br>scanf("%d",&amp;n);<br>y=ff(n);<br>printf("%d!=%ld",n,y);<br>}<br>long ff(int n)<br>{ ……<br>else f=ff(n-1)*n;<br>……<br>}<br>main()<br>{ ……<br>y=ff(n);<br>……<br>}<br>   程序中给出的函数ff是一个递归函数。主函数调用ff 后即进入函数ff执行,如果n&lt;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) 的返<br>回值为6*4=24,最后返回值ff(5)为24*5=120。<br><br>  例5. 9也可以不用递归的方法来完成。如可以用递推法,即从1开始乘以2,再乘以3…直到n。递推法比递归法更容易理解和实现。但是有些问题则只能用递归算法才能实现。典型的问题是Hanoi塔问题。<br>   <br>   [例5.10]Hanoi塔问题<br>一块板上有三根针,A,B,C。A针上套有64个大小不等的圆盘, 大的在下,小的在上。如图5.4所示。要把这64个圆盘从A针移动C针上,每次只能移动一个圆盘,移动可以借助B针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。<br>本题算法分析如下,设A上有n个盘子。<br>如果n=1,则将圆盘从A直接移动到C。<br>如果n=2,则:<br>1.将A上的n-1(等于1)个圆盘移到B上;<br>2.再将A上的一个圆盘移到C上;<br>3.最后将B上的n-1(等于1)个圆盘移到C上。<br>如果n=3,则:<br>A. 将A上的n-1(等于2,令其为n`)个圆盘移到B(借助于C),<br>步骤如下:<br>(1)将A上的n`-1(等于1)个圆盘移到C上,见图5.5(b)。<br>(2)将A上的一个圆盘移到B,见图5.5(c)<br>(3)将C上的n`-1(等于1)个圆盘移到B,见图5.5(d)<br>B. 将A上的一个圆盘移到C,见图5.5(e)<br>C. 将B上的n-1(等于2,令其为n`)个圆盘移到C(借助A),<br>步骤如下:<br>(1)将B上的n`-1(等于1)个圆盘移到A,见图5.5(f)<br>(2)将B上的一个盘子移到C,见图5.5(g)<br>(3)将A上的n`-1(等于1)个圆盘移到C,见图5.5(h)。<br>到此,完成了三个圆盘的移动过程。<br>从上面分析可以看出,当n大于等于2时, 移动的过程可分解为<br>三个步骤:<br>第一步 把A上的n-1个圆盘移到B上;<br>第二步 把A上的一个圆盘移到C上;<br>第三步 把B上的n-1个圆盘移到C上;其中第一步和第三步是类同的。<br>当n=3时,第一步和第三步又分解为类同的三步,即把n`-1个圆盘从一个针移到另一个针上,这里的n`=n-1。 显然这是一个递归过<br>程,据此算法可编程如下:<br>move(int n,int x,int y,int z)<br>{<br>if(n==1)<br>printf("%c--&gt;%c\n",x,z);<br>else<br>{<br>move(n-1,x,z,y);<br>printf("%c--&gt;%c\n",x,z);<br>move(n-1,y,x,z);<br>}<br>}<br>main()<br>{<br>int h;<br>printf("\ninput number:\n");<br>scanf("%d",&amp;h);<br>printf("the step to moving %2d diskes:\n",h);<br>move(h,'a','b','c');<br>}<br>move(int n,int x,int y,int z)<br>{<br>if(n==1)<br>printf("%--&gt;%c\n",x,z);<br>else<br>{<br>move(n-1,x,z,y);<br>printf("%c--&gt;%c\n",x,z);<br>move(n-1,y,x,z);<br>}<br>}<br>main()<br>{ ……<br>move(h,'a','b','c');<br>}<br>   从程序中可以看出,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 时程序运行的结果为<br>input number:<br>4<br>the step to moving 4 diskes:<br>a→b<br>a→c<br>b→c<br>a→b<br>c→a<br>c→b<br>a→b<br>a→c<br>b→c<br>b→a<br>c→a<br>b→c<br>a→b<br>a→c<br>b→c<br><br>变量的作用域<br><br>  在讨论函数的形参变量时曾经提到, 形参变量只在被调用期间才分配内存单元,调用结束立即释放。 这一点表明形参变量只有在函数内才是有效的, 离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量, C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。 C语言中的变量,按作用域范围可分为两种, 即局部变量和全局变量。<br><br>一、局部变量<br><br>  局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。<br>例如:<br>int f1(int a) /*函数f1*/<br>{<br>int b,c;<br>……<br>}a,b,c作用域<br>int f2(int x) /*函数f2*/<br>{<br>int y,z;<br>}x,y,z作用域<br>main()<br>{<br>int m,n;<br>}<br>m,n作用域 在函数f1内定义了三个变量,a为形参,b,c为一般变量。在 f1的范围内a,b,c有效,或者说a,b,c变量的作用域限于f1内。同理,x,y,z的作用域限于f2内。 m,n的作用域限于main函数内。关于局部变量的作用域还要说明以下几点:<br><br>1. 主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。 2. 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。<br><br>3. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。如在例5.3 中,形参和实参的变量名都为n,是完全允许的。4. 在复合语句中也可定义变量,其作用域只在复合语句范围内。例如:<br>main()<br>{<br>int s,a;<br>……<br>{<br>int b;<br>s=a+b;<br>……b作用域<br>}<br>……s,a作用域<br>}[例5.11]main()<br>{<br>int i=2,j=3,k;<br>k=i+j;<br>{<br>int k=8;<br>if(i==3) printf("%d\n",k);<br>}<br>printf("%d\n%d\n",i,k);<br>}<br>main()<br>{<br>int i=2,j=3,k;<br>k=i+j;<br>{<br>int k=8;<br>if(i=3) printf("%d\n",k);<br>}<br>printf("%d\n%d\n",i,k);<br>}<br>   本程序在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。<br><br>二、全局变量<br><br>全局变量也称为外部变量,它是在函数外部定义的变量。 它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。 但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。 例如:<br>int a,b; /*外部变量*/<br>void f1() /*函数f1*/<br>{<br>……<br>}<br>float x,y; /*外部变量*/<br>int fz() /*函数fz*/<br>{<br>……<br>}<br>main() /*主函数*/<br>{<br>……<br>}/*全局变量x,y作用域 全局变量a,b作用域*/<br>   从上例可以看出a、b、x、y 都是在函数外部定义的外部变量,都是全局变量。但x,y 定义在函数f1之后,而在f1内又无对x,y的说明,所以它们在f1内无效。 a,b定义在源程序最前面,因此在f1,f2及main内不加说明也可使用。<br><br>[例5.12]输入正方体的长宽高l,w,h。求体积及三个面x*y,x*z,y*z的面积。<br>int s1,s2,s3;<br>int vs( int a,int b,int c)<br>{<br>int v;<br>v=a*b*c;<br>s1=a*b;<br>s2=b*c;<br>s3=a*c;<br>return v;<br>}<br>main()<br>{<br>int v,l,w,h;<br>printf("\ninput length,width and height\n");<br>scanf("%d%d%d",&amp;l,&amp;w,&amp;h);<br>v=vs(l,w,h);<br>printf("v=%d s1=%d s2=%d s3=%d\n",v,s1,s2,s3);<br>}<br>   本程序中定义了三个外部变量s1,s2,s3, 用来存放三个面积,其作用域为整个程序。函数vs用来求正方体体积和三个面积, 函数的返回值为体积v。由主函数完成长宽高的输入及结果输出。由于C语言规定函数返回值只有一个, 当需要增加函数的返回数据时,用外部变量是一种很好的方式。本例中,如不使用外部变量, 在主函数中就不可能取得v,s1,s2,s3四个值。而采用了外部变量, 在函数vs中求得的s1,s2,s3值在main 中仍然有效。因此外部变量是实现函数之间数据通讯的有效手段。对于全局变量还有以下几点说明:<br><br>1. 对于局部变量的定义和说明,可以不加区分。而对于外部变量则不然,外部变量的定义和外部变量的说明并不是一回事。外部变量定义必须在所有的函数之外,且只能定义一次。其一般形式为: [extern] 类型说明符 变量名,变量名… 其中方括号内的extern可以省去不写。<br>例如: int a,b;<br>等效于:<br>extern int a,b;<br>   而外部变量说明出现在要使用该外部变量的各个函数内, 在整个程序内,可能出现多次,外部变量说明的一般形式为: extern 类型说明符 变量名,变量名,…; 外部变量在定义时就已分配了内存单元, 外部变量定义可作初始赋值,外部变量说明不能再赋初始值, 只是表明在函数内要使用某外部变量。<br><br>2. 外部变量可加强函数模块之间的数据联系, 但是又使函数要依赖这些变量,因而使得函数的独立性降低。从模块化程序设计的观点来看这是不利的, 因此在不必要时尽量不要使用全局变量。<br><br>3. 在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。<br>[例5.13]int vs(int l,int w)<br>{<br>extern int h;<br>int v;<br>v=l*w*h;<br>return v;<br>}<br>main()<br>{<br>extern int w,h;<br>int l=5;<br>printf("v=%d",vs(l,w));<br>}<br>int l=3,w=4,h=5;<br>   本例程序中,外部变量在最后定义, 因此在前面函数中对要用的外部变量必须进行说明。外部变量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,返回主函数后输出。变量的存储类型各种变量的作用域不同, 就其本质来说是因变量的存储类型相同。所谓存储类型是指变量占用内存空间的方式, 也称为存储方式。<br><br>变量的存储方式可分为“静态存储”和“动态存储”两种。<br><br>  静态存储变量通常是在变量定义时就分定存储单元并一直保持不变, 直至整个程序结束。5.5.1节中介绍的全局变量即属于此类存储方式。动态存储变量是在程序执行过程中,使用它时才分配存储单元, 使用完毕立即释放。 典型的例子是函数的形式参数,在函数定义时并不给形参分配存储单元,只是在函数被调用时,才予以分配, 调用函数完毕立即释放。如果一个函数被多次调用,则反复地分配、 释放形参变量的存储单元。从以上分析可知, 静态存储变量是一直存在的, 而动态存储变量则时而存在时而消失。我们又把这种由于变量存储方式不同而产生的特性称变量的生存期。 生存期表示了变量存在的时间。 生存期和作用域是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系,又有区别。 一个变量究竟属于哪一种存储方式, 并不能仅从其作用域来判断,还应有明确的存储类型说明。<br><br>  在C语言中,对变量的存储类型说明有以下四种:<br>auto     自动变量<br>register   寄存器变量<br>extern    外部变量<br>static    静态变量<br>   自动变量和寄存器变量属于动态存储方式, 外部变量和静态变量属于静态存储方式。在介绍了变量的存储类型之后, 可以知道对一个变量的说明不仅应说明其数据类型,还应说明其存储类型。 因此变量说明的完整形式应为: 存储类型说明符 数据类型说明符 变量名,变量名…; 例如:<br>static int a,b;           说明a,b为静态类型变量<br>auto char c1,c2;          说明c1,c2为自动字符变量<br>static int a[5]={1,2,3,4,5};    说明a为静整型数组<br>extern int x,y;           说明x,y为外部整型变量<br></P>
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
16
 楼主| 发表于 2006-6-11 22:52 | 只看该作者
重庆商务网,重庆电子商务第一网!
下面分别介绍以上四种存储类型:<br><br>一、自动变量的类型说明符为auto。<br>   这种存储类型是C语言程序中使用最广泛的一种类型。C语言规定, 函数内凡未加存储类型说明的变量均视为自动变量, 也就是说自动变量可省去说明符auto。 在前面各章的程序中所定义的变量凡未加存储类型说明符的都是自动变量。例如:<br>{ int i,j,k;<br>char c;<br>……<br>}等价于: { auto int i,j,k;<br>auto char c;<br>……<br>}<br>   自动变量具有以下特点:<br>1. 自动变量的作用域仅限于定义该变量的个体内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量只在该复合语句中有效。 例如:<br>int kv(int a)<br>{<br>auto int x,y;<br>{ auto char c;<br>} /*c的作用域*/<br>……<br>} /*a,x,y的作用域*/<br><br>2. 自动变量属于动态存储方式,只有在使用它,即定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。例如以下程序:<br>main()<br>{ auto int a,s,p;<br>printf("\ninput a number:\n");<br>scanf("%d",&amp;a);<br>if(a&gt;0){<br>s=a+a;<br>p=a*a;<br>}<br>printf("s=%d p=%d\n",s,p);<br>}<br>{ auto int a;<br>printf("\ninput a number:\n");<br>scanf("%d",&amp;a);<br>if(a&gt;0){<br>auto int s,p;<br>s=a+a;<br>p=a*a;<br>}<br>printf("s=%d p=%d\n",s,p);<br>}<br>s,p是在复合语句内定义的自动变量,只能在该复合语句内有效。而程序的第9行却是退出复合语句之后用printf语句输出s,p的值,这显然会引起错误。<br><br>3. 由于自动变量的作用域和生存期都局限于定义它的个体内( 函数或复合语句内), 因此不同的个体中允许使用同名的变量而不会混淆。 即使在函数内定义的自动变量也可与该函数内部的复合语句中定义的自动变量同名。例5.14表明了这种情况。<br>[例5.14]<br>main()<br>{<br>auto int a,s=100,p=100;<br>printf("\ninput a number:\n");<br>scanf("%d",&amp;a);<br>if(a&gt;0)<br>{<br>auto int s,p;<br>s=a+a;<br>p=a*a;<br>printf("s=%d p=%d\n",s,p);<br>}<br>printf("s=%d p=%d\n",s,p);<br>}<br>   本程序在main函数中和复合语句内两次定义了变量s,p为自动变量。按照C语言的规定,在复合语句内,应由复合语句中定义的s,p起作用,故s的值应为a+ a,p的值为a*a。退出复合语句后的s,p 应为main所定义的s,p,其值在初始化时给定,均为100。从输出结果可以分析出两个s和两个p虽变量名相同, 但却是两个不同的变量。<br><br>4. 对构造类型的自动变量如数组等,不可作初始化赋值。<br><br>二、外部变量外部变量的类型说明符为extern。<br><br>在前面介绍全局变量时已介绍过外部变量。这里再补充说明外部变量的几个特点:<br>1. 外部变量和全局变量是对同一类变量的两种不同角度的提法。全局变是是从它的作用域提出的,外部变量从它的存储方式提出的,表示了它的生存期。<br><br>2. 当一个源程序由若干个源文件组成时, 在一个源文件中定义的外部变量在其它的源文件中也有效。例如有一个源程序由源文件F1.C和F2.C组成: F1.C<br>int a,b; /*外部变量定义*/<br>char c; /*外部变量定义*/<br>main()<br>{<br>……<br>}<br>F2.C<br>extern int a,b; /*外部变量说明*/<br>extern char c; /*外部变量说明*/<br>func (int x,y)<br>{<br>……<br>}<br>在F1.C和F2.C两个文件中都要使用a,b,c三个变量。在F1.C文件中把a,b,c都定义为外部变量。在F2.C文件中用extern把三个变量说明为外部变量,表示这些变量已在其它文件中定义,并把这些变量的类型和变量名,编译系统不再为它们分配内存空间。 对构造类型的外部变量, 如数组等可以在说明时作初始化赋值,若不赋初值,则系统自动定义它们的初值为0。<br><br>三、静态变量<br><br>  静态变量的类型说明符是static。 静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量, 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态外部变量,或称静态全局变量。 对于自动变量,前面已经介绍它属于动态存储方式。 但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。<br>由此看来, 一个变量可由static进行再说明,并改变其原有的存储方式。<br><br>1. 静态局部变量<br>   在局部变量的说明前再加上static说明符就构成静态局部变量。<br>例如:<br>static int a,b;<br>static float array[5]={1,2,3,4,5};<br>   <br>   静态局部变量属于静态存储方式,它具有以下特点:<br>(1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。<br><br>(2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。<br><br>(3)允许对构造类静态局部量赋初值。在数组一章中,介绍数组初始化时已作过说明。若未赋以初值,则由系统自动赋以0值。<br><br>(4)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。 根据静态局部变量的特点, 可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。<br>[例5.15]main()<br>{<br>int i;<br>void f(); /*函数说明*/<br>for(i=1;i&lt;=5;i++)<br>f(); /*函数调用*/<br>}<br>void f() /*函数定义*/<br>{<br>auto int j=0;<br>++j;<br>printf("%d\n",j);<br>}<br>   程序中定义了函数f,其中的变量j 说明为自动变量并赋予初始值为0。当main中多次调用f时,j均赋初值为0,故每次输出值均为1。现在把j改为静态局部变量,程序如下:<br>main()<br>{<br>int i;<br>void f();<br>for (i=1;i&lt;=5;i++)<br>f();<br>}<br>void f()<br>{<br>static int j=0;<br>++j;<br>printf("%d\n",j);<br>}<br>void f()<br>{<br>static int j=0;<br>++j;<br>printf("%d/n",j);<br>}<br>由于j为静态变量,能在每次调用后保留其值并在下一次调用时继续使用,所以输出值成为累加的结果。读者可自行分析其执行过程。<br><br>2.静态全局变量<br>   全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它<br>的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。<br><br>四、寄存器变量<br><br>  上述各类变量都存放在存储器内, 因此当对一个变量频繁读写时,必须要反复访问内存储器,从而花费大量的存取时间。 为此,C语言提供了另一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写, 这样可提高效率。寄存器变量的说明符是register。 对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量。<br>[例5.16]求∑200i=1imain()<br>{<br>register i,s=0;<br>for(i=1;i&lt;=200;i++)<br>s=s+i;<br>printf("s=%d\n",s);<br>}<br>本程序循环200次,i和s都将频繁使用,因此可定义为寄存器变量。<br>对寄存器变量还要说明以下几点:<br><br>1. 只有局部自动变量和形式参数才可以定义为寄存器变量。因为寄存器变量属于动态存储方式。凡需要采用静态存储方式的量不能定义为寄存器变量。 2. 在Turbo C,MS C等微机上使用的C语言中, 实际上是把寄存器变量当成自动变量处理的。因此速度并不能提高。 而在程序中允许使用寄存器变量只是为了与标准C保持一致。3. 即使能真正使用寄存器变量的机器,由于CPU 中寄存器的个数是有限的,因此使用寄存器变量的个数也是有限的。<br><br>内部函数和外部函数<br><br>  函数一旦定义后就可被其它函数调用。 但当一个源程序由多个源文件组成时, 在一个源文件中定义的函数能否被其它源文件中的函数调用呢?为此,C语言又把函数分为两类:<br><br>一、内部函数<br><br>  如果在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其它文件中的函数调用, 这种函数称为内部函<br>数。定义内部函数的一般形式是: static 类型说明符 函数名(形参表) 例如:<br>static int f(int a,int b) 内部函数也称为静态函数。但此处静态static 的含义已不是指存储方式,而是指对函数的调用范围只局限于本文件。 因此在不同的源文件中定义同名的静态函数不会引起混淆。<br><br>二、外部函数<br>   外部函数在整个源程序中都有效,其定义的一般形式为: extern 类型说明符 函数名(形参表) 例如:<br>extern int f(int a,int b)如在函数定义中没有说明extern或static则隐含为extern。在一个源文件的函数中调用其它源文件中定义的外部函数时,应 用extern说明被调函数为外部函数。例如:<br>F1.C (源文件一)<br>main()<br>{<br>extern int f1(int i); /*外部函数说明,表示f1函<br>数在其它源文件中*/<br>……<br>}<br>F2.C (源文件二)<br>extern int f1(int i); /*外部函数定义*/<br>{<br>……<br>}<br><br>本章小结<br><br>1. 函数的分类<br>(1)库函数:由C系统提供的函数;<br>(2)用户定义函数:由用户自己定义的函数;<br>(3)有返回值的函数向调用者返回函数值,应说明函数类型( 即返回值的类型 );<br>(4)无返回值的函数:不返回函数值,说明为空(void)类型;<br>(5)有参函数:主调函数向被调函数传送数据;<br>(6)无参函数:主调函数与被调函数间无数据传送;<br>(7)内部函数:只能在本源文件中使用的函数;<br>(8)外部函数:可在整个源程序中使用的函数。<br><br>2. 函数定义的一般形式<br>[extern/static] 类型说明符 函数名([形参表]) 方括号内为可选项。<br><br>3. 函数说明的一般形式 [extern] 类型说明符 函数名([形参表]);<br><br>4. 函数调用的一般形式 函数名([实参表])<br><br>5. 函数的参数分为形参和实参两种,形参出现在函数定义中,实参出现在函数调用中,发生函数调用时,将把实参的值传送给形参。 6. 函数的值是指函数的返回值,它是在函数中由return语句返回的。<br><br>7. 数组名作为函数参数时不进行值传送而进行地址传送。形参和实参实际上为同一数组的两个名称。因此形参数组的值发生变化,实参数组的值当然也变化。<br><br>8. C语言中,允许函数的嵌套调用和函数的递归调用。<br><br>9. 可从三个方面对变量分类,即变量的数据类型,变量作用域和变量的存储类型。在第二章中主要介绍变量的数据类型,本章中介绍了变量的作用域和变量的存储类型。<br><br>10.变量的作用域是指变量在程序中的有效范围, 分为局部变量和全局变量。 11.变量的存储类型是指变量在内存中的存储方式,分为静态存储和动态存储,表示了变量的生存期。<br><br>12.变量分类特性表存储方式存储类型说明符何处定义生存期作用域赋值前的值可赋初值类型动态存储自动变量 auto 寄存器变量 register 函数或复合语句内被调用时在定义它的函数或复合语句内不定基本类型int或char外部变量extern函数之外整个源程序整个源程序静态局部变量static 函数或复合语句内静态全局变量static 函数之外整个源程序在定义它的函数或复合语句内在定义它的源文件内0任何类型。 <br><br>
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
17
 楼主| 发表于 2006-6-13 17:27 | 只看该作者
重庆女性论坛
<FONT color=red>第七章  指针</FONT><br><br>     指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; 能很方便地使用数组和字符串; 并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。指针极大地丰富了C语言的功能。 学习指针是学习C语言中最重要的一环, 能否正确理解和使用指针是我们是否掌握C语言的一个标志。同时, 指针也是C语言中最为困难的一部分,在学习中除了要正确理解基本概念,还必须要多编程,上机调试。只要作到这些,指针也是不难掌握的。<br><br>     指针的基本概念 在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针, 其中存放的数据才是该单元的内容。在C语言中, 允许用一个变量来存放指针,这种变量称为指针变量。因此, 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中,设有字符变量C,其内容为“K”(ASCII码为十进制数 75),C占用了011A号单元(地址用十六进数表示)。设有指针变量P,内容为011A, 这种情况我们称为P指向变量C,或说P是指向变量C的指针。 严格地说,一个指针是一个地址, 是一个常量。而一个指针变量却可以被赋予不同的指针值,是变。 但在常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址, 是常量,“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。<br> <br>  既然指针变量的值是一个地址, 那么这个地址不仅可以是变量的地址, 也可以是其它数据结构的地址。在一个指针变量中存放一<br>个数组或一个函数的首地址有何意义呢? 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址, 也就找到了该数组或函数。这样一来, 凡是出现数组,函数的地方都可以用一个指针变量来表示, 只要该指针变量中赋予数组或函数的首地址即可。这样做, 将会使程序的概念十分清楚,程序本身也精练,高效。在C语言中, 一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构, 而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址, 它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。 这也是引入“指针”概念的一个重要原因。<br><br><br><br><br>  指针变量的类型说明<br><br>  对指针变量的类型说明包括三个内容:<br><br>  (1)指针类型说明,即定义变量为一个指针变量; <br><br>  (2)指针变量名;<br><br>  (3)变量值(指针)所指向的变量的数据类型。<br><br>  其一般形式为: 类型说明符 *变量名; <br><br>  其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。<br><br>  例如: int *p1;表示p1是一个指针变量,它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量, 应由向p1赋予的地址来决定。<br><br>  再如:<br><br>staic int *p2; /*p2是指向静态整型变量的指针变量*/<br>float *p3; /*p3是指向浮点变量的指针变量*/<br>char *p4; /*p4是指向字符变量的指针变量*/ 应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量, 时而又指向一个字符变量。 <br><br>  指针变量的赋值<br><br>  指针变量同普通变量一样,使用之前不仅要定义说明, 而且必须赋予具体的值。未经赋值的指针变量不能使用, 否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误。在C语言中, 变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。 C语言中提供了地址运算符&amp;来表示变量的地址。其一般形式为: &amp; 变量名; 如&amp;a变示变量a的地址,&amp;b表示变量b的地址。 变量本身必须预先说明。设有指向整型变量的指针变量p,如要把整型变量a 的地址赋予p可以有以下两种方式:<br><br>  (1)指针变量初始化的方法 <br><br>int a;<br>int *p=&amp;a; <br><br>  (2)赋值语句的方法<br><br>int a;<br>int *p;<br>p=&amp;a; <br><br>  不允许把一个数赋予指针变量,故下面的赋值是错误的: int *p;p=1000; 被赋值的指针变量前不能再加“*”说明符,如写为*p=&amp;a 也是错误的<br><br>  指针变量的运算<br><br>  指针变量可以进行某些运算,但其运算的种类是有限的。 它只能进行赋值运算和部分算术运算及关系运算。<br><br>  1.指针运算符<br><br>  (1)取地址运算符&amp;<br><br>  取地址运算符&amp;是单目运算符,其结合性为自右至左,其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中,我们已经了解并使用了&amp;运算符。<br><br>  (2)取内容运算符*<br><br>  取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。需要注意的是指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。<br><br>main(){<br> int a=5,*p=&amp;a;<br> printf ("%d",*p);<br>}<br>...... <br><br>  表示指针变量p取得了整型变量a的地址。本语句表示输出变量a的值。<br><br>  2.指针变量的运算<br><br>  (1)赋值运算<br><br>  指针变量的赋值运算有以下几种形式:<br><br>  ①指针变量初始化赋值,前面已作介绍。<br><br>  ②把一个变量的地址赋予指向相同数据类型的指针变量。例如:<br><br>int a,*pa;<br>pa=&amp;a; /*把整型变量a的地址赋予整型指针变量pa*/ <br><br>  ③把一个指针变量的值赋予指向相同类型变量的另一个指针变量。如:<br><br>int a,*pa=&amp;a,*pb;<br>pb=pa; /*把a的地址赋予指针变量pb*/ <br><br>  由于pa,pb均为指向整型变量的指针变量,因此可以相互赋值。<br><br>  ④把数组的首地址赋予指向数组的指针变量。<br><br>  例如:<br><br>int a[5],*pa;<br>pa=a; (数组名表示数组的首地址,故可赋予指向数组的指针变量pa) <br><br>  也可写为:<br><br>pa=&amp;a[0]; /*数组第一个元素的地址也是整个数组的首地址, <br><br>  也可赋予pa*/<br><br>  当然也可采取初始化赋值的方法:<br><br>int a[5],*pa=a; <br><br>  ⑤把字符串的首地址赋予指向字符类型的指针变量。例如: char *pc;pc="c language";或用初始化赋值的方法写为: char *pc="C Language"; 这里应说明的是并不是把整个字符串装入指针变量, 而是把存放该字符串的字符数组的首地址装入指针变量。 在后面还将详细介绍。<br><br>  ⑥把函数的入口地址赋予指向函数的指针变量。例如: int (*pf)();pf=f; /*f为函数名*/<br><br><br><br>  (2)加减算术运算<br><br>  对于指向数组的指针变量,可以加上或减去一个整数n。设pa是指向数组a的指针变量,则pa+n,pa-n,pa++,++pa,pa--,--pa 运算都是合法的。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1 在概念上是不同的。因为数组可以有不同的类型, 各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。<br><br>  例如:<br><br>int a[5],*pa;<br>pa=a; /*pa指向数组a,也是指向a[0]*/<br>pa=pa+2; /*pa指向a[2],即pa的值为&amp;pa[2]*/ 指针变量的加减运算只能对数组指针变量进行, 对指向其它类型变量的指针变量作加减运算是毫无意义的。(3)两个指针变量之间的运算只有指向同一数组的两个指针变量之间才能进行运算, 否则运算毫无意义。 <br><br>  ①两指针变量相减<br><br>  两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址) 相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2 是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H,而浮点数组每个元素占4个字节,所以pf1-pf2的结果为(2000H-2010H)/4=4,表示pf1和 pf2之间相差4个元素。两个指针变量不能进行加法运算。 例如, pf1+pf2是什么意思呢?毫无实际意义。<br><br>  ②两指针变量进行关系运算<br><br>  指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:<br><br>pf1==pf2表示pf1和pf2指向同一数组元素<br>pf1&gt;pf2表示pf1处于高地址位置<br>pf1&lt;pf2表示pf2处于低地址位置<br>main(){<br> int a=10,b=20,s,t,*pa,*pb; <br> pa=&amp;a;<br> pb=&amp;b;<br> s=*pa+*pb;<br> t=*pa**pb;<br> printf("a=%d\nb=%d\na+b=%d\na*b=%d\n",a,b,a+b,a*b);<br> printf("s=%d\nt=%d\n",s,t);<br>}<br>...... <br><br>  说明pa,pb为整型指针变量<br><br>  给指针变量pa赋值,pa指向变量a。<br><br>  给指针变量pb赋值,pb指向变量b。<br><br>  本行的意义是求a+b之和,(*pa就是a,*pb就是b)。<br><br>  本行是求a*b之积。<br><br>  输出结果。<br><br>  输出结果。<br><br>  ...... <br><br>  指针变量还可以与0比较。设p为指针变量,则p==0表明p是空指针,它不指向任何变量;p!=0表示p不是空指针。空指针是由对指针变量赋予0值而得到的。例如: #define NULL 0 int *p=NULL; 对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。<br><br>main(){<br> int a,b,c,*pmax,*pmin;<br> printf("input three numbers:\n");<br> scanf("%d%d%d",&amp;a,&amp;b,&amp;c);<br> if(a&gt;b){<br>  pmax=&amp;a;<br>  pmin=&amp;b;<br> }<br> else{<br>  pmax=&amp;b;<br>  pmin=&amp;a;<br> }<br> if(c&gt;*pmax) pmax=&amp;c;<br> if(c&lt;*pmin) pmin=&amp;c;<br> printf("max=%d\nmin=%d\n",*pmax,*pmin);<br>}<br>......  <br><br>  pmax,pmin为整型指针变量。<br><br>  输入提示。<br><br>  输入三个数字。<br><br>  如果第一个数字大于第二个数字...<br><br>  指针变量赋值<br><br>  指针变量赋值<br><br>  指针变量赋值<br><br>  指针变量赋值<br><br>  判断并赋值<br><br>  判断并赋值 <br><br>  输出结果<br><br>  ......<br><br><br><br>  数组指针变量的说明和使用<br><br>  指向数组的指针变量称为数组指针变量。 在讨论数组指针变量的说明和使用之前,我们先明确几个关系。<br><br>  一个数组是由连续的一块内存单元组成的。 数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量) 组成的。每个数组元素按其类型不同占有几个连续的内存单元。 一个数组元素的首地址也是指它所占有的几个内存单元的首地址。 一个指针变量既可以指向一个数组,也可以指向一个数组元素, 可把数组名或第一个元素的地址赋予它。如要使指针变量指向第i号元素可以把i元素的首地址赋予它或把数组名加i赋予它。<br><br>  设有实数组a,指向a的指针变量为pa,从图6.3中我们可以看出有以下关系:<br><br>  pa,a,&amp;a[0]均指向同一单元,它们是数组a的首地址,也是0 号元素a[0]的首地址。pa+1,a+1,&amp;a[1]均指向1号元素a[1]。类推可知a+i,a+i,&amp;a<br><br>  指向i号元素a。应该说明的是pa是变量,而a,&amp;a都是常量。在编程时应予以注意。<br><br>main(){<br> int a[5],i;<br> for(i=0;i&lt;5;i++){<br>  a=i;<br>  printf("a[%d]=%d\n",i,a);<br> }<br> printf("\n");<br>} <br><br>  主函数<br><br>  定义一个整型数组和一个整型变量<br><br>  循环语句 <br><br>  给数组赋值<br><br>  打印每一个数组的值 <br><br>  ......<br><br>  输出换行<br><br>  ......<br><br>  数组指针变量说明的一般形式为:<br><br>  类型说明符 * 指针变量名 <br><br>  其中类型说明符表示所指数组的类型。 从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。<br>引入指针变量后,就可以用两种方法来访问数组元素了。<br><br>  第一种方法为下标法,即用a形式访问数组元素。 在第四章中介绍数组时都是采用这种方法。<br><br>  第二种方法为指针法,即采用*(pa+i)形式,用间接访问的方法来访问数组元素。<br><br>main(){<br> int a[5],i,*pa;<br> pa=a;<br> for(i=0;i&lt;5;i++){<br>  *pa=i;<br>  pa++;<br> }<br> pa=a;<br> for(i=0;i&lt;5;i++){<br>  printf("a[%d]=%d\n",i,*pa);<br>  pa++;<br> }<br>} <br><br>
重庆商务网,重庆电子商务第一网,欢迎入驻!
18
 楼主| 发表于 2006-6-13 17:28 | 只看该作者
说普通话,从我做起,全面提升重庆文明程度
主函数<br><br>  定义整型数组和指针<br><br>  将指针pa指向数组a<br><br>  循环<br><br>  将变量i的值赋给由指针pa指向的a[]的数组单元<br><br>  将指针pa指向a[]的下一个单元<br><br>  ......<br><br>  指针pa重新取得数组a的首地址<br><br>  循环<br><br>  用数组方式输出数组a中的所有元素<br><br>  将指针pa指向a[]的下一个单元<br><br>  ......<br>  ...... <br><br>  下面,另举一例,该例与上例本意相同,但是实现方式不同。<br><br>main(){<br> int a[5],i,*pa=a;<br> for(i=0;i&lt;5;){<br>  *pa=i;<br>  printf("a[%d]=%d\n",i++,*pa++);<br> }<br>} <br><br>  主函数<br><br>  定义整型数组和指针,并使指针指向数组a<br><br>  循环<br><br>  将变量i的值赋给由指针pa指向的a[]的数组单元<br><br>  用指针输出数组a中的所有元素,同时指针pa指向a[]的下一个单元<br><br>  ......<br>  ......<br> <br>  数组名和数组指针变量作函数参数<br><br>  在第五章中曾经介绍过用数组名作函数的实参和形参的问题。在学习指针变量之后就更容易理解这个问题了。 数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址, 形参得到该地址后也指向同一数组。 这就好象同一件物品有两个彼此不同的名称一样。同样,指针变量的值也是地址, 数组指针变量的值即为数组的首地址,当然也可作为函数的参数使用。<br><br>float aver(float *pa);<br>main(){<br> float sco[5],av,*sp;<br> int i;<br> sp=sco;<br> printf("\ninput 5 scores:\n");<br> for(i=0;i&lt;5;i++) scanf("%f",&amp;sco);<br> av=aver(sp);<br> printf("average score is %5.2f",av);<br>}<br>float aver(float *pa)<br>{<br> int i;<br> float av,s=0;<br> for(i=0;i&lt;5;i++) s=s+*pa++;<br> av=s/5;<br> return av;<br>} <br><FONT color=red>第八章 文件</FONT><br><br>文件的基本概念<br>   所谓“文件”是指一组相关数据的有序集合。 这个数据集有一个名称,叫做文件名。 实际上在前面的各章中我们已经多次使用了文件,例如源程序文件、目标文件、可执行文件、库文件 (头文件)等。文件通常是驻留在外部介质(如磁盘等)上的, 在使用时才调入内存中来。从不同的角度可对文件作不同的分类。从用户的角度看,文件可分为普通文件和设备文件两种。<br><br>  普通文件是指驻留在磁盘或其它外部介质上的一个有序数据集,可以是源文件、目标文件、可执行程序; 也可以是一组待输入处理的原始数据,或者是一组输出的结果。对于源文件、目标文件、 可执行程序可以称作程序文件,对输入输出数据可称作数据文件。<br><br>  设备文件是指与主机相联的各种外部设备,如显示器、打印机、键盘等。在操作系统中,把外部设备也看作是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和写。 通常把显示器定义为标准输出文件, 一般情况下在屏幕上显示有关信息就是向标准输出文件输出。如前面经常使用的printf,putchar 函数就是这类输出。键盘通常被指定标准的输入文件, 从键盘上输入就意味着从标准输入文件上输入数据。scanf,getchar函数就属于这类输入。<br><br>  从文件编码的方式来看,文件可分为ASCII码文件和二进制码文件两种。<br><br>  ASCII文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码。例如,数5678的存储形式为:<br>ASC码:  00110101 00110110 00110111 00111000<br>      ↓     ↓    ↓    ↓<br>十进制码: 5     6    7    8 共占用4个字节。ASCII码文件可在屏幕上按字符显示, 例如源程序文件就是ASCII文件,用DOS命令TYPE可显示文件的内容。 由于是按字符显示,因此能读懂文件内容。<br><br>  二进制文件是按二进制的编码方式来存放文件的。 例如, 数5678的存储形式为: 00010110 00101110只占二个字节。二进制文件虽然也可在屏幕上显示, 但其内容无法读懂。C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。 输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。 因此也把这种文件称作“流式文件”。<br><br>  本章讨论流式文件的打开、关闭、读、写、 定位等各种操作。文件指针在C语言中用一个指针变量指向一个文件, 这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。 定义说明文件指针的一般形式为: FILE* 指针变量标识符; 其中FILE应为大写,它实际上是由系统定义的一个结构, 该结构中含有文件名、文件状态和文件当前位置等信息。 在编写源程序时不必关心FILE结构的细节。例如:FILE *fp; 表示fp是指向FILE结构的指针变量,通过fp 即可找存放某个文件信息的结构变量,然后按结构变量提供的信息找到该文件, 实施对文件的操作。习惯上也笼统地把fp称为指向一个文件的指针。文件的打开与关闭文件在进行读写操作之前要先打开,使用完毕要关闭。 所谓打开文件,实际上是建立文件的各种有关信息, 并使文件指针指向该文件,以便进行其它操作。关闭文件则断开指针与文件之间的联系,也就禁止再对该文件进行操作。   在C语言中,文件操作都是由库函数来完成的。 在本章内将介绍主要的文件操作函数。<br><br>文件打开函数fopen<br><br>  fopen函数用来打开一个文件,其调用的一般形式为: 文件指针名=fopen(文件名,使用文件方式) 其中,“文件指针名”必须是被说明为FILE 类型的指针变量,“文件名”是被打开文件的文件名。 “使用文件方式”是指文件的类型和操作要求。“文件名”是字符串常量或字符串数组。例如:<br>FILE *fp;<br>fp=("file a","r");<br>其意义是在当前目录下打开文件file a, 只允许进行“读”操作,并使fp指向该文件。<br>又如:<br>FILE *fphzk<br>fphzk=("c:\\hzk16',"rb")<br>其意义是打开C驱动器磁盘的根目录下的文件hzk16, 这是一个二进制文件,只允许按二进制方式进行读操作。两个反斜线“\\ ”中的第一个表示转义字符,第二个表示根目录。使用文件的方式共有12种,下面给出了它们的符号和意义。<br>文件使用方式        意 义<br>“rt”      只读打开一个文本文件,只允许读数据<br>“wt”      只写打开或建立一个文本文件,只允许写数据<br>“at”      追加打开一个文本文件,并在文件末尾写数据<br>“rb”      只读打开一个二进制文件,只允许读数据<br>“wb”       只写打开或建立一个二进制文件,只允许写数据<br>“ab”       追加打开一个二进制文件,并在文件末尾写数据<br>“rt+”      读写打开一个文本文件,允许读和写<br>“wt+”      读写打开或建立一个文本文件,允许读写<br>“at+”      读写打开一个文本文件,允许读,或在文件末追加数 据<br>“rb+”      读写打开一个二进制文件,允许读和写<br>“wb+”      读写打开或建立一个二进制文件,允许读和写<br>“ab+”      读写打开一个二进制文件,允许读,或在文件末追加数据<br><br>对于文件使用方式有以下几点说明:<br>1. 文件使用方式由r,w,a,t,b,+六个字符拼成,各字符的含义是:<br>r(read): 读<br>w(write): 写<br>a(append): 追加<br>t(text): 文本文件,可省略不写<br>b(banary): 二进制文件<br>+: 读和写<br><br>2. 凡用“r”打开一个文件时,该文件必须已经存在, 且只能从该文件读出。<br><br>3. 用“w”打开的文件只能向该文件写入。 若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。<br><br>4. 若要向一个已存在的文件追加新的信息,只能用“a ”方式打开文件。但此时该文件必须是存在的,否则将会出错。 5. 在打开一个文件时,如果出错,fopen将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:<br>if((fp=fopen("c:\\hzk16","rb")==NULL)<br>{<br>printf("\nerror on open c:\\hzk16 file!");<br>getch();<br>exit(1);<br>}<br>   这段程序的意义是,如果返回的指针为空,表示不能打开C盘根目录下的hzk16文件,则给出提示信息“error on open c:\ hzk16file!”,下一行getch()的功能是从键盘输入一个字符,但不在屏幕上显示。在这里,该行的作用是等待, 只有当用户从键盘敲任一键时,程序才继续执行, 因此用户可利用这个等待时间阅读出错提示。敲键后执行exit(1)退出程序。<br><br>6. 把一个文本文件读入内存时,要将ASCII码转换成二进制码, 而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。<br><br>7. 标准输入文件(键盘),标准输出文件(显示器 ),标准出错输出(出错信息)是由系统打开的,可直接使用。文件关闭函数fclose文件一旦使用完毕,应用关闭文件函数把文件关闭, 以避免文件的数据丢失等错误。 fclose函数<br><br>调用的一般形式是: fclose(文件指针); 例如:<br>fclose(fp); 正常完成关闭文件操作时,fclose函数返回值为0。如返回非零值则表示有错误发生。文件的读写对文件的读和写是最常用的文件操作。<br><br>在C语言中提供了多种文件读写的函数:<br>·字符读写函数 :fgetc和fputc<br>·字符串读写函数:fgets和fputs<br>·数据块读写函数:freed和fwrite<br>·格式化读写函数:fscanf和fprinf<br><br>  下面分别予以介绍。使用以上函数都要求包含头文件stdio.h。字符读写函数fgetc和fputc字符读写函数是以字符(字节)为单位的读写函数。 每次可从文件读出或向文件写入一个字符。<br>
19
 楼主| 发表于 2006-6-13 17:29 | 只看该作者
经典百事通,你的生活好助手~
一、读字符函数fgetc<BR><BR>  fgetc函数的功能是从指定的文件中读一个字符,函数调用的形式为: 字符变量=fgetc(文件指针); 例如:ch=fgetc(fp);其意义是从打开的文件fp中读取一个字符并送入ch中。<BR><BR>  对于fgetc函数的使用有以下几点说明:<BR>1. 在fgetc函数调用中,读取的文件必须是以读或读写方式打开的。<BR><BR>2. 读取字符的结果也可以不向字符变量赋值,例如:fgetc(fp);但是读出的字符不能保存。<BR><BR>3. 在文件内部有一个位置指针。用来指向文件的当前读写字节。在文件打开时,该指针总是指向文件的第一个字节。使用fgetc 函数后, 该位置指针将向后移动一个字节。 因此可连续多次使用fgetc函数,读取多个字符。 应注意文件指针和文件内部的位置指针不是一回事。文件指针是指向整个文件的,须在程序中定义说明,只要不重新赋值,文件指针的值是不变的。文件内部的位置指针用以指示文件内部的当前读写位置,每读写一次,该指针均向后移动,它不需在程序中定义说明,而是由系统自动设置的。<BR><BR>[例10.1]读入文件e10-1.c,在屏幕上输出。<BR>#include&lt;stdio.h&gt;<BR>main()<BR>{<BR>FILE *fp;<BR>char ch;<BR>if((fp=fopen("e10_1.c","rt"))==NULL)<BR>{<BR>printf("Cannot open file strike any key exit!");<BR>getch();<BR>exit(1);<BR>}<BR>ch=fgetc(fp);<BR>while (ch!=EOF)<BR>{<BR>putchar(ch);<BR>ch=fgetc(fp);<BR>}<BR>fclose(fp);<BR>}<BR>   本例程序的功能是从文件中逐个读取字符,在屏幕上显示。 程序定义了文件指针fp,以读文本文件方式打开文件“e10_1.c”, 并使fp指向该文件。如打开文件出错, 给出提示并退出程序。程序第12行先读出一个字符,然后进入循环, 只要读出的字符不是文件结束标志(每个文件末有一结束标志EOF)就把该字符显示在屏幕上,再读入下一字符。每读一次,文件内部的位置指针向后移动一个字符,文件结束时,该指针指向EOF。执行本程序将显示整个文件。<BR><BR>二、写字符函数fputc<BR><BR>  fputc函数的功能是把一个字符写入指定的文件中,函数调用的 形式为: fputc(字符量,文件指针); 其中,待写入的字符量可以是字符常量或变量,例如:fputc('a',fp);其意义是把字符a写入fp所指向的文件中。<BR><BR>  对于fputc函数的使用也要说明几点:<BR>1. 被写入的文件可以用、写、读写,追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,写入字符从文件首开始。如需保留原有文件内容,希望写入的字符以文件末开始存放,必须以追加方式打开文件。被写入的文件若不存在,则创建该文件。<BR><BR>2. 每写入一个字符,文件内部位置指针向后移动一个字节。<BR><BR>3. fputc函数有一个返回值,如写入成功则返回写入的字符, 否则返回一个EOF。可用此来判断写入是否成功。<BR><BR>[例10.2]从键盘输入一行字符,写入一个文件, 再把该文件内容读出显示在屏幕上。<BR>#include&lt;stdio.h&gt;<BR>main()<BR>{<BR>FILE *fp;<BR>char ch;<BR>if((fp=fopen("string","wt+"))==NULL)<BR>{<BR>printf("Cannot open file strike any key exit!");<BR>getch();<BR>exit(1);<BR>}<BR>printf("input a string:\n");<BR>ch=getchar();<BR>while (ch!='\n')<BR>{<BR>fputc(ch,fp);<BR>ch=getchar();<BR>}<BR>rewind(fp);<BR>ch=fgetc(fp);<BR>while(ch!=EOF)<BR>{<BR>putchar(ch);<BR>ch=fgetc(fp);<BR>}<BR>printf("\n");<BR>fclose(fp);<BR>}<BR>   程序中第6行以读写文本文件方式打开文件string。程序第13行从键盘读入一个字符后进入循环,当读入字符不为回车符时, 则把该字符写入文件之中,然后继续从键盘读入下一字符。 每输入一个字符,文件内部位置指针向后移动一个字节。写入完毕, 该指针已指向文件末。如要把文件从头读出,须把指针移向文件头, 程序第19行rewind函数用于把fp所指文件的内部位置指针移到文件头。 第20至25行用于读出文件中的一行内容。<BR><BR>[例10.3]把命令行参数中的前一个文件名标识的文件, 复制到后一个文件名标识的文件中, 如命令行中只有一个文件名则把该文件写到标准输出文件(显示器)中。<BR>#include&lt;stdio.h&gt;<BR>main(int argc,char *argv[])<BR>{<BR>FILE *fp1,*fp2;<BR>char ch;<BR>if(argc==1)<BR>{<BR>printf("have not enter file name strike any key exit");<BR>getch();<BR>exit(0);<BR>}<BR>if((fp1=fopen(argv[1],"rt"))==NULL)<BR>{<BR>printf("Cannot open %s\n",argv[1]);<BR>getch();<BR>exit(1);<BR>}<BR>if(argc==2) fp2=stdout;<BR>else if((fp2=fopen(argv[2],"wt+"))==NULL)<BR>{<BR>printf("Cannot open %s\n",argv[1]);<BR>getch();<BR>exit(1);<BR>}<BR>while((ch=fgetc(fp1))!=EOF)<BR>fputc(ch,fp2);<BR>fclose(fp1);<BR>fclose(fp2);<BR>}<BR>   本程序为带参的main函数。程序中定义了两个文件指针 fp1 和fp2,分别指向命令行参数中给出的文件。如命令行参数中没有给出文件名,则给出提示信息。程序第18行表示如果只给出一个文件名,则使fp2指向标准输出文件(即显示器)。程序第25行至28行用循环语句逐个读出文件1中的字符再送到文件2中。再次运行时,给出了一个文件名(由例10.2所建立的文件), 故输出给标准输出文件stdout,即在显示器上显示文件内容。第三次运行,给出了二个文件名,因此把string中的内容读出,写入到OK之中。可用DOS命令type显示OK的内容:字符串读写函数fgets和fputs<BR><BR>一、读字符串函数fgets函数的功能是从指定的文件中读一个字符串到字符数组中,函数调用的形式为: fgets(字符数组名,n,文件指针); 其中的n是一个正整数。表示从文件中读出的字符串不超过 n-1个字符。在读入的最后一个字符后加上串结束标志'\0'。例如:fgets(str,n,fp);的意义是从fp所指的文件中读出n-1个字符送入字符数组str中。<BR>[例10.4]从e10_1.c文件中读入一个含10个字符的字符串。<BR>#include&lt;stdio.h&gt;<BR>main()<BR>{<BR>FILE *fp;<BR>char str[11];<BR>if((fp=fopen("e10_1.c","rt"))==NULL)<BR>{<BR>printf("Cannot open file strike any key exit!");<BR>getch();<BR>exit(1);<BR>}<BR>fgets(str,11,fp);<BR>printf("%s",str);<BR>fclose(fp);<BR>}<BR>   本例定义了一个字符数组str共11个字节,在以读文本文件方式打开文件e101.c后,从中读出10个字符送入str数组,在数组最后一个单元内将加上'\0',然后在屏幕上显示输出str数组。输出的十个字符正是例10.1程序的前十个字符。<BR><BR>  对fgets函数有两点说明:<BR>1. 在读出n-1个字符之前,如遇到了换行符或EOF,则读出结束。<BR>2. fgets函数也有返回值,其返回值是字符数组的首地址。<BR><BR>二、写字符串函数fputs<BR><BR>fputs函数的功能是向指定的文件写入一个字符串,其调用形式为: fputs(字符串,文件指针) 其中字符串可以是字符串常量,也可以是字符数组名, 或指针 变量,例如:<BR>fputs(“abcd“,fp);<BR>其意义是把字符串“abcd”写入fp所指的文件之中。[例10.5]在例10.2中建立的文件string中追加一个字符串。<BR>#include&lt;stdio.h&gt;<BR>main()<BR>{<BR>FILE *fp;<BR>char ch,st[20];<BR>if((fp=fopen("string","at+"))==NULL)<BR>{<BR>printf("Cannot open file strike any key exit!");<BR>getch();<BR>exit(1);<BR>}<BR>printf("input a string:\n");<BR>scanf("%s",st);<BR>fputs(st,fp);<BR>rewind(fp);<BR>ch=fgetc(fp);<BR>while(ch!=EOF)<BR>{<BR>putchar(ch);<BR>ch=fgetc(fp);<BR>}<BR>printf("\n");<BR>fclose(fp);<BR>}<BR>   本例要求在string文件末加写字符串,因此,在程序第6行以追加读写文本文件的方式打开文件string 。 然后输入字符串, 并用fputs函数把该串写入文件string。在程序15行用rewind函数把文件内部位置指针移到文件首。 再进入循环逐个显示当前文件中的全部内容。<BR><BR>数据块读写函数fread和fwrite<BR><BR>  C语言还提供了用于整块数据的读写函数。 可用来读写一组数据,如一个数组元素,一个结构变量的值等。读数据块函数调用的一般形式为: fread(buffer,size,count,fp); 写数据块函数调用的一般形式为: fwrite(buffer,size,count,fp); 其中buffer是一个指针,在fread函数中,它表示存放输入数据的首地址。在fwrite函数中,它表示存放输出数据的首地址。 size 表示数据块的字节数。count 表示要读写的数据块块数。fp 表示文件指针。<BR>例如:<BR>fread(fa,4,5,fp); 其意义是从fp所指的文件中,每次读4个字节(一个实数)送入实数组fa中,连续读5次,即读5个实数到fa中。<BR>[例10.6]从键盘输入两个********,写入一个文件中, 再读出这两个学生的数据显示在屏幕上。<BR>#include&lt;stdio.h&gt;<BR>struct stu<BR>{<BR>char name[10];<BR>int num;<BR>int age;<BR>char addr[15];<BR>}boya[2],boyb[2],*pp,*qq;<BR>main()<BR>{<BR>FILE *fp;<BR>char ch;<BR>int i;<BR>pp=boya;<BR>qq=boyb;<BR>if((fp=fopen("stu_list","wb+"))==NULL)<BR>{<BR>printf("Cannot open file strike any key exit!");<BR>getch();<BR>exit(1);<BR>}<BR>printf("\ninput data\n");<BR>for(i=0;i&lt;2;i++,pp++)<BR>scanf("%s%d%d%s",pp-&gt;name,&amp;pp-&gt;num,&amp;pp-&gt;age,pp-&gt;addr);<BR>pp=boya;<BR>fwrite(pp,sizeof(struct stu),2,fp);<BR>rewind(fp);<BR>fread(qq,sizeof(struct stu),2,fp);<BR>printf("\n\nname\tnumber age addr\n");<BR>for(i=0;i&lt;2;i++,qq++)<BR>printf("%s\t%5d%7d%s\n",qq-&gt;name,qq-&gt;num,qq-&gt;age,qq-&gt;addr);<BR>fclose(fp);<BR>}<BR>   本例程序定义了一个结构stu,说明了两个结构数组boya和 boyb以及两个结构指针变量pp和qq。pp指向boya,qq指向boyb。程序第16行以读写方式打开二进制文件“stu_list”,输入二个********之后,写入该文件中, 然后把文件内部位置指针移到文件首,读出两块********后,在屏幕上显示。<BR><BR>格式化读写函数fscanf和fprintf<BR><BR>fscanf函数,fprintf函数与前面使用的scanf和printf 函数的功能相似,都是格式化读写函数。 两者的区别在于 fscanf 函数和fprintf函数的读写对象不是键盘和显示器,而是磁盘文件。这两个函数的调用格式为: fscanf(文件指针,格式字符串,输入表列); fprintf(文件指针,格式字符串,输出表列); 例如:<BR>fscanf(fp,"%d%s",&amp;i,s);<BR>fprintf(fp,"%d%c",j,ch);<BR>用fscanf和fprintf函数也可以完成例10.6的问题。修改后的程序如例10.7所示。<BR>[例10.7]<BR>#include&lt;stdio.h&gt;<BR>struct stu<BR>{<BR>char name[10];<BR>int num;<BR>int age;<BR>char addr[15];<BR>}boya[2],boyb[2],*pp,*qq;<BR>main()<BR>{<BR>FILE *fp;<BR>char ch;<BR>int i;<BR>pp=boya;<BR>qq=boyb;<BR>if((fp=fopen("stu_list","wb+"))==NULL)<BR>{<BR>printf("Cannot open file strike any key exit!");<BR>getch();<BR>exit(1);<BR>}<BR>printf("\ninput data\n");<BR>for(i=0;i&lt;2;i++,pp++)<BR>scanf("%s%d%d%s",pp-&gt;name,&amp;pp-&gt;num,&amp;pp-&gt;age,pp-&gt;addr);<BR>pp=boya;<BR>for(i=0;i&lt;2;i++,pp++)<BR>fprintf(fp,"%s %d %d %s\n",pp-&gt;name,pp-&gt;num,pp-&gt;age,pp-&gt;<BR>addr);<BR>rewind(fp);<BR>for(i=0;i&lt;2;i++,qq++)<BR>fscanf(fp,"%s %d %d %s\n",qq-&gt;name,&amp;qq-&gt;num,&amp;qq-&gt;age,qq-&gt;addr);<BR>printf("\n\nname\tnumber age addr\n");<BR>qq=boyb;<BR>for(i=0;i&lt;2;i++,qq++)<BR>printf("%s\t%5d %7d %s\n",qq-&gt;name,qq-&gt;num, qq-&gt;age,<BR>qq-&gt;addr);<BR>fclose(fp);<BR>}<BR>   与例10.6相比,本程序中fscanf和fprintf函数每次只能读写一个结构数组元素,因此采用了循环语句来读写全部数组元素。 还要注意指针变量pp,qq由于循环改变了它们的值,因此在程序的25和32行分别对它们重新赋予了数组的首地址。<BR><BR>文件的随机读写<BR><BR>  前面介绍的对文件的读写方式都是顺序读写, 即读写文件只能从头开始,顺序读写各个数据。 但在实际问题中常要求只读写文件中某一指定的部分。 为了解决这个问题可移动文件内部的位置指针到需要读写的位置,再进行读写,这种读写称为随机读写。 实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。文件定位移动文件内部位置指针的函数主要有两个, 即 rewind 函数和fseek函数。<BR><BR>  rewind函数前面已多次使用过,其调用形式为: rewind(文件指针); 它的功能是把文件内部的位置指针移到文件首。 下面主要介绍<BR>fseek函数。<BR><BR>  fseek函数用来移动文件内部位置指针,其调用形式为: fseek(文件指针,位移量,起始点); 其中:“文件指针”指向被移动的文件。 “位移量”表示移动的字节数,要求位移量是long型数据,以便在文件长度大于64KB 时不会出错。当用常量表示位移量时,要求加后缀“L”。“起始点”表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾。<BR>其表示方法如表10.2。<BR>起始点    表示符号    数字表示<BR>──────────────────────────<BR>文件首    SEEK—SET    0<BR>当前位置   SEEK—CUR    1<BR>文件末尾   SEEK—END     2<BR>例如:<BR>fseek(fp,100L,0);其意义是把位置指针移到离文件首100个字节处。还要说明的是fseek函数一般用于二进制文件。在文本文件中由于要进行转换,故往往计算的位置会出现错误。文件的随机读写在移动位置指针之后, 即可用前面介绍的任一种读写函数进行读写。由于一般是读写一个数据据块,因此常用fread和fwrite函数。下面用例题来说明文件的随机读写。<BR><BR>[例10.8]在学生文件stu list中读出第二个学生的数据。<BR>#include&lt;stdio.h&gt;<BR>struct stu<BR>{<BR>char name[10];<BR>int num;<BR>int age;<BR>char addr[15];<BR>}boy,*qq;<BR>main()<BR>{<BR>FILE *fp;<BR>char ch;<BR>int i=1;<BR>qq=&amp;boy;<BR>if((fp=fopen("stu_list","rb"))==NULL)<BR>{<BR>printf("Cannot open file strike any key exit!");<BR>getch();<BR>exit(1);<BR>}<BR>rewind(fp);<BR>fseek(fp,i*sizeof(struct stu),0);<BR>fread(qq,sizeof(struct stu),1,fp);<BR>printf("\n\nname\tnumber age addr\n");<BR>printf("%s\t%5d %7d %s\n",qq-&gt;name,qq-&gt;num,qq-&gt;age,<BR>qq-&gt;addr);<BR>}<BR>   文件stu_list已由例10.6的程序建立,本程序用随机读出的方法读出第二个学生的数据。程序中定义boy为stu类型变量,qq为指向boy的指针。以读二进制文件方式打开文件,程序第22行移动文件位置指针。其中的i值为1,表示从文件头开始,移动一个stu类型的长度, 然后再读出的数据即为第二个学生的数据。<BR><BR>
20
 楼主| 发表于 2006-6-13 17:30 | 只看该作者
<P>文件检测函数<br><br>C语言中常用的文件检测函数有以下几个。<br>一、文件结束检测函数feof函数调用格式: feof(文件指针);<br>功能:判断文件是否处于文件结束位置,如文件结束,则返回值为1,否则为0。<br><br>二、读写文件出错检测函数ferror函数调用格式: ferror(文件指针);<br>功能:检查文件在用各种输入输出函数进行读写时是否出错。 如ferror返回值为0表示未出错,否则表示有错。<br><br>三、文件出错标志和文件结束标志置0函数clearerr函数调用格式: clearerr(文件指针);<br>功能:本函数用于清除出错标志和文件结束标志,使它们为0值。<br><br>C库文件<br><br>C系统提供了丰富的系统文件,称为库文件,C的库文件分为两类,一类是扩展名为".h"的文件,称为头文件, 在前面的包含命令中我们已多次使用过。在".h"文件中包含了常量定义、 类型定义、宏定义、函数原型以及各种编译选择设置等信息。另一类是函数库,包括了各种函数的目标代码,供用户在程序中调用。 通常在程序中调用一个库函数时,要在调用之前包含该函数原型所在的".h" 文件。<br>在附录中给出了全部库函数。<br>ALLOC.H    说明内存管理函数(分配、释放等)。<br>ASSERT.H    定义 assert调试宏。<br>BIOS.H     说明调用IBM—PC ROM BIOS子程序的各个函数。<br>CONIO.H    说明调用DOS控制台I/O子程序的各个函数。<br>CTYPE.H    包含有关字符分类及转换的名类信息(如 isalpha和toascii等)。<br>DIR.H     包含有关目录和路径的结构、宏定义和函数。<br>DOS.H     定义和说明MSDOS和8086调用的一些常量和函数。<br>ERRON.H    定义砦蟠氲闹欠?lt;BR&gt;FCNTL.H    定义在与open库子程序连接时的符号常量。<br>FLOAT.H    包含有关浮点运算的一些参数和函数。<br>GRAPHICS.H   说明有关图形功能的各个函数,图形错误代码的常量定义,正对不同驱动程序的各种颜色值,及函数用到的一些特殊结构。<br>IO.H      包含低级I/O子程序的结构和说明。<br>LIMIT.H    包含各环境参数、编译时间限制、数的范围等信息。<br>MATH.H     说明数学运算函数,还定了 HUGE VAL 宏, 说明了matherr和matherr子程序用到的特殊结构。<br>MEM.H     说明一些内存操作函数(其中大多数也在STRING.H 中说明)。<br>PROCESS.H   说明进程管理的各个函数,spawn…和EXEC …函数的结构说明。<br>SETJMP.H    定义longjmp和setjmp函数用到的jmp buf类型, 说明这两个函数。<br>SHARE.H    定义文件共享函数的参数。<br>SIGNAL.H    定义SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,说明rajse和signal两个函数。<br>STDARG.H    定义读函数参数表的宏。(如vprintf,vscarf函数)。<br>STDDEF.H    定义一些公共数据类型和宏。<br>STDIO.H    定义Kernighan和Ritchie在Unix System V 中定义的标准和扩展的类型和宏。还定义标准I/O 预定义流:stdin,stdout和stderr,说明 I/O流子程序。<br>STDLIB.H    说明一些常用的子程序:转换子程序、搜索/ 排序子程序等。<br>STRING.H    说明一些串操作和内存操作函数。<br>SYS\STAT.H   定义在打开和创建文件时用到的一些符号常量。<br>SYS\TYPES.H  说明ftime函数和timeb结构。<br>SYS\TIME.H   定义时间的类型time[ZZ(Z] [ZZ)]t。<br>TIME.H     定义时间转换子程序asctime、localtime和gmtime的结构,ctime、 difftime、 gmtime、 localtime和stime用到的类型,并提供这些函数的原型。<br>VALUE.H    定义一些重要常量, 包括依赖于机器硬件的和为与Unix System V相兼容而说明的一些常量,包括浮点和双精度值的范围。<br><br>本章小结<br><br>1. C系统把文件当作一个“流”,按字节进行处理。<br><br>2. C文件按编码方式分为二进制文件和ASCII文件。<br><br>3. C语言中,用文件指针标识文件,当一个文件被 打开时, 可取得该文件指针。<br><br>4. 文件在读写之前必须打开,读写结束必须关闭。 5. 文件可按只读、只写、读写、追加四种操作方式打开,同时还必须指定文件的类型是二进制文件还是文本文件。<br><br>6. 文件可按字节,字符串,数据块为单位读写,文件也可按指定的格式进行读写。<br><br>7. 文件内部的位置指针可指示当前的读写位置,移动该指针可以对文件实现随机读写。</P>
<P><FONT color=red>第九章:结构与联合</FONT><br><br><br>结构类型定义和结构变量说明<br><br>  在实际问题中,一组数据往往具有不同的数据类型。例如, 在学生登记表中,姓名应为字符型;学号可为整型或字符型; 年龄应为整型;性别应为字符型;成绩可为整型或实型。 显然不能用一个数组来存放这一组数据。 因为数组中各元素的类型和长度都必须一致,以便于编译系统处理。为了解决这个问题,C语言中给出了另一种构造数据类型——“结构”。 它相当于其它高级语言中的记录。<br><br>  “结构”是一种构造类型,它是由若干“成员”组成的。 每一个成员可以是一个基本数据类型或者又是一个构造类型。 结构既是一种“构造”而成的数据类型, 那么在说明和使用之前必须先定义它,也就是构造它。如同在说明和调用函数之前要先定义函数一样。<br><br>一、结构的定义<br><br>定义一个结构的一般形式为:<br>struct 结构名<br>{<br>成员表列<br>};<br>成员表由若干个成员组成, 每个成员都是该结构的一个组成部分。对每个成员也必须作类型说明,其形式为:<br>类型说明符 成员名;<br>成员名的命名应符合标识符的书写规定。例如:<br>struct stu<br>{<br>int num;<br>char name[20];<br>char sex;<br>float score;<br>};<br>   在这个结构定义中,结构名为stu,该结构由4个成员组成。 第一个成员为num,整型变量;第二个成员为name,字符数组;第三个成员为sex,字符变量;第四个成员为score,实型变量。 应注意在括号后的分号是不可少的。结构定义之后,即可进行变量说明。 凡说明为结构stu的变量都由上述4个成员组成。由此可见, 结构是一种复杂的数据类型,是数目固定,类型不同的若干有序变量的集合。<br><br>二、结构类型变量的说明<br><br>说明结构变量有以下三种方法。以上面定义的stu为例来加以说明。<br>1. 先定义结构,再说明结构变量。如:<br>struct stu<br>{<br>int num;<br>char name[20];<br>char sex;<br>float score;<br>};<br>struct stu boy1,boy2;<br>说明了两个变量boy1和boy2为stu结构类型。也可以用宏定义使一个符号常量来表示一个结构类型,例如:<br>#define STU struct stu<br>STU<br>{<br>int num;<br>char name[20];<br>char sex;<br>float score;<br>};<br>STU boy1,boy2;<br><br>2. 在定义结构类型的同时说明结构变量。例如:<br>struct stu<br>{<br>int num;<br>char name[20];<br>char sex;<br>float score;<br>}boy1,boy2;<br><br>3. 直接说明结构变量。例如:<br>struct<br>{<br>int num;<br>char name[20];<br>char sex;<br>float score;<br>}boy1,boy2;<br><br>  第三种方法与第二种方法的区别在于第三种方法中省去了结构名,而直接给出结构变量。三种方法中说明的boy1,boy2变量都具有图7.1所示的结构。说明了boy1,boy2变量为stu类型后,即可向这两个变量中的各个成员赋值。在上述stu结构定义中,所有的成员都是基本数据类型或数组类型。成员也可以又是一个结构, 即构成了嵌套的结构。例如,图7.2给出了另一个数据结构。 按图7.2可给出以下结构定义:<br>struct date{<br>int month;<br>int day;<br>int year;<br>}<br>struct{<br>int num;<br>char name[20];<br>char sex;<br>struct date birthday;<br>float score;<br>}boy1,boy2;<br>   首先定义一个结构date,由month(月)、day(日)、year(年) 三个成员组成。 在定义并说明变量 boy1 和 boy2 时, 其中的成员birthday被说明为data结构类型。成员名可与程序中其它变量同名,互不干扰。结构变量成员的表示方法在程序中使用结构变量时, 往往不把它作为一个整体来使用。<br><br>  在ANSI C中除了允许具有相同类型的结构变量相互赋值以外, 一般对结构变量的使用,包括赋值、输入、输出、 运算等都是通过结构变量的成员来实现的。<br><br>  表示结构变量成员的一般形式是: 结构变量名.成员名 例如:boy1.num 即第一个人的学号 boy2.sex 即第二个人的性别 如果成员本身又是一个结构则必须逐级找到最低级的成员才能使用。例如:boy1.birthday.month 即第一个人出生的月份成员可以在程序中单独使用,与普通变量完全相同。<br><br>结构变量的赋值<br><br>前面已经介绍,结构变量的赋值就是给各成员赋值。 可用输入语句或赋值语句来完成。<br>[例7.1]给结构变量赋值并输出其值。<br>main(){<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>} boy1,boy2;<br>boy1.num=102;<br>boy1.name="Zhang ping";<br>printf("input sex and score\n");<br>scanf("%c %f",&amp;boy1.sex,&amp;boy1.score);<br>boy2=boy1;<br>printf("Number=%d\nName=%s\n",boy2.num,boy2.name);<br>printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);<br>}<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy1,boy2;<br>boy1.num=102;<br>boy1.name="Zhang ping";<br>printf("input sex and score\n");<br>scanf("%c %f",&amp;boy1.sex,&amp;boy1.score);<br>boy2=boy1;<br>printf("Number=%d\nName=%s\n",boy2.num,boy2.name);<br>printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);<br><br>  本程序中用赋值语句给num和name两个成员赋值,name是一个字符串指针变量。用scanf函数动态地输入sex和score成员值,然后把boy1的所有成员的值整体赋予boy2。最后分别输出boy2 的各个成员值。本例表示了结构变量的赋值、输入和输出的方法。<br><br>结构变量的初始化<br>   如果结构变量是全局变量或为静态变量, 则可对它作初始化赋值。对局部或自动结构变量不能作初始化赋值。<br>[例7.2]外部结构变量初始化。<br>struct stu /*定义结构*/<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>} boy2,boy1={102,"Zhang ping",'M',78.5};<br>main()<br>{<br>boy2=boy1;<br>printf("Number=%d\nName=%s\n",boy2.num,boy2.name);<br>printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);<br>}<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy2,boy1={102,"Zhang ping",'M',78.5};<br>main()<br>{<br>boy2=boy1;<br>……<br>}<br>本例中,boy2,boy1均被定义为外部结构变量,并对boy1作了初始化赋值。在main函数中,把boy1的值整体赋予boy2, 然后用两个printf语句输出boy2各成员的值。<br>[例7.3]静态结构变量初始化。<br>main()<br>{<br>static struct stu /*定义静态结构变量*/<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy2,boy1={102,"Zhang ping",'M',78.5};<br>boy2=boy1;<br>printf("Number=%d\nName=%s\n",boy2.num,boy2.name);<br>printf("Sex=%c\nScore=%f\n",boy2.sex,boy2.score);<br>}<br>static struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy2,boy1={102,"Zhang ping",'M',78.5};<br>   本例是把boy1,boy2都定义为静态局部的结构变量, 同样可以作初始化赋值。<br><br>结构数组<br><br>数组的元素也可以是结构类型的。 因此可以构成结构型数组。结构数组的每一个元素都是具有相同结构类型的下标结构变量。 在实际应用中,经常用结构数组来表示具有相同数据结构的一个群体。如一个班的学生档案,一个车间职工的工资表等。<br>结构数组的定义方法和结构变量相似,只需说明它为数组类型即可。例如:<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy[5];<br>定义了一个结构数组boy1,共有5个元素,boy[0]~boy[4]。每个数组元素都具有struct stu的结构形式。 对外部结构数组或静态结构数组可以作初始化赋值,例如:<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy[5]={<br>{101,"Li ping","M",45},<br>{102,"Zhang ping","M",62.5},<br>{103,"He fang","F",92.5},<br>{104,"Cheng ling","F",87},<br>{105,"Wang ming","M",58};<br>}<br>当对全部元素作初始化赋值时,也可不给出数组长度。<br>[例7.4]计算学生的平均成绩和不及格的人数。<br>struct stu<br>{<br>int num;<br>char *name;<br>char sex;<br>float score;<br>}boy[5]={<br>{101,"Li ping",'M',45},<br>{102,"Zhang ping",'M',62.5},<br>{103,"He fang",'F',92.5},<br>{104,"Cheng ling",'F',87},<br>{105,"Wang ming",'M',58},<br>};<br>main()<br>{<br>int i,c=0;<br>float ave,s=0;<br>for(i=0;i&lt;5;i++)<br>{<br>s+=boy.score;<br>if(boy.score&lt;60) c+=1;<br>}<br>printf("s=%f\n",s);<br>ave=s/5;<br>printf("average=%f\ncount=%d\n",ave,c);<br>}<br>本例程序中定义了一个外部结构数组boy,共5个元素, 并作了初始化赋值。在main函数中用for语句逐个累加各元素的score 成员值存于s之中,如score的值小于60(不及格)即计数器C加1, 循环完毕后计算平均成绩,并输出全班总分,平均分及不及格人数。<br><br>[例7.5]建立同学通讯录<br>#include"stdio.h"<br>#define NUM 3<br>struct mem<br>{<br>char name[20];<br>char phone[10];<br>};<br>main()<br>{<br>struct mem man[NUM];<br>int i;<br>for(i=0;i&lt;NUM;i++)<br>{<br>printf("input name:\n");<br>gets(man.name);<br>printf("input phone:\n");<br>gets(man.phone);<br>}<br>printf("name\t\t\tphone\n\n");<br>for(i=0;i&lt;NUM;i++)<br>printf("%s\t\t\t%s\n",man.name,man.phone);<br>}<br>   本程序中定义了一个结构mem,它有两个成员name和phone 用来表示姓名和电话号码。在主函数中定义man为具有mem 类型的结构数组。在for语句中,用gets函数分别输入各个元素中两个成员的值。然后又在for语句中用printf语句输出各元素中两个成员值。<br></P>
发布商机信息及企业宣传推广--请移步注册重庆商务网!!
快捷入口:给经典重庆客服留言
您需要登录后才可以回帖 登录 | 入驻经典

本版积分规则

连接招租|手机版| ( 苏ICP备13006526号-1 )

GMT+8, 2024-5-29 17:08

  • 欢迎关注重庆发展,多发帖多回帖才能持续保持帐号活力哟!请不要发表任何政 治,领 导,官 员,人 事及其它违法违规类言论,以免帐号被封禁。感谢您的支持和理解!
经典重庆旗下网站 | 24小时客服:13424176859 308675020

爱重庆,爱上经典重庆! 爱重庆,就上经典重庆!

快速回复 返回顶部 返回列表