页数:80页文件大小:1.22 MB来源: C语言程序设计ppt课件
本课件系统讲解C语言的指针,从变量与地址、指针与指针变量的概念入手,说明取地址与间接访问运算,随后展开指针变量的定义初始化与运算、指针与一维数组及二维数组的对应关系、指针与字符串,再介绍返回指针值的函数、指向函数的指针,最后讲解指针数组与多级指针的用法。
第八章指针章节封面,标示本章主线:指针的概念、指针变量、指针与数组、指针与字符串、返回指针值的函数、指针与函数、指针数组和多级指针。
指针是C语言的重要组成部分,精华所在。使用指针可以:
变量与地址:内存中每个字节有一个编号,即地址。定义 int i;float k; 后,编译或函数调用时为其分配内存单元(如 i 占 2000~2001,k 占 2002~2005)。
变量是对程序中数据存储空间的抽象,变量的地址由系统分配,程序只可使用不能修改。
指针与指针变量
整型变量 i 的值为 10、地址为 2000;指针变量 i_pointer 的内容为 2000,即指向 i 的地址。变量地址存入指针变量即构成指向关系。
& 与 * 运算符
i_pointer ----- 指针变量,它的内容是地址量 *i_pointer ---- 指针的目标变量,它的内容是数据 &i_pointer --- 指针变量占用内存的地址 i = *i_pointer = *(&i) i_pointer = &i = &(*i_pointer)直接访问与间接访问
i = 3;*i_pointer = 20;两种方式作用于同一内存单元,i 与 *i_pointer 等价。
整型变量 i 的值为 10,指针变量 i_pointer 指向 i:
k = i; k = *i_pointer; 一般形式:[存储类型] 数据类型 *指针名;
int *p1, *p2; float *q; static char *name;注意:
int *p1, *p2; 与 int *p1, p2; 不同,后者 p2 是整型变量一般形式:[存储类型] 数据类型 *指针名 = 初始地址值;
int i; int *p = &i; int *q = p; 赋给指针变量的是地址值,不是赋给目标变量。
void main() { int i = 10; int *p; *p = i; printf("%d", *p); } void main() { int i = 10, k; int *p; p = &k; *p = i; printf("%d", *p); }指针变量未赋值时其值是随机数,向随机地址写入会破坏内存,必须先赋值再使用。
零指针(空指针):指针变量值为零。
int *p = 0; #define NULL 0 int *p = NULL; while (p != NULL) { ... } 用途:避免指针变量的非法引用;在程序中常作为状态比较。注意 p=NULL 与未对 p 赋值不同。
void main() { int a; int *pa = &a; a = 10; printf(" a: %d\n", a); printf("*pa: %d\n", *pa); printf(" &a: %x\n", &a); printf(" pa: %x\n", pa); printf("&pa:%x\n", &pa); }输出:a: 10 / *pa: 10 / &a: f86 / pa: f86 / &pa: f88。指针变量自身也占内存单元。
void main() { int *pa, *pb, *p, a, b; scanf("%d %d", &a, &b); pa = &a; pb = &b; if (a < b) { p = pa; pa = pb; pb = p; } printf("a=%d,b=%d\n", a, b); printf("max=%d,min=%d\n", *pa, *pb); }运行结果:a=5,b=9 / max=9,min=5。只交换指针的指向,变量 a、b 本身的值不变。
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("\n%d,%d\n", a, b); }形参 x、y 只是实参 a、b 的副本(COPY),交换发生在副本上。
输入 5 9,调用 swap(a, b) 后 main 中变量 a=5、b=9 保持不变,输出 5, 9,说明值传递无法实现两数交换。
void swap(int x, int y) { int temp; temp = x; x = y; y = temp; } void main() { int a, b; int *pa, *pb; scanf("%d%d", &a, &b); pa = &a; pb = &b; if (a < b) swap(*pa, *pb); printf("\n%d,%d\n", a, b); }实参写 *pa、*pb 时传的是变量值,仍为值传递。
swap 中 x、y 得到 a、b 的副本,交换后 main 中 a=5、b=9 不变,运行结果仍为 5, 9。要真正交换须传地址。
void swap(int *p1, int *p2) { int temp; temp = *p1; *p1 = *p2; *p2 = temp; } void main() { int a, b; int *pa, *pb; scanf("%d%d", &a, &b); pa = &a; pb = &b; if (a < b) swap(pa, pb); printf("\n%d,%d\n", a, b); }形参 p1、p2 得到的是 pa、pb 的地址值(COPY),但它们指向 main 中的 a、b,通过 *p1、*p2 间接访问即可交换。
调用 swap(pa, pb) 后,main 中 a=9、b=5,输出 9,5。地址传递实现共享内存与双向传递。
void swap(int *p1, int *p2) { int *p; *p = *p1; *p1 = *p2; *p2 = *p; }正确写法应为 int temp; int *p = &temp;。指针变量在使用前必须赋值,否则向随机地址写入可能破坏系统。
void swap(int *p1, int *p2) { int *p; p = p1; p1 = p2; p2 = p; }形参 p1、p2 互换后随函数调用结束释放,实参 pa、pb 的指向不变。
运行结果:5,9。本质上 C 语言参数传递只有一种方式——值传递,地址传递传的是地址值,通过间接访问才能改变实参所指内容。
int array[10]; int *p; p = &array[0]; int array[10]; int *p = &array[0]; int *p = array;数组名是表示数组首地址的地址常量。
指针变量的赋值运算
p = &a; 将变量 a 的地址赋给 pp = array; 将数组首地址赋给 pp = &array[i]; 将数组元素地址赋给 pp = NULL; 空指针p1 = p2; 指针间赋值p = 1000;(非法)、i = p;(非法)p = &(i+5);,不能取 register 变量地址 p = &k;,不能改变变量地址 &i = p;指针的算术运算
p ± i 等价于 p ± i×d(i 为整型数,d 为 p 指向变量所占字节数)p++、p--、p += i、p -= i 等p1 - p2 为两指针间元素个数,即 (p1-p2)/dp1 + p2 无意义int a[10]; int *p = &a[2]; p++; *p = 1; int *p1 = &a[2], *p2 = &a[5]; p2 - p1 = 3; p 指向 float 数时 p+1 相当于地址加 4(放大因子)。
指针变量的关系运算
p == NULL 或 p != NULL 判断指针是否为空等价关系
数组元素:a[i] p[i] *(p+i) *(a+i) 元素地址:&a[i] &p[i] p+i a+i| 表示法 | 元素地址 | 元素 |
|---|---|---|
| 下标法 | &a[0] &a[1] &a[2] &a[9] | a[0] a[1] a[2] a[9] |
| 指针法 | p p+1 p+2 p+9 | *p *(p+1) *(p+2) *(p+9) |
| 数组名法 | a a+1 a+2 a+9 | *a *(a+1) *(a+2) *(a+9) |
应用场合:顺序访问用指针法速度快,随机访问用下标法直观。[ ] 是变址运算符,a[i] 即 *(a+i),优先级 1,自左向右结合。
int a[] = {1,2,3,4,5,6,7,8,9,10}, *p = a, i; 数组元素地址的正确表示: (A)&(a+1) (B)a++ (C)&p (D)&p[i] int a[] = {8,1,2,5,0,4,7,6,3,9}; a[*(a+a[3])] 的值等于: (A)8 (B)0 (C)3 (D)9数组名是地址常量,a++、a-- 非法,p++、a+1、*(a+2) 合法。
注:a[*(a+a[3])] 中 a[3]=5,*(a+5)=a[5]=4,故结果为 a[4]=0,对应选项 B。
void main() { int a[5], *pa, i; pa = a; for (i = 0; i < 5; i++) scanf("%d", &a[i]); for (i = 0; i < 5; i++) printf("%d: %d\n", i, a[i]); }也可用 *(a+i)、pa[i]、*(pa+i)、&pa[i] 等形式访问同一元素。
void main() { int a[5], *pa = a, i; for (i = 0; i < 5; i++) scanf("%d", pa++); pa = a; for (i = 0; i < 5; i++, pa++) printf("%d: %d\n", i, *pa); }指针变量可以指到数组之后的内存单元,使用前应确保其指向有效元素。
void main() { int a[] = {1, 2, 3, 4, 5}; int *p; p = a; printf("%d\n", (*p)++); printf("%d\n", *++p); printf("%d\n", ++*p); printf("%d\n", *p--); p += 3; printf("%d\n%d\n", *p, *(a+3)); }数组 a 经运算后为 2、3、3、4、5,输出依次为 1、2、3、3 与 4、4。
数组名作函数参数是地址传递,实参与形参的对应关系:
| 实参 | 形参 |
|---|---|
| 数组名 | 数组 int x[ ]、指针变量 int *x |
| 指针变量 | 数组 int x[ ]、指针变量 int *x |
int sum(int x[], int n) { int i, sum = 0; for (i = 0; i < n; i++) sum += x[i]; return(sum); } void main() { int a[10] = {3,7,9,11,0,6,7,5,4,2}, *p; p = a; printf("The sum is:%d\n", sum(a, 10)); }形参 int x[ ] 等价于 int *x;调用可写 sum(a,10)、sum(p,10)、sum(&a[0],10);循环体可写 sum += *x++;、sum += *(x+i); 或 {sum += x[0]; x++;}。
对 int *p 与 int a[10]:
p[i] a[i] *(p+i) *(a+i)int a[ ] int *aint *p 写成 int p[ ]对于一维数组 int array[10];:
array[i] *(array+i)int a[3][4]; a ---- 二维数组首地址,即第 0 行首地址 a+i ---- 第 i 行首地址 a[i] *(a+i) ---- 第 i 行第 0 列元素地址 a[i]+j *(a+i)+j ---- 第 i 行第 j 列元素地址 a[i][j] *(a[i]+j) *(*(a+i)+j) (*(a+i))[j]a+i = a[i] = *(a+i) = &a[i][0],值相等但含义不同:a+i 指向行,a[i] 指向列。数组 a 由 a[0]、a[1]、a[2] 三个一维数组构成。
对 int a[3][4]:a+i 表示第 i 行首地址,指向行;a[i] 即 *(a+i),等于 &a[i][0],表示第 i 行第 0 列元素地址,指向列。二者数值相同,类型不同。
void 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]+12; p++) { if ((p - a[0]) % 4 == 0) printf("\n"); printf("%4d ", *p); } }p = a[0];、p = *a;、p = &a[0][0];、p = *(a+0); 均正确,p = a; 类型不符。
定义形式:数据类型 (*指针名)[一维数组维数];,如 int (*p)[4];,括号不能少。
int (*p)[4] 与 int *p[4] 不同,前者是行指针,后者是指针数组p ± i 偏移 i × sizeof(数据类型) × 数组列维数对 int a[3][4] 与 p:p[i][j] *(p[i]+j) *(*(p+i)+j) (*(p+i))[j]。
void main() { int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23}; int i, j, (*p)[4]; for (p = a, i = 0; i < 3; i++, p++) { for (j = 0; j < 4; j++) printf("%4d ", *(*p+j)); printf("\n"); } }*(*p+j) 等价于 p[0][j];此处 p = a[0];、p = *a;、p = &a[0][0]; 均不正确,应写 p = a;。
void main() { int a[3][4] = {{1,2,3,4},{3,4,5,6},{5,6,7,8}}; int i; int (*p)[4] = a, *q = a[0]; for (i = 0; i < 3; i++) { if (i == 0) (*p)[i+i/2] = *q+1; else { p++; ++q; } } for (i = 0; i < 3; i++) printf("%d,", a[i][i]); printf("%d,%d\n", *((int *)p), *q); }输出:2,4,7,5,3。i=0 时 a[0][0] 被改为 2,随后 p 与 q 逐次后移。
对 int a[3][4]; int (*p1)[4] = a; int *p2 = a[0];,实参形参对应关系:
| 实参 | 形参 |
|---|---|
| 数组名 a | 数组名 int x[][4]、指针变量 int (*q)[4] |
| 指针变量 p1 | 数组名 int x[][4]、指针变量 int (*q)[4] |
| 指针变量 p2 | 指针变量 int *q |
void get_sum_row(int x[2][3], int result[], int row, int col) { int i, j; for (i = 0; i < row; i++) { result[i] = 0; for (j = 0; j < col; j++) result[i] += x[i][j]; } } void main() { int a[2][3] = {3,6,9,1,4,7}; int sum_row[2], row = 2, col = 3, i; get_sum_row(a, sum_row, row, col); for (i = 0; i < row; i++) printf("The sum of row[%d]=%d\n", i+1, sum_row[i]); }形参 int x[2][3] 可写为 int x[][3] 或 int (*x)[3],元素用 *(*(x+i)+j) 访问;结果 18、12。
对 int a[5][10] 与 int (*p)[10]:
*(*(p+i)+j) a[i][j]int x[][10] int (*x)[10]void main() { char string[] = "I love China!"; printf("%s\n", string); printf("%s\n", string+7); }输出:I love China! / China!。数组名 string 是地址常量,string += 7; 与 string = "I Love China!"; 均非法。
void main() { char *string = "I love China!"; printf("%s\n", string); string += 7; while (*string) { putchar(string[0]); string++; } }字符指针初始化即把字符串首地址赋给 string,等价于 char *string; string = "I love China!";;*string != 0 判断字符串是否结束。
void copy_string(char from[], char *to) { while ((*to = *from) != '\0') { from++; to++; } } void main() { char *a = "I am a teacher.", b[20]; copy_string(a, b); printf("string_a=%s\nstring_b=%s\n", a, b); }循环体可简写为 *to++ = *from++;,遇字符串结束标志 \0 停止复制。
char *cp; 与 char str[20];
str = "I love China!";(非法);cp = "I love China!";(合法)char *cp; scanf("%s", cp);(非法),应改为 char *cp, str[10]; cp = str; scanf("%s", cp);字符串用一维字符数组存放,字符数组具有一维数组的所有特点:数组名是指向首地址的地址常量、可用指针法与下标法引用、数组名作函数参数是地址传递。
区别:存储格式有字符串结束标志;赋值与初始化方式不同;输入输出用 %s 与 %c。
char str[] = {"Hello!"}; char str[] = "Hello!"; char str[] = {'H','e','l','l','o','!'}; char *cp = "Hello"; int a[] = {1,2,3,4,5}; int *p = {1,2,3,4,5}; str = "Hello"; cp = "Hello!"; scanf("%s", str); printf("%s", str); gets(str); puts(str);函数定义形式:
数据类型 *函数名(形参表) { 函数体; return(指针); }例:int *f(int x, int y)。使用场合:被调函数需要返回多于一个的值。
#include <stdio.h> char *strin(char c); void main() { char *ch, c; c = getchar(); ch = strin(c); printf("%s\n", ch); } char *strin(char c) { char str[5]; int i; for (i = 0; i < 4; i++) str[i] = c + i; str[i] = '\0'; return(str); }输入 A 期望输出 ABCD,但 str 是存放在动态存储区(栈)的局部动态变量,函数返回后空间释放,返回的指针无效;应将 str 声明为 static char str[5]; 存放在静态存储区。不能返回形参或局部动态变量的地址作函数返回值。
函数指针:函数在编译时被分配的入口地址,用函数名表示。
定义形式:数据类型 (*指针变量名)( );,如 int (*p)();,括号不能省略,int (*p)() 与 int *p() 不同。
p = max;(max 须有函数原型声明)c = max(a,b); 等价于 c = (*p)(a,b);下列定义的含义
int *p; —— 整型指针变量int (*p)[3]; —— 指向一维数组的指针int *p(int); —— 返回指针的函数int (*p)(int); —— 指向函数的指针,函数返回 int 型变量int *(*p)(int); —— 指向函数的指针,函数返回 int 型指针指针数组:数组中的元素为指针变量,用于处理二维数组或多个字符串。
定义形式:[存储类型] 数据类型 *数组名[数组长度说明];,如 int *p[4];,注意与 int (*p)[4] 区分。
main() { int b[2][3], *pb[2]; pb[0] = b[0]; pb[1] = b[1]; pb[0] = &b[0][0]; pb[1] = &b[1][0]; pb[0] = *b; pb[1] = *(b+1); }void main() { int b[2][3], *pb[] = {b[0], b[1]}; ... }初始化时把各行首地址依次赋给 pb[0]、pb[1],之后可用 pb[i][j] 访问元素。
void main() { char a[] = "Fortran", b[] = "Lisp", c[] = "Basic"; char *p[4]; p[0] = a; p[1] = b; p[2] = c; p[3] = NULL; } p[0] = "Fortran"; p[1] = "Lisp"; p[2] = "Basic"; p[3] = NULL; char *p[] = {"Fortran", "Lisp", "Basic", NULL};char name[5][9] = {"gain", "much", "stronger", "point", "bye"}; char *name[5] = {"gain", "much", "stronger", "point", "bye"};void main() { int b[2][3], *pb[2]; int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) b[i][j] = (i+1)*(j+1); pb[0] = b[0]; pb[1] = b[1]; for (i = 0; i < 2; i++) for (j = 0; j < 3; j++, pb[i]++) printf("b[%d][%d]:%2d\n", i, j, *pb[i]); }也可写为 printf("b[%d][%d]:%2d\n", i, j, *(pb[i]+j));,更直观且不会移动指针。
例:对字符串排序(简单选择排序)
char *name[] = {"Follow me", "BASIC", "Great wall", "FORTRAN", "Computer"};i=0 趟:从 name[0] 起依次与后面各串比较,记录最小串下标 k,本趟选出 BASIC 为最小。
i=1 趟:在剩余字符串中继续比较,k 随比较结果更新,选出次小串并与 name[1] 交换指针。排序过程中只交换指针数组中记录的指向,不移动字符串本身,效率高于逐个字符复制。
i=2 趟:在剩余字符串中继续比较,k 随比较结果更新,选出第三小串与 name[2] 交换。选择排序每趟确定一个位置。
i=3 趟:比较剩余两个串,确定 name[3],排序结束。五个字符串共需 n-1 趟比较。
#include <stdio.h> #include <string.h> void sort(char (*name)[20], int n); void print(char (*name)[20], int n); void main() { char name[][20] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"}; int n = 5; sort(name, n); print(name, n); } void sort(char (*name)[20], int n) { char temp[20]; int i, j, k; for (i = 0; i < n-1; i++) { k = i; for (j = i+1; j < n; j++) if (strcmp(name[k], name[j]) > 0) k = j; if (k != i) { strcpy(temp, name[i]); strcpy(name[i], name[k]); strcpy(name[k], temp); } } } void print(char (*name)[20], int n) { int i; for (i = 0; i < n; i++) printf("%s\n", name[i]); }形参写成数组形式即 char name[][20],排序时逐串复制字符,移动数据量大。
用指针数组 char *name[5] 指向 Follow me、BASIC、Great Wall、FORTRAN、Computer,i=0 趟中 k 指向当前最小串,趟末交换 name[0] 与 name[k] 的指向。
i=1 趟:k 初始为 1,与后续各串比较后更新,趟末交换指针,使 name[1] 指向剩余串中最小者。
i=2 趟:继续在剩余串中选出最小者并交换指针,字符串本身不发生移动。
i=3 趟:比较最后两个串并完成指针交换,排序结束。整个排序只交换指针值,开销远小于整串复制。
void main() { char *name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer "}; int n = 5; sort(name, n); print(name, n); } void sort(char *name[], int n) { char *temp; int i, j, k; for (i = 0; i < n-1; i++) { k = i; for (j = i+1; j < n; j++) if (strcmp(name[k], name[j]) > 0) k = j; if (k != i) { temp = name[i]; name[i] = name[k]; name[k] = temp; } } } void print(char *name[], int n) { int i; for (i = 0; i < n; i++) printf("%s\n", name[i]); }形参 char *name[ ] 为指针数组,交换的只是各元素中保存的地址。
一级指针:指针变量中存放目标变量的地址,为单级间接寻址。
int *p; int i = 3; p = &i; *p = 5;二级指针:指针变量中存放一级指针变量的地址,为二级间接寻址。
int **p2; int *p1; int i = 3; p1 = &i; p2 = &p1; **p2 = 5;定义形式:[存储类型] 数据类型 **指针名;,如 char **p;
*p 是 p 间接指向对象的地址,**p 是 p 间接指向对象的值int i, **p; p = &i; 错误,二级指针不能用普通变量的地址赋值int ***p;,四级 char ****p;int i = 3; int *p1; int **p2; p1 = &i; p2 = &p1; **p2 = 5;#include <stdio.h> void swap(int *r, int *s) { int *t; t = r; r = s; s = t; } void main() { int a = 1, b = 2, *p, *q; p = &a; q = &b; swap(p, q); printf("%d,%d\n", *p, *q); }形参 r、s 是指针 p、q 的副本,交换副本后 main 中 p 仍指向 a、q 仍指向 b。
运行结果:1,2。要在被调函数中改变 main 里指针变量的指向,必须传指针变量的地址,即使用二级指针形参。
#include <stdio.h> void swap(int **r, int **s) { int *t; t = *r; *r = *s; *s = t; } void main() { int a = 1, b = 2, *p, *q; p = &a; q = &b; swap(&p, &q); printf("%d,%d\n", *p, *q); }实参为 &p、&q,形参 r、s 得到指针变量的地址,通过 *r、*s 修改其指向。
运行结果:2,1。p 改为指向 b、q 改为指向 a,说明二级指针形参可改变实参指针的指向。
char *name[5] = {"hello", "good", "world", "bye", ""}; char **p = name; name[0]~name[4] 分别指向 hello、good、world、bye 与空串,p 指向数组首元素。
#define NULL 0 void main() { char **p; char *name[] = {"hello", "good", "world", "bye", ""}; p = name; p++; printf("%o : %s ", *p, *p); p += 2; while (**p != '\0') printf("%s\n", *p++); }输出:644 : good bye。用 *p 可输出地址(%d、%o、%x)或字符串(%s),*p++ 即 *(p++)。
对 int **p 与 int *q[10]:
int *q[ ] 与 int **q 完全等价;作为变量定义两者不同本章小结
int *p;int (*p)[4];int (*p)();int *p();int *p[4];int **p;申请内存函数 malloc
void *malloc(unsigned size)int *p; p = (int *)malloc(10 * sizeof(int)); 释放内存函数 free
void free(void *p)#include <stdlib.h> void try(int a[], int m) { int k; for (k = 0; k < m; k++) a[k] = k*10; for (k = 0; k < m; k++) printf("%d,", a[k]); } void main() { int *p; p = (int *)malloc(10 * sizeof(int)); printf("\nResult:"); try(p, 10); free(p); }下载地址:{{downurl}}
{{{a}}}
{{#b}}{{{b}}}
{{/b}} {{/title}} {{#nolog}}登陆注册
{{/nolog}} {{#djsx}}点我刷新
{{/djsx}} {{#gocz}} {{/gocz}} {{#ffqr}} {{/ffqr}}