? 第一章 C语言概述
? 第二章 数据类型、运算符与表达式
? 第三章 最简单的 C程序设计
? 第四章 逻辑运算和判断选取控制
? 第五章 循环控制
? 第六章 数组
? 第八章 编译预处理
? 第九章 指针
? 第十章 结构体与共用体
? 第十二章 文件的基本操作
? 第十三章 位运算
C程序的变量所存放的数据,
? 数值型数据:整数、实数
通过前面的学习,我们已知道,
? 字符型数据:字符、字符串
? 结构型数据:数组、结构体、共用体
? 占有一定长度的内存 单元
如,int x; x占二字节、二个单元
? 每一个变量都有一个地址,为无符号整数,
它不同于一般的整数。
问题,? 能否对地址运算?
? 能否用一个变量保存地址?
这些变量具有的性质,
§ 9.1 指针的概念
一、数据在内存中的存放
设,系统分配 i的起始地址为 2000的单元
内存,为一个连续编号 (连续地址 )且以一个
单元为一个字节的连续存贮区。
若程序中定义了三个 int变量 i,j,k
int i= – 5,j=5,k=10;
则, j的起始地址有可能为为 2002的单元
k的起始地址有可能为 2004的单元
2000
2001
2002
2003
2004
2005
3001
– 5
+5
10
2000
i
j
k
当程序中要用它们的值时,
y=i+j+k;
找到 j的地址 2002,
将 2002,20003中的数据 5读出;
找到 k的地址 2004,
将 2004,2005中的数据 10读出。
分别 找到 i的地址 2000,
将 2000,2001中的数据 –5读出;
则系统通过一张变量名与地址对应关系表,
直接访问,直接使用存放该数据的变量名。
间接访问,如果将某一变量的地址 (如 i的地址 2000)
存放到另一个变量 x,则可通过 x来存
取 i的值。
上述过程称为变量的,直接访问”
然后把这些数据进行算术运算。
i 相当于使用 ?5 使用变量
如,用 pi,pj,pk来存放 i,j,k的地址
2000
2002
2004
? 5
5
10
3001
3003
3005
3007
2000
2002
2004
pi
pj
pk
i
j
k
若要得到变量 i的值,可以先访问 pi,得到 i的
地址,再通过该地址找到它 i的值。
显然,pi与 i是通过 i的地址联系起来的。
定义,一个 变量的 地址称为该变量的指针 。
因此,i的指针的值为 2000。
如, pi就是指针变量。
而存放地址 (指针 )的变量叫做指针变量 。
一,指针变量的定义形式,
表示该变量为指向某类型变量的指针变量。
如, int ?p; ( p为指向整型变量的指针)
char *s;( s为指向字符型变量的指针)
float *t;( t为指向浮点型变量的指针)
[存储类型 ] 类型名 ?变量名
§ 9.2 指针变量的定义和引用
重要概念,
指针变量也有各种类型 (如上面所示 ),但指针变
量的值只能是整型值。
指针变量的引用方式,
*变量名 ----表示所指变量的值。
变量名 ----表示指向变量的指针 (地址 )。
若 int *p; char *s; float *t;
*p=5; *s=a; *t=3.6;
但 p=p+1;----------------并不代表 p=6
它代表的是地址+ 1
若有,float *t;
且,*t=3.6;
设 t的地址为 2000,则 t+1?2004
例,若有,int *p;
且,*p=5;
设 p的地址为 2000,则 p+1?2002
若有,char *s;
且,*s=a;
设 s的地址为 2000,则 s+1?2001
二、引用指针变量
? 将一个变量的地址 (指针 )赋给一个指针变量,
用取地址运算符,&
int i,j,?p;
i=3;
p=&i;
如果将整数赋给地址量 p=1000; 编译会
提出警告性错误,但程序还是能正常运行,
这是很危险的。
(只能这样赋值, ?p=1000;)
? 存取指针变量所指向变量 (目标变量 )的值,
用指针运算符 *,即,*p 为 i
?,&为同级运算符,结合性自右至左。
则当 ?&或 &?在一起时,具有抵消作用
则,p=?&i相当于 p=i
如上例,int i,?p
i=3
p=&i
程序示例
若对指针变量进行取址运算,
&p会有什么结果? 请思考,
三、指针变量作为函数参数
但当用地址 (指针变量 )作参数时,作用为传
址,与数组名类似。
形参,于是,形参数 前面讲过:函数实参
传递
单向
据值的改变不会影响实参。
例,swap(p1,p2)
int ?p1,?p2;
{ int p;
p=?p1;
?p1=?p2
要求,形参、实参均为地址量。
?p2= p;
}
main( )
{ int a,b;
int ?x1,?x2;
scanf("%d,%d",&a,&b);
x1=&a;
x2=&b;
swap(x1,x2);
printf("a=%d,b=%d \n",a,b);
}
程序运行结果,
输入, 10,20
输出, a=20,b=10
指针变量,
(实参 )
指针变量,
(形参 )
10
20
a
b
&a p1
p2
x1
x2 &b
程序中,实参与形参共用同一个内存单元,
通过修改地址所指的值来交换数据,
20
10
a
b
&a p1
p2
x1
x2 &b
指针变量,
(实参 )
指针变量,
(形参 )
1,注意函数中 p为普通变量,并非地址量 ;
2,如果 swap函数中的交换语句改为,
int ?p1,?p2,?p;
p=p1; p1=p2; p2=p;
则仅将 p1,p2的指向改变,函数返回后,p1,
p2释放,a,b中的内容依然未改变。
调用函数时,
&a x1
x2 &b
实参
10
20
a
b
p1
p2
形参
sway(p1,p2)
int p1,p2;
{ int p;
p=p1;
p1=p2;
p2=p;
}
3,不用地址量作参数,不能实现交换,即,
4,结论,
若要使变量的值通过函数调用而发生
改变,则形参必须是指针型,实参为地址
量 &<变量名 >或指针型。
另,全局变量和数组名作为参数也可
改变变量的值,
§ 9.3 数组的指针及指向数组的指针变量
? 一个变量的地址为该变量的指针。当用一个
变量来存放该地址 (指针 )时,称为指针变量。
? 一个数组元素相当于一个简单变量。于是,
亦可用一个指针变量来指向数组元素。
不同之处, 数组元素连续地占用内存单元,则当
一个元素的指针已知时,其它元素的
指针亦可知道。
定义方法与简单变量指针定义相同,但引用
略有不同
例, int a[10];
int ?p; /*定义 */
p=&a[0]; /*将 a的第 0个元素的地址赋给 p*/
C语言规定, 数组 a的首地址即用 &a[0]表示,
亦可用 a表示
所以, p=&a[0]; 和 p=a等价
一、数组元素指针变量的定义与引用
例, /*----exp12_3.c----*/
#include<stdio.h>
main( )
{ int al=123,a2=234,a3=345;
int *pl,*p2,*p3;
int as[3]={1,2,3},*ps[3];
pl=&a1;
p2=p1+1;
p3=p2+1;
printf("p1=%ld\n p2=%ld\n p3=%ld\n",p1,p2,p3);
printf ("*p1=%ld\n *p2=%ld\n *p3=%ld\n",
*p1,*p2,*p3);
printf ("ps[0]=%ld\n ps[1]=%ld\n ps[2]=%ld\n",
ps[0],ps[1],ps[2]);
printf ("*ps[0]=%ld\n *ps[1]=%ld\n *ps[2]=%ld\n",
*ps[0],*ps[1],*ps[2]);
}
ps[0]=&as[0];
ps[1]=ps[0]+1;
ps[2]=ps[1]+1;
运行结果,p1=262737918
p2=262737920
p3=262737922
*p1=123
*p2=0
*p3=320
ps[0]=262737896
ps[1]=262737898
ps[2]=262737900
*ps[0]=1
*ps[1]=2
*p2[2]=3
地址
a1的值
系统给定的值
数组元素地址
数组元素的值
注意, 指针变量在未赋值前指向一个不定值
例, #include <stdio.h>
main ( )
{ int *p;
printf( "*p=%d,p=%ld",*p,p);
}
运行结果, *p=0,p=3842
int *p=&a[0]; 或 int p=a; ?(等价于 )
int ?p; p=a;
不能有这样的语句,
?p=&a[0];
1,由首地址指针来引用数组中的其它元素。
设 p为 a的首地址,p=a,或 p=&a[0];
×
可以在定义指针变量时赋初值,
int a[4]={1,2,3,4};
p+1为 a[1]的地址,若 a为 int,p+1相当于地址 +2,
而当 a为 float时,p+1相当于地址 +4,
p+1 代表的是地址加一
指针 地址, 数组元素
a[0]
a[1]
a[2]
?
b[0]
b[1]
b[2]
?
pa
pa+1
pa+2
?
pb
pb+1
pb+2
?
2000
2002
2004
?
2080
2084
2088
如, int a[3],*pa;
flaot b[3],*pb;
pa=a; pb=b;
则指针与数组元素的
关系为,
一般地, p+i 代表 a[i] 的地址,
引用 a[i]的值, 可使用 a[i],?(p+i),?(a+i)
例, /*----exp12_2.c----*/
#include <stdio.h>
main( )
{ int a[4]={1,2,3,4},*p,i;
p=a;
i=0;
do
{ printf("*p=%d,p=%ld\n",*p,p);
p=p+1
i=i+1 }
}
while (i<4)
运行结果,
*p=1,p=269750264
*p=2,p=269750266
*p=3,p=269750268
*p=4,p=269750270
例, int a[10],i;
int ?p;
p=a;
则, ?(p+i)和 a[i]都是取数组元素的值,而 p+i可
直接指向 a[i]。
2,在寻找数组元素时,用 p+i比 a[i++]速度快
3,关于指针的运算
? p++ / p – – 的作用,
指针运算符 ?与 ++,– –同级,且自右至左。
而 p– –相当于 p=p –1,即指向 a[4]
若有, int a[10], *p;
p=&a[5];
则,p++相当于 p=p+1,即指向 a[6]
? (?p)++ 或 (?p) – –
(?p)++,将 p指向的变量的值自增 1;
(?p) – –,将 p指向的变量的值自减 1。
?p++相当于 ?(p++) (结合方向:自右向左 )
若 p=&a[0],则 *(p++)取出 a[0]的值 ;
?(++p),首先使 p?p+1,p指向 a[1],
?(++p)取出 a[1]的值 。
? ?p++与 ?(++p)的含义
例, /* ----exp12_4.c----*/
#include <stdio.h>
main( )
{ int a[5]={11,22,33,44,55},*p;
p=&a[0];
printf("*p++=%d\n",*p++);
printf("*p=%d\n",*p);
printf("(*p)++=%d\n",(*p)++);
printf("*p=%d\n",*p);
}
运行结果,
*p++=11
*p=22
(*p)++=22
*p=23
取出 a[0]的值
指向 a[1]的值
先取 a[1]的值
使 a[1]的值增加 1
前面已叙述过,当实、形参均为数组名时,调
用时将实参数组首地址传递 (单向 )给形参数组,使
它们共享内存。
例,编写函数,将数组各元素值取反。
二、数组名作函数参数
/*exp12-5.c*/
#include<stdio.h>
void invert(x,n );
main ( )
{ int i,a[10]={1,2,3,4,5,6,7,8,9,10}
invert(a,10);
for (i=0; i<10; i++)
{ if i%4==0
printf("\n")
printf("a[%d]=%d,",i,a[i]); }
}
void invert(int x[ ],int n)
{ int i;
for (i=0; i<n; i++)
x[i]= –x[i];
return;
}
运行结果,
a[0]= ?1,a[1]= ?2,a[3]= ?3,a[4]= ?4,
a[5]= ?5,a[6]= ?6,a[7]= ?7,a[4]= ?8,
a[5]= ?5,a[10]= ?10,
分析参数传递情况,
即, x,a共享同一段内存单元。
前面已分析, 可用指针表示数组。即:指针运算
引用数组元素,于是,可用指针变
量作为形参接收实参数组首地址。
a[10],a[0] a[1] a[2] … … … … … … a[9] a
x
‖
x[0]
‖
x[1] … … … … … …
‖
x[9]
实参
形参
函数改为,
void invert (x,n)
int ?x,n;
{ int ?i;
for (i=x; i<(x+n) ;i++) ?x= – ?x
return;
}
参数传递情况, a[0] a[1] a[2] … … a[9] a,
x x+1 x+9
? x ?(x+1) ?(x+9) …
注意,在主函数中也不一定要用数组名 a,只要
用一指针变量即可。设 int ?p; p=&a[0],则,
invert(p,n); 即可完成同样功能
总结以上情况,有四种参数传递形式,
特别需要指出的是,上面四种参传递形式实质
上是实现了实、形参共用内存单元。
(1) 实参、形参均为数组名
(2) 实参为数组名、形参为指针变量
(3) 实参、形参均为指针变量
(4) 实参为指针变量,形参为数组名。
1,多维数组的地址,将一维数组内容扩充,也
可用一指针变量指向多维数组,以二维数
组为例。
设,static int a[3][4] = {{1,2,3,4},{ 5,6,7,8},
{9,10,11,12}};
三、指向多维数组的指针和指针变量。
12
1 2 3 4
5 6 7 8
9 10 11
第 1行
第 2行
第 3行
a[0],
a[1],
a[2],
a[0]
a[0]+1
a[0]+2
a[0]+3
a[1]
a[1]+1
a[1]+2
a[1]+3
a[2]
a[2]+1
a[2]+2
a[2]+3
数组名 a[0] 数组名 a[1] 数组名 a[2]
a a+1 a+2
可把数组 a看成由 3个元素组成,
设首地址 a:2000,则 a+1:2008,a+2,2016
从一维数组中我们认为,a[0]与 ?a,?(a+0)等价
所以,a[0][i]的地址 &a[0][i]还可表示为 ?(a+i)
于是,a[i][j]可表示为 *(*(a+i)+j)
? a[0][i]的地址,a[0]+i,?a+i,和 &a[0][i]
a[1][i]的地址,a[1]+i,?(a+1)+i,&a[1][i]
? a[i][j]的地址,a[i]+j= ?(a+i)+j
#include<stdio.h>
main()
{ int i,j,a[3] [4]={1,2,3,4,5,6,7,8,9,10,11,12};
for (i=0; i<3; i++)
printf("addr of a[0] [%d ] is,%ld\n",i,(a+i));
for (i=0; i<3; i++)
for (j=0; j<4; j++)
printf("addr of a[%d] [%d] is,%ld\n",i,j,(a+i)+j);
}
例, /* exp12.6.c */
运行结果,
addr of a[0] [0] is,261820392
addr of a[0] [1] is,261820400
addr of a[0] [2] is,261820408
addr of a[0] [0] is,261820392
addr of a[0] [1] is,261820394
addr of a[0] [2] is,261820396
addr of a[0] [3] is,261820398
addr of a[1] [0] is,261820400
addr of a[1] [1] is,261820402
addr of a[1] [2] is,261820404
addr of a[1] [3] is,261820406
addr of a[2] [0] is,261820408
addr of a[2] [1] is,261820410
addr of a[2] [2] is,261820412
addr of a[2] [3] is,261820414
有了多维数组的地址概念后,可用指针变量来
指向多维数组
设二维数组的大小为 m*n,则第 i,j个元素相对于
a[0][0]的个数为 n?i + j (i=0,1,…, n–1)
2,多维数组的指针
? 指向数组元素
当用一个指针变量指向第 1行的首地址后,即
可搜索到全部元素。
设,int ?p;
p=a[0];
则 a[i][j] 的地址为 p+i ? n+j
指针 p 数组 a
?
?
?
?
?
?
?
?
?
?
????
????
????
a[i][j]的地址,p+i?n+j
例,设有一 3× 4的二维数组,利用指针逐行逐个
输出元素。
/*----exp12_7.c----*/
#include <stdio.h>
main()
{ int a[3] [4]={1,2,3,4,5,6,7,8,9,10,11,12};
int ?p;
p=a;
printf("a[2] [3]=%d\n",?(p+2?4+3));
printf("number of a is:");
for (;p<a[0]+12; p++)
{ if ((p?a[0] )%4==0)
printf("\n");
printf("%?4d",?p);
}
}
思考,
运行结果,a[2] [3]=12
number of a is,
1 2 3 4
5 6 7 8
9 10 11 12
是否可以将 a[0]直接写成 a,
for (p=a; p<a+12; p++
{
?
}
仍为上例为例, 当 p=a,若要使 p+1为下一行首地址,
第 i行首地址,相当于 a[i]
这时,需将 p的定义改为,int (?p)[4];
? 指向数组的每一行
?(?(p+i)+j)
? 不能去掉 ( ),否则为 int ?p[4]为指针数组;
于是引用第 (i,j)个元素的方法,
注意,
? 之所以这样定义是为了使 p+1的地址为 a[1]。
例, /*----exp12_8.c---*/
#include <stdio.h>
main( )
{ int a[3] [4]={1,2,3,4,5,6,7,8,9,11,12}
int (?p) [4]
p=a;
printf("addr of a[0] [0] is:%d\n",?(p+0));
printf("addr of a[1] [0] is:%d\n",?(p+1));
printf("addr of a[2] [0] is:%d\n",?(p+2));
printf("a[1] [3] =%d\n",*(?(p+1)+3));
运行结果,
addr of a[0] [0] is,4068
addr of a[1] [0] is,4076
addr of a[2] [0] is,4084
a[1] [3]=8
设有三个学生,各有四门功课,求总的平均
分,各学生总分,且将三个学生的成绩按总分
排序。
要求用指针变量,
例 1,
? 实参可用指向一维数组的指针变量;即,?数组名
? 实参可用指向数组的指针变量 (实数组名 )
输入三学生单科成绩
一、流程图,
计算各学生总分
计算总平均分
按各学生总分排序
打印各科成绩总分及
总平均分
结束
调用函数 sumscore(p,n)
指针,指向一维数组
调用函数 average(p,n)
调用函数 sort(p,n)
调用函数 printscore(p,n)
学生人数
指向二维数组 人数
返回值 float
指向一维数组
人数
指向二维数组 人数
main ( )
{ void sumscore ( );
float average( );
void sort( );
void printscore( );
static float score[3][5]={{65,80,90,100},
{80,66,75,90},
{58,60,90,83}};
二、程序
1,主函数
sumscore(?score,3);
printf("\n average=%f",average(score,3));
sort(?score,3);
printscore(score,3);
}
void sumscore(p,n)
float ?p; int n;
{ float aver,?p_end;
p_end=p+5 ? n –1;
for (; p<=p_end; p=p+5)
?(p+4)= ?p+ ?p(p+1)+ ?(p+2)+ ?(p+3);
}
2,sumscore函数 –––求总分
float average(p,n)
float (?p)[5];
int n;
{ float aver=0.0;
int i;
for (i=0; i<n; i++) aver=aver+ ?(?(p+i)+4);
aver=aver/n
return(aver);
}
3,average函数 –––求总平均分
有二种方法,(1)循环时用简单变量
(2)循环时用指针变量
方法 (1)
void sort (p,n)
float ?p;
int n;
{ int i,j,k,m; float t;
for (i=0; i<n –1; i++)
3,sort函数 –––排序
{ k=i;
for (j=i+1; j<n; j++)
if (?(p+4+5 ? i)< ?(p+4+5 ? j)) k=j;
if (k!=i) for (m=0; m<5; m++)
{t= ?(p+m+5 ? i);
?(p+m+5 ? i)= ?(p+m+5 ? k);
?(p+m+5 ? k)=t;
}
}
}
方法 (2)
void sort (p,n)
float ?p
int n;
{ float ?p1,?p_end,?k,t;
int m;
p_end=p+5 ? n –1;
p=p+4;
for (; p<p_end –5; p+=5)
{ k=p;
for (p1=p+5; p1<p_end; p1+=5)
if (?p< ?p1) k=p1;
if (k!=p) for (m=0; m<5; m++)
{ t= ?(p –m);
?(p – m)= ?(k1 – m);
?(k1 –m)=t;
}
}
}
void printscore(p,n)
float (?p)[5];
int n;
{ int i,j;
printf("\n A B C D sum\n");
for (i=0; i<0; i++ )
{ printf("\n");
for (j=0; j<4; j++)
prinft("%5.2f",?(?(p+i)+j));
}
}
4,printscore函数 –––打印输出