C语言教程,指针
学习目的:指针是经常被用到的 一种重要的数据类型,也是 C语
言的一个特色内容,运用指针
能够有效地表示复杂的数据结
构。正确使用指针能够写出特别紧凑高效的程序。
指针内容介绍
1 地址与指针
2 指针与数组
3 指针与字符串
4 指针与函数
5 指针数组和指向指针的指针
指针:即地址;地址:存储单元首字节的地址
指针变量:存放单元地址的变量或存放变量 地址 的变量,
称为指向变量的指针即 指针变量。
访问数据两种方法:
直接,用变量名;
间接,用变量的地
址通过变量的指针
变量
int a=3,*p;
p=&a;
printf(“%d,%d”,a,*p);
0xaa06
指针变量在内存中用两个字节存储,分析如下
int *p;
printf("\n%d",sizeof(p));
*与 &两个运算符,*取值,运算对象是一
个地址; &取址,运算对象是一个变量
对 *号的理解,*是取值运算符;也可以理解
为类型说明符,如
int *p;p=(int *)malloc(2); (float)5/2
*与 &两个运算符,*取值,运算对象是一个地
址; &取址,运算对象是一个变量。对 *号的
理解,*是取值运算符;也可以理解为类型说
明符。如
int *p;p=(int *)malloc(2); (float)5/2
指针变量、地址具有基类型,决定系统从该地
址处连续的几个字节中取数据,决定所指的数
据在内存中的存储结构(多少个字节,采用什
么编码表示等)。
例,
int a=0x141,*p1;
char *p2;
p1=&a;p2=&a;
printf("%d %d",*p1,*p2);
运行结果为,321 65
一个地址若没有基类型,则不能用该地址去
取值,int a;后 &a的基类型是 int型。
可以使用 (int *)对地址的基本类型进行强制
的类型转换,分析如下程序,
int a=3,b,c,d;
b=&a;
c=*(b);
d=*(int *)b;
printf("\n%d %x %d %d",*&a,b,c,d);
指针变量的定义,
类型说明符 *变量名;
例如,int *p1;
对指针变量的定义包括三个内容,
(1)指针类型说明,即定义变量为一个指针变量;
(2)指针变量名;
(3)变量值 (指针 )所指向的变量的数据类型。
指针变量初始化的方法,
int a;
int *p=&a;
指针变量的引用
未经赋值的指针变量不能使用,否则将造成系统
混乱,甚至死机。 int a=3,*p;printf(“%d”,*p);
指针变量的赋值只能赋予地址,决不能赋予任何
其它数据,否则将引起错误。
在C语言中,变量的地址是由编译系统分配的,
对用户完全透明,用户不知道变量的具体地址,
但可以通过 &运算符来得到。
不允许把一个数赋予指针变量,故下面的赋值是
没有意义的、错误的,int *p; p=1000;
注意:被赋值的指针变量前不能再加,*”,如
写为 *p=&a 是错误的。
指针变量和一般变量一
样,存放在它们之中的值
是可以改变的,也就是说
可以改变它们的指向,假
设,
int i,j,*p1,*p2;
i='a'; j='b'; p1=&i; p2=&j;
则建立如下图所示的联系,
这时赋值表达式, p2=p1; 就
使 p2与 p1指向同一对象 i,此
时 *p2就等价于 i,而不是 j,图
所示,
如果执行如下表达式,
*p2=*p1; 则表示把 p1指向
的内容赋给 p2所指的区域,
此时就变成图所示,
【 例 】 输入 a和 b两个整数,按先大后小的顺序输
出 a和 b。分析程序,
main( )
{ int *p1,*p2,*p,a,b;
scanf("%d,%d",&a,&b);
p1=&a;p2=&b;
if(a<b)
{p=p1;p1=p2;p2=p;}
printf("\na=%d,b=%d\n",a,b);
printf("max=%d,min=%d\n",*p1,*p2);
}
5 3
p1 p2
指针变量作为函数参数,可以将一个变量的地址传送到
另一个函数中,可以在被调函数中引用主调函数中的变量。
【 例 】 输入的两个整数按大小顺序输出。用指针
类型的数据作函数参数。
swap(int *p1,int *p2)
{int temp; temp=*p1; *p1=*p2; *p2=temp; }
main( )
{ int a,b; int *pointer_1,*pointer_2;
scanf("%d,%d",&a,&b);
pointer_1=&a;pointer_2=&b;
if(a<b) swap(pointer_1,pointer_2);
printf("\n%d,%d\n",a,b); }
调用 swap(pointer_1,pointer_2)函数时
执行执行 swap函数的函
数体使 *p1和 *p2的值互
换,也就是使 a和 b的值
互换。
函数调用结束后,p1和 p2不复
存在(已释放)如图。
最后在 main函数中输出的 a和 b的值是已经过交换的
值。分析如下程序,请找出下列程序段的错误,
swap(int *p1,int *p2)
{int t,*temp=&t;
*temp=*p1; /*此语句有问题 */
*p1=*p2; *p2=temp; }
请考虑下面的函数能否实现实现 a和 b互换。
swap(int x,int y)
{int temp; temp=x; x=y; y=temp;} 答,
swap(int *p1,int *p2)
{int *p; p=p1;p1=p2;p2=p;}
main( )
{ int a,b,*pointer_1,*pointer_2;
scanf("%d,%d",&a,&b);
pointer_1=&a;pointer_2=&b;
if(a<b) swap(pointer_1,pointer_2);
printf("\n%d,%d\n",*pointer_1,*pointer_2); }
其中的问题在于不能实现如图所示的第四步( d)。
数组指针和指向数组的指针变量
数组的指针是指数组的起始地
址,数组元素的指针是数组
元素的地址。例如,
int a[10]; /*定义 a为包含 10个
整型数据的数组 */
int *p=a; /*定义 p为指向整型变
量的指针,指向数组 a的起
始地址 */
int *p1=&a[0]; /*定义 p1为指向
整型变量的指针,指向数组
a的起始地址 */ int *p2=&a[3];
/*定义 p2为指向整型变量的
指针,指向数组元素 a[3]的
地址 */
0x3000
0x3006 a[3] 0x3006
数组名代表数组的首地址,也就是第 0号元素
的地址。因此,下面两个语句等价,
p=&a[0];
p=a;
在定义指针变量时可以赋给初值,
int *p=&a[0];
它等效于,int *p; p=&a[0];
当然定义时也可以写成,int *p=a;
通过指针引用数组元素
如果指针变量 p已指向数
组中的一个元素,则
p+1指向同一数组中的
下一个元素。 分析
p+1中 1的含义,表示
一个单元,值为 1个单
元的字节数。
引入指针变量后,就可以
用两种方法来访问数组
元素了。
如果 p的初值为 &a[0],则,
1)p+i和 a+i就是 a[i]的地址,
或者说它们指向 a数组
的第 i个元素。
2) *(p+i)或 *(a+i)就是 p+i或 a+i所指向的数组元素,
即 a[i]。例如,*(p+5)或 *(a+5)就是 a[5]。
3) 指向数组的指针变量也可以带下标,如 p[i]与
*(p+i)等价。
几个注意的问题,
1)指针变量的值可以改变。如 p++是合法的;而
a++是错误的。因为 a是数组名,是常量。
2)若 p,q均指向数组中的元素,分析 p+q,p-q,
p*q,p%q,p<q,p>q的含义
3)要注意指针变量的当前值。
请看下面的程序,找出错误。
main( )
{ int *p,i,a[10];
p=a;
for(i=0;i<10;i++) *p++=i;
for(i=0;i<10;i++) printf("a[%d]=%d\n",i,*p++);}
改正。
main( )
{ int *p,i,a[10];
p=a;
for(i=0;i<10;i++) *p++=i;
p=a; //这里不可少
for(i=0;i<10;i++) printf("a[%d]=%d\n",i,*p++); }
3)从上例可以看出,虽然定义数组时指定它包含 10
个元素,但指针变量可以指到数组以后的内存单
元,系统并不认为非法。
4)*p++,由于 ++和 *同优先级,结合方向自右而左,
等价于 *(p++)。
5)*(p++)与 *(++p)作用不同。若 p的初值为 a,则
*(p++)等价 a[0],*(++p)等价 a[1]。
6)(*p)++表示 p所指向的元素值加 1。
7)如果 p当前指向 a数组中的第 i个元素,则,
*(p--)相当于 a[i--]; *(++p)相当于 a[++i];
*(--p)相当于 a[--i]。
数组名作函数参数
函数的形参定义为数组的形式,实际上,
该数组名是一个指针变量;数组名作为
函数调用时的实参,实际上传递给形参
的是数组的首地址,即实参数组名常量
所代表的值
如果有一个实参数组,想在函数中改变
此数组的元素的值,实参与形参的对应
关系有以下4种形式,
1) 形参和实参都是数组名。
main( )
{int a[10];
……
f(a,10)
…… }
2) 实参用数组,形参用指针变量。
main( )
{int a[10];
……
f(a,10)
…… }
f(int x[],int n)
{ …… }
f(int *x,int n)
{ ……}
3)实参、型参都用指针变量。
4)实参为指针变量,形参为数组名。
【 例 】 将数组 a中的 n个整数按相反顺序存放。
算法为:将 a[0]与 a[n-1]对换,再 a[1]与 a[n-2] 对
换 ……,直到将 a[(n-1/2)]与 a[n-int((n-1)/2)]对换。
今用循环处理此问题,设两个“位置指示变量” i和 j,i的初值为 0,j的初值为 n-1。将 a[i]与
a[j]交换,然后使 i的值加 1,j的值减 1,再将 a[i]
与 a[j]交换,直到 i=(n-1)/2为止,如图所示。
void inv(int x[],int n) /*形参 x是数组名 */
{ int temp,i,j,m=(n-1)/2;
for(i=0;i<=m;i++)
{j=n-1-i; temp=x[i];x[i]=x[j];x[j]=temp;}
return; }
main( ) //例 1不用指针
{int i,a[10]={3,7,9,11,0,6,7,5,4,2};
printf("The original array:\n");
for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n");
inv(a,10);
printf("The array has benn inverted:\n");
for(i=0;i<10;i++) printf("%d,",a[i]);
printf("\n");}
void inv(int *x,int n)
{int *p,m,temp,*i,*j;
m=(n-1)/2; i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j--) {temp=*i;*i=*j;*j=temp;}
return; }
main( )//例 2用指针变量改写将 n个整数按相反顺
序存放。
{int i,arr[10]={3,7,9,11,0,6,7,5,4,2},*p;
p=arr;
printf("The original array:\n");
for(i=0;i<10;i++,p++) printf("%d,",*p);
printf("\n"); p=arr; inv(p,10);
printf("The array has benn inverted:\n");
for(p=arr;p<arr+10;p++) printf("%d,",*p); }
对数组及数组元素的地址表示的理解(类比 int
b[3]={1,2,3};),
1.b是数组名,代表数组首地址和第 0个元素(即
b[0])的地址; *b则等价于 b[0]; &b[0]等价于 b
b[0],b[1],b[2]是三个元素,值为 1,2,3;
2,b+0,b+1,b+2是三个元素的地址,等价于
&b[0],&b[1],&b[2];
3.*(b+0),*(b+1),*(b+2)等价于 b[0],b[1],b[2];
即,b,b+0,&b[0]等价; *b,*(b+0),b[0]等价 ;
b+1,&b[1]等价; *(b+1),b[1]等价。
归纳:数组名代表第一个元素的地址。
把 a理解为一个一维数组,a是数组名,代表数
组首地址和地 0个元素(即 a[0])的地址
*a则等价于 a[0]; &a[0]等价于 a
a[0], a[1],a[2]是其三个元素,值为行 1、行 2、
行 3。
a+0,a+1,a+2是三个元素的地址(即行地址),等价于
&a[0],&a[1],&a[2];
*(a+0),*(a+1),*(a+2)等价于 a[0],a[1],a[2];
即 a,a+0,&a[0]等价; *a,*(a+0),a[0]等价
a+1,&a[1]等价; *(a+1),a[1]等价
归纳:数组名代表第一个元素的地址
字符串的指针和指向字符串的针指变量,
在 C语言中,可以用两种方法访问一个字符串。
1)用字符数组存放一个字
符串,然后输出该字符
串。 【 例 】
main( )
{char string[]=”I love
China!”;
printf("%s\n",string); }
说明:和前面介绍的数
组属性一样,string是
数组名,它代表字符数
组的首地址。
2) 用字符串指针指向一个字符串。 【 例 】
main( )
{ char *string=”I love China!”;
printf("%s\n",string); }
字符串的指针变量:就是字符指针变量!允许,
char *string;
string=”I love China!”; 但不允许,char
string[80];
string=”I love China!”;
举例,【 例 】 输出字符串中 n个字符后的所有字符。
main( )
{ char *ps="this is a book"; int n=10;
ps=ps+n; printf("%s\n",ps); }
【 例 】 分析程序:在输入的字符串中查找有无
‘ k?字符。
main( )
{char st[20],*ps; int i;
printf("input a string:\n"); ps=st;
scanf(“%s”,ps); //pick-load-file<回车 >
for(i=0;ps[i]!='\0';i++)
if(ps[i]==?k?){printf(“there is a ?k? in the string\n”);
break; }
if(ps[i]=='\0') printf("There is no 'k' in the string\n");
}
结果,there is a ?k? in the string
函数的地址、函数的指针和函数指针变量
类比理解:变量的地址、变量的指针和变量的指针变量。
int a,*p=&a;
int (*pf)( );/*定义函数指针变量 */
int max( ){……}/* 定义函数 */
pf=max;/*给函数指针变量赋值,函数名也可以理解为一
个地址常量 */
(*pf)( )/*通过函数指针变量调用函数 */
if(a>b)return a;
else return b;
函数代码
0x3000 pf
max
0x300
函数指针变量定义的一般形式为,类型说明符 (*指针变量名 )();
函数指针变量形式调用函数的步骤如下,
1) 先定义函数指针变量,如后一程序中第 9行 int
(*pmax)();定义 pmax为函数指针变量。
2)把被调函数的入口地址 (函数名 )赋予该函数指针变量,
如程序中第 11行 pmax=max;
3)用函数指针变量形式调用函数,如程序第 14行
z=(*pmax)(x,y);
4)调用函数的一般形式为,(*指针变量名 ) (实参表 )
注意以下两点,
a)函数指针变量不能进行算术运算,这是与数组指针变量
不同的。数组指针变量加减一个整数可使指针移动指
向后面或前面的数组元素,而函数指针的移动是毫无
意义的。
b)函数调用中 "(*指针变量名 )"的两边的括号不可少,其中
的 *不应该理解为求值运算,在此处它只是一种表示符
号。
【 例 】 本例用来说明用指针形式实现对函数调用
的方法。
int max(int a,int b)
{if(a>b)return a; else return b; }
main( )
{ int max(int a,int b),int(*pmax)(),int x,y,z;
pmax=max;
printf("input two numbers:\n");
scanf("%d%d",&x,&y);
z=(*pmax)(x,y);
printf("maxmum=%d",z);}
结束