第五章 指针
教学目标
教学内容
小结教学目标
理解指针和地址的概念
掌握指针定义与指针运算
了解空指针和 void指针
了解常量指针和指针常量
理解指针与数组的关系
掌握指针与字符串
掌握指向指针的指针教学内容
内存与内存地址
内存中变量的存储
指针变量的定义与引用
空指针和 void指针
指针运算
常量指针和指针常量
指针与数组
指针与字符串
指向指针的指针内存( 1)
内存:
内存是计算机中存储程序以及数据的地方。
位( bit)是计算机表示信息的最小单位。
内存是由一系列连续的存储单元组成,其中的每一个存储单元我们称为“字节”。
字节( byte)是最基本的存储单元单元。
内存( 2)
内存地址:
( 1)存储单元的“编号”就是该存储单元在内存中的地址,它是从 0开始的。
( 2)每个存储单元的编号都是唯一的。
( 3)存储单元的编号采用一个十六进制数。
例如 64kB内存的地址为 0000H~FFFFH
内存中变量的存储变量的直接访问通过变量名访问内存单元的方式称为“直接访问”。
例如:
int i=3,j=4,k=4;
k=k*j的执行过程是:
( 1)取得变量 k,j的地址 2004H,2002H。
( 2)将地址 2004H,2002H的值取出,然后相乘。
( 3)将相乘后的结果放到地址 2004H开始的内存单元中。
变量的间接访问引入指针变量,用来保存变量的地址。
定义指针变量:
类型 *指针变量名 ;
获得变量地址,(单目运算符 &)
int i=3,j=4,k=5;
int *p =&i,*q=&k;
使用指针变量,间接访问内存变量:
k=*p;
指针变量示例 1
指针变量示例 2
指针变量的定义与操作( 1)
( 1)定义并初始化指针变量
int i=3;
int *p = &i;
( 2)定义指针变量,将变量地址赋值给指针变量
int i=3;
int *p;
p = &i;
指针变量的定义与操作( 2)
( 3)将整数值赋值存放到指针变量指向的内存地址
int i=3;
int *p = &i;
*p=5;//相当于 *&i= 5;
( 4)将整型变量的值存放到指针变量指向的内存地址
int i=3,j=7;
int *p = &i;
*p=j;//相当于 *&i= j;
*运算符与 &运算符的优先级相同,且都是“右结合”。
*p=…,中的 *与指针变量定义中的 *;是不同的。
指针赋值的注意事项
1,指针变量不能与普通变量相互赋值。
int x,*px;
px=x; //在 VC中会出现警告提示。
2、指向同一种数据类型的指针变量之间可以相互赋值而指向不同数据类型的指针变量之间不可以相互赋值。
int a,*p1,*p2; int a,*p1; float *p2;
p1=&a; p1=&a ;
p2=p1; p2=p1 ;
//在 VC中会出现“类型不一致”的警告提示。
3、指针变量与普通变量一样,也可在定义的同时赋初值。
int x,y; int *px=&x;
将已定义变量 x的 地址赋给指针变量 px;
空指针
( 1)什么是空指针?
不指向任何实际的对象或者函数的指针为空指针。
( 2)将指针初始化为空指针,这不是说指针的存储单元为空,而是给指针赋予一个 ASCII码值为 0的字符
NULL。
例如,char *p=NULL;
( 3)将指针变量( p1,p2,p3,p4)进行如下操作,皆可以使得指针成为空指针:
p1 = 0;p2 = 0L;p3 = '\0'; p4=(void*)0;
void指针
( 1)指向 void的指针为 void指针。
( 2)因为没有对象的类型是 void,所以 void指针又称为万能指针。
( 3) void指针示例,(指针类型的强制转换)
int i=3;
int *p1=&i;
float*fp,f1=3.1f;
int *p4=(void*)0;
void *pvoid;
pvoid=p1;
printf("int:pvoid=%d\n",*(int*)pvoid);
fp=&f1;
pvoid=fp;
printf("float:pvoid=%f\n",*(float*)pvoid);
输出结果= 3; 3.10000
代码阅读与分析 示例 1
例 1:阅读分析程序
#include<stdio.h>
int main()
{
int a=10,b;
int *ipa;
*ipa=a; //编译警告,运行出错 ! 指针未初始化
//ipa=&a;
b=*ipa;
printf("%d\n",b);
a=100;
b=*ipa;
printf("%d\n",b);
return 0;
}
代码阅读与分析 示例 2
例 1:阅读分析程序
main()
{ int *px,x,y;
x=10;
*px=x; y=*px; x=20;
y=*px;
printf(“x=%d,y=%d”,x,y);
}
该程序执行后输出,x=20,y=10
点评:
( 1) 由于指针 px没有赋初值,并不指向变量 x,所以 x改变后,y
的值并没有改变 。
( 2) 如果把 *px=x;改为 *px=&x;,则最后的 y
值为 20
代码阅读与分析 示例 3
例 1:阅读分析程序
main()
{ int *px,x,y;
x=10;
*px=x; y=*px; x=20;
y=*px;
printf(“x=%d,y=%d”,x,y);
}
该程序执行后输出,x=20,y=10
点评:
( 1) 由于指针 px没有赋初值,并不指向变量 x,所以 x改变后,y
的值并没有改变 。
( 2) 如果把 *px=x;改为 *px=&x;,则最后的 y
值为 20
取地址运算( &)和取内容运算( *)
取地址运算符( &)和取内容运算符( *)都是单目运算符,它们互为逆运算。
&和 *的优先级别相同,为“自右向左”的结合方向。
如有定义:
int x,*px=&x;
则 &(*px),表示指针 px(取指针的目标变量 *px(即变量 x)的地址)
*(&x),表示变量 x(取变量 x的地址中所存储的内容)
取地址与取内容运算-示例
#include <stdio.h>
int main()
{
int x=3,*px=&x;
printf("取得整型变量的地址,&x => %xH\n",&x);
printf("取得指针变量的值,px => %xH\n",px);
printf("先取得指针变量 px指向的整型变量,然后取得该整型变量的地址:
&(*px) => %xH\n",&(*px));
printf("先取得整型变量 x的地址,然后取得该地址在内存中的存储内容:
*(&x) => %d\n",*(&x));
return 0;
}
指针与整数的加减运算指针变量加上或减去一个整数 n,是指针由当前所指向的位置向前或向后移动 n个数据的位置。
如果 p是一个指针,n是一个正整数,p+n操作后的实际地址是:
p+n*sizeof(数据类型) (sizeof为系统函数)
例:假设指针的当前地址值为,p=2000,q=2400
main(){
int *p; float *q;
printf(“%x,%x\n”,p,q);
printf(“%x,%x\n”,p+=1,q+=1);
}
注意,VC中 short为 2字节,int为 4字节 。
指针自增(自减)运算指针自增(自减)运算是地址运算。指针自增(自减)后指向下一个(上一个)数据。
要注意指针自增(自减)运算的优先级。
注意下面两个表达式的意义:
x=(*p)++; 将目标变量 *p的值赋给变量 x,然后变量 *p自增 1。
x=++(*p); 将目标变量 *p的值自增 1后赋给变量 x。
另外,注意 *p++,*(p++)与 *(++p)的用法区别指针自增(自减)运算 -示例
#include <stdio.h>
int main()
{
int a[] = {1,2,3,4,5,6,7};
int *p=&a;
printf("*(p++) = %d\n",*(p++) );
printf("*p = %d\n",*p );
printf("*(++p) = %d\n",*(++p) );
printf("*p = %d\n",*p );
printf("(*p)++ = %d\n",(*p)++ );
printf("*p = %d\n",*p );
printf("++(*p) = %d\n",++(*p) );
printf("*p = %d\n",*p );
printf("p-&a = %d\n",p-&a );
printf("*(p+3) = %d\n",*(p+3) );
printf("*(p-2) = %d\n",*(p-2) );
return 0;
}
运行结果:
1
2
3
3
3
4
5
5
2
6
1
指针相减运算
( 1)两个指向同种数据类型的指针可以相减。
( 2)指向同一数组的两个指针相减,其差为这两个指针所指向的数组元素之间所相差的元素个数。
( 3)指针相减不是两个指针值的单纯相减,而与数据类型的存储长度有关。
运算结果为:
两指针地址值之差 / 一个数据项存储字节数指针的关系运算两个指向同种数据类型的指针可以作关系运算,表示它们所指向的地址之间的关系。
指针间允许 4种关系运算:
<,> 比较两指针所指向地址的大、小关系
==,!= 判断两指针是否指向同一地址若 p指针指向的地址小于 q指针指向的地址,p<q
的结果为非 0,否则为 0。
注意:指针不能与一般数值进行关系运算,但可和 0
进行 ==和! =的关系运算。 p==0;p!=0; 用于判断指针 p是否为空指针。
常量指针和指针常量常量指针,const Type *pointer ;
例如,const float pi=3.14f; float f1=2.3f;
const float *pb1=&pi,*pb2;
( 1)又称为“指向常量的指针”,“只读指针”,
所指的对象本身可以是常量,也可以不是常量。
( 2)可以改变常量指针的内容。
例如,pb1=&f1; pb2=pb1; pb2=&pi;
( 3)不允许改变常量指针指向地址在内存中的内容。
例如,*pb1=12.3f; 是不允许的常量指针和指针常量指针常量,Type * const pointer ;
例如,int a1=3,b1=5;
int *const pa=&a1;
指针常量,
( 1)指针本身是常量,无法改变。
下面语句将报错:
pa=&b1;//++pa;//pa++;//pa=pa+2;
( 2)可以通过指针常量读写数据。
例如,*pa=30; b1=b1**pa;
( 3)指针常量声明时必须初始化。
指针变量和一维数组一、一维数组与指针
1、一维数组和数组元素的地址在 C语言中,数组名代表该数组的首地址,即数组中第一个元素的地址,也称为该数组的指针。每一个数组元素也有一个地址,称为该元素的指针。
例如有以下说明语句:
int a[10],*p;
则语句 p=a;和 p=&a[0];是等价的。它们都是把数组 a的起始地址赋给指针变量 p。同理,p=a+1;和 p=&a[1];两个语句也是等价的。它们的作用是把数组 a中第二个元素 a[1]
的地址赋给指针变量 p。
如果指针变量 p=a,以此类推,下面三个表达式等价
p+i a+i &a[i]
他们均表示第 i个元素的地址指针和一维数组
2、通过指针引用数驵元素
(1)通过一维数组名所代表的地址引用数组元素假设有如下说明语句:
int a[10],*p;
则,p=a;把 a[0]的地址值赋给指针变量 p,
即,p指向 a[0]
而,p+i 代表了 a数组第 i个元素的地址,
*(p+i) 代表了第 i个元素 a[i]。
例:通过数组名输入一维数组
main()
{int a[10],i;
for(i=0;i<10;i++)
scanf(“%d”,a+i);}
请注意:以前这里采用 &a[i]
表示数组元素 a[i]的地址,
而现在可以用 a+i表示数组元素 a[i]的地址指针和一维数组
(2) 通过带下标的指针引用数组元素假设有如下说明语句:
int a[10],*p;
在 p=a的条件下,则 *(p+i)可以写成 p[i]。
在 p=a的条件下,对 a[i]数组元素的引用方式还可以是,*(a+i),*(p+i),p[i]。
注意:
a和 a[0]具有不同含意。前者是一个地址常量,是存储单元 a[0]的地址;
而后者是一个变量名,代表一个存放数据的存储单元。
p是地址变量,它可以指向任何地址变量,因此,可以对其进行加、
减和赋值运算。 p++,p=a,p=&a[i] ;是合法的。
而由于数组名 a代表一个地址常量,其值是不能改变的,因此
a++,a=p,a+=i;是非法的指针和一维数组-示例 1
#include <stdio.h>
int main()
{
int a[10]={9,8,7,6,5,4,3,2,1,0},*p;
int i;
//数组名 a是一个指针常量,指向 a[0]的地址,不允许 a++;a+=1;
//a[i]是一个变量名,代表数组 a的第 i个存储单元
p=a;
printf("*(a+5)= %d\n",*(a+5));
printf("a[5]= %d\n",a[5]);
printf("*(p+5)= %d\n",*(p+5));
printf("p[5]= %d\n",p[5]);
return 0;
}
指针和一维数组-示例 1
#include <stdio.h>
int main()
{
int a[10]={9,8,7,6,5,4,3,2,1,0},*p; //数组名 a是一个指针常量
int i;
for(i=0; i<sizeof(a)/sizeof(int); i++) //允许 p++;p+=1;
printf("a[%d]= %d\n",i,*p++);
p=a;
for(i=0; i<sizeof(a)/sizeof(int); i++){
printf("a[%d]= %d\n",i,*p);
p+=1;
}
for(i=0; i<sizeof(a)/sizeof(int); i++) //不允许 a++;a+=1;
//printf("a[%d]= %d\n",i,*a++); //a+=1;
printf("a[%d]= %d\n",i,*(a+i));
return 0;
}
指针和二维数组一个二维数组可以看成是一个一维数组,其中每个元素又是一个包含若干个元素的一维数组。
假设一个二维数组的定义为:
int a[2][3];
则 a数组是一个由 a[0]和 a[1]两个元素组成的一维数组,而 a[0]和 a[1]又分别代表一个一维数组。
a[0] a[0][0],a[0][1],a[0][2]
a[1] a[1][0],a[1][1],a[1][2]
因此,a[0]和 a[1]实质上分别是包含有三个整型元素的一维数组名,它们分别代表 a数组每行元素的起始地址。
指针和二维数组二维数组 a中任一元素 a[i][j]的地址(即 &a[i][j])与
a[i]的关系为:
&a[i][j]=a[i]+j
注意,*(a+1)代表 a[1]; *(a[0]+1)则代表元素 a[0][1]。
二维数组任一元素 a[i][j]的引用方式还可以采用:
( 1) *( a[i]+j) ( 2) *( *(a+i)+j)
( 3)( *(a+i)[j]) ( 4) *( &a[0][0]+2*i+j)
指针数组
( 1)一个指针变量只能存放一个地址。
( 2)指针数组可以存放多个相同类型的地址。
( 3)定义指针数组的一般形式:
类型标识符 *指针数组名 [常量表达式 ];
( 4)指针数组示例:
int a[3][4],*p[3];
char s[5][10],*ps[5];
通过一个指针数组引用二维数组元素对于一个二维数组,可以定义一个指针数组 p或一个一维数组指针 q,用来间接访问二维数组的各个元素:
int a[M][N],*p[M],(*q)[N];
其中,p是一个指针数组,包含 M个指针元素
q是一个整型指针,指向一个包含 N个元素的一维整型数组。
二维数组元素 a[i][j]的引用形式包括:
( 1) *(p[i]+j) 或 *(q[i]+j) ( 2) *(*(p+i)+j) 或 *(*(q+i)+j)
( 3) (*(p+i))[j] 或 * (*(p+i))[j] ( 4) p[i][j] 或 q[i][j]
指针与二维数组-示例
int a[2][3]={1,2,3,4,5,6},*p[2],(*q)[3];
//p是一个指针数组,包含 2个指针元素
//q是一个整型指针,指向一个包含 3个元素的一维整型数组。
设置 p[0]=a[0],p[1]=a[1],那么
a[1][k]=*(p[1]+k)=*(*(p+1)+k)=*(*(p+1)+k)=p[1][k]
设置 q=a,那么
a[1][k]=*(q[1]+k)=*(*(q+1)+k)=*(*(q+1)+k)=q[1][k]
字符串指针与字符数组
char *str=“Hello!";
char c[ ]=“Welcome";
( 1)在字符串指针的声明中,编译器在内存中为字符串指针 str分配空间,把字符串常量,Hello!”放在内存中的其他位置,然后把指针 str的值初始化为这个字符串的地址。
( 2)在字符串数组的声明,相当于
char c[ ]={?W?,?e?,?l?,?c?,?o?,?m?,?e?,?\0?};
由于字符串数组声明并没有数组的大小,所以编译器为数组 c分配了 8个字符的内存。
字符串指针与字符数组在 C语言中,可以用两种方法访问一个字符串
1,用字符数组存放一个字符串例,main()
{chara[]=“i ama boy”;
printf(“%s”,a);
}
和一般数组一样,字符数组名是存放字符串起始地址的常数符号名 。
2.用字符指针指向一个字符串例,main()
{char*ch,a[]=“iama boy”;
ch=a; /*将字符数组 a的首地址赋给字符指针变量 */
printf(“%s”,ch);
字符串指针
[例 ] 用指向字符串常数的指针输出字符串常数 。
main()
{
char*p=”Theoryandproblems”;
printf(”%s\n”,p);
p=”Theoryand problemsofprog”;//字符串指针指向的字符串发生了变化
printf(”%s\n”,p);
}
运行结果:
Theoryandproblems
Theoryandproblemsof prog
字符数组示例-字符串合并
#include <stdio.h>
int main()
{
int i=0,j=0;
char s1[30],s2[30],*p1,*p2;
scanf("%s\n%s",s1,s2);
while(s1[i]!='\0')
i++;
while(s2[j]!='\0')
s1[i++]=s2[j++]; /*将 s2合并到 s1的后面 */
s1[i]='\0'; /*对合并后的字符串 s1设置结束标志 */
printf("%s\n",s1);
return 0;
}
字符串指针示例-字符串合并
#include <stdio.h>
int main()
{
int i=0,j=0;
char s1[30],s2[30],*p1,*p2;
scanf("%s\n%s",s1,s2);
p1=s1;
p2=s2;
printf(" s1 = %s\n",p1);
printf(" s2= %s\n",p2);
while(*p1!='\0')
p1++; /*把指针移到 s1字符串的未尾 */
while(*p2!='\0')
*p1++=*p2++; /*将 s2合并到 s1的后面 */
*p1='\0'; /*对合并后的字符串 s1设置结束标志 */
printf(" s1+s2 = %s\n",s1);
return 0;
}
指针数组示例
# include<string.h>
main( )
{
char *str[ ]={“turbo c”,,turbo pascal”,“basic”,“dbase”,
“lisp”,“fortran”};
int i,j,n; char *temp;
scanf(“%d”,&n);
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
if(strcmp(str[i],str[j])>0)
{ temp=str[i]; str[i]=str[j]; str[j]=temp; }/*选择法排序 */
for(i=0;i<n;i++)
printf(“%s\n”,str[i]);
}
指向指针的指针
( 1)如果第一个指针的值是第二个指针的地址,第二个指针的值,才是目标变量的地址,那么,第一个指针称为指向指针的指针或指针型指针。
( 2)定义指针型指针的一般形式,
类型标识符 **指针变量名;
例如 main( )
{
int x,*q,**p;
x=10;
q=&x; /* 变量 x的地址赋给指针 q */
p=&q; /* 指针 q的地址赋给指针 p */
printf(“**p=%d\n”,**p);
}
请想一想输出结果是什么?
指向指针的指针-示例 1
[例 ]用指针型指针输出数组元素。
main( )
{
static int a[5]={2,4,6,8,10};
static int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
int **p,j;
p=num;
for(j=0;j<5;j++)
{printf(“%d\t”,**p); p++;}
}
程序运行的结果是什么?
指向指针的指针-示例 2
[例 ]用选择法对从键盘输入的 10个数按升序排列,并按每行 5个数据打印排序前后的数据。
main(){
floatf[10],temp;
float*p;int k,j;
for(k=0;k<10;k++)
scanf(“%f”,&f[k]);
p=f;
for(k=0;k<10;k++,p++)
{ if(k%5==0)printf(“\n”);
printf(“%7.2f”,*p);}
for(p=f,k=0;k<9;k++)
for(j=k+1;j<10;j++)
if(*(p+k)>*(p+j))
{temp=*(p+k);*(p+k)=*(p+j);
*(p+j)=temp;}
printf(“\n”);
for(k=0;k<10;k++)
{if(k%5==0)printf(“\n”);
printf(“%7.2f”,*p++);}
}
指向指针的指针-示例 3
[例 ]输入两个字符串,从第一个字符串中删去所有第二个字符串中相同的字符。
main(){
char*p1,*p2,s1[30],s2[30];
intj,i;
scanf(“%s\n%s”,s1,s2);
p1=s1;p2=s2;
for(i=0;*(p2+i)!=?\0?;i++)
for(j=0;*(p1+j)!=?\0?;j++)
if(*(p2+i)==*(p1+j))
strcpy(p1+j,p1+j+1);
for(i=0;*(p1+i)!=?\0?;i++)
printf(“%c”,*(p1+i));
printf(“\n”);
}
输入字符串:
abacdefg hijk
aefg
输出结果:
bcd hijk
小结
指针定义与指针运算
空指针和 void指针
常量指针和指针常量
指针与数组
指针与字符串
指向指针的指针