C#程序设计第10章指针.docx

上传人:b****5 文档编号:7645164 上传时间:2023-01-25 格式:DOCX 页数:44 大小:69.46KB
下载 相关 举报
C#程序设计第10章指针.docx_第1页
第1页 / 共44页
C#程序设计第10章指针.docx_第2页
第2页 / 共44页
C#程序设计第10章指针.docx_第3页
第3页 / 共44页
C#程序设计第10章指针.docx_第4页
第4页 / 共44页
C#程序设计第10章指针.docx_第5页
第5页 / 共44页
点击查看更多>>
下载资源
资源描述

C#程序设计第10章指针.docx

《C#程序设计第10章指针.docx》由会员分享,可在线阅读,更多相关《C#程序设计第10章指针.docx(44页珍藏版)》请在冰豆网上搜索。

C#程序设计第10章指针.docx

C#程序设计第10章指针

第10章指针

指针是C语言最灵活的部分,它充分体现了C语言简洁、紧凑、高效等重要特色。

可以说,没掌握指针就没掌握C的精华。

指针部分概念复杂,使用灵活,初学时常会出错,学习过程中应十分小心,多思考、多比较、多上机,尽量采用图示帮助分析问题与解决问题。

10.1变量的地址和指针变量

10.1.1变量的地址及变量存取方式

指针之所以难学是因为它与内存有着密切的联系,简单地说,指针就是内存地址。

这里首先要区分三个比较接近的概念:

名称;内容(值)和地址。

名称是给内存空间取的一个容易记忆的名字;内存中每个字节都有一个编号,就是“地址”;在地址所对应的内存单元中存放的数值即为内容或值。

为了帮助读者理解三者之间的联系与区别,我们不妨打个比方,有一座教师办公楼,各房间都有一个编号,如,101,102,……,201,202,……。

一旦各房间被分配给相应的职能部门后,各房间就挂起了部门名称:

如,电子系、计算机系、机械工程系等,假如电子系被分配在101房间,我们要找到电子系的教师(内容),可以去找电子系(按名称找),也可以去找101房间(按地址找)。

类似地,对一个存储空间的访问既可以指出它的名称,也可以指出它的地址。

C语言规定编程时必须首先说明变量名、数组名,这样编译系统就会给变量或数组分配内存单元。

系统根据程序中定义的变量类型,分配固定长度的空间,微机的C编译系统为整型变量分配2个字节,为实型变量分配4个字节,为字符型变量分配1个字节。

例如:

inti,j,k;是程序中定义的三个整型变量,C编译系统在编译过程中为这三个变量分配空闲的内存空间,并记录下各自对应的地址,如图10-1所示。

2000(i)

1

2002

2004(j)

8

2006(k)

9

图10-1

从用户角度看,访问变量i和访问地址2000是对同一空间的两种访问形式;而对系统来说,对变量i的访问归根结缔还是对地址的访问,因而若在程序中执行如下赋值语句:

i=1,j=8,k=9;编译系统会将数值1,8,9依次填充到地址为2000,2004,2006内存空间中。

系统对变量访问形式分成两种:

⑴直接访问

按变量地址存取的变量值的方式称为“直接访问”方式。

说明:

用变量名对变量的访问也属于直接访问,因为在编译后,变量名和变量地址之间有对应关系,对变量名的访问系统自动转换成利用地址对变量的访问。

⑵间接访问

将变量的地址存放在一种特殊变量中,利用这个特殊变量进行访问。

如图10-2所示,特殊变量p存放的内容是变量i的地址,利用变量p来访问变量i的方法称为“间接访问”。

图10-2

在C语言中,如果变量p中的内容是另一个变量i的地址,则称变量p指向变量i,或称p是指向变量i的指针变量,形象地用图10-2所示的箭头表示。

由此可以得出结论:

变量的指针即为变量的地址,而存放其它变量地址的变量是指针变量。

10.1.2指针变量的定义和指针变量的基类型

1.指针变量定义

基类型*变量名

2.示例

int*pointer_1,*pointer_2;

float*f;

char*pc;

3.说明

⑴C语言规定所有变量必须先定义后使用,指针变量也不例外,为了表示指针变量是存放地址的特殊变量,定义变量时在变量名前加指向符号“*”。

⑵定义指针变量时,不仅要定义指针变量名,还必须指出指针变量所指向的变量的类型即基类型,或者说,一个指针变量只能指向同一数据类型的变量。

由于不同类型的数据在内存中所占的字节数不同,如果同一指针变量一会儿指向整型变量,一会儿指向实型变量,就会使该系统无法管理变量的字节数,从而引发错误。

⑶示例第一行定义了两个指向整型数据的指针变量pointer_1,pointer_2,第二行定义了指向实型数据的指针变量f,第三行定义了指定字符型数据的指针变量pc。

10.1.3指针变量赋值

将指针变量指向某个变量的方法是将被指变量的地址赋值给该指针变量,这里就要用到取址运算符“&”。

例如

inti;

int*p;

p=&i;

上述命令的执行将使指针变量p指向变量i。

如图10-2所示。

注意:

虽然变量的地址&i是一个整型数据,但一般情况下不要给指针变量送一个整型常量,如p=1000是不允许的,这是因为变量的地址是由编译系统分配的,用户一般不知道,也不必知道。

10.1.4指针变量引用

1.指针运算符

&:

取地址运算符

*:

指针运算符(间址访问运算符)

2.指针变量引用举例

【例10-1】通过指针变量访问整型变量

main()

{inti=100,j=10;

int*pi,*pj;

pi=&i;/*将pi指向i*/

pj=&j;/*将pj指向j*/

printf(“%d,%d\n”,i,j);/*直接访问变量i,j*/

printf(“%d,%d”,*pi,*pj);/*间接访问变量i,j*/

}

运行结果

100,10

100,10

程序说明:

⑴int*pi,*pj;语句定义了变量pi,pj是指向整型变量的指针变量,但没指定它们指向哪个具体变量。

⑵pi=&i;pj=&j;语句确定了pi,pj的具体指向,pi指向i,pj指向j。

不能误写成:

*pi=&i,*pj=&j;

⑶printf(“%d,%d\n”,i,j);语句通过变量名直接访问变量的方法,这是我们最常用的手段。

⑷printf(“%d,%d”,*pi,*pj);语句通过指向变量i,j的指针变量来访问变量i,j的方法,*pi表示变量pi所指向的单元的内容,即i的值;*pj表示变量pj所指向的单元的内容,即j的值,因而两个printf语句输出的结果均为变量i,j所对应的值。

需要明确的是这里的*是对变量pi,pj所指向单元的值的引用;而int*pi,*pj;语句处pi,pj没有具体的指向,*定义了pi,pj属于指针变量,而非间址运算符。

3.“*”与“&”运算符的进一步说明

⑴如果已执行了”pointer_1=&a;”语句,则&*pointer_1的值是&a。

因为“*”与“&”运算符的优先级相同,并且是自右向左结合,所以先进行*pointer_1的运算得到变量a,再进行&运算得到的值为变量a的地址。

⑵如果已执行了“a=100;”语句,则*&a的值是a即100。

因为先进行&a运算得到a的地址,再进行*运算,得到a地址的内容a。

⑶指针加1,不是纯加1,而是加一个所指变量的字节个数。

例如

int*p1,a=100;

p1=&a;

p1++;

假如a的地址是2000,p1++后p1的值为2002,而非2001,如图10-3所示,如果p1是指向实型单精度变量的指针变量,其初值为2000,则p1++后的值为2004。

100

50

……

 

图10-3

10.1.5指针变量作为函数的参数

函数的参数不仅可以是整型、实型等基本数据类型,还可以是指针类型。

它的作用是把地址传给被调函数。

下面通过一个示例来说明。

【例10-2】输入a和b,按从小到大的顺序输出。

voidswap(int*p1,int*p2)

{intt;

t=*p1;

*p1=*p2;

*p2=t;}

main()

{inta,b;

int*q1,*q2;

q1=&a;

q2=&b;

scanf(“%d,%d”,q1,q2);

printf(“%d,%d”,q1,q2);

if(a>b)swap(q1,q2);

printf(“%d,%d”,a,b);

printf(“%d,%d”,q1,q2);}

运行情况

9,5↙

__,__

5,9

__,__

其中__表示q1,q2的地址值,从程序的输出结果可以看出,a,b的值发生交换,但q1,q2的值并未交换。

程序说明

在被调函数swap中,将形参p1,p2说明成指针型变量,该函数的作用是交换两个变量的值。

程序运行时,先执行main函数,输入两个数9,5给变量a,b。

将a,b的地址分别赋值指针变量q1,q2,然后执行if语句,由于a>b,因此执行swap函数,在调用过程中,首先将实参q1,q2的值传递给形参p1,p2,经虚实结合后,形参p1指向变量a,形参p2指向变量b,如图10-4(a)所示。

接着执行swap函数体,将*p1(a)与*p2(b)中的值交换,互换后的情况如图10-4(b)所示。

函数调用结束后,形参p1,p2将释放,如图10-4(c)所示。

最后在main函数中输出的a和b的值即为交换后的值(a=5,b=9),由于q1,q2在调用swap函数前后没有改变,main函数两次输出的q1,q2的值均相等。

 

图10-4

不正确的使用及处理

⑴swap函数中的中间变量定义成指针类型变量

voidswap(int*p1,int*p2)

{int*t;

t=*p1;

*p1=*p2;

*p2=t;}

函数将出现语法错误,原因是由于变量t无指向,所以不能引用变量*t。

⑵被调函数中的地址交换

voidswap(int*p1,int*p2)

{int*t;

t=p1;

p1=p2;

p2=t;}

swap函数调用结束后,变量a和b中的值没有交换。

原因是函数swap交换了变量p1,p2的值,无法通过值传递形式返回主函数中的p1,p2。

⑶利用普通变量作函数参数

voidswap(intx,inty)

{intt;

t=x;

x=y;

y=t;}

main()

{

swap(a,b)

}

主函数中直接将a,b作为实参传递给swap函数,形参数据在swap函数中交换后并不返回主函数。

10.2数组的指针与指向数组的指针变量

数组是由若干相同类型的元素构成的有序序列,这些元素在内存中占据了一组连续的存储空间,每个元素都有一个地址,数组的地址指的是数组的起始地址,这个起始地址也称为数组的指针。

10.2.1指向数组的指针

如果一个变量中存放了数组的起始地址,那么该变量称为指向数组的指针变量,指向数组的指针变量的定义遵循一般指针变量定义规则。

它的赋值与一般指针变量的赋值相同。

如果有以下定义

inta[10],*p;

p=&a[0];

注意,如果数组为int型,则指针变量必须指向int类型。

上述语句组的功能是将指针变量p指向a[0],由于a[0]是数组a的首地址,所以指针变量p指向数组a。

如图10-5所示。

a

p→a[0]

a[1]

图10-5

C语言规定,数组名代表数组的首地址,因此,下面两个语句功能相同;

p=a;

p=&a[0];

允许用一个已经定义过的数组的地址作为定义指针时的初始化值。

例如

floatscore[20];

float*pf=score;

注意:

上述语句的功能是将数组score的首地址赋给指针变量pf,这里的*是定义指针类型变量的说明符,而非指针变量的间址运算符,不是将数组score的首地址赋给*pf。

10.2.2通过指针引用数组元素

已知指向数组的指针后,数组中各元素的起始地址可以通过起始地址加相对值的方式来获得,从而增加了访问数组元素的渠道。

C语言规定,如果指针变量p指向数组中的一个元素,则p+1指向同一数组中的下一个元素(而不是简单地将p的值加1),如果数组元素类型是整型,每个元素占2个字节,则p+1意味着将p的值加2,使它指向下一个元素。

因此,p+1所代表的地址实际上是p+1*d,d是一个数组元素所占的字节数(对整型数组,d=2;对实型数组,d=4;对字符型数组,d=1)。

1.地址表示法

当p定义为指向a数组的指针变量后,就会产生对同一地址不同的表示方法。

例如数组元素a[5]的地址有三种不同的表示形式

p+5,a+5,&a[5]

2.访问表示法

与地址表示法相对应,访问数组元素也有多种表示法。

例如数组元素a[5]可通过下列三种形式访问

*(p+5),*(a+5),a[5]

3.指针变量带下标

指向数组的指针变量可以带下标,如p[5]与*(p+5)等价。

4.指针变量与数组名的引用区别

指针变量可以取代数组名进行操作,数组名表示数组的首地址,属于常量,它不能完成取代指针变量进行操作。

例如,设p为指向数组a的指针变量,p++可以,但a++不行。

5.++与+i不等价

用指针变量对数组逐个访问时,一般有两种方式,*(p++)或*(p+i),表面上这两种方式没多大区别,但实际上有很大差异,像p++不必每次都重新计算地址,这种自加操作比较快的,能大大提高执行效率。

根据以上叙述,引用一个数组元素,可以用两个方法:

⑴下标法,通过数组元素序号来访问数组元素,用a[i]形式来表示。

⑵指针法,通过数组元素的地址访问数组元素,用*(p+i)或*(a+i)的形式来表示。

【例10-3】任意输入10个数,将这十个数按逆序输出。

⑴用下标法访问数组

main()

{inta[10],i;

for(i=0;i<10;i++)

scanf(“%d”,&a[i]);

printf(“\n”);

for(i=9;i>=0;i--)

printf(“%d”,a[i]);

}

2数组名访问数组

main()

{inta[10],i;

for(i=0;i<10;i++)

scanf(“%d”,&a[i]);

printf(“\n”);

for(i=9;i>=0;i--)

printf(“%d”,*(a+i));

}

3指针变量访问数组

A)main()

{inta[10],i,*p;

for(i=0;i<10;i++)

scanf(“%d”,&a[i]);

printf(“\n”);

for(i=9;i>=0;p--)

printf(“%d”,*(p+i));

}

B)main()

{inta[10],I,*p;

p=a;

for(i=0;i<10;i++)

scanf(“%d”,p+i);

printf(“\n”);

for(p=a+9;p>=a;p--)

printf(“%d”,*p);

}

将上述三种算法比较如下:

⑴例10-3中⑴,⑵,⑶A)执行效率是相同的,编译系统需要将a[i]转换成*(a+i)处理的,即先计算地址再访问数组元素。

⑵⑶B)执行效率比其它方法快,因为它有规律地改变地址值的方法(p--)能大大提高执行效率。

⑶要注意指针变量的当前值。

请看下面程序,分析其能否达到依次输出10个数组元素的目的,为什么?

main()

{inta[10],i,*p;

p=a;

for(i=0;i<10;i++)

scanf(“%d”,p++);

printf(“\n”);

for(i=0;i<10;i++,p++)

printf(“%d”,*p);

}

有关指针变量运算下面将进一步加以说明。

如果指针变量p指向数组a,比较以下表达式的含义。

1达式*p++,由于++与*运算符优先级相同,结合方向为自右向左,故*p++的作用是先得到*p的值,再使p+1→p。

同样表达式*p--的作用是先得到*p的值,再使p-1→p。

2达式*++p,先使p+1→p,再得到*p的值。

同样表达式*--p的作用是先使p-1→p,再得到*p的值。

3表达式(*p)++表示p所指向的数组元素值(*p)加1,变量p的值不会改变。

同样,(*p)--,表示p所指向的数组元素的值(*p)减1。

10.2.3数组名作为函数参数

正如在函数部分所述,数组名也可作为函数的参数,如:

main()sort(intx[],intn)

{{

inta[10];

..

..

..

sort(a,10);

..

..

..

}

}

由于数组名代表数组的首地址,故在函数调用时(sort(a,10);)按“虚实结合”的原则,把以数组名a为首地址的内存变量区传递给被调函数中的形参数组x,使得形参数组x与主调函数的数组a具有相同的地址,故在函数sort中这块内存区中的数据发生变化的结果就是主调函数中数据的变化,如图10-6所示,这种现象好像是被调函数有多个值返回主函数,实际上“单向”传递原则依然没变。

 

图10-6

有了指针的概念后,对数组名作为函数参数可以有进一步的认识,实际上,能够接受并存放地址值的形参只能是指针变量,C编译系统都是将形参数组名作为指针变量来处理的。

因此函数sort的首部也可以写成

sort(int*x,intn)

在函数调用过程中,x首先接受实参数组a的首地址,也就指向了数组元素a[0],前面已经讲过,指针变量x指向数组后,就可以带下标,即x[i]与*(x+i)等价,它们都代表数组中下标为i的元素。

由于函数参数有实参、形参之分,所以数组指针作为函数参数分以下四种情况:

1.形参、实参为数组名;在第8章中已作详细介绍。

2.形参是指针变量,实参是数组名;

【例10-4】用选择法对10个整数排序。

voidsort(int*b,intn)/*形参b为指针变量*/

{inti,j,k,t;

for(i=0;i

{k=i;

for(j=i+1;j

if(*(b+j)<*(b+k))k=j;

if(k!

=i)

{t=*(b+k);*(b+k)=*(b+i);*(b+i)=t;}

}

main()

{inta[10],i;

for(i=0;i<10;i++)

scanf(“%d”,&a[i]);

sort(a,10)/*实参为数组名*/

for(i=0;i<10;i++)

printf(“%d,”,a[i]);

printf(“\n”);}

运行结果

1-2129–5610031102↙

–56,-2,0,1,2,3,9,10,12,100

程序分析

形参是指针变量b,函数调用时,它接受数组a的首地址,即指针变量b指向数组a,表达式*(b+i)表示数组中第i个元素;通过函数sort改变了数组元素的顺序,返回主函数后,可以输出按从小到大排序后的数组。

3.形参、实参均为指针变量

【例10-5】将数组a中前n个元素按相反顺序存放。

设n=6,解此算法要求将a[0]与a[5]交换,a[1]与a[4]交换,将a[2]与a[3]交换。

通过分析,我们发现被交换的两个数组元素下标的和为n-1⑸,今用循环来处理此问题,设定两个“位置指针变量”i和j,i初值为x,j的初值为x+n-1,将a[i]与a[j]交换,然后将i增加1,j减少1,再交换a[i]与a[j],直到i≥j结束循环,如图10-7所示。

2

4

6

8

10

12

14

16

18

20

↑i

↑j

12

10

8

6

4

2

14

16

18

20

图10-7

voidinv(int*x,intn)/*形参x为指针变量*/

{int*p,*i,*j,temp;

for(i=x,j=x+n-1;i

{temp=*i;*i=*j;*j=temp;}

}

main()

{inti,n,a[10]={2,4,6,8,10,12,14,16,18,20};

int*p;

printf(“theoriginalarray:

\n”);

for(i=0;i<10;i++)

printf(“%d,”,a[i]);

printf(“\n”);

p=a;/*给实参指针变量p赋值*/

printf(“inputton:

\n”);

scanf(“%d”,&n);

inv(p,n);/*实参p为指针变量*/

printf(“thearrayafterinvented:

\n”);

for(p=a;p

printf(“%d,”,*p);

printf(“\n”);}

运行结果

theoriginalarray:

2,4,6,8,10,12,14,16,18,20,

inputton:

6↙

12,10,8,6,4,2,14,16,18,20

程序分析:

若实参为指针变量,在调用函数前必须给指针变量赋值,使它指向某一数组,注意本例中的第一个“p=a;”语句。

本程序显示前6个整数按逆序排列后的结果。

想一想,能否对算法进行修改,要求只使用一个位置指针变量?

4.形参是数组名,实参为指针变量

将【例10-5】稍作改动,形如

voidinv(intx[],intn)/*形参x为数组名*/

{

}

main()

{inta[10],n;

int*p;

p=a;/*给实参指针变量p赋值*/

inv(p,n);/*实参p为指针变量*/

}

说明:

调用函数前必须给实参指针变量赋值。

在函数inv中,既可以用下标法x[i],也可以用指针法*(x+i)处理第i个数组元素,处理结果在返回主函数后有效。

10.2.4指向多维数组的指针和指针变量

用指针变量可以指向一维数组,也可以指向多维数组。

多维数组的首地址称为多维数组的指针,存放这个指针的变量称为指向多维数组的指针变量。

多维数组的指针并不是一维数组指针的简单拓展,它具有自己的独特性质,在概念上和使用上,指向多维数组的指针比指向一维数组的指针更复杂。

1.多维数组的地址

多维数组的首地址是这片连续存储空间的起始地址,它既可以用数组名表示,也可以用数组中第一个元素的地址表示。

以二维数组为例,设有一个二维数组s[3][4],其定义如下

ints[3][4]={{0,2,4,6},{1,3,5,7},{9,10,11,12}};

这是一个3行4列的二维数组,如图10-8所示,s数组包含3行,即由3个元素组成:

s[0],s[1],s[2]。

而每一行又是一个一维数组,包含4个元素,如,s[0]包含s[0][0],s[0][1],s[0][2],s[0][3];……。

s[0]

2000

0

2002

2

2004

4

2006

6

s[1]

2008

1

2010

3

2012

5

2014

7

s[2]

2016

9

2018

10

2020

11

2022

12

图10-8

从二维数组的角度看,s代表二维数组的首地址,也是第0行的首地址,s+1代表第1行的首地址,从s[0]到s[1]要跨越一个一维数组的空间(包含4个整型元素,共8个字节)。

若s数组首地址为2000,则s+1为2008;s+2代表第2个一维数组的首地址,值为2016。

s[0],s[1],s[2]既然是一维数组名,C语言又规定数组名代表数组的首地址,因此s[0]表示第0行一维数组的首地址,即&s[0][0];s[1]表示第1行一维数组的首地址,即&s[1][0];s[2]表示第2行一维数组的首地址,即&s[2][0]。

s[0]+1表示第0行一维数组第1个元素的地址&s[0][1];s[1]+2=&s[1][2]……。

对各元素内容的访问也可以写成*(s[0]+1),*(s[1]+2)。

2.行转列的概念

经过上述分析,我们知

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1