1.09k likes | 1.21k Views
第十章 指针 指针是 C 语言中的一个重要的概念,也是 C 语言的一个重要特色。每一个学习和使用 C 语言的人,都应当深入地学习和掌握指针。可以说,不掌握指针就不掌握 C 语言的精华。 指针的概念比较复杂,使用也比较灵活,因此初学时常会出错,务请在学习本章内容时 十分小心,多思考、多比较、多上机,在实践中掌握它 。. 重点: 1 、指针的概念; 2 、变量的、函数的、数组的、字符 串的指针; 3 、指针数组。 难点:数组的指针。. 10.1 地址和指针的概念. 内存用户数据区. : :.
E N D
第十章 指针 指针是C语言中的一个重要的概念,也是C语言的一个重要特色。每一个学习和使用C语言的人,都应当深入地学习和掌握指针。可以说,不掌握指针就不掌握C语言的精华。 指针的概念比较复杂,使用也比较灵活,因此初学时常会出错,务请在学习本章内容时十分小心,多思考、多比较、多上机,在实践中掌握它。
重点:1、指针的概念; 2、变量的、函数的、数组的、字符 串的指针; 3、指针数组。 难点:数组的指针。
10.1 地址和指针的概念 内存用户数据区 : : 简言之,指针就是地址。 有变量的、函数的、数组的、字符串的指针等。 一、内存的访问方式 1、直接访问 在程序中一般是通过变量名来对内存单元进行存取操作的。程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的,称为“直接访问”方式。 如:i=3, j=6, k=9 通过变量i、j、k 来访问存储单元。 2000 2002 2004 3 6 9 : : i j k
内存用户数据区 : : 2、间接访问 如变量 i,不知道其值及地址,而只知道它的地址放在3010、3011中,因此可通过存放地址的单元找到变量 i 的值,此为“间接访问”方式。 对间接访问,可定义一变量存放地址,如定义i_pointer, 赋值i_pointer=&i。要存取变量i的值,也可以采用间接方式:先找到存放“i的地址”的变量,从中取出i的地址(2000),然后到2000、2001字节取出i的值(3)。 因此,为了表示将数值3送到变量中,可有两种表达方法: (1) 将3送到变量i所标志的单元中。 (2) 将3送到变量i_pointer所“指向”的单元. 2000 2002 2004 3010 3 6 9 2000 : : : i j k i_pointer
二、指针的概念 一个变量的地址称为该变量的“指针”。例如,地址2000是变量i的指针。 存放地址的变量叫“指针变量”。换句话说,指针变量存放地址。上述的i_pointer就是一个指针变量。
10.2 变量的指针和指向变量的指针变量 内容:指针变量的定义及引用; 指针变量作为函数的参数。 如前所述,变量的指针就是变量的地址。 存放变量地址的变量是指针变量。 为了表示指针变量和它所指向的变量之间的联系,在程序中用“*”表示“指向”。 例如,i_pointer代表指针变量,而* i_pointer是它所指向的变量。见下图。 可以看到,* i_pointer和变量i是同 一回事。下面两个语句作用相同: • i=3; • * i_pointer=3; i_pointer *i_pointer 3 2000 i 2000
一、指针变量的定义 一般形式: 基类型 *指针变量名 如:int *p1; char *p2; float *p3; 说明: (1) * 表示指针型变量,意为“指向…..的指针”。变量名是p1,p2,p3,而不是*p1,*p2,*p3 (2) 指针变量有确定的指向的数据类型。 注1:不论指针变量为何种基类型,该指针变量所占的内存单元都是两个字节。 注2:一个指针变量只能指向同一个类型的变量。(人为保证,若不一致,也可以运行,只是结果错误。)
二、指针变量的引用 1、给指针变量赋值 指针变量中只能存放地址(指针),不要将一个整型量赋给一个指针变量。只能赋变量的地址。 如: p1=&i; 合法 pointer_1=100; 不合法 有两个有关的运算符: • &:取地址运算符 • *:指针运算符
j=*p+2后 : : : : 1000 1002 1004 1000 1002 1004 i j p i j p 1000 1002 1004 i j p 3 1000 1000 1002 1004 3 5 1000 1000 2、用指针变量对变量赋值 如: int i , j , *p; p=&i; *p=3; j=*p+2; “p=&i”的意义为:指针变量p指向变量 i 。 注意:三个*p含义不一样。第一个*p是定义一个指针变量,变量名为p。后两个*p是指针变量p所指向的存储单元,此处与i是一回事。 定义后 p=&i后 *p=3后 i j p
例10.1 main() {int a, b; int *pointer_1, *pointer_2; a=100;b=10; pointer_1=&a; /*把变量a的地址赋给pointer_1*/ pointer_2=&b; /*把变量b的地址赋给pointer_2*/ printf(”%d, %d\n”, a, b); printf(”%d, %d\n”, *pointer_1, *pointer_2); } 运行结果为: 100, 10 100, 10
关于运算符&和 * 的几点说明: (1)结合性 自右向左 如:int a, b, *p1, *p2; p1=&a; p2=&b; &*p1=? 相当于 &(*p1) 等于 &(*p1)=&(a)=&a=p1 *&a=? 相当于 *(&a)=a 所以,&* 和 *& 相当于单位运算符,&和*可理解成是两个互逆的运算。 (2)优先级 处于第二位
(3)自增自减运算 如: int a, b, *p; p=&a; a=3; b=5; (*p)++; /* 相当于 a++ a==4 */ *p++; /* 相当于 *(p++) */ 若p为1000, 则p++为1002 若float a, *p; p=&a; 当p为1000,则p++为1004。
例10.2 输入a和b两个整数,按先大后小的顺序输出a和b。 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("\na=%d, b=%d\n”, a, b); printf("max=%d, min=%d\n“, *p1, *p2); } 运行情况如下: 输入 5,9 a=5,b=9 max=9,min=5
交换地址(指针变量),使p1指向大数b,而变量a、b并未交换。交换地址(指针变量),使p1指向大数b,而变量a、b并未交换。 p1 a p1 a &a &b 5 5 p p p2 b p2 b &b 9 &a 9
三、指针变量作为函数参数 功能:将一个变量的地址传送到另一函数中,实参和形参均用指针。 例10.3 题目同例10.2,即对输入的两个整数按大小顺序输出。 今用函数处理,而且用指针类型的数据作函数参数。程序如下: swap(int *p1,int *p2) {int temp; temp=*p1; *p1=*p2;*p2=temp; } main() {int a, b; int *pointer_1, *pointer_2; scanf(“%d, %d”, &a, &b); pointer_1=&a;pointer_2=&b; if(a<b) swap(pointer_1, pointer_2); printf(“\n%d, %d\n”, a, b); }
传递的是变量的指针,在函数中交换的是变量中的内容,主函数中读取交换了的内容。这里指针未变,与上题相反。传递的是变量的指针,在函数中交换的是变量中的内容,主函数中读取交换了的内容。这里指针未变,与上题相反。
下面例子不能正确交换数据: swap(int *p1,int *p2) {int *p; p=p1; p1=p2; p2=p; } main() { int a, b; int *pointer_1,*pointer_2; scanf(”%d%d”,&a,&b); pointer_l=&a;pointer_2=&b; if(a<b) swap(pointer_ 1,pointer_2); printf(”\n%d,%d\n”,* pointer_1,*pointer_2); }
pointer_1 a &a 5 pointer_2 b &b 9 pointer_1 a p1 a p1 a pointer_1 a &a 5 &b 5 &b 5 &a 5 出错的原因是参数不是双向传递。 pointer_2 b p2 b p2 b pointer_2 b &b 9 &b 9 &a 9 &a 9 (a) (b) (c ) (d) 问题出在第四步,形参p1,p2的改变,不影响实参pointer_1,pointer_2的值,因为函数参数是单向传递的。(d)应为右图:
例10.4 输入a、b、c三个整数,按大小顺序输出。 swap(int *pt1,int *pt2) {int temp; temp=*pt1; *pt1=*pt2; *pt2=temp; } exchange(int *q1, int *q2, int *q3) {if(*q1<*q2) swap(q1, q2); if(*q1<*q3) swap(q1, q3); if(*q2<*q3) swap(q2, q3); }
main() { int a, b, c, *p2, *p3; scanf(”%d, %d, %d“, &a, &b, &c); pl=&a; p2=&b; p3=&c; exchange(p1, p2, p3); printf("\n%d, %d, %d\n", a, b, c); } 运行情况如下: 输入:9,0,10 回车 输出:10,9,0
10. 3 数组的指针和指向数组的指针变量 重点:指向数组元素的指针变量及其应用 指向一维数组的指针变量及其应用 指向多维数组元素的指针变量及其应用 难点:多维数组的指针变量及其应用 数组的指针是数组的起始地址,数组元素的指针是元素的地址。 引用数组可以用下标法,也可以用指针法,通过指向数组元素的指针找到数组元素。 使用指针法能使目标程序占内存少,运行速度快。
一、指向数组元素的指针变量的定义与赋值 1、定义 定义一个指向数组元素的指针变量的方法,与指向变量的指针变量相同。 类型标识符 * 变量名 2、赋值 将某元素或数组的首地址赋给指针变量。 例如:int a[10]; int *p; p=&a[0]; 注意:对p赋的是地址,而不是数值。 可在定义 时赋初值 或 int *p=&a[0]; 或 int *p=a;
a数组 p, ( 2000) p+1, a+1 p+2, a+2 p+3, a+3 p+i, a+i p+7, a+7 p+8, a+8 p+9, a+9 1 2 3 4 8 9 0 2000 a[0] a[1] a[2] a[3] a[i] a[7] a[8] a[9] p 二、通过指针引用数组元素 元素赋值、取值 如: int a[10], *p; p=a; *p=1; 相当于 a[0]=1; 1、各元素地址 (用上定义) 指针 p 增1便指向下一单元。 所以 p 指向 a[0] p+1 指向 a[1] p+2 指向 a[2] : p+i 指向 a[i] : p+9 指向 a[9]
因 p=a,因此 p+i 与 a+i 等价。所以有 a 指向 a[0] a+1 指向 a[1] a+2 指向 a[2] : a+i 指向 a[i] : a+9 指向 a[9]
2、引用元素 直接引用 a[0]=1; a[i]=6; 通过地址引用 如:*p=1; ….. *(p+i)=6; 或者 *a=1; ….. *(a+i)=6; “[ ]” 实际是变址运算符。 如a[i]的实际地址为a+i*2,“2”是指整型数据占两个字节。 3、指向数组的指针变量也可带下标 如: p[i]与*(p+i) 等价。 根据以上叙述,引用一个数组元素,可以用: 1、下标法,如a[i]形式; 若 “p=a;” 则p[i]与a[i] 等价 2、指针法,如 *(a+i)或 *(p+i)
例10. 5 输出数组中的全部元素。 main() {int a[10]={1,2,3,4,5,6,7,8,9,0}; int *p, i; for(i=0;i<10;i++) printf("%d ",a[i]); printf("\n"); for(i=0;i<10;i++) printf("%d ",*(a+i)); printf("\n"); for(p=a;p<(a+10);p++) printf("%d ",*p); printf("\n"); }
以上三种方法比较: • 前两种方法执行效率是相同的,先计算元素地址。 • 第三种方法相对要快,用指针变量直接指向元素,不必每次都重新计算地址。 • 用下标法比较直观,能直接知道是第几个元素。 使用指针变量时,有几个问题要注意: (1) 可以改变指针变量的值。p可变,而a不可变。即有p++,而无a++。a是指针常量。 (2) 要注意指针变量的当前值。当p变时,注意p当前的值。
例 10.6 通过指针变量输出数组a的10个元素。 main() { int *p, i, a[10]; p=a; printf("Enter 10 numbers:\n"); for(i=0;i<10;i++) scanf("%d",p++); printf("\n"); for(i=0;i<10;i++,p++) printf("%d ",*p);}
运行情况: 1 2 3 4 5 6 7 8 9 0 22153 234 0 0 30036 25202 11631 8259 28483 问题在于: 经过第一个for循环读入数据后,p已指向a数组的末尾,因此,在执行第二个for循环时,p的起始值不是&a[0]了,而是a+10。即p指向的是a数组下面的10个元素。 解决方法: 只要在第二个for循环之前加一个赋值语句: p=a; 使p的初始值回到&a[0],这样结果就对了。
(3) 从上例可以看到,虽然定义数组时指定它包含10个元素,可以用p指向当前的数组元素。但实际上指针变量p可以指向数组以后的内存单元。系统不认为非法,但往往会使程序得不到预期的结果。 在使用指针变量指向数组元素时,应切实保证指向数组中有效的元素。
(4) 关于指针变量的一些运算 (设p=a) ① p++; (或p--;)使p指向下一个(或上一个)元素。 ② *p++等价于 *(p++),先得到*p的值,再做p++ *p--等价于 *(p--) , 先得到*p的值,再做p-- ③ *(p++) 与*(++p)作用不同。前者是先取*p值,然后使p值加1。后者是先使p加1,再取*p。 ④ (*p)++表示p所指向的元素值加1,而不是指针值加1。 ⑤ 如果p当前指向a数组中第i个元素,则: *(p--)相当于a[i--],先对p进行*运算,再使p自减。 *(++p)相当于a[++i],先使p自加,再作*运算。 *(--p)相当于a[--i],先使p自减,再作*运算。
三、用数组名作函数参数 1、数组名作函数参数 实参数组名代表该数组首元素的地址。而形参是用来接收从实参传递过来的数组首元素的地址。 因此,形参应该是一个指针变量(只有指针变量才能存放地址)。实际上,C编译都是将形参数组名作为指针变量来处理的。
2、指针变量作函数参数 f(int arr[], int n) 等价于 f(int *arr, int n) 如果在f(int arr[], int n)函数中用sizeof运算符测定arr所占的字节数: printf(“%d”, sizeof(arr)); 结果为2。这就说明了系统把arr作为指针变量来处理。 当arr接收了实参数组的首元素地址后,arr就指向实参数组首元素,也就是指向array[0]。arr+1指向array[1], arr+2指向array[2], ……。*(arr+i)和arr[i]是无条件等价的。
用变量名和用数组名作函数参数的比较 实参类型 变量名 数组名 要求形参的类型 变量名 数组名或指针变量 传递的信息 变量的值 实参数组首元素地址 通过函数调用能 否改变实参的值 C语言中,用下标法和指针法都可以访问一个数组. 应该说明:实参数组名代表一个固定的地址,或者说是指针型常量,而形参数组是一个指针变量,在函数调用时,它的值等于实参数组起始地址,但在函数执行期间,它可以再被赋值。见课本217页f(arr[], int n)。 不能 能
例10.7 将数组a中n个整数按相反顺序存放。 算法:将a[0]与a[n-1]对换,a[1]与a[n-2]对换……,a[i]与a[n-1-i]对换……,直到将a[(n-1)/2]与a[n-(n-1)/2-1]对换。 void inv(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;} return; }
main() { int i, a[10]={0,1,2,3,4,5,6,7,8,9}; printf("the orginal array:\n"); for(i=0;i<10;i++) printf("%d ",a[i]); printf("\n"); inv(a,10); printf("the array has been inverted:\n"); for(i=0;i<10;i++) printf("%d ",a[i]); printf("\n"); }
a数组 a数组 i++,x p=x+m j - - 9 8 7 6 3 2 1 0 a[0] a[1] a[2] a[3] a[6] a[7] a[8] a[9] :: i++,x p=x+m j - - a[0] a[1] a[2] a[3] a[6] a[7] a[8] a[9] 0 1 2 3 6 7 8 9 :: 指针变量作函数参数(形参) void inv(int *x, int n) {int *p, t,*i,*j, m=(n-1)/2; j=x+n-1;p=x+m; for(i=x; i<p; i++, j--) { t=*i; *i=*j; *j=t;} return; }
例10.8 从10个数中找出其中最大值和最小值。 方法一:形参和实参均用数组。 int Max,Min; /*全局变量*/ void max_min_value(int array[],int n) { int *p,*array_end; array_end=array+n; Max=Min=*array; for(p=array+1;p<array_end; p++) if(*p>Max) Max=*p; else if(*p<Min) Min=*p; return; }
main() {int i, number[10]; printf("Enter 10 data\n"); for(i=0;i<10;i++) scanf("%d",&number[i]); for(i=0;i<10;i++) printf("%d ",number[i]); max_min_value(number,10); printf("\nmax=%d,min=%d\n",Max,Min); }
方法二:形参和实参均用指针 int Max,Min; max_min_value(int *array, int n) { int *p,*array_end; array_end=array+n; Max=Min=*array; for(p=array+1;p<array_end;p++) if(*p>Max) Max=*p; else if(*p<Min) Min=*p; }
main() {int i, number[10], *p; p=number; printf("Enter 10 data\n"); for(i=0;i<10;i++, p++) scanf("%d", p); p=number; for(i=0;i<10;i++) printf("%d ", *(p+i)); max_min_value(p,10); printf("\nmax=%d,min=%d\n",Max,Min); }
对于数组作为参数,可以用数组名和指针作为参数,共有四种组合(221~222):对于数组作为参数,可以用数组名和指针作为参数,共有四种组合(221~222): 形参和实参均数组 形参用数组而实参用指针 形参用指针而实参用数组 形参和实参均用指针 传递的均是数组的首地址。
例10.10 用选择法对10个整数由大到小顺序排序。 实参用指针,形参用数组。 main() {int *p,i,a[10]; p=a; for(i=0; i<10; i++) scanf("%d",p++); p=a; sort(p,10); for(i=0;i<10;i++) {printf("%d ",*p); p++;} }
sort(int x[],int n) { 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;} } }
或: sort(int *x, int n) { 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;} } }
四、指向多维数组的指针和指针变量 先讨论多维数组元素的地址,然后讨论指向多维数组元素的指针变量以及用指向数组的指针做函数参数。 (一)多维数组元素的地址 根据数组的性质,二维数组看作是一维数组,如 int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; 存放情况如下图所示。看成一维数组时,元素为a[0]、a[1]、a[2], 每个元素又是一维数组。 1、数组的首地址 为 a 2、各行的首地址 (1)用a表示a+i为第i行首地址 第0行首地址为 a ,第1行首地址为 a+1,第2行为 a+2。 因为看成一维 数组,所以a+1指向了a[1],即第一行。
(2)用a[i]表示 因为a[0]、a[1]、a[2]是一维数组名,所以a[0]代表一维数组a[0]中第0列元素的地址,即&a[0][0],为a的第0行第0列元素的地址。a[1]的值是第1行第0列元素的地址&a[1][0],……,a[i]为第 i 行、第0列元素的地址。 各行首地址的变化情况: 设数组首地址为2000,则各行首地址为:2000、2008、2016, 各行相差 4*2=8。
3、某行某列的地址(即各元素的地址) 可有多种表示方式: (1)用a[i]表示 第i行、第j列元素的地址为:a[i]+j (2)用a表示为: 对一维数组,a[0]、a[1]、a[2]和 *(a+0)、*(a+1)、*(a+2)均表示元素,所以 a[0]与*(a+0)等价,即a[0]与 *a等价 a[1]与*(a+1)等价, a[2]与 *(a+2)等价, 对二维数组,则a[0]、a[1]、a[2]和 *(a+0)、*(a+1)、*(a+2)表示各行第0列元素的地址,所以第i行、第j列元素的地址为:*(a+i)+j (3)用&a[i][j]表示
4、元素的表示方法 即第i行、第j列的元素表示方法 (1)下标法:a[i][j] (2)用a[i]+j 地址表示: *(a[i]+j) (3)用*(a+i)+j 地址表示: *(*(a+i)+j) 5、地址表示方法汇总
例10.11 #define format "%d,%d\n" main() { int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23}; printf(format, a, *a); 158,158 printf(format, a[0], *(a+0)); 158,158 printf(format, &a[0], &a[0][0]); 158,158 printf(format, a[1], a+1); 166,166 printf(format, &a[1][0], *(a+1)+0); 166,166 printf(format, a[2], *(a+2)); 174,174 printf(format, &a[2], a+2); 174,174 printf(format, a[1][0], *(*(a+1)+0)); 9,9 }