第十章 指针
10.1 地址和指针的概念
10.2 变量的指针和指向变量的指针变量
10.3 数组的指针和指向数组的指针变量
10.4 字符串的指针和指向字符串的指针变量
10.5 函数的指针和指向函数的指针
10.6 返回指针值的函数
10.1 地址和指针的概念一,地址与指针
1,地址与取地址运算
C程序中的变量在内存中占有一个可标识的存储区,
每一个存储区是由若干个字节组成,每一个字节都有自己的地址,而一个存储区的 地址是指该存储区中第一个字节的地址
C语言允许在程序中使用变量的地址
( 通过 地址运算符 &可得到 )
如,float x ; 变量 x 的地址 ---- &x
int a[10] ; 数组变量 a 的地址 ---- 数组名 a
2,指针与指针变量
(1) 变量的访问方式
① 直接访问,通过变量名或地址访问变量的存储区例,scanf (,%d”,&x ) ;
x = sqrt(x) ;
printf (,%d”,x ) ;
② 间接访问,将一个变量的地址存放在另一个变量中如将变量 x 的地址存放在变量 p 中,访问 x 时先找到 p,
再由 p 中存放的地址找到 x p x
2012 1010
1010
(2) 指针,一个 变量的指针就是该变量的地址 (指针就是地址 )
如变量 x的指针为 1010。
(3) 指针变量,存放某个变量的地址的变量,它用来指向另一个变量。如 p就是存放变量 x的地址的指针变量。
10.2 变量的指针和指向变量的指针变量一,指针变量的定义
1,格式,数据类型 * 指针变量名 ;
例 int *p1 ;
char *p2 ; 2,说明,
(1) 在变量定义时,* 号表示该变量是指针变量
( 注意,指针变量是 p1,p2,不是 *p1,*p2 )
(2) 定义指针变量后,系统为其分配存储空间,用以存放其他变量的地址,但在对指针变量赋值前,它并没有确定的值,也不指向一个确定的变量例,int x,*p ;
x = 5 ;
p x
2012 1010
51234
注,指针变量 p的值是随机值,
此时 p 和 x 并无关联
(3) 使指针变量指向一个确定的变量必须进行赋值
int x,*p ;
x = 5 ;
p = &x ; p x
2012 1010
51010
二,指针变量的引用
1,指针运算符 *
(1) p与 *p不同,p是指针变量,p的值是 p所指向的变量的地址
*p 是 p 所指向的变量,*p的值是 p所指向的变量的值
*p 的值为 5 (*p 表示 x),而 p 的值为 1010
(2) 引用指针变量时的 * 与 定义指针变量时的 * 不同定义变量时的 * 只是表示其后的变量 p是指针变量;
引用时 *表示取指针变量 p所指向的变量 x的值。
int a,*p ;
p = &a ;
scanf (,%d”,p ) ;
printf (“%d\n”,*p ) ;
*p = 12 ;
printf (“%d\n”,*p ) ;
p a
2012 1010
51010 12
让 p指向 a
对 a 重新赋值等价于 a=12
等价于 &a
2,& 与 *
p = &a ;
*&a? *(&a)? *p?a&*p? &(*p)? &a
由于 &和 *运算符优先级相等,所以按照从右到左的顺序进行结合运算。
2 a
b
c
d
5
3,*与 ++,- -
int a = 2,b = 5,c,d,*p ;
p 的值为 a 的地址,*p 的值为 2
语句执行之后 p 的值不变,*p 的值为 3
(2) c = *p++ ;
等价于 c = *p ; p++ ;
执行后 c 的值为 3,*p 的值为 5
(3) d = *++p ;
等价于 ++p ; d = *p ;
执行后 d 的值为 3,*p 的值为 3
p
2012 1010
1010(1) p = &a ;
(*p)++ ; ( 等价于 a++ ; )
1012
3
3
1014 3
关于 *p++的说明:由于 *和 ++
运算符的优先级一样,遵从从右到左的顺序,所以等价于
*(p++)。由于 ++在 p的后面,
则先操作后加一,所以写成
c = *p ; p++ ;
由于 p 的数据类型为 int所以
p++等价于 p=p+2。参看课本
P212 第二自然段的说明。
例 10,2输入 a和 b两个整数,按先大后小的顺序输出 a和 b。
#include <stdio.h>
void 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(“a=%d,b=%d \n”,a,b);
printf(“max=%d,min=%d \n”,*p1,*p2) ;
}
a
b
p1
p2
p &a
&b
&a&b
&a
5
9
输出结果,
a=5,b=9
max=9,min=5
本程序的目的是:实现在指针变量 p1中存放值较大的变量的地址; p2中存放值较小的变量的地址。
三,指针变量作函数参数例,2个数按大小顺序输出
(整型变量做参数)
#include <stdio.h>
void swap( int x,int y)
{ int temp;
temp = x ;
x = y;
y = temp;
}
void main( )
{ int a,b ;
scanf(“%d%d”,&a,&b);
if (a<b) swap(a,b);
printf(“a=%d,b=%d\n”,a,b);
}
a
b
x
y
temp5
说明,该程序不能实现 a 和 b
的交换因为实参 a,b 对形参
x,y 是“值传递”,x 和 y 的变化不影响 a 和 b
所以输出为,a=5,b=9
9
main swap
5 5
9
9
5
例 10,3 ①
(指针变量做参数)
#include <stdio.h>
void swap1(int *p1,int *p2)
{ int temp;
temp = *p1 ;
*p1 = *p2;
*p2 = temp;/*交换的是指针变量指向的变量的值 */
}
void main( )
{ int a,b,*pt1,*pt2 ;
scanf(“%d%d”,&a,&b);
pt1 = &a ; pt2 = &b ;
if (a<b) swap1( pt1,pt2) ;
printf(“a=%d,b=%d\n”,a,b);
}
a
b
p1
p2
temp
&b
&apt1
pt2
5
说明,这种方法是交换 p1和
p2所指向的变量的值,即交换 main函数中 a 和 b的值所以输出为,a=9,b=5
main swap1
5
9
&a
&b
9
5
例 10,3 ②
#include <stdio.h>
(指针变量做参数)
void swap2( int *p1,int *p2)
{ int *temp;
*temp = *p1 ;
*p1 = *p2;
*p2 = *temp;
}
说明,这种方法可以实现将 a,b的值互换,但是可能会破坏系统的正常工作状态,因为 temp是一个指针变量但是在函数中并没有给 temp一个确定的地址,这样它所指向的内存单元是不可预见的,而对 *temp的赋值可能带来危害
a
b
pt1
pt2
main
&a
&b
p1
p2
temp
&b
&a
swap2
随机值
5
5
95
9
例 10,3 ③
#include <stdio.h>
(指针变量做参数)
void swap3( int *p1,int *p2)
{ int *p;
p = p1 ;
p1 = p2;
p2 = p; /*交换的是指针变量的值 */
}
p1
p2
p&a
&a
&b
这种方法不能实现将 a,b的值互换,
因为只交换形参 p1和 p2的值,使它们的指向发生改变,但是 main函数中的 a和 b的值并没有进行交换所以输出为,a=5,b=9
5a
9b
&apt1
&bpt2
main
&b
&a
swap3
10.3 数组的指针和指向数组的指针变量一,指向一维数组的指针和指针变量
1,一维数组及元素的地址表示
int a[5] = { 1,2,3,4,5 } ; 数组的地址,a
元素 地址
*a a[0] &a[0] a
*(a+1) a[1] &a[1] a+1
*(a+2) a[2] &a[2] a+2
*(a+3) a[3] &a[3] a+3
*(a+4) a[4] &a[4] a+4
2,用指针变量引用数组元素
(1) 定义指针变量
int *p,a[5] = { 1,2,3,4,5 } ;
p = a ; /*指针 p的值为内存中数组 a的起始单元的地址 */
(2) 引用数组元素 下标法 地址法 指针法第 k个元素 a[k] *(a+k) *(p+k)
第 k个元素的地址 &a[k] a+k p+k
注意,指针变量也可以加下标 p[k] 等价于 a[k]
① 分别用三种方法输出数组元素,其效率不同,
下标法与地址法的效率相同,指针法的效率较快
② 用指针变量访问数组元素时要注意下标是否越界例,将数组 a中全部元素加 1,再输出 a
#include <stdio.h>
void main( )
{ int a[5] = {1,3,5,7,9 },*p,j ;
for ( p=a ; p<a+5 ; p++ )
printf(“%3d”,*p); /*按指针访问数组 */
printf(“\n”) ;
for ( j=0 ; j<5 ; j++)
a[j]=a[j]+1 ; /*按数组名和下标访问数组 */
for ( j=0 ; j<5 ; j++)
printf(“%3d”,*(p+j) ) ; /*按指针访问数组 */
printf(“\n”) ;
}
p=a ;
1
3
5
7
9
a
a+1
a+2
a+3
a+4
2
4
6
8
10
p
使用指针变量时要注意它的当前值可以用 p++,但不能用 a++
因为 a 代表数组的起始地址它是地址常量,不能改变而 p 是一个指针变量
3,指向数组的指针变量作函数参数
① 实参和形参都用数组名例 10,7 将数组 a中 n个整数按相反顺序存放 a[0]
a[1]
a[2]
a[3]
a[4]
a[5]
x[0]
x[1]
x[2]
x[3]
x[4]
x[5]
1
3
4
6
7
9
9
7
6
4
3
1
main inv1
#include <stdio.h>
void inv1( int x[ ],int n )
{ 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 ;
}
}
void main( )
{ int i,a[6]={ 1,3,4,6,7,9 };
inv1(a,6 );
for( i=0; i<6; i++ ) printf(“%3d”,a[i] );
printf(“\n”);
}
例 10,7 ② 实参用数组名,形参用指针变量
#include <stdio.h>
void inv2(int *x,int n)
{ int temp,m=(n-1)/2;
int *p,*i,*j ;
i=x ; j=x+n-1;
p=x+m;
for( ; i<=p ; i++,j- - )
{ temp=*i ;
*i=*j ;
*j=temp ; }
}
void main( )
{ int i,a[6]={ 1,3,4,6,7,9 };
inv2(a,6 );
for( i=0; i<6; i++ )
printf(“%3d”,a[i] );
printf(“\n”);
}
1
3
4
6
7
9
a[0]
a[1]
a[2]
a[3]
a[4]
a[5]
ax
ai
6n
a+5j
a+2p
inv2
9
1
7
3
6
4
2m
temp 1
a+1
4
2
3
34
i和 j的指向会变化,
p的指向保持不变例 10.7 ③ 实参和形参都用指针变量
#include <stdio.h>
void inv3(int *x,int n);
void main( )
{ int *p,a[6]={1,3,4,6,7,9};
p = a ;
inv3( p,6 );
for( p=a ; p<a+6 ; p++ )
printf(“%3d”,*p );
printf(“\n”);
}
void inv3(int *x,int n)
{ int temp,m=(n-1)/2;
int *p,*i,*j ;
i=x ; j=x+n-1; p=x+m;
for( ; i<=p ; i++,j-- )
{ temp=*i ; *i=*j ;
*j=temp ; }
}
例 10.7 ④ 实参用指针变量,形参用数组名
#include <stdio.h>
void inv4(int x[ ],int n);
void main( )
{ int *p,a[6]={1,3,4,6,7,9};
p = a ;
inv4( p,6 ) ;
for( p=a ; p<a+6 ; p++ )
printf(“%3d”,*p );
printf(“\n”);
}
void inv4(int x[ ],int n)
{ 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; }
}
实参为指针变量 p
形参为指针变量 x
实参为指针变量 p
形参为数组名 x
实参类型 变量名 数组名或指针变量要求形参的类型 变量名 数组名或指针变量传递的信息 变量的值 数组的起始地址通过函数调用能否改变实参的值不能 能
(值传递) (地址传递)
例 求数组中最大和最小元素主函数中定义数组 a[5],调用函数中定义数组 arr[5]和指针变量 p。
最大值和最小值放在全局变量 max
和 min中。
main
a[0]
a[1]
a[2]
a[3]
a[4]
a[5]
6n
arr+1p
arr+6end
max
min
4
7
1
9
0
5
aarr
4
4
79
10#include <stdio.h>int max,min ;
void m1(int arr[ ],int n) /*形参为数组名 */
{ int *p,*end ;
end=arr+n ;
max=min=*arr ;
for(p=arr+1 ; p<end ; p++)
if( *p>max ) max=*p ;
else if( *p<min ) min=*p ;
}
void main( )
{ int i,a[6];
for(i=0 ; i<6 ; i++) scanf(“%d”,&a[i] );
m1( a,6 ); /*实参为数组名 */
printf(“max=%d,min=%d\n”,max,min);
}
调用函数用指针变量 p 读取数组中的每一个值与 max和 min相比较。
10.4 字符串的指针和指向字符串的指针变量一、字符指针
1,定义 指向字符串的指针变量
char *p =,China”;
说明,这里没有定义字符数组,
但字符串在内存中还是以数组形式存放的,字符串在内存中占有一片连续的存储单元,以‘ \0?结束注意,赋值只是把字符串的首地址赋给 p,
而不是把字符串赋给 p,p 是一个指针变量,
它不能存放一个字符串,只能存放一个地址
C
h
i
n
a
\0
2460
2461
2462
2463
2464
2465
2460p
将字符串的首地址赋给 p
2,输出字符串,
printf(“%s\n”,p ) ;
输出字符串时,先输出 p 指向的第一个字符,然后系统自动执行 p++,
使 p 指向下一个字符,再输出该字符,
直到遇到‘ \0? 为止也可以用循环逐个输出字符串中的字符,
for ( ; *p!=?\0?; p++ ) printf(“%c”,*p ) ;
C
h
i
n
a
\0
2460
2461
2462
2463
2464
2465
2460p 24612462246324642465
二,字符数组与字符指针变量的区别
char s[ ] =“C Language” ; char *p =“C Language” ;
1,存储方式不同,s 存放的是字符串中的 字符和‘ \0?
p 存放的是字符串的 首地址
2,虽然 s 和 p 都能代表字符串的首地址,但 s是数组名,
是一个常量,而 p是一个指针变量,
因此 s++ 的 写法是错的,而 p++ 的写法是对的
char *p1,*p2 =,abcd”;
p1 =,China” ;
char s[12],str[ ] =,good” ;
s[12] =,China” ;
s =,China” ;
3,赋值方式不同
s 可以进行初始化,
但不能使用赋值语句
p 既可以初始化,
也可以赋值错
4,定义数组 s 时,系统会给 s 分配一片连续的存储单元定义指针变量 p 时,只给 p 分配一个存储单元三、字符串指针作函数参数例 10,19 实现字符串复制,① 形参和实参都是数组名
#include <stdio.h>
void copystr(char from[ ],char to[ ])
{ int i = 0 ;
while ( from[i]!=?\0? )
{ to[i] = from[i] ;
i++;
}
to[i] =?\0? ;
}
void main( )
{ char a[ ] =,cat”,b[ ] =,tiger”;
puts(a); puts(b);
copystr(a,b);
puts(a); puts(b);
}
c
a
t
\0
t
i
g
e
r
\0
a b
from a to b c
a
t
\0
相当于 b[i]=a[i]
输出结果:
cat
tiger
cat
cat
例 10,19 ② 实参和形参都是指针变量
void copystr(char *from,char *to)
{ for( ; *from!=?\0?; from++,to++ )
*to = *from ;
*to =?\0? ;
}
void main( )
{ char *a =,cat”,*b =,tiger”;
puts(a); puts(b);
copystr(a,b);
puts(a); puts(b);
}
c
a
t
\0
t
i
g
e
r
\0
a b
from a to b c
a
t
\0
a+1 b+12 23 3
10.5 函数的指针和指向函数的指针变量一、函数的指针,函数的入口地址在程序执行过程中调用函数时,计算机会转去执行函数体内的语句,因此计算机必须知道函数在什么地方。
实际上 函数在内存中也要占据一片存储单元,这片存储单元一个起始地址,我们称其为 函数的入口地址,
即函数的指针,这个函数的入口地址是 用函数名来表示 。
因此我们可以定义一个指针变量,让它的值等于函数的入口地址,然后可以通过这个指针变量来调用函数,该指针变量称为指向函数的指针变量二、指向函数的指针变量
1,定义格式,数据类型 ( *指针变量名 ) ( 形参表列 ) ;
int ( *pt ) ( int arr[ ],int n ) ;
说明,① 数据类型,指针变量所指向的函数的返回值类型
② 形参表列,即指针变量所指向的函数的形参表列
③ 格式中的小括号不能省略
2,应用
(1) 让指针变量指向函数 pt = add ;
因为函数名为函数的入口地址,所以直接将函数名赋给指针变量即可
(2) 使用指针变量调用函数格式,(*指针变量名 ) ( 实参表列 )
例 求一维数组中全部元素的和
#include <stdio.h>
int add( int b[ ],int n);
void main( )
{ int a[6]={ 1,3,5,7,9,11},total ;
int (*pt) ( int b[ ],int n ) ;
pt = add ;
total = (*pt) ( a,6 ) ;
printf(,total = %d \n”,total ) ;
}
int add( int b[ ],int n )
{ int i,sum = 0 ;
for ( i = 0 ; i<n ; i++ )
sum = sum+b[i];
return(sum);
}
定义指向函数的指针变量令指针变量 pt 指向函数 add
通过 pt 调用函数 add
10.6 返回指针值的函数前面我们用到的函数,有些无返回值,有些有返回值,
返回值 类型多为 int,float,char,一个函数的返回值也可以是一个指针类型的数据 (即地址 )
定义函数,数据类型 * 函数名 ( 形参表列 )
{ 函数体 ; }
例,int * fun ( int a,int b )
{ 函数体 ; }
说明,定义一个返回指针值的函数与以前定义函数格式基本类似,只是 在函数名前加 *,它表明该函数返回一个指针值,而这个指针值是指向一个 int 型数据
#include <stdio.h>
#include <string.h>
#define SIZE 100
char buf[SIZE] ;
char *p=buf ;
char *alloc( int n)
{ char *begin ;
if ( p+n <= buf+SIZE )
{ begin=p ; p=p+n;
return(begin);
}
else return(NULL);
}
void main( )
{ char *p1,*p2 ; int i ;
p1=alloc(10);
strcpy(p1,”123456789”);
p2=alloc(5);
strcpy(p2,”abcd”);
printf(“buf=%p\n”,buf);
printf(“p1=%p\n”,p1);
printf(“p2=%p\n”,p2);
puts(p1); puts(p2);
for( i=0 ; i<15 ; i++)
printf(“%c”,buf[i]);
}
习题 10.19 编写一个函数 alloc(n),用来在内存区新开辟一个连续的空间( n
个字节)。此函数的返回值是一个指针,指向新开辟的连续空间的起始地址。
buf[0]
buf[1]
:
:
buf[9]
buf[10]
:
buf[14]
buf[15]
:
:
buf[99]
bufp buf+10
p1
p2
main
begin
n
alloc
buf
buf
buf+10
1
2
:
9
\0
a
:
\0
10
15
begin
n
alloc
buf+10
5
课后作业,P278
10.4 10.17