460 likes | 605 Vues
第十章 指针. 10.1 指针与地址 10.2 指针与变量 10.3 指针与数组 10.4 指针与字符串 10.5 指针与函数 10.6 指针数组和指向指针的指针. 变量名 a b c. 地址 2000 2002 2004. 3. 7. A. 10.1 指针与地址. 1. 地址:内存区的每一个字节的编号。 若有: int a, b; char c; a=3; b=7; c=‘A’;. p. 2000. 3. a.
E N D
第十章 指针 10.1 指针与地址 10.2 指针与变量 10.3 指针与数组 10.4 指针与字符串 10.5 指针与函数 10.6 指针数组和指向指针的指针
. . 变量名 a b c 地址 2000 2002 2004 . . . . 3 7 A 10.1 指针与地址 1. 地址:内存区的每一个字节的编号。 若有:int a, b; char c; a=3; b=7; c=‘A’;
p 2000 3 a 2. 两种访问方式 ①直接访问:通过变量名 如:scanf(“%d”, &a); printf(“%d”, a); ②间接访问:通过一个中间变量 把变量a的地址存放在另一个变量p中,通过变量p来间接访问变量a。 3. 指针:地址的形象化称谓,用来表示指向关系。 4. 指针变量:存放地址值的变量,如p。
10.2 指针与变量 10.2.1 指针变量的定义 • 形式:基类型 *指针变量名 • 基类型:表示指针变量可存放哪一类型变量的地址。 • *:表示该变量为一指针变量。 • 如:int *p ; • 表示定义了一个指针变量p,p只能指向int型的变量,即p只能存放一个int型变量的地址值。
p a 10.2.2 指针变量的引用 先定义后使用,使指针变量有一确定的指向(赋初值)。 1. 两个相关的运算符 (1)&:取地址运算符 (2)* :指针运算符,取指向。 例:int a, *p ; //此时的*并非指针运算符 p=&a ; //指针变量p指向a *p=3 ; //*p表示p所指向的量,即a &a 3
2. 注意: (1)指针变量只能存放地址,不能存放其他非地址类型的数据。 如:int a, *p; p=&a ; /*对*/ p=a ; /*错*/ (2)指针变量只能存放同一类型变量的地址。 如:int a, *p; char c; p=&a ; /*对*/ p=&c ; /*错*/
3. 直接访问和间接访问 若有定义:int a, *p; p=&a; (1)直接访问 scanf(“%d”, &a); printf(“%d”, a); (2)间接访问 scanf(“%d”, p); printf(“%d”, *p); 形式: scanf(“%d”, 指针变量名); printf(“%d”, *指针变量名);
例10.1 通过指针变量访问整型变量 main( ) {int a=10, b=20; int *p, *q; p=&a; q=&b; printf(“%d, %d\n”, a, b); printf(“%d, %d\n”, *p, *q); } 结果:10,20 10,20
p p &a &a 5 8 a a q q &b &b 8 5 b b *p 例10.2 利用指针变量交换a和b的值。 ①交换整型变量的值 main( ) {int a=5, b=8; int *p, *q, t; p=&a; q=&b; t=*p; *p=*q; *q=t; printf(“%d,%d\n”, a, b); printf(“%d,%d\n”,*p, *q); } 结果:8,5 8,5 *q
p &a 5 a q &b 8 b ②交换指针变量的值(地址) main( ) {int a=5, b=8; int *p, *q, *t; p=&a; q=&b; t=p; p=q; q=t; printf(“%d,%d\n”, a, b); printf(“%d,%d\n”,*p, *q); } 结果:5,8 8,5 p &b 5 a q &a 8 b
4. 进一步说明两个运算符 • 优先级:相同 • 结合性:右结合性 若有 int a, *p; p=&a; 则:&*p <=> &a <=> p *&a <=> *p <=> a • ++与* :优先级相同,右结合性 若有 int a, *p; p=&a; 则:*p++ <=> *(p++) :地址值加一 (*p)++ <=> a++ :变量值加一
main函数 swap函数 a p1 p &a 5 &a b q1 q &b 8 &b a p1 p &a 8 &a b q1 q &b 5 &b 10.2.3 指针变量作为函数参数 例10.3 交换a和b的值 ①交换整型变量的值 void swap(int *p, int *q) {int t; t=*p; *p=*q; *q=t; } main( ) {int a=5, b=8; int *p1=&a, *q1=&b; swap( p1, q1 ); printf(“%d,%d\n”, a, b); }
main函数 swap函数 a p1 p &a 5 &a ②交换指针变量的值 void swap(int *p, int *q) {int *t; t=p; p=q; q=t; } main( ) {int a=5, b=8; int *p1, *q1; p1=&a; q1=&b; swap( p1, q1 ); printf(“%d,%d\n”, a, b); } 结果:5,8 b q1 q &b 8 &b a p1 p &a 5 &b b q1 q &b 8 &a
结论:用指针变量作为函数参数,在被调函数的执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来。这样,就可以在主调函数中使用这些改变了的值。因此,用指针变量作参数,可以得到多个变化了的值。结论:用指针变量作为函数参数,在被调函数的执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值的变化依然保留下来。这样,就可以在主调函数中使用这些改变了的值。因此,用指针变量作参数,可以得到多个变化了的值。
10.3 指针与数组 10.3.1 指向数组元素的指针 用法与指向变量的指针变量相同 • int a[5], *p ; p=&a[0]; 等价于 p=a ; • 也可以在定义的同时赋初值 int a[5], *p=a ; /*先定义数组,再给指针变量赋值*/
10.3.2 通过指针引用数组元素 例10.5 输出数组中的全部元素 1. 下标法 main( ) {int a[5], j; for(j=0; j<5; j++) scanf(“%d”, &a[j]); for(j=0; j<5; j++) printf(“%5d”, a[j]); }
2. 指针法 • 如*(a+j)或*(p+j),等价于a[j]。 main( ) {int a[5], j; for(j=0; j<5; j++) scanf(“%d”, &a[j]); for(j=0; j<5; j++) printf(“%5d”, *(a+j) ); } main( ) {int a[5], j,*p; for(j=0; j<5; j++) scanf(“%d”, &a[j]); for( p=a; p<a+5; p++) printf(“%5d”, *p); }
3. 说明 (1)指针变量的值可以改变,但数组首地址不能改变。 若有定义:int a[5], *p=a; 则:p++; /*对*/ a++; /*错,因为数组的首地址是一个常量*/ (2)注意指针变量的当前值
p p p a[0] a[1] a[2] a[3] a[4] main( ) {int a[5], j, *p; p=a; for(j=0; j<5; j++,p++) scanf(“%d”, p); p=a; for(j=0; j<5; j++,p++) printf(“%5d”, *p); }
(3)注意指针变量的运算 ① p++ :指向下一个元素 ② *p++ :先取出p所指向的元素的值,然后p指向下一个元素。 ③ (*p)++ : 将p所指向的元素的值加一
10.3.3 数组名作函数参数 四种形式: (1)形参和实参都用数组名 f (int x[ ], int n) {……} main( ) {int a[10]; … f (a, 10); … } 例10.7
实际上,能够接受并存放地址值的只能是指针变量。实际上,能够接受并存放地址值的只能是指针变量。 (2)实参用数组名,形参用指针变量。 f (int *x, int n) {……} main( ) {int a[10]; … f (a, 10); … } 将例10.7中的形参改为指针变量
(3)实参和形参都用指针变量 f (int *x, int n) {……} main( ) {int a[10], *p; p=a; … f (p, 10); … } 将例10.7中的参数改为指针变量
(4)实参用指针变量,形参用数组名 f (int x[ ], int n) {……} main( ) {int a[10], *p; p=a; … f (p, 10); … } • 注意:如果用指针变量作实参,必须先使指针变量有确定值,指向一个已定义的数组。
用第一种形式求解例10.8 用第二种形式求解例10.8 10.3.4 指向多维数组的指针和指针变量 通过前面的介绍,我们可以得到以下两个结论: (1)一维数组名代表数组的起始地址,是一个指针常量,指向数组的第一个元素。 (2)一维数组中任一元素的地址都可以用数组名加上一个偏移量来表示。有等价关系: a <=> &a[0], a+1 <=> &a[1], … , a+i <=> &a[i] *a <=> a[0], *(a+1) <=> a[1], … ,*(a+i) <=> a[i] 在C语言中,可以把二维数组解释为以一维数组为元素的一维数组。
二维数组元素 列指针 a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3] a[0]+1 行指针 a+1 a[1]+1 a+2 a[2]+1 1. 二维数组的地址 int a[3][4]; a a[0] *(a+0)+1 a[1] *(a+1)+1 a[2] *(a+2)+1
前加 * 前加 & 行 列 列 行 • 二维数组元素a[i][j]还可以表示为: *(a[i]+j) 和 *(*(a+i)+j)。 • 行指针:指向一维数组的指针变量,加一表示加一行,如:a、a+1、a+2 等。 • 列指针:指向二维数组元素的指针变量,加一表示加一列(元素),如:a[0]和*a、a[1]+1和*(a+1)+1、a[2]+2和*(a+2)+2 等。 • 行、列指针的互换:
2. 指向二维数组的指针变量 (1)列指针:指向数组元素的指针变量 定义:与指向变量的指针变量的定义相同。 初始化:给出一维数组名(二维数组所包含的) 。 例10.12 用列指针输出数组元素的值。 main( ) {int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; int *p ; for( p=a[0]; p<=a[0]+11; p++) printf(“%4d”, *p); }
(2)行指针:指向由m个元素组成的一维数组的指针变量。(2)行指针:指向由m个元素组成的一维数组的指针变量。 定义:基类型 (*指针变量名)[m] ; 初始化:给出二维数组名。 例10.13 输出二维数组任一行任一列元素的值。 main( ) {int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; int (*p)[4], i, j; p=a; scanf(“%d,%d”, &i, &j); printf(“%d\n”, *(*(p+i)+j) ); }
10.4 指针与字符串 10.4.1 字符串的表示形式 1. 下标法 例 10.18 注意:通过数组名,以%s的格式可以对一个字符串进行整体的输入输出。而对一个数值型数组,则不能企图用数组名输出它的全部元素。 如:int a[10]; …… printf(“%d\n”, a); 是不对的,数值型数组只能逐个逐个地输出元素。
2. 指针法 (1)用字符指针指向一个字符串 如:char s[ ]=“I am happy”; char *p=s; 或 char *p= “I am happy”; 此时,系统自动为字符串常量开辟存储空间,并将字符串首地址赋给指针变量p。 例10.19
10.4.2 字符指针作函数参数 用字符指针作函数参数,将一个字符串的首地址从一个函数传递到另一个函数,在被调函数中可以改变字符串的内容,在主函数中可以得到改变了的字符串。 1. 例10.20 2. 用字符指针处理例10.20 3. 简化copy函数 (1)void copy(char *from,char *to) { while( (*to=*from) !=‘\0’) /*先赋值后判断*/ { from++; to++; } }
(2)void copy(char *from,char *to) {while( (*to++=*from++) !='\0') ; } 10.4.3 字符指针变量和字符数组的区别 1. 存放内容 字符数组由多个元素组成,存放若干个字符;而字符指针变量存放的是地址。 2. 赋值方式 (1)在定义的同时 char str[ ]=“I am happy”; /*整体赋初值*/ char *s=“I am happy”;
(2)在赋值语句中 char str[20]; str[0]=‘I’; str[1]=‘ ’; str[2]=‘a’; …… /*只能逐个赋值*/ char *s; s=“I am happy”; /*s存放字符串常量的首地址*/ 3. 编译时的处理方式 在编译时,为数组分配一段内存单元,要为数组元素赋值;为指针变量分配一个内存单元,要为指针变量赋一地址值,使其有确定的指向。
a[0] a a b b c c \0 d \0 e a[1] d f e g f f g g \0 \0 例 main( ) {char a[2][5]={“abc”, “defg”}; char *p,*s; p=a[0]; s=a[1]; while(*p) p++; while(*s) *p++=*s++; printf(“%s\n%s\n”, a[0], a[1]); } 结果:abcdefgfg fgfg
10.5 函数和指针 10.5.1 函数的指针和指向函数的指针变量 1. 定义:数据类型 (*指针变量名) ( ) ; 2. 说明: (1)数据类型要与函数返回值的类型一致 (2)括号不可省 3. 使用: (1)只需将函数名赋给指针变量,不必写参数。 (2)通过指针变量调用函数,只需用(*指针变量)代替函数名即可。 例 求两个数中的最大值
10.5.2 返回指针值的函数 • 定义:数据类型 *函数名(参数类型1 参数名1, …) • 与函数的定义相比多了一个‘*’ • 例10.25 • 区别:int *p( ) ; int (*p)( ) ;
10.6 指针数组和指向指针的指针 10.6.1 指针数组 1. 概念P248:存放指针的数组。 2. 定义:数据类型 *数组名[常量表达式] 只比整型数组的定义多了一个‘*’ 3. 区别: int *p[4] ; int (*p)[4] ;
name[0] China\0 name[1] Japan\0 name[2] USA\0 name[3] India\0 4. 使用:常用来指向若干个字符串,使字符串的处理更方便灵活。 (1)定义的同时整体初始化,赋值语句中逐个赋值。 如:char *p=“China”; char *name[4]={“China”, “Japan”, “USA”, “India”}; 将各字符串常量的首地址赋值各个指针数组元素。 例10.27
10.6.2 指向指针的指针 1. 概念:指向指针数据的指针变量,即存放另一指针型数据的地址。 2. 定义:基类型 **指针变量名 比指向变量的指针变量多了一个‘*’。 3. 使用:要将一指针数组名或二维数组名赋值给它,使用方法类似于行指针。
name China\0 Japan\0 USA\0 India\0 例10.28 使用指向指针的指针。 main( ) {char *name[4]={“China”, “Japan”, “USA”, “India”}; char **p ; int j; for(j=0; j<4; j++) { p=name+j; printf(“%s\n”, *p); } }
p &a[0] 1 &a[1] 3 &a[2] 5 &a[3] 7 &a[4] 9 例10.29 使用指向指针的指针 main( ) {static int a[5]={1,3,5,7,9}; 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 ”, **p); p++; } }
10.6.3 指针数组作main函数的形参 main函数可以有形参,main函数是由系统以命令的方式调用的,实参与命令一起给出。 • 带参main函数的原型: void main(int argc, char *argv[ ]) • 说明:①argc是命令行中参数的个数(包括文件名) ②argv是一个指向字符串的指针数组 • 命令行的一般形式: 文件名 参数1 参数2 …… 参数n
argv file1\0 China\0 Beijing\0 例 main(int argc, char *agrv[ ]) { while(argc>1) { argv++ ; printf(“%s\n”, *argv); argc-- ; } } 若以下输入命令: file1 China Beijing 则结果为:China Beijing
作业 (一) 作业均要求用指针方法处理 1. 习题10.1 2. 习题10.3 3. 习题10.4 4. 有一个班4个学生,5门课。要求使用列指针求出第一个学生的平均成绩,用行指针求出第一门课的平均成绩。
(二) 作业均要求用指针方法处理 1. 习题10.6 2. 习题10.7 3. 写一函数,用于判断一个串是否为“回文”(顺读和逆读结果相同)。例如:“abcdcba”为回文,“abcdba”不是回文。 4. 用指向指针的指针的方法将一个字符串逆序输出。