第04章 函数与C程序结构_第1页
第04章 函数与C程序结构_第2页
第04章 函数与C程序结构_第3页
第04章 函数与C程序结构_第4页
第04章 函数与C程序结构_第5页
已阅读5页,还剩105页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

程序设计技术C语言数据描述和C程序设计初步

结构化程序设计基础和C语言的控制结构

数组及其应用

函数与C程序结构

指针与函数

指针与数组

字符串及其应用结构体类型和联合体类型C语言的文件处理及其应用位运算与枚举类型函数与程序结构函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法4.1函数的定义和调用C程序的一般结构

C源程序源文件1源文件i源文件n预处理语句函数1函数n说明/定义部分执行部分图4.1C程序结构从用户使用角度分:标准函数如printf、scanf函数等。自定义函数解决用户自己的需要。

从函数形式分:

有参函数主调函数向被调函数传值,一般会得到一个返回值。无参函数不传值,一般无返回值,主要执行一些操作。4.1函数的定义和调用4.1.1函数的定义和声明

函数定义:函数要完成的功能。

1)函数定义形式:

返回类型符函数名(形式参数表及其说明)//函数头

{

变量定义和说明及函数执行语句//函数体

}

2)函数体:由变量定义与函数执行语句组成。二者全无则是空函数,先占位置,以后补上。函数的定义内容如下:函数返回值类型可是任何有效类型,void表示函数无返回值。函数名用户自定义标识符,不能重名。形式参数(简称形参)—函数定义时设置的参数作用是为函数接受外来数据提供变量名、类型和数目。如示例中的doublearea(doubler)中的r。4.1.1函数的定义和声明定义prn函数:void型、无参定义area函数:返回值为double型,参数r也是double型。函数的定义函数体函数体return<表达式>语句函数的返回值必须要通过函数中的return语句获得。1、一个函数中可以有多个return语句,执行到哪个return,哪个return就起作用。例如根据传过来的参数判断,if满足某个条件就返回if下表达式的值,否则返回else下表达式的值。执行哪个return就看传过来的参数是否满足某种条件。

2、return(x)等价于returnx

3、return还可以返回表达式的值如return(x>y?x:y)返回两者中的较大值。函数执行结果(按函数定义的返回类型)返回给主调函数。如果函数定义时返回类型为void,则不需要return语句。4.1.1函数的定义和声明intfind_char(chars1,chars2){if(s1==s2)return1;

elsereturn-1;}参数传递返回值参数传递返回值

注意:C语言中的每一个函数都是一个独立的代码块。除调用它的语句之外,不能被任何其它函数中的任何语句所访问。在一个函数的内部不能定义其他函数(即函数不能嵌套定义)。这个规定保证了每个函数都是一个相对独立的程序模块。由多个函数组成的C程序中,各个函数的定义顺序是任意的,它不影响C程序运行时函数的执行顺序

函数的定义

2)函数的声明

将函数的名字、类型及形式参数的类型、个数及顺序等通知编译系统,以便在调用该函数时进行对照检查,如函数名是否正确、实际参数的类型和个数是否与形式参数一致等等。函数的声明标准库函数的声明使用标准库函数时,由于系统提供的标准库函数的说明都分门别类集中在一些称为“头文件”的文本文件中,所以在程序中如果要调用系统标准库函数,也要在程序的适当位置使用编译预处理语句来进行声明。

例如:

#include<stdio.h>

或#inlcude“stdio.h”

作用:将调用有关库函数的必要信息包含到本源文件中来。函数的声明用户自定义函数的声明

对于用户自定义函数,如果被调用函数(简称被调函数)与调用它的函数(简称主调函数)在同一源文件中,在函数调用之前,需要对被调函数进行声明。被调函数声明的一般形式:

返回类型符函数名(形式参数表及其说明);

函数的声明在函数声明中,形参变量名字无关紧要(可与函数定义不同或缺省)

area函数的声明语句可写成如下两种形式:

doublearea(double);

/*缺省形参名*/

doublearea(doublex);

/*形参名与定义不同*/

注意:被调函数的定义出现在主调函数之前,不必进行说明,其原因是编译系统此时已经知道了被调函数的返回类型。函数的声明标准库函数声明自定义函数的声明对比对比函数调用一个函数调用另外一个函数以完成某一特定的功能称之为函数调用。调用者称为主调函数被调者称为被调函数函数调用的一般形式:

函数名(实参表);调用时填入的参数,称为实际参数,简称实参。实参的个数、类型和顺序,应该与被调函数的形参个数、类型和顺序一致。4.1.2值参数传递的函数调用三种调用函数的方式①函数语句(p119):将函数调用作为一个单独的C语句,此种方式主要对应于返回值为空类型(void)或输出语句设计在被调函数中的函数调用。②函数表达式:函数作为一项,出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的。③函数实参:函数调用作为另外一个函数调用的实际参数出现。此时要求函数被调用后必须要返回一个确定值以作为其外层函数调用的实际参数。如:printf(“%d\n”,area(r1)+area(r2));4.1.2值参数传递的函数调用如:prn();如:sum=area(r1)+area(r2);调用area函数调用prn函数传给r返回32.153600传给r返回91.362400函数声明部分3.25.4printf(“sum=%lf\n”,area(r1)+area(r2));

当一个函数调用另一个函数时,实参的值传递到形参变量中就实现了主调函数到被调函数间的数据传递。函数间参数传递有两种方式:

传值方式传地址方式

4.1.2值参数传递的函数调用函数调用时实参的种类实参是值参数变量常数表达式实参是地址值参数指针变量变量的地址(&a)数组名或某数组元素地址函数名4.1.2值参数传递的函数调用

例4.3传值方式(P119)#include<stdio.h>voidmain(){ inta=10,b=5; voidswap(intx,inty);/*函数声明语句*/

swap(a,b);

/*函数调用语句*/ printf(“swap调用后:a=%d,b=%d\n”,a,b);}voidswap(intx,inty)/*函数的定义*/{ inttemp; temp=x,x=y,y=temp; printf(“swap调用中:x=%d,y=%d”,x,y);}4.1.2值参数传递的函数调用105x=5,y=10a=10,b=5为被调函数的形参变量(局部变量)分配存储;实际参数拷贝到对应形式参数(拷贝完成后形参与实参无关);控制转到被调函数执行;程序控制流程从被调函数返回主调函数(系统自动撤消为被调函数建立的形参局部变量);105abyxtemp10510①②5③10函数调用执行过程与参数传递上一张下一张重要结论:

函数的传值调用方式是一种数据复制方式,在这种方式下,实参值通过复制的方式传递给形参变量,实参与形参各自占用内存不同的存储单元,当数据传递结束后,它们互不相干。因此,在被调函数中无论形参怎样变化,都不会影响主调函数中实参的值。4.1.2值参数传递的函数调用函数与程序结构函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法4.2函数的嵌套调用和递归调用

4.2.1函数的嵌套调用

(P131)

C语言规定:在C程序中所有的函数都是平行的。即在C程序中函数不能嵌套定义,但函数可以嵌套调用,即一个函数在被调用的过程中又调用了另外的一个函数。一个两层嵌套函数调用的过程如下图所示:被嵌套调用函数4.2.1函数的嵌套调用4.2.1函数的嵌套调用(P132)例4.12:计算s=1k+2k+3k+……+Nk

程序设计思路:可以把问题分解为两个模块:求幂次方模块和求和模块,在求和模块中要包含求幂次方模块。

f1()函数的参数为n和k,其返回值是n的k次方。

f2()函数的参数为n和k,返回值是幂次方的累加和。4.2.1函数的嵌套调用s=1k+2k+3k+……+Nk#include<stdio.h>longf1(intx,inty) {longpower=x;intj;for(j=1;j<y;j++)power=power*x;returnpower;}longf2(intn,intk) {longsum=0;inti;for(i=1;i<=n;i++)sum=sum+f1(i,k);returnsum;}voidmain(){intn,k,s;

scanf("%d,%d",&n,&k);s=f2(n,k);printf("自然数1~%d的%d次方和为:%ld",n,k,s);}

例4.12程序演示

43(4,3)43434.2.2函数的递归调用

一个函数直接地或间接地自己调用自己,称为函数的递归调用。

直接递归调用:调用函数在函数体内直接调用该函数自身。

间接递归调用:调用函数调用其他的被调函数,最后又由被调函数调用到该函数自己递归调用可以看成是一种特殊的嵌套调用,它与一般的嵌套调用有明显的不同点:

4.2.2函数的递归调用①每次嵌套调用的函数都是该函数本身;②嵌套调用不会无限制进行下去,总会在某种条件下结束;③每次调用时,本次的函数体并没有执行完毕。故必须依靠系统提供一个特殊部件(堆栈)存放未完成的操作,以保证当递归调用结束时不会丢失操作。④在递归调用时将递归过程中未执行的操作依次从堆栈栈底开始存入(简称压栈),当递归结束时再依存放时的相反顺序将它们从堆栈栈顶弹出(简称出栈),系统用堆栈指针指示应该存入和取出数据的位置。堆栈:一段先进后出的内存区域。4.2.2函数的递归调用fac(5)→5*fac(4)fac(4)→4*fac(3)fac(3)→3*fac(2)fac(2)→2*fac(1)fac(1)→1递归压栈方向(回推)fac(2)→2*fac(1)→2*1→2fac(3)→3*fac(2)→3*2→6fac(4)→4*fac(3)→4*6→24fac(5)→5*fac(4)→5*24→120递归回溯方向(递推)图4.12递归调用过程示意例4.14程序演示例4.14

用递归方式求n!。4.2.2函数的递归调用

例4.14

用递归方式求n!。递归公式:n!=n*(n-1)!(n>1)

递归结束条件:n!=1(n<=1)

递归算法描述:n<=1fac(n)return1returnfac(n-1)*nTF图4.14求n!的递归算法555fac(4)*5fac(2)*3fac(1)*2fac(3)*45544433322211(P133)4.13递归调用例,反向输出输入的字符串。

#include<stdio.h>voidmain() {voidreverse(); reverse(); } voidreverse() {charch; ch=getchar(); if(ch==’#’) putchar(ch); else {reverse(); putchar(ch); } }输入数据:ab#输出结果:putchar(‘a’)putchar(‘b’)#ba4.2.2函数的递归调用例4.13程序演示4.2.2函数的递归调用例4.15

求菲波拉契数列。已知一对小兔出生一个月后变成一对成兔,两个月后这对成兔就会生出一对小兔,三个月后这对成兔将生出第二对小兔,而第一对小兔又长大变成一对成兔,即一月成熟,二月生育,如此类推。请用计算机求解一对小兔经n月后将繁衍成多少对兔子?依题意分析出递归关系:n=1或n=2

递归结束条件n>=3

递归公式n=1或n=2f(n)return1returnf(n-1)+f(n-2)TF图4.13菲波拉契数列算法例4.15程序演示菲波拉契数列的递归调用过程f(2)f(3)f(4)f(1)f(2)f(5)f(1)f(3)f(2)第一分支第二分支4.2.2函数的递归调用通过三个示例的分析,递归方式的实现也是基于C语言的条件控制结构。递归函数设计的基本框架是相对固定的。

其一般形式描述如下:

if

递归结束条件成立

return

已知结果

else

将问题转化为同性质的较简单子问题;以递归方式求解子问题(递归方程);

上几例中的递归方程:

fac(n-1)*n例4.14putchar(ch)例4.13fib(n-1)+fib(n-2)例4.15

函数与程序结构函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法

一个变量的性质可以从两方面进行分析:

变量的作用域:变量能够起作用的空间范围。

变量的生存期:其变量值存在的时间范围。

它们的基本意义如下:一个变量在某个函数、某个源程序文件或某几个源程序文件范围内是有效的,则称其有效的范围为该变量的作用域,在此范围内可以访问或引用该变量。一个变量的值在某一时刻是存在的,则认为这一时刻属于该变量的“生存期”,或称其在此时刻“存在”。4.3变量的作用域和生存期(P138)

为了能够有效地确定变量的作用域和生存期两项属性,C语言用存储类别对变量进行限定。变量定义的完整形式为:

[存储类别符]<数据类型符>变量表;存储类别用于指定变量在内存中的存放方法。数据类型说明变量的取值范围及允许的操作;(如int:-2147483648~2147483647)变量的存储类别有四种自动型(auto)寄存器型(register)静态型(static)外部参照型(extern)4.3变量的作用域和生存期

可在程序的三个位置定义变量:函数内部、函数的参数定义和函数外部。按变量的作用域分类

全局变量(外部变量):指定义在所有函数外的变量,其作用范围从定义处开始到源文件结束为止。全局变量的定义形式:

[extern]

<数据类型符>变量表;

局部变量(自动变量):有三个位置可定义

①函数形参;②函数体内部;③复合语句内部。其作用域限定在定义范围内。局部变量的定义形式:

[auto]

<数据类型符>变量表;4.3.1变量的作用域用户使用内存存储空间的情况程序区静态存储区动态存储区用户区所有的全局(外部)变量及静态局部变量都存放在静态存储区,程序执行完毕才释放。函数的形参局部(自动)变量vodimain(){……}intx;voidf1();{inty;{intz;……}}voidf2(){……}全局变量与局部变量的关系xyz0164例4.18全局变量的作用域4x4.3.1变量的作用域局部变量(自动变量)的作用域局部变量的建立和撤消由系统自动进行。如在某函数中定义了自动变量,只有该函数被调用时,系统才为函数范围内的局部变量分配存储单元;当函数执行完毕,自动变量被系统自动撤销。在局部变量定义中省略auto的均为自动变量。

⑴自动变量的值在一个函数的两次调用之间不会保留。

⑵不同函数中定义的同名局部变量之间是毫无关系的。4.19程序演示25254.3.1变量的作用域同名全局变量与局部变量作用域重叠问题即在某些特定的情况下,出现全局变量、函数内定义的局部变量、复合语句中定义的局部变量名字相同的现象。即在全局变量与局部变量作用域重叠的情况下,C语言规定按“定义就近原则”来引用变量。

⑴如果定义的全局变量与函数中局部变量同名,程序进入函数内,使用定义的同名局部变量。

⑵在复合语句中如果定义有与较大范围(函数局部或全局)变量同名的变量,则使用该小局部范围内定义的同名局部变量;同名全局变量与局部变量作用域重叠问题4.21程序演示0x=20x=10x=04.3.2变量的生存期

程序运行中,不同存储类别的变量,占用的存储区域不同,分配的存储时间(生存期)也不同。按变量的生存期分类局部变量(自动变量)的生存期:这类变量存储于内存的动态存储区,它在程序运行中使用到该变量的时间段存在。即程序进入该函数或复合语句时才分配存储空间,当该函数或复合语句执行完后存储空间被撤销。(未初始化的变量是随机值)

全局变量或静态变量(全局或局部)的生存期:这类变量存储于内存的静态存储区,它在编译时分配存储空间,在程序运行的整个期间都存在。4.3.2变量的生存期

在C程序设计中,为了合理选择变量的存储类别,有必要对不同存储类别的变量在程序中的作用分两方面进行讨论。全局变量的存储类别对于全局变量而言,能够起作用的存储类别为extern和static。局部变量的存储类别对局部变量能够起作用的存储类别为auto和static。1)全局变量的存储类别extern

扩展作用域static

限制作用域限制强于扩展,即若两种存储说明对某全局变量同时出现,以限制说明为准。intx;staticinty;externintx;externintx;externinty;yxxy是静态全局变量不能扩展作用域X原作用范围File1.cpp源文件扩展xFile2.cpp源文件扩展xexterninty;使用extern声明,可扩充全局变量在一个源程序的作用域例4.22程序演示1)全局变量的存储类别X原作用域X被扩充后的作用域100110130

自动变量的生存期与其所在函数被调用运行的时间相同,并且自动变量的值在函数的多次调用中都不会保留。为满足在函数的多次调用中,局部变量仍能在保持原来值基础上继续使用,C语言提供了静态存储类别(static)。

静态局部变量的定义形式:

static

数据类型符变量表;2)局部变量的存储类别静态局部变量具有如下特点:①系统在编译时就为它分配了存储空间,且这个存储空间在程序的整个运行期间是固定的,因此它的生存期是整个程序的运行期间。②静态局部变量的初始化是在程序编译时进行的。如果在定义时没有对它进行初始化,那么系统将它自动初始化为0(int型)、0.000000(float或double型)、’\0’(char字符型)。③静态局部变量的值在函数多次调用中具有可继承性。④静态局部变量的值只能在定义它的局部范围内使用。在它的作用域范围之外,该静态局部变量虽然存在,但不能对它进行访问。2)局部变量的存储类别例4.23静态局部变量与自动变量的比较1

/*Name:ex04-23.cpp*/2 #include<stdio.h>3 voidmain()4 {voidf1();5 f1();6 f1();7 }8voidf1()9 {inta=10;10 staticintb=10;11 a+=100;12 b+=100;13printf("a=%d,b=%d\n",a,b);14}例4.23程序演示2)局部变量的存储类别110110a=110,b=110110210a=110,b=210b++;总结:1、自动变量在函数内外的“作用域”和“生存期”是一致的,即离开函数后其值不能被引用,值也不存在。2、静态外部变量和外部变量的“作用域”和“生存期”也是一致的,离开函数后变量值仍存在,且可被引用。3、而静态局部变量的“作用域”和“生存期”不一致,离开函数后,变量值仍存在,但不能被引用。即仍在生存期但其作用域已不存在。函数与程序结构函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法编译预处理概念预处理命令是由美国国家标准化协会(ANSIC)统一规定的,但它不是C语言本身的组成部分,不能直接对之进行编译,必须在编译前先处理它。如:#defineN20

预处理就是把程序中所有的N置换为20;又如:#include<stdio.h>预处理则是把“stdio.h”文件中的实际内容代替该命令,经过预处理后就可以对程序进行编译了。编译预处理语句可以出现在C源程序的任何位置,其作用范围是从出现点开始到源程序末尾。编译预处理语句主要有如下三种:宏定义文件包含条件编译4.4编译预处理

宏定义分为代参数的宏定义和不代参数的宏定义不代参数的宏定义

定义形式:#define

宏标识符字符串

调用形式:

宏标识符(直接用在表达式中)

宏定义的作用:在宏定义的作用范围之内,将所有的宏标识符用指定的字符串替换。

字符串:可以是字符串常量、已定义的宏、表达式或语句组成的字符串等。

注意:

使用#define

宏标识符语句可撤消宏定义。4.4.1宏定义4.4.1宏定义例4.24宏定义预处理/*Name:ex04-24.cpp*/#include<stdio.h>#definePI3.1415926#defineR2.0voidmain(){doublecircum();doublearea();printf(“circum=%f\n",circum());printf("area=%f\n",area());}doublecircum(){return2.0*PI*R;}

doublearea(){returnPI*R*R;}例4.24程序演示不进行宏代换的情况宏名出现在一个标识符中例:#defineloc

12345 intlocal; int12345al;(不会进行这种替换)宏名出现在字符串常量中例:#definePI

3.14语句printf(“ThevalueofPIis:%f\n”,PI);结果是:ThevalueofPIis:3.1400004.4.1宏定义此处不替换4.4.1宏定义例4.25宏调用替换问题的理解/*Name:ex04-25.cpp*/#include<stdio.h>#defineN

2#defineMN+2#defineMN2*Mvoidmain(){ intx=MN; printf("x=%d\n",x); }错误理解:N←2M←4(2+2)MN←8(2*4)输出结果:x=8正确理解:N←2M←2+2MN←2*2+2正确结果:x=6程序演示(N+2)带参数的宏定义

定义形式:

#define

宏标识符(形参表)表达式

调用形式:宏标识符(实参表)

宏调用的作用:将所有的宏标识符用指定的表达式替换并且用实际参数代替表达式中的形式参数。

注意:为了避免当实际参数是表达式时引起的宏调用错误,最好将宏定义中表达式样式字符串的形式参数用圆括号括起来。4.4.1宏定义4.4.1宏定义例4.26代参数宏定义使用示例/*Name:ex04-26.cpp*/#include<stdio.h>#definePI3.14159#defineS(r)PI*r*rvoidmain(){ doublea,b,area1,area2; a=3.3; b=3.2; area1=S(a); area2=S(a+b);

printf("area1=%f\narea2=%f\n",area1,area2);}程序演示#defineS(r)PI*(r)*(r)展开后:area2=3.14159*a+b*a+barea1=34.211943area2=24.127256area2=3.14159*(a+b)*(a+b)area1=34.211943area2=132.732287展开后:area1=3.14159*a*a带参数的宏与函数的主要区别:函数的调用需要进行控制的转移,而带参数的宏仅仅是表达式的替换。带参数的宏没有一个确定的类型。在宏中随着带入的实参类型的不同,其结果的类型随之而变。函数调用时,对实际参数有数据类型的限制,要求与定义类型一致;带参数的宏进行调用时没有实参数据类型的限制,实参可以是任意类型。函数调用存在从实际参数向形式参数传递数据的过程,而带参数的宏调用中不存在传递过程,因而宏调用一般比函数调用具有较高的时间效率。4.4.1宏定义4.4.1宏定义例4.27宏调用替换问题的理解/*Name:ex04-27.cpp*/#include<stdio.h>#defineMin(x,y)(x)<(y)?(x):(y)voidmain(){ inta=1,b=2,c=3,d=4,t;

t=Min(a+b,c+d)*1000;

printf("t=%d\n",t);}

正确结果为:t=程序演示3

t=(a+b)<(c+d)?(a+b):(c+d)*10004.4.2文件包含

文件包含的一般形式:

#include<文件名>

或#include

“文件名” 文件包含的功能:在编译本源程序文件之前,将指定的文件整个内容嵌入到本文件之中。 文件包含中使用尖括号:意味着指示编译系统按系统设定的标准目录搜索被包含的文件;文件包含中使用双引号:意味着按指定的路径搜索,未指定路径时则在当前目录中搜索。

#include<stdio.h>D:\Cxsjc\VC6\VC98\Include\stdio.h#include"D:\Cxsjc\VC6\VC98\Include\stdio.h"条件编译使用条件编译可以对C语言的源程序内容进行有选择性地编译。条件编译可有效地提高程序的可移植性,并广泛地应用在商业软件中,为一个程序提供各种不同的版本。条件编译命令常用形式一

#if<条件1>

<程序段1> #elif<条件2> <程序段2> …… #else <缺省程序段> #endif

4.4.3条件编译例4-28输入字符,根据需要设置条件编译,使之能将字母全改为大写字母输出,或全改为小写字母输出,直到键入#结束。

0104.4.3条件编译

使用条件编译可为一个程序提供各种不同的版本。

只需要修改此处只需要修改此处,就可以把相应的头文件包含进去。函数与程序结构函数的定义和调用函数的嵌套调用和递归调用变量的作用域和生存期编译预处理多源文件C程序的组织方法多源文件C程序概念

C程序是由函数组成的模块化结构程序。组成一个程序的所有函数模块可以放在同一个源程序文件中,也可以分别放在几个不同的源程序文件中。一般情况下,一个较大的C程序可以由若干个源文件组成。多源文件C程序的组织方法使用文件包含的方法;使用工程文件的方法;4.5多源文件C程序的组织方法4.5.1使用文件包含的方法使用文件包含的方法使用预编译语句#include将其它C源程序文件包含到本源文件,将它们组合成为一个完整的C程序。/*Name:ex04-30a.cpp*/#include<stdio.h>#include"ex04-30b.cpp"voidmain(){ intn; printf("Inputthen:"); scanf("%d",&n); printf("%d!=%ld",n,fac(n));}/*Name:ex04-30b.cpp*/longfac(longn){if(n<=1)return(1);elsereturn(fac(n-1)*n);}程序演示4.1.3指针与地址值参数传递的函数调用(P120)如果要在被调函数中对主调函数的实参单元进行操作,则需要将主调函数中实参的内存地址值传递给被调函数对应的形参单元。在传地址方式下,被调函数中用于接收对应地址值的形参必须是指针变量或数组。本小节主要介绍指针变量的基本用法和地址值参数传递的函数调用。指针与函数◆指针是c语言的一个重要特色,它能够:

调用函数时获得1个以上的结果(返回值)

方便使用字符串

方便使用数组

动态分配内存

有效表示复杂的数据结构

直接处理内存单元的地址等使用指针能使程序简洁、紧凑、高效、灵活。1)指针与指针变量的概念指针(一个变量的地址)

指针就是放入指针变量中的地址。指针变量(专门用来存放变量地址的变量)

内存单元地址是用有序无符号整型数进行编址。为了能够操作这些地址量,有必要构造一种变量来存储它们,这种变量称为“指针变量”(见图2)。4.1.3指针与地址值参数传递的函数调用地址的概念

变量的地址在程序中定义的变量,编译系统按变量类型分配空间。一旦分配完成,变量与内存单元的首地址就建立了一种对应关系。如:char型占1个字节

int型占4个字节

float型占4个字节

double型占8个字节例:inti=2,j=5,k;假定编译时给i、j、k分配的内存空间如图所示:变量i占2000、2001、2002、2003四个字节;变量j占2004、2005、2006、2007四个字节;变量k占2008、2009、2010、2011四个字节。

1、直接访问变量的方式

printf(“%d”,i);其含义是从2000~2003单元中取出数据

2并输出,表面看是通过变量名来存取数据,实际上编译后已将变量名转换为其对应的地址,对变量的存取实际上是通过地址进行的。⑵

scanf(“%d”,&i);其含义是将从键盘输入的值放入2000~2003内存单元中。⑶

k=i+j;的含义是从2000~2003单元中取出i值2,再从2004~2007单元中取出j值5,相加后将其和送入变量k所占的2008~2011单元中。2、间接访问变量的方式先定义一种特殊的变量来存放变量的地址如

int*pi;

假定pi被分配了3000~3003四个字节的内存单元

pi=&i;将变量i的起始地址2000存放到pi的3000~3003单元中,就可以先找到pi,取出变量i的起始地址(2000),然后到2000~2003单元中取出i值2,这种访问变量的方式就叫间接访问。地址指向变量i的单元,在c语言中形象地称地址为“指针”,而专门用来存放指针(即地址)的变量称为指针变量,如上例中地址2000就是变量i的指针,而pi就是指向变量i的指针变量。二、变量的指针和指向变量的指针变量变量的指针就是变量的地址,存放变量地址的变量就是指针变量,它用于指向另一个变量,这种指向关系用“*”表示。所以*pi与i是一回事

int*pi,i;pi=&i;i=2;*pi=2;

二者等价可解释为将2赋给指针变量pi所指向的变量,也就是i。i1、定义一个指针变量一般形式:基类型*指针变量名注意:①定义为某类型的指针变量只能指向与之类型相匹配的变量;

②如基类型为int型,使指针移动一个位置或指针+1意味着移动4个字节,而double则会移动8个字节。例inti,j;int*pi,*pj;/*只是定义了两个指针变量,但没有指向*/pi=&i;/*使pi指向变量i*/pj=&j;/*使pj指向变量j*/即将变量i的地址存放到指针变量pi中,于是pi就指向了变量i,同理,pj指向了变量j。例:floata;

int*pa;

pa=&a;×

类型不匹配,用int型指针指向了float型变量。2、指针变量的引用注意:指针变量中只能存放地址如:int*p1;

p1=2000;错误,2000是整数而不是地址!将变量地址赋给指针变量一定要通过取地址运算符“&”。取出指针变量所指向变量的内容时要用指针运算符“*”。如:inta=6;int*pa;pa=&a;printf(“%d”,*pa);结果为:6pa指向变量a,*pa即变量a的内容也就是a本身。,a,pa);1244996变量a的首地址看一个例子:利用指针的交换来交换a、b变量的值#include<stdio.h>voidmain(){inta=10,b=20;int*pa,*pb,*p,temp;pa=&a;pb=&b;p=pa;pa=pb;pb=p;

printf(“a=%d,b=%d\n”,a,b);}a=10,b=20temp=*pa;*pa=*pb;*pb=tempa=20,b=10注意:如果有pa=&a;则:①

&*pa取地址运算符“&”与指针运算符“*”同级,右结合,所以&*pa等价于

&(*pa)等价于

&a就是变量a的地址。②*&a等价于*(&a)变量a的地址相当于pa,而*pa就是变量a本身。③(*pa)++相当于a++,即将a值取出再加1,如果不加括号呢?*pa++等价于*(pa++),因为*和++同级,且右结合,而pa++是后缀,相当于先做*pa,即取出a,再将pa++就指向了变量a的以后的单元,不再指向变量a了。apa&aa指针变量作为函数的形参使用指针变量作为函数的形参实现的是传地址值调用方式,其主要特征为:主调函数中,以数据对象存放的地址值(地址或指针)作为实参调用另外一个函数;被调函数的形参必须是可以接收地址值的指针变量;实参和形参数据类型必须相同

传地址值调用与传值调用的区别p1a地址a地址a地址*p1a

重要结论传地址值调用时,数据在主调函数和被调函数中均使用同一存储单元,所以在被调函数中对数据任何的变动必然会反映到主调函数中。4)地址值参数传递调用/*指针变量作参数*/voidswap(int*x,int*y){inttemp;temp=*x;*x=*y;*y=temp;}4)地址值参数传递调用在主调函数中inta=10,b=20;int*pa=&a,*pb=&b;swap(pa,pb);printf("a=…b=…",a,b);ab1020paxpbytemp/*普通变量作参数*/voidswap(intx,inty){intt;t=x;x=y;y=t;}4)地址值参数传递调用inta=10,b=20;swap(a,b);printf("a=%,b=%d\n",a,b);4.1.4数组参数传递的函数调用

在C程序中,既可以用数组元素作为函数的参数,也可将数组整体作为函数的参数。(参见P186表6.2)使用数组元素作为参数传递,是实现函数间的传值调用。将数组整体作为参数传递时,用数组名或某数组元素的地址作为函数的实参,实现的是函数间的传地址值调用。4.1.4数组参数传递的函数调用/*ex04-07.cpp*/#include<stdio.h>#include<stdlib.h>#include<time.h>#defineN5voidmain(){ voidmyprint(intx); inta[N],b[N][N],i,j; srand(time(NULL)); printf("数组a...\n"); for(i=0;i<N;i++) { a[i]=rand()%100;

myprint(a[i]); }printf("数组b...\n");for(i=0;i<N;i++){for(j=0;j<N;j++){b[i][j]=rand()%100;

myprint(b[i][j]);}printf("\n");}}voidmyprint(intx){ printf("%4d",x);}例4.7数组元素作值传递一维数组作函数的参数数组名或某元素地址作为函数的实参,实参数组将它的全部或部分存储区域提供给形参数组共享,即形参数组与实参数组是同一存储区域或是实参数组存储区域的一部分。当函数调用结束后,形参数组消失,主调函数的数组就保存了形参数组操作的结果。使用数组名或数组第一个元素的地址是把整个实参数组传递给被调函数。使用某个数组元素的地址作为主调函数的实参可以实现传递部分数组元素到被调函数。 abs[]/*sv[]/*va或&a[0]作实参b+i或&b[i]作实参4.1.4数组参数传递的函数调用4.1.4数组参数传递函数调用例4.8P127编制求和函数并通过该函数求数组的各元素和。数组名a作实参数组v[]为形参数组a和v共享全部存储单元。#include<stdio.h>#defineN10voidmain(){intsum(intv[],intn);inttotal;inta[N]={1,2,3,4,5,6,7,8,9,10};total=sum(a,N);printf("total=%ld\n",total);}intsum(intv[],intn){inti,s=0;for(i=0;i<n;i++)s+=v[i];returns;}例4.8数组名作地址传递total=sum(&a[3],N-5);total=30v[0]、v[1]、v[2]、v[3]、v[4]a[3]、a[4]、a[5]、a[6]、a[7]a+34.1.4数组参数传递函数调用数组的起始地址数组的起始地址a

平面起始地址(二级地址)&a[0][0]

线性起始地址(一级地址)a[0]

线性起始地址(一级地址)*a

线性的起始地址(一级地址)图4.8二维数组起始地址的表示

二维数组作函数的参数

二维数组存储时也是有序地占用一片连续的内存区域,数组名表示这段存储区域的首地址。

注意:二维数组的起始地址有多种表示法,有平面起始地址和线性起始地址之分,例如a[4][4]。

二维数组起始地址的等价表示:aa[0]&a[0][0]*a

a[0]a[1]a[2]a[3]4.1.4数组参数传递函数调用使用二维数组名作为实参用二维数组名作为函数参数实现的是“传地址值调用”,其本质是实参数组将它的全部存储区域提供给形参数组共

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论