白 雪 飞
baixf@ustc.edu.cn
中国科学技术大学电子科学与技术系
Dept,of Elec,Sci,& Tech.,USTC
Fall,2003
第 7章 指 针
C语言程序设计 - 第 7章 指针 2
目 录
指针概念
指针变量和指针运算
指向数组的指针
指向字符串的指针
指向函数的指针
返回指针值的函数
指针数组和指向指针的指针
C语言程序设计 - 第 7章 指针 3
指针 (Pointer)
指针表示变量等的存储地址
使用指针可以获得紧凑、高效的代码
使用指针也可能使程序晦涩难懂
指针的使用灵活方便
指针操作容易出错且难以调试
指针与数组关系密切
C语言程序设计 - 第 7章 指针 4
指针与地址
地址
通过首地址和数据类型可以访问内存中某一数据
数据类型决定所占用存储单元数
指针
就是地址
和类型有关
3 7 H
4 5 H
A 6 H
.,,
.,,
.,,
.,,
.,,
}
s h o r t s ; / * 0 x A 6 3 7 * /
c h a r c ; / * 0 x 4 5 * /
}
s h o r t * p s ; / * 0 x 2 0 3 4 * /
}
c h a r * p c ; / * 0 x 3 0 8 8 * /
3 4 H
低 地 址高 地 址
2 0 H
8 8 H
3 0 H
2 0 3 4 H
2 0 3 5 H
3 0 8 8 H
4 2 3 6 H
4 2 3 7 H
5 7 0 2 H
5 7 0 3 H
C语言程序设计 - 第 7章 指针 5
指针变量和指针运算
变量的指针和指针变量
指针变量的定义
地址运算符和指针运算符
指针变量的引用
指针的运算
C语言程序设计 - 第 7章 指针 6
变量的指针和指针变量
变量的指针
内存中存储某个变量的存储单元的首地址
指针 (地址 )实质上是一个整数 (不是 C的整型 )
可以通过变量的地址来间接的访问变量
指针变量
指针 (地址 )是一个数据,也可以用另一个变量来存放,即指针变量
通过指针变量可以间接访问变量或内存数据
C语言程序设计 - 第 7章 指针 7
指针变量的定义
一般形式
基类型 *指针变量名 ;
说明
“基类型”表示该指针指向的数据的类型
可以定义基类型为空类型 void的指针变量
举例
int *pi;
char *pc1,c,*pc2;
void *p;
C语言程序设计 - 第 7章 指针 8
地址运算符 (Address Operator)
地址运算符 &
获得操作数的地址 (指针 )
单目运算符,自右向左结合,优先级较高
操作数应为各种类型的内存变量、数组元素、
结构体成员等
操作数不能是表达式、常量、寄存器变量
举例
scanf("%f",&score);
int i,*p=&i;
C语言程序设计 - 第 7章 指针 9
指针运算符 (Indirection Operator)
指针运算符 *
获得指针指向的内存数据
又称“间接访问运算符”
单目运算符,自右向左结合,优先级较高
操作数为具有指针 (地址 )意义的值
举例
int i,*p=&i;
(*p)++; /* i++; */
C语言程序设计 - 第 7章 指针 10
指针变量的引用
指针变量也要“先赋值,后使用”
没有赋值的指针变量所存储的地址数据是不确定的,对它的引用非常危险
对指针的赋值要注意类型匹配,必要时可以使用强制类型转换,但要慎重使用
*p可以用于与指针 p的基类型相同类型的 变量 可以使用的 任何场合
指针变量可以作为函数的参数
C语言程序设计 - 第 7章 指针 11
指针变量引用举例 (07-01.C)
int a,b,c,*pa,*pb,*pc;
pa = &a;
pb = &b;
pc = &c;
a = 100;
printf("*pa=%d\n",*pa); /* *pa=100 */
*pb = 200;
printf("b=%d\n",b); /* b=200 */
scanf("%d",pc); /* 输入 34 */
printf("c=%d\n",c); /* c=34 */
C语言程序设计 - 第 7章 指针 12
指针变量与所指变量的关系
10
20
pa
pb
a
b
int a,b;
int *pa,*pb;
pa = &a;
pb = &b;
*pa = 10;
b = 20;
pa = pb;
pb = &a;
&a,&*pa *pa,*&a
C语言程序设计 - 第 7章 指针 13
指针变量作为函数参数
参数传递
仍然遵循,单向值传递,的规则
这里的传递规则是 指针类型参数的值 的传递
作为参数的 指针型实参的值 不会改变
但是对 指针型实参所指向的内存数据 所作的操作将不会随函数的返回而恢复
用途
借助指针类型参数可以改变多个数据的值
C语言程序设计 - 第 7章 指针 14
指针类型函数参数举例 (07-02.C)
void swap(int *x,int *y)
{
int t;
t=*x,*x=*y,*y=t;
}
void main()
{
int a=1,b=4;
int *pa,*pb;
pa=&a,pb=&b;
swap(pa,pb);
}
&a
a
&bpb
1
4
4
1 bpa
&a
&by
x
参数传递
C语言程序设计 - 第 7章 指针 15
指针的运算
运算类型
算术运算:加、减、自增、自减
关系运算:所有关系运算
赋值运算:一般赋值、加赋值、减赋值
上述运算在一定约束条件下才有意义 (后详 )
变量说明
p,q是同类型的指针变量
n是整型变量
C语言程序设计 - 第 7章 指针 16
指针的算术运算运算方式 说 明
p+n p之后第 n个元素的地址
p-n p之前第 n个元素的地址
p++ p作为当前操作数,然后后移一个 元素
++p p后移一个 元素,然后作为当前操作数
p-- p作为当前操作数,然后前移一个 元素
--p p前 移一个 元素,然后作为当前操作数
p-q 表示 p和 q两者之间的 元素 个数
条件,p,q是指向同一数据集合 (数组 )的指针
注意避免数组越界
C语言程序设计 - 第 7章 指针 17
指针的关系运算
条件
p,q是指向同一数据集合 (数组 )的指针
运算方式
p<q,p<=q,p==q,p!=q,p>=q,p>q
p<q:判断 p所指元素是否在 q所指元素之前
其他运算的含义与上述类似
若 p,q不是指向同一数据集合的指针,则运算无意义
C语言程序设计 - 第 7章 指针 18
指针的赋值运算
条件
p,q是指向同一数据类型的指针
n是整型数据
有意义的赋值方式
p=q
p=q+n,p=q-n (要求 q指向数组 )
p+=n,p-=n (要求 p指向数组 )
注意避免数组越界
C语言程序设计 - 第 7章 指针 19
指针的运算说明
指针的运算还包括
指针运算
对指向数组的指针的下标运算
对指针变量的取地址运算
对指向结构体的指针的指向成员运算
除上述运算方式 (包括约束条件 )外的其他运算都没有意义
无意义的指针运算不一定会出现语法错误,但可能造成危险的操作
C语言程序设计 - 第 7章 指针 20
指针的运算举例
short a[5],*p,*q;
p = &a[0];
q = p+2;
p += 3;
printf("%d",*p++);
scanf("%d",*--q);
if (p>q)
printf("%d",p-q);
else
printf("%d",q-p);
.,,
.,,
低 地 址高 地 址
}
a [ 0 ]
}
a [ 1 ]
}
a [ 2 ]
}
a [ 3 ]
}
a [ 4 ]
p
q
3
个short
C语言程序设计 - 第 7章 指针 21
指向数组的指针
指针与数组的关系
指向数组的指针
通过指针引用数组元素
数组用作函数参数
指向二维数组的指针
C语言程序设计 - 第 7章 指针 22
指针与数组的关系
数组名是,常量指针,
数组名表示数组的首地址,因此数组名也是一种指针 (地址 )
数组名表示的地址 (指针 )不能被修改,所以称之为“常量指针”
数组的指针
数组的起始地址
与数组名表示的指针相同
与数组的第一个元素 (a[0])的地址相同
C语言程序设计 - 第 7章 指针 23
数组和指针的用法
数组名不能被赋值和修改,若指针指向数组,则两者的其他用法基本相同
定义指针时,只分配一段用来存放地址的空间,而没有分配存放数据的空间
定义数组时,为所有元素分配相应的连续的存储空间,但没有存放地址的空间
指针应赋值后才能使用
数组名不能被赋值,可以直接使用
C语言程序设计 - 第 7章 指针 24
指向数组的指针
char a[10],*p;
p = &a[0];
char a[10],*p=&a[0];
char a[10],*p;
p = a;
char a[10],*p=a;
...
...
a[0]
a[1]
a[2]
a[3]
a[4]
a[5]
a[9]
a[6]
a[8]
a[7]
ap &a[0]
C语言程序设计 - 第 7章 指针 25
通过指针引用数组元素
当一个指针变量指向数组或某个数组元素时,可以通过这个指针变量引用所有的数组元素
引用数组元素的方法
下标运算符 [],例如 a[i],p[i]
指针运算符 *,例如 *(a+i),*(p+i)
注意数组名不能被修改和赋值
注意防止下标越界
C语言程序设计 - 第 7章 指针 26
通过指针引用数组元素图示
...
...
a[0]
a[1]
a[2]
a[i]
a[9]
p[0],*p,*ap,a
p+1,a+1 p[1],*(p+1),*(a+1)
q+i-2,p+i,a+i p[i],*(p+i),*(a+i)q[i-2],*(q+i-2),
p+9,a+9 p[9],*(p+9),*(a+9)
q,p+2,a+2 p[2],*(p+2),*(a+2) q[0],*q
C语言程序设计 - 第 7章 指针 27
数组名和指针引用数组元素比较 (1)
指针指向数组首地址
前提条件,int a[10],*p=a;
a[i],p[i],*(a+i),*(p+i)等用法都是合法的,且 它们都表示同一个数组元素
a+i(或 p+i)不是简单的在 a(或 p)表示的地址值上简单的加 i,而是加上 i个基类型所需的地址偏移量,即加上 i*sizeof(int)
指针值可以改变,如 p++为 下一元素的地址
数组名的值不能修改,如 a++是非法操作
C语言程序设计 - 第 7章 指针 28
数组名和指针引用数组元素比较 (2)
指针指向某个数组元素
前提条件,p=a+i;
*(p++)与 a[i++]等价
*(p--)与 a[i--]等价
*(++p)与 a[++i]等价
*(--p)与 a[--i]等价
注意不能使用 *(a++)或 a=p+i这种形式
注意区分运算顺序,*(p++)与 (*p)++
注意防止下标越界,注意掌握指针位置
C语言程序设计 - 第 7章 指针 29
通过指针引用数组元素举例
int a[10],i,*p;
p = a; /* 指针需要先赋值 */
while (p<a+10) /* 指针在数组范围内移动 */
scanf("%d",p++); /* 指针向下移动 */
p = a; /* 指针指向正确位置 */
for (i=0; i<10; i++)
printf("%d",p[i]); /* 指针使用 [] */
C语言程序设计 - 第 7章 指针 30
数组用作函数参数
数组元素用作函数实参
与同类型的一般变量用法相同
数组用作函数参数
数组类型可以作为函数参数类型
数组可以用作函数的形参和实参
定义函数时,数组型形参实际上作为指针型形参处理,实参可用相同类型的数组或指针
声明数组类型形参时,不需要指定数组长度
一般应把数组长度作为另一个参数传递
C语言程序设计 - 第 7章 指针 31
以数组作为实参的几种方法 (1)
形参用数组名实参用数组名
形参用指针变量实参用数组名
f(int x[],int n)
{,..,.,}
main()
{ int a[10];
...,..
f(a,10);
}
f(int *x,int n)
{,..,.,}
main()
{ int a[10];
...,..
f(a,10);
}
C语言程序设计 - 第 7章 指针 32
以数组作为实参的几种方法 (2)
形参用数组名实参用指针变量
形参用指针变量实参用指针变量
f(int x[],int n)
{,..,.,}
main()
{ int a[10],*p=a;
...,..
f(p,10);
}
f(int *x,int n)
{,..,.,}
main()
{ int a[10],*p=a;
...,..
f(p,10);
}
C语言程序设计 - 第 7章 指针 33
数组用作函数参数举例
选择排序法
5
2
8
4
6
3

8
2
5
4
6
3
8
6
5
4
2
3
8
6
5
4
2
3
8
6
5
4
2
3
8
6
5
4
3
2
C语言程序设计 - 第 7章 指针 34
例 1:选择排序法 (07-03.C)
void sort(int x[],int n) /* int *x */
{
int i,j,k,t;
for (i=0; i<n-1; i++) {
k = i;
for (j=i+1; j<n; j++)
if(x[j]>x[k]) k=j;
if (k!=i)
t=x[i],x[i]=x[k],x[k]=t;
}
}
C语言程序设计 - 第 7章 指针 35
例 1:选择排序法 (续 )
void main()
{
int a[10],*p,i;
p = a;
for (i=0; i<10; i++)
scanf("%d",p++);
p = a;
sort(p,10); /* sort(a,10); */
for (p=a,i=0; i<10; i++)
printf("%d",*p++);
}
C语言程序设计 - 第 7章 指针 36
指向二维数组的指针 (1)
a[0]
a[1]
a[2]
a
a+1
a+2
char a[3][4];
*a
*(a+1)
*(a+2)
a是一个长度为 3的数组数组元素是长度为 4的数组
a,a+1,a+2都是指针,它们的基类型是长度为 4的字符数组,它们与下面定义的指针 p同类型
char (*p)[4];
C语言程序设计 - 第 7章 指针 37
指向二维数组的指针 (2)
a
a+1
a+2
a[0]
a[1]
a[2]
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
*a
a[0]
*a+1
a[0]+1
*a+2
a[0]+2
*a+3
a[0]+3
a[2]
*(a+2)
a[2]+1
*(a+2)+1
a[1]+3
*(a+1)+3
a[1][3]
*(*(a+1)+3)
a[1]
*(a+1)
a[2][3]
*(*(a+2)+3)
a[0][3]
*(*a+3)
char *
char
char *
基类型为
char[4]
的指针
C语言程序设计 - 第 7章 指针 38
指向二维数组的指针总结
表示二维数组
a:指向二维数组的指针类型
表示第 i行
a[i],*(a+i):指向一维数组的指针类型
表示第 i行 j列的元素
a[i][j],*(*(a+i)+j)
*(a[i]+j),(*(a+i))[j],char类型
注意 a和 *a都是指针,但是基类型不同
注意 *(a+i)和 *a+i的区别
C语言程序设计 - 第 7章 指针 39
指向二维数组的指针变量
指向数组元素的指针变量
指向二维数组的元素
类型为 char *p;
根据一维数组元素和二维数组元素的对应关系,可以访问所有的二维数组元素
基类型为一维数组的指针变量
指向二维数组的行
类型为 char (*p)[4];
把每一行作为一个一维数组来处理
C语言程序设计 - 第 7章 指针 40
指向二维数组元素的指针变量
一维数组与二维数组
char a[M][N];? char a[M*N];
a[i][j]? a[i*N+j]
使用指向元素的指针访问二维数组元素
char a[M][N];
char *p=a[0]; /* p=*a; */
则 p[i*N+j],*(p+i*N+j),a[i][j]
表示二维数组第 i行 j列的元素
C语言程序设计 - 第 7章 指针 41
指向二维数组的行的指针变量
二维数组是基类型为一维数组的指针
可以使用与二维数组同类型的指针变量
使用指向行的指针访问二维数组元素
int a[M][N];
int (*p)[N]=a; /* p=a; */
则 p[i],*(p+i),a[i]表示数组的第 i行且 p[i][j],*(*(p+i)+j),*(p[i]+j)、
(*(p+i))[j]表示二维数组第 i行 j列的元素
C语言程序设计 - 第 7章 指针 42
二维数组的指针作函数参数
二维数组的地址也可以用作函数参数
用指向数组元素的指针作为参数
用指向二维数组的行的指针作为参数
举例
void foo(int *p,int n);
void bar(int (*p)[4],int n);
int a[3][4]; /* 定义二维数组 */
foo(*a,12); /* 二维数组的行作为参数 */
bar(a,3); /* 二维数组名作为参数 */
C语言程序设计 - 第 7章 指针 43
指向字符串的指针
指针指向存放字符串的字符数组
与前述,指向数组的指针,类似
直接用字符指针指向字符串
字符串常量按字符数组处理,在存储器中占有一定的空间,并有自己的地址 (指针 )
可以把字符串常量的地址赋给字符指针变量
通过这个字符指针变量可以修改字符串常量
两个内容完全一样的字符串常量,在存储器中是不同的字符串,具有不同的存储空间
C语言程序设计 - 第 7章 指针 44
直接用字符指针指向字符串
可以用字符指针直接指向字符串常量
可以用字符串常量对字符指针直接赋值
这是把字符串常量的 地址 赋给字符指针
而不是把字符串的 内容 赋给字符指针
使用字符指针可以修改字符串的内容
只有利用指针才能再次访问某字符串常量
注意防止越过原字符串常量的范围
注意字符串末尾应保留结束标志 '\0'
C语言程序设计 - 第 7章 指针 45
字符串指针举例
char *s="I love";
char *t;
t = "China!";
s[0] = 'U';
puts(s); /* U love */
s[6] = '~';
puts(s);
/* U love~China! */
s[12] ='~';
puts(t); /* China~ */
I
l
o
v
e
\0
C
h
i
n
a
!
\0
s s[0]
s[6]
t
s[12]
U
~
~
C语言程序设计 - 第 7章 指针 46
字符串指针作函数参数举例
void strcpy(char *s,char *t)
{
while(*t++=*s++); /* 逐个字符复制 */
}
void main()
{
char *str1="C Language",str2[20];
strcpy(str1,str2);
puts(str2); /* C Language */
}
C语言程序设计 - 第 7章 指针 47
字符数组和字符指针变量比较 (1)
定义
char astr[]="Hello,World!";
char *pstr="Hello,World!";
数组在定义时分配存放若干字符的空间
指针定义时只分配存放一个地址的空间
Hello,World!\0pstr:
Hello,World!\0astr:
C语言程序设计 - 第 7章 指针 48
字符数组和字符指针变量比较 (2)
数组可以直接使用
指针要先指向一个字符串后才能使用
字符串常量只能对数组赋初值,把字符串的各个字符放到数组中,并且不能在其他场合对数组整体赋值
指针可以用字符串常量或字符数组任意赋值,但只是把字符串的地址赋给指针
数组名的值不能修改
指针可以任意修改
C语言程序设计 - 第 7章 指针 49
指向函数的指针
函数的指令存储在内存中的一段空间中
函数也有相应的内存地址
函数的入口地址就是函数的指针
函数名代表函数的入口地址
函数的指针可以用相应类型的指针变量表示,即指向函数的指针变量
函数也可以用通过指针变量间接调用
C语言程序设计 - 第 7章 指针 50
指向函数的指针变量
定义形式
类型 (*变量名 )([参数类型列表 ]);
说明
与函数原型类似,函数名用 (*变量名 )代替
“参数类型列表”可以省略,但一般不要省略
主要用于函数的参数
先赋值,后使用,一般用同类型函数名赋值
不能进行算术运算和关系运算
C语言程序设计 - 第 7章 指针 51
指向函数的指针变量使用举例
int max(int x,int y)
{ return x>y?x:y; }
void main()
{
int (*p)(int,int); /* 定义指针变量 */
int a,b,c;
scanf("%d%d",&a,&b);
p = max; /* 用函数名赋值 */
c = (*p)(a,b); /* c=max(a,b); */
}
C语言程序设计 - 第 7章 指针 52
指向函数的指针用作函数参数举例
一元函数定积分的梯形法数值求解
O x
f ( x )
a
b
h



2
)(
)(
2
)(
)(
1
1
bf
xf
af
hdxxf
hiax
n
ab
h
n
i
i
b
a
i
C语言程序设计 - 第 7章 指针 53
例:一元函数定积分 (07-04.C)
double integral(double (*f)(double),
double a,double b)
{
double s,h;
int n=100,i;
h = (b-a)/n;
s = ((*f)(a)+(*f)(b))/2.0;
for(i=1; i<n; i++)
s += (*f)(a+i*h);
return s*h;
}
C语言程序设计 - 第 7章 指针 54
例:一元函数定积分 (续 )
#include <stdio.h>
#include <math.h>
void main()
{
double y1,y2,y3;
y1 = integral(sin,0.0,1.0);
y2 = integral(cos,0.0,2.0);
y3 = integral(exp,0.0,3.5);
printf("%lf\n%lf\n%lf\n",y1,y2,y3);
}
C语言程序设计 - 第 7章 指针 55
返回指针值的函数
函数的返回值可以是指针类型
定义形式
类型 *函数名 (参数列表 );
举例
int *foo(int x,int y);
说明
函数调用可以结合使用 *和 []运算符
注意与指向函数的指针区别
int (*foo)(int x,int y);
C语言程序设计 - 第 7章 指针 56
返回指针值的函数举例 (1)
int *f(int *px,int *py) /* 返回整型指针 */
{
return *px>*py?px:py; /* 较大数的地址 */
}
void main()
{
int a=2,b=3,c=9;
*f(&a,&b)=c; /* 赋值给 a和 b中较大的数 */
printf("%d\n",b); /* 输出 9 */
}
C语言程序设计 - 第 7章 指针 57
返回指针值的函数举例 (2)
int *f(int *a,int *b) /* 返回整型指针 */
{
return *a>*b?a:b; /* 返回第一个元素 */
} /* 较大的数组地址 */
void main()
{
int i,a[]={1,2,3,4},b[]={5,6,7,8};
for (i=0; i<4; i++)
printf("%d\n",f(a,b)[i]);
} /* 打印数组 b的元素 */
C语言程序设计 - 第 7章 指针 58
指针数组和指向指针的指针
指针数组
类型 *数组名 [长度 ];
元素是指针类型的数组
举例,char *p[4];
注意与基类型为数组的指针区分
char (*p)[4];
指向指针的指针
基类型为指针类型的指针
举例,char **p;
C语言程序设计 - 第 7章 指针 59
指针数组举例
/* 把所有名字的所有字母全部改成大写 */
void main()
{
char *name[]={"Tom","John","Kate"};
int i,j;
for (i=0; i<3; i++)
for (j=0; *(name[i]+j); j++)
if (name[i][j]>='a' &&
name[i][j]<='z')
name[i][j]-=32;
}
C语言程序设计 - 第 7章 指针 60
指向指针的指针举例
/* 利用指向字符指针的指针打印字符串数组 */
void main()
{
char *name[]={"Tom","John","Kate"};
char **p;
int i;
p = name;
for (i=0; i<3; i++)
printf("%s\n",*p++);
}
C语言程序设计 - 第 7章 指针 61
命令行参数
main函数的几种形式
int main();
int main(int argc,char *argv[]);
int main(int argc,char **argv);
说明
返回值类型一般为 int,也可以是其他类型
argc为命令行参数的个数
argv为命令行参数字符串数组
命令行参数包括文件名本身
C语言程序设计 - 第 7章 指针 62
命令行参数举例 — echo命令
>echo C Language
argc == 3;
argv[0] == "echo";
argv[1] == "C";
argv[2] == "Language";
#include <stdio.h>
int main(int argc,char *argv[])
{
while(--argc > 0)
printf("%s%c",*++argv,(argc>1)?' ':'\n');
return 0;
}
C语言程序设计 - 第 7章 指针 63
复杂的声明形式
复杂类型变量的声明容易混淆
指针数组和指向数组的指针
int *a[5]; int (*a)[5];
指向函数的指针和返回指针值的函数
void (*f)(); void *f();
过于复杂的声明形式使程序晦涩难懂,
而且容易出错
可以用 typedef关键字把复杂类型的变量声明用若干个容易理解的小步骤表示
C语言程序设计 - 第 7章 指针 64
分析声明形式的方法
从标识符开始,逐层分析其意义
按运算符优先级和结合方向的顺序进行
可能涉及的运算符包括
()自左向右结合改变结合顺序;或声明一个函数,向外一层是函数返回值类型声明
[]自左向右结合声明一个数组,向外一层是数组元素类型声明
* 自右向左结合声明一个指针类型,向外一层是指针基类型声明
C语言程序设计 - 第 7章 指针 65
声明形式分析举例
char (*(*x[3])())[5];
x是一个长度为 3的数组数组的元素是指针类型指针是指向函数的函数的返回值是指针类型指向长度为 5的字符数组
x is an array[3] of pointer to function
returning pointer to array[5] of char
C语言程序设计 - 第 7章 指针 66
void类型指针
定义形式
void *p;
说明
定义一个指针,但不指定它指向的数据类型
不能通过 *p引用它指向的数据
void*指针可以与其他任何类型的指针相互赋值和比较,而不需要显式的强制类型转换
经常作为函数形参和返回值的类型
C语言程序设计 - 第 7章 指针 67
结束
The End