第7章 指针.docx
- 文档编号:6349687
- 上传时间:2023-01-05
- 格式:DOCX
- 页数:37
- 大小:41.31KB
第7章 指针.docx
《第7章 指针.docx》由会员分享,可在线阅读,更多相关《第7章 指针.docx(37页珍藏版)》请在冰豆网上搜索。
第7章指针
第七章指针
上一章我们学习了如何使用数组存放多个相同类型的数据并进行运算,但数组的长度在定义时必须给定,以后不能改变。
例如,数组a[10]的长度是10,程序中只能引用10个数组元素a[0]~a[9]。
如果事先无法确定需要处理的数据数量,又该如何处理呢?
一种方法是估计一个上限,并将该上限作为数组长度,这常常会造成空间浪费。
另一种方法是利用指针实现存储空间的动态分配。
指针是C语言中一个非常重要的概念,也是C语言的特色之一。
使用指针可以对复杂数据进行处理,能对计算机的内存分配进行控制,在函数调用中使用指针还可以返回多个值。
在本章中,除了介绍指针的基本概念外,还要解释如何使用指针作为函数的参数,以及指针用于数组和字符处理方式。
内存单元地址
7.1用选择法对10个整数排序Pa数组
P+0,a+05a[0]1000
例7.1.1对10个整数排序p+1,a+18a[1]1002
#include
main()p+3,a+36a[3]1006
{inti,j,t,*p,a[10];p+4,a+41a[4]1008
p=a;p+5,a+57a[5]1010
printf("请随意输入10个数:
\n");p+6,a+63a[6]1012
for(i=0;i<10;i++)p+7,a+70a[7]1014
scanf("%d",p+i);p+8,a+84a[8]1016
p=a;p+9,a+99a[9]1018
printf("这10个数是:
\n");
for(i=0;i<10;i++)图7.1a数组的数据在内存中的存放形式
printf("%d",*(p+i));注意:
一个整型数据占2个内存单元(2个字节)
printf("\n");
p=a;
printf("从大到小的排序是:
\n");
for(i=0;i<10;i++)
for(j=i;j<9;j++)
if((*(p+i))<(*(p+j+1)))
{t=*(p+i);
*(p+i)=*(p+j+1);
*(p+j+1)=t;
}
for(i=0;i<10;i++)
printf("%d",*(p+i));
printf("\n");
}
运行结果:
请随意输入10个数:
5826173049(输入时数字之间一定有空格)
这10个数是:
5826173049
从大到小的排序是:
9876543210
7.1.2地址和指针
在C语言中,如果定义了一个变量,在编译时就会根据该变量的类型给它分配相应的内存单元。
例如,假设int型变量占2个字节,则分配2个字节的内存单元,char型变量占1个字节,float实型变量和double实型变量则分别需要4个和8个字节的内存单元。
(一般一个字节8位)。
计算机为了对内存单元中的数据进行操作,一般是按“地址”存取的,也就是说对内存单元进行标识编号。
如果把存储器看成一个建筑物,建筑物内的房间就是存储器单元,房间号就是地址。
设有如下变量定义:
intx=20,y=1,z=155;
因为int型变量的存储长度为2个字节,因此假设C编译器将它们分配到1000~1001、1002~1003、1004~1005的内存单元中,如图7.2(a)所示。
在程序中,通过变量名进行操作,如调用函数printf(“%d”,x),输出x的值20。
而程序执行时是将变量翻译为它所在的内存地址进行操作的,上述输出操作可以描述为:
将x所在的内存地址1000~1001单元的内容输出。
这种使用变量的方法就叫做“直接访问”。
一般以变量所在的内存单元的第一个字节的地址作为它的地址,如变量x的内存地址是1000,y的地址是1002,z的地址是1004,变量x、y、z的内容分别为20、1、155。
注意:
一定区分内存单元的内容和内存单元的地址。
地址内存单元变量地址内存单元变量
100020x100020x
10021y10021y
1004155z1004155z
200020001000p
2002
(a)内存单元(b)变量和指针
图7.2内存单元和地址
在C程序中还有一种使用变量的方法,即通过变量的地址进行操作:
用指针(point)访问内存和操纵地址。
地址和指针是计算机中的两个重要概念,程序代码被放置到计算机内存中执行,变量或者程序代码被存储在以字节为单位组织的存储器中。
假设再定义一个变量p,它位于2000单元,该单元中存放了变量x的地址1000,见图7.2(b)所示。
此时,取出变量p的值1000,就可以访问内存1000单元,实现对变量x的操作,也就是说,通过变量p,可以间接访问变量x。
与直接使用变量x相比较,使用变量p访问变量x的过程实现了对变量x的间接操作,因此,在C语言中,把这种间接操作的变量叫做指针变量,简称为指针。
C语言使用指针对变量的地址进行操作。
指针是用来存放内存地址的变量,如果一个指针变量的值是另一个变量的地址,就称该指针变量指向那个变量。
前面提到的p就是指针变量,它存放了变量x的地址,即指针变量p指向变量x。
前面的章节中,已经多次看到了把地址作为scanf()的输入参数的用法。
例如,调用函数scanf(“%d”,&n),把输入的值存放到一个特定的地址所指示的n变量所在的内存单元里。
如果n是一个变量,那么&n就是变量的内存地址或存储位置。
这里的&称做地址运算符。
&是一元运算符,与其它的一元运算符有同样的优先级和从右到左的结合性。
8.1.3指针变量的定义
如果在程序中声明一个变量并使用地址作为该变量的值,那么这个变量就是指针变量。
定义指针变量要使用指针声明符*。
例如:
inti,*p;
声明变量i是int型,*p是指向int型变量的指针。
任何指针值的合法范围包括特殊的地址0和一组正整数,在具体C系统中会把它们解释为机器地址。
指针声明符*在定义指针变量时被使用,说明被定义的那个变量是指针。
定义指针变量的一般形式为:
类型名*指针变量名;
类型名指定指针变量所指向变量的类型,必须是有效的数据类型,如int、float、char等。
指针变量名是指针变量的名称,必须是一个合法的标识符。
在许多场合,可以把指针变量简称为指针,但实际上指针和指针变量在含义上存在一定的差异。
一般来说,在C语言中,指针被认为是一个概念,是计算机内存地址的代名词之一,而指针变量本身就是变量,和一般变量不同的是它存放的是地址。
大多数情况下,并不特别强调它们的区别,这里如果未加声明,把指针和指针变量同等对待,都是指存放内存地址的指针变量。
指针变量用于存放变量的地址,由于不同类型的变量在内存中占用不同大小的存储单元,所以只知道内存地址,还不能确定该地址上的对象。
因此在定义指针变量时,除了指针变量名,还需要说明该指针变量所指向的内存空间上所存放数据的类型。
下面是一些指针定义的例子:
int*p;//定义一个指针变量p,指向整型变量
char*cp;//定义一个指针变量cp,指向字符型变量
float*fp;//定义一个指针变量fp,指向实型变量
double*dp1,*dp2;//定义2个指针变量dp1和dp2,指向双精度实型变量
注意:
定义多个指针变量时,每一个指针变量前面都必须加上*。
指针被定义后,必须将指针和一个特定的变量进行关联后,才可以使用指针,也就是说,指针变量也要先赋值再使用,当然指针变量被赋的值应该是地址。
假设有定义:
inti,*p;下面的语句可以对指针变量p赋值:
p=&i;
p=0;
p=NULL;
P=(int*)1732;
第1条语句中的指针p被看做是指向变量i或包含变量i的地址,也就是将指针p和变量i关联起来,这也是指针最常用的赋值方法,图7.3给出了指针变量p和整型变量i之间的关联。
第2条和第3条语句说明了怎样把特殊值0赋值给变量p,这时指针的值为NULL。
常量NULL在系统文件stdio.h中定义,其值为0,将它赋给指针时,代表空指针。
C语言中的空指针不指向任何单元。
在最后一条语句中,使用强制类型转换(int*)来避免编译错误,表示指向int型变量的指针。
不提倡使用这类语句,因为一般不将绝对地址赋给指针,但特殊值NULL例外。
Pi
变量i的地址
图7.3指针p指向变量i的示意图
在定义指针变量时,要注意以下几点:
(1)指针变量名是一个标识符,要按照C标识符的命名规则对指针变量进行命名
(2)指针变量的数据类型是它所指向的变量的类型,一般情况下一旦指针变量的类型被确定后,它只能指向同一个类型的变量。
(3)在定义指针变量时需要使用指针声明符*,但指针声明符并不是指针的组成部分。
在对指针变量命名时(除整型变量外),建议用其类型名的首字母作为指针名的首字符,用p或ptr作为名字,以使程序具有较好的可读性。
如将单精度浮点型指针命名为fp、fptr等。
7.1.4指针的基本运算
如果指针的值是某个变量的地址,通过指针就能间接访问那个变量,这些操作由取地址运算符&和间接访问运算符*完成。
此外,相同类型的指针还能进行赋值、比较和算术运算。
1、取地址运算和间接访问运算
单目运算符&用于给出变量的地址。
例如:
int*p,a=3;
p=&a;
将整型变量a的地址赋给整型指针p,使指针p指向变量a。
也就是说,用运算符&取变量a的地址,并将这个地址值作为指针p的值,使指针p指向变量a。
注意:
指针的类型和它所指向变量的类型必须相同。
在程序中(不是指针变量被定义的时候),单目运算符*用于访问指针所指向的变量,它也称为间接访问运算符。
例如,当p指向a时,*p和a访问同一个存储单元,*p的值就是a的值,如图7.4所示。
Pa
&a3*p
图7.4指针运算示意图
指针和地址的概念比较抽象,理解上也比较困难。
下面通过一些实例进一步解释取地址运算和间接访问运算的使用,以更好地理解指针和地址的含义。
例7.2指针取地址运算和间接访问运算
//取地址运算和使用指针访问变量
#include
intmain()
{inta=3,*p;//此行是程序的第4行:
定义整型变量a和整型指针p
p=&a;//把变量a的地址赋给指针p,即p指向a
printf("a=%d,*p=%d\n",a,*p);//输出变量a的值和指针p所指向变量的值
*p=10;
printf("a=%d,*p=%d\n",a,*p);
printf("Entera:
");
scanf("%d",&a);//输入a
printf("a=%d,*p=%d\n",a,*p);
(*p)++;//将指针所指向的变量加1
printf("a=%d,*p=%d\n",a,*p);
return0;
}
运行结果:
a=3,*p=3
a=10,*p=10
Entera:
5
a=5,*p=5
a=6,*p=6
第4行的inta=3,*p和其后出现的*p,尽管形式是相同的,但两者的含义完全不同。
第4行定义了指针变量,p是变量名,*表示其后的变量是指针;而后面出现的*p代表指针p所指向的变量。
本例中,由于p指向变量a,因此,*p和a的值一样。
再如表达式*p=*p+1、++*p和(*p)++,分别将指针p所指向变量的值加1。
而表达式*p++等价于*(p++),先取*p的值作为表达式的值,再将指针p的值加1,运算后,p不再指向变量a。
同样,在下面这几条语句中:
inta=1,x,*p;
p=&a;
x=*p++;
指针p先指向a,其后的语句x=*p++,将p所指向的变量a的值赋给变量x,然后修改指针的值,使得指针p不再指向变量a。
从以上例子可以看出,要正确理解指针操作的意义,带有间接地址访问符*的变量的操作在不同的情况下会有完全不同的含义,这既是C的灵活之处,也是初学者最容易出错的地方。
下面的例子,是理解如何使用指针改变变量的值。
例7.3通过指针操作改变所指向的变量的值
#include
intmain()
{inta=1,b=2,t;
int*p1,*p2;
p1=&a;p2=&b;//关联指针和变量,使p1指向a,p2指向b
printf("a=%d,b=%d,*p1=%d,*p2=%d\n",a,b,*p1,*p2);
t=*p1;*p1=*p2;*p2=t;//交换*p1和p2的值
printf("a=%d,b=%d,*p1=%d,*p2=%d\n",a,b,*p1,*p2);
return0;
}
运行结果:
a=1,b=2,*p1=1,*p2=2
a=2,b=1,*p1=2,*p2=1
由于指针p1和p2分别指向变量a和b,所以*p1和a的值相同,*p2和b的值相同,如图7.5所示。
交换*p1和*p2的值,就是交换a和b的值,即改变指针p1和p2所指向变量的值,而指针p1和p2的值没有改变。
P1ap1a
&a1*p1&a2*p1
P2bp2b
&b2*p2&b1*p2
(a)交换前*p1和*p2的值(b)交换后*p1和*p2的值
图7.5改变指针所指向变量的值
2、赋值运算
一旦指针被定义并赋值后,就可以如同其它类型变量一样进行赋值运算,例如:
inta=3,*p1,*p2;
p1=&a;//使指针p1指向整型变量a
p2=p1;
将变量a的地址赋给指针p1,再将p1的值赋给指针p2,因此指针p1和p2都指向变量a,如图7.6所示。
此时,*p1、*p2和a访问同一个存储单元,它们的值一样。
P1
&aa
3*p1
P2*p2
&a
图7.6指针赋值
注意:
只能将一个指针的值赋给另一个相同类型的指针。
例7.4指针赋值运算
#include
intmain()
{inta,b,c,*p1,*p2;
a=2;b=4;c=6;
p1=&a;p2=&b;//*p1指向a,p2指向b,见图7.7(a)
printf("a=%d,b=%d,c=%d,*p1=%d,*p2=%d\n",a,b,c,*p1,*p2);
p2=p1;
p1=&c;//改变指针p1和p2的值,见图7.7(b)
printf("a=%d,b=%d,c=%d,*p1=%d,*p2=%d\n",a,b,c,*p1,*p2);
return0;
}
运行结果:
a=2,b=4,c=6,*p1=2,*p2=4
a=2,b=4,c=6,*p1=6,*p2=2
改变指针p1和p2的值后,它们分别指向变量c和a,见图8.7(b),此时,*p1和c的值一样,*p2和a的值一样。
给指针赋值是使指针和所指向变量之间建立关联的必要过程。
指针之间的相互赋值只能在相同类型的指针之间进行,可以在定义时对指针进行赋值,也可以在程序运行过程中根据需要对指针重新赋值。
但要特别注意:
指针只要在被赋值以后才能正确被使用。
指针的算术运算和比较运算将在以后介绍。
P1ap1a
&a2*p1&c2*p2
P2bp2b
&b4*p2&a4
cc
66*p1
(a)(b)
7.7指针赋值示意图
7.1.5指针变量的初始化
C语言中的变量在使用前必须先定义并赋值,指针变量在定义后也要先赋值才能被使用。
在定义指针变量时,可以同时对它赋初值。
例如:
inta;
int*p1=&a;//在定义指针p1的同时给其赋值,使指针p1指向变量a
int*p2=p1;//在定义指针p2的同时对其赋值,使p2和p1的值相同
以上对指针p1和p2的赋值都是在定义时进行的,使得指针p1和p2都指向变量a。
例7.5指针变量初始化
#include
intmain()
{inta=1,b=2;
int*p1=&a,*p2=&b,*pt;//在定义指针p1和p2的同时对其赋值
printf("a=%d,b=%d,*p1=%d,*p2=%d\n",a,b,*p1,*p2);
pt=p1;//交换p1和p2的值
p1=p2;
p2=pt;
printf("a=%d,b=%d,*p1=%d,*p2=%d\n",a,b,*p1,*p2);//输出
return0;
}
运行结果:
a=1,b=2,*p1=1,*p2=2
a=1,b=2,*p1=2,*p2=1
以上程序在定义初始化指针p1和p2,使它们分别指向变量a和b,如图7.8(a)所示。
3条语句”pt=p1;p1=p2;p2=pt;”交换p1和p2的值后,改变了指针p1和p2的值,此时,p1指向b,p2指向a,见图7.8(b)所示,这里使用pt作为临时指针变量,也使得pt最后指向了变量a。
P1ap1a
&a1*p1&b1*p2
P2bp2b
&b2*p2&a2*p1
Ptpt
&a
(a)交换指针p1和p2前的值(b)交换指针p1和p2后的值
图7.8交换两个指针的示意图
以上程序与例7.3程序相比,经过交换操作,*p1和*p2的值都由1和2变成了2和1,但采用的方法却有本质区别。
在本例中交换指针p1和p2的值后,变量a和b的值并没有改变,通过改变指针值的方法达到了交换指针的目的。
在例7.3中,交换的是指针所指向的变量即*p1和*p2的值,即改变了a和b的值,但指针p1和p2的值没有改变。
也就是说,本例是直接改变指针的值,而例7.3是改变指针所指向变量的值。
因为指针是用来存放内存地址的变量,它的值可以是另一个变量的地址。
因此,不仅可以像使用其它类型变量那样使用指针,包括直接对指针赋值,以及引用指针的值;还可以通过指针间接访问它所指向的那个变量,改变或引用指针所指向变量的值。
注意:
要区分对指针的操作和对指针所指向变量的操作。
需要再次指出的是,定义指针变量后,就可以使用它,但必须先赋值后引用。
指针如果没有被赋值,它的值是不确定的,即它指向一个不确定的单元,使用这样的指针,可能会出现难以预料的结果,甚至导致系统操作错误。
7.2指针作为函数的参数
在第四章中,已经介绍过C语言中的函数参数包括实参和形参,两者的类型要一致。
函数参数可以是整型、字符型和浮点型,当然也可以是指针类型。
如果将某个变量的地址作为函数的实参,相应的形参就是指针。
把变量作为参数传递给函数时,如果将变量的值传递给函数,这种机制称为按值调用。
如果函数调用能改变主调函数中变量的值,这样的机制称为引用调用(Call-by-Reference)。
引用调用中,在函数定义时,将指针作为函数的参数,在函数调用时,把变量的地址作为实参。
通过解析以下示例程序,来回顾并区别使用普通变量和指针变量作为参数的情况。
例7.7分别使用变量和指针作为函数参数
在程序中,main()函数调用了3个函数swap1()、swap2()、swap3(),还定义了变量a和b,程序设计的目的是要求通过函数调用,交换main()中变量a和b的值。
请分析在swap1()、swap2()、swap3()这3个函数中,哪个函数可以实现这样的功能。
#include
intmain()
{inta=1,b=2;
int*pa=&a,*pb=&b;
voidswap1(intx,inty),swap2(int*px,int*py),swap3(int*px,int*py);
swap1(a,b);//使用变量a,b调用函数swap1()
//虽然a、b值带到swap1()中,传给x、y并进行了值交换,但没改变主函数中的a、b值。
printf("Aftercallingswap1:
a=%db=%d\n",a,b);
a=1;
b=2;
//将存放a、b的地址内容在swap2()中进行交换,也就是交换了a、b的内容
swap2(pa,pb);//使用指针pa,pb调用函数swap2()
printf("Aftercallingswap2:
a=%db=%d\n",a,b);
a=1;
b=2;
//在swap3中进行了存放a、b地址的交换,而没影响主函数的a、b内容
swap3(pa,pb);//使用指针pa,pb调用swap3()
printf("Aftercallingswap3:
a=%db=%d\n",a,b);
return0;
}
voidswap1(intx,inty)
{intt;
t=x;
x=y;
y=t;
}
voidswap2(int*px,int*py)
{intt;
t=*px;
*px=*py;
*py=t;
}
voidswap3(int*px,int*py)
{int*pt;
pt=px;
px=py;
py=pt;
}
运行结果:
Aftercallingswap1:
a=1b=2
Aftercallingswap2:
a=2b=1
Aftercallingswap3:
a=1b=2
函数swap1()使用的是变量调用,也就是值调用,参数的传递是从实参变量到形参变量的单方向上值的传递,即使在swap1()函数中改变了形参的值,也不会反过来影响到实参的值。
因此,调用swap1()函数不能改变main()函数中的实参a和b的值。
换句话说,swap1()函数的调用过程没有实现a和b的值的交换。
函数swap2()的实参是指针变量pa和pb,其值分别是变量a和b的地址
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第7章 指针