1 / 81

第八章

第八章. 使用指针. 为什么引入指针. 指针为函数提供修改变量值的手段 指针为 C 的 动态内存 分配系统提供支持 指针为动态数据结构(如例链表、队列、二叉树等)提供支持 指针可以改善某些子程序的 效率. 为什么引入指针. 编程:求 某班 某科考试的学生成绩平均分。. #define N 50 main() { int a[N] , i, sum=0, ave,num; printf(“input the number of students:”); scanf(“%d”, & num ); for(i=0; i<num, i++)

aletta
Download Presentation

第八章

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第八章 使用指针

  2. 为什么引入指针 • 指针为函数提供修改变量值的手段 • 指针为C的动态内存分配系统提供支持 • 指针为动态数据结构(如例链表、队列、二叉树等)提供支持 • 指针可以改善某些子程序的效率

  3. 为什么引入指针 编程:求某班某科考试的学生成绩平均分。 #define N 50 main() { int a[N], i, sum=0, ave,num; printf(“input the number of students:”); scanf(“%d”, &num); for(i=0; i<num, i++) { scanf(“%d”, &a[i]); sum+=a[i]; } ave=sum/num; printf(“average:%d”, ave); } 数组固定大小 *a 浪费内存空间 a=(int *)malloc(num*sizeof(int)); 动态内存

  4. 指针的故事 • 铁杆C/C++程序员最挚爱的武器:指针 • 指针造就了C/C++的高效和强大 • 很多不可能的任务由指针完成 • main() • {char*a="main(){char*a=%c%s%c;printf(a,34,a,34);}"; • printf(a,34,a,34);}

  5. 指针的故事 • “该程序执行了非法操作,即将关闭” • 这种错误几乎全是由指针和数组导致的 • 黑客攻击服务器利用的bug绝大部分都是指针和数组造成的

  6. 内容提要 • 指针的概念; • 难点:对指针数据类型的理解 • 数组的下标法引用和指针法引用; • 难点:二维数组的地址和指针概念 • 利用字符指针存取字符串; • 难点:字符数组和字符指针的区别与联系 • 指针数组应用; • 难点:指向数组的指针与指针数组的区别

  7. 关于指针的原则 • 学习原则 • 一定要学会 • 其实通常的应用很简单 • 与使用变量几乎无异 • 使用原则 • 永远要清楚每个指针指向了哪里 • 永远要清楚指针指向的位置是什么

  8. 8. 1 地址和指针的概念 • 内存 • 地址 • 寻址方式 • 指针变量 • 求地址和访问地址运算符

  9. 内存(Random Access Memory)与地址(Address) 8. 1 地址和指针的概念 • 计算机内的存储部件,所有指令和数据都保存在内存 • 速度快,但是掉电即失 • 可以随机访问 • 只要指名要访问的内存单元的地址,就可以立即访问到该单元 • 地址是一个无符号整数,其字长一般与主机相同 • 内存中的每个字节都有唯一的一个地址 • 地址按字节编号,按类型分配空间

  10. 8. 1 地址和指针的概念 内存中每个字节(存储单元)有一个编号-----地址 double k; int i; 内存 1 …... 2000 i 2004 k 编译时为其分配内存单元 指针:可根据变量地址找到变量的存储单元,地址起到指示性作用,一个变量的地址称为该变量的指针 …...

  11. …... …... 8. 1 地址和指针的概念 • 指针变量:专门存放变量地址的变量叫~ int *i_pointer; int i; 指针 2000 整型变量i 10 变量的内容 2004 变量i_pointer 变量的地址 2000 i_pointer=&i; 指针变量

  12. …... …... 8. 1 地址和指针的概念 • 指针变量:专门存放变量地址的变量叫~ 指针 2000 整型变量i 10 指向 2004 变量i_pointer i=10; *i_pointer=10; 2000 指针变量 • 直接访问:按变量地址存取变量值 • 间接访问:通过存放变量地址的变量去访问变量

  13. 8. 1 地址和指针的概念 保险柜A KEY1 直接访问 KEY1 保险柜B 间接访问 KEY2 KEY1 KEY2

  14. 8. 1 地址和指针的概念 • 求地址和访问地址运算 (1) 求地址运算(&) *, &互为逆运算 (2)访问地址运算(*) 对用户——“直接”访问 i=100; 对内存——“间接”访问 int *p; p=&i; *p=100; 含义:取指针所指向变量的内容 单目运算符 优先级: 2 结合性:自右向左 p-----指针变量,它的内容是地址量 *p----指针的目标变量,它的内容是数据 &p---指针变量占用内存的地址 p=&i=&*p i=*p =*&i

  15. 指针变量的定义 一般形式: 数据类型 *指针名; 8.2 变量的指针和指向变量的指针变量 <例> int *p1,*p2; float *q ; char *name; 合法标识符 指针的目标变量的数据类型 表示定义指针变量 不是‘*’运算符 注意: (1)int *p1, *p2;与 int *p1, p2; (2)指针变量名是p1,p2 ,不是*p1,*p2 (3)指针变量只能指向定义时所规定类型的变量 (4)指针变量定义后,变量值不确定,应用前必须先赋值

  16. 8.2 变量的指针和指向变量的指针变量 • 指针变量的初始化 一般形式:数据类型 *指针名=初始地址值; <例>int i; int *p=&i; <例> int i, *p; p=&i; <例>int i; int *p=&i; int *q=p; 用已初始化指针变量作初值

  17. 指针变量必须先赋地址值,再使用 <例>main( ) { int i=10,j; int *p; p=&i; j=(*p)++; printf(“%d\n”,*p); printf(“%d,%d”,i,j); } 8.2 变量的指针和指向变量的指针变量 11 11,10

  18. 8.2 变量的指针和指向变量的指针变量 p指向内存 第10号 存储单元 • 指针变量必须先赋地址值,再使用 <例>main( ) { int i=10; int *p; p=i; *p=20; printf(“%d,%d\n”,i,*p); } TC编译系统下,运行结果:10,20 VC编译系统下,运行时会报错:

  19. ..... 2000 整型变量i 10 2004 ..... 指针变量p 2008 ..... • 指针变量必须先赋地址值,再使用 8.2 变量的指针和指向变量的指针变量 <例>main( ) { int i=10,k; int *p; p=&k; *p=i; printf(“%d”,*p); } <例>main( ) { int i=10; int *p; *p=i; printf(“%d”,*p); } 随机值 运行时报错

  20. …... 整型变量a f86 10 f87 指针变量pa f88 f86 f89 f8a f8b f8c …... <例> 指针的概念 8.2 变量的指针和指向变量的指针变量 运行结果: a:10 *pa:10 &a:f86(hex) pa:f86(hex) &pa:f88(hex) main() { short a; short *pa=&a; a=10; printf("a:%d\n",a); printf("*pa:%d\n",*pa); printf("&a:%x(hex)\n",&a); printf("pa:%x(hex)\n",pa); printf("&pa:%x(hex)\n",&pa); }

  21. 8.2.3 指针变量作为函数参数 • 实参和形参结合的两种方式 • 值传递方式 • 方式:函数调用时,为形参分配单元,并将实参的值复制到形参中;调用结束,形参单元被释放,实参单元仍保留并维持原值 • 特点: • 形参与实参占用不同的内存单元 • 单向传递

  22. 实参和形参结合的两种方式 • 值传递方式 swap(int x,int y) { int temp; temp=x; x=y; y=temp; } main() { int a,b; scanf("%d,%d",&a,&b); if(a<b) swap(a,b); printf("\n%d,%d\n",a,b); } <例> 将数从大到小输出

  23. <例> 交互两数。 实参和形参结合的两种方式 • 值传递方式 swap(int x,int y) { int temp; temp=x; x=y; y=temp; } main() { int a,b; a=5;b=9; if(a<b) swap(a,b); printf("\n%d,%d\n",a,b); } 9 9 a b 5 5 5 9 x y temp 形参、实参是简单变量时,单向值传递,实参a和b的值没有交换。

  24. 8.2.3 指针变量作为函数参数 • 实参和形参结合的两种方式 • 值传递方式 • 地址传递方式 • 方式:函数调用时,将数据的存储地址作为参数传递给形参 • 特点: • 形参与实参占用同样的存储单元 • “双向”传递 • 实参和形参必须是地址常量或变量 数组名或指针

  25. 地址传递 <例>交换两数。 swap(int *q1, int *q2) { int q; q=*q1; *q1=*q2; *q2=q; } main() { int a,b; int *p1,*p2; a=5; b=9; p1=&a; p2=&b; if(a<b)swap(p1,p2); printf("\n%d,%d\n",a,b); } p2 a b p1 5 &b 9 9 &a 5 q2 q1 &b &a q 形参、实参是指针变量时,实现“双向”传递 5 实参本身没有改变,改变的是实参所指向的对象 运行结果:9,5

  26. <例>交换两数。 swap(int *q1, int *q2) { int *q; q=q1; q1=q2; q2=q; } main() { int a,b; int *p1,*2; a=5; b=9; p1=&a; p2=&b; if(a<b)swap(p1,p2); printf("\n%d,%d\n",a,b); } p2 a b p1 5 &b 9 &a q2 q1 &b &a &a &b q &a 改变的是形参本身,并不能影响实参,当然也不能影响实参所指向的对象 运行结果:5,9

  27. <例>交换两数。 swap(int *q1, int *q2) { int *q; *q=*q1; *q1=*q2; *q2=*q; } main() { int a,b; int *p1,*2; a=5; b=9; p1=&a; p2=&b; if(a<b)swap(p1,p2); printf("\n%d,%d\n",a,b); } p2 a b p1 5 &b 9 9 5 &a q2 q1 q 但是,指针q未赋值,就取*运算,*q=….. 危险 运行结果:9,5

  28. (1) swap(int *q1, int *q2) { int *q; q=q1; q1=q2; q2=q; } (2) swap(int *q1, int *q2) { int *q; *q=*q1; *q1=*q2; *q2=*q; } 或者更改:int k, *q=&k; × × main() { int a,b; int *p1,*p2; a=5; b=9; p1=&a; p2=&b; if(a<b)swap(p1,p2); printf("\n%d,%d\n",a,b); } (3) swap(int *q1, int *q2) { int q; q=*q1; *q1=*q2; *q2=q; } 

  29. <练>09国二考题 程序运行后的输出结果是 void fun(int *a, int *b) { int *c; c=a;a=b; b=c; } main() { int x=3, y=5, *p=&x, *q=&y; fun(p,q); printf("%d,%d", *p, *q); fun(&x, &y); printf("%d,%d\n", *p, *q); } A. 3,5,5,3 B.3,5,3,5 C. 5,3,3,5 D. 5,3,5,3

  30. <练>09国二考题 程序运行后的输出结果是 void f(int *p, int *q); main() { int m=1, n=2, *r=&m; f(r, &n); printf("%d,%d", m,n); } void f(int *p, int *q) { p=p+1; *q=*q+1; } A. 1,3 B. 2,3 C. 1,4 D. 1,2

  31. <练>09国二考题 程序运行后的输出结果是 int b=2; int fun(int *k) { b=*k+b; return (b); } main() { int a[10]={1,2,3,4,5,6,7,8},i; for(i=2;i<4;i++) { b=fun(&a[i])+b; printf("%d", b); } } A. 10 12 B. 8 10 C. 10 28 D. 10 16

  32. 8.2 通过指针引用数组 p .... a[0] a[1] a[2] a[3] ... a[9] 通过指针来引用数组元素 • 指向数组元素的指针变量 整型指针 p &a[0] a 例 int a[10]; int *p; p=&a[0]; // p=a; 例 int a[10]; int *p=&a[0]; // int *p=a; 数组名是表示数组首地址的地址常量

  33. p .... a[0] a[1] a[2] a[3] ... a[9] 8.2 通过指针引用数组 通过指针来引用数组元素 • 指向数组元素的指针变量 整型指针 p &a[0] a a[5]=10; *(a+5)=10; 四者等效 执行速度快 *(p+5)=10; p[5]=10; a[i]  p[i]  *(p+i) *(a+i)

  34. 有何感觉? 8.2 通过指针引用数组 我明白了,一维数组中p=a时,p和a都可以相互替换。 有一个地方不能替换,知道是什么地方吗? p++不能用a++替换!! 为什么? 因为a是常量,5++即5=5+1是个低级错误!

  35. p p .... a[0] a[1] a[2] a[3] ... a[9] 8.2 通过指针引用数组 通过指针来引用数组元素 • 指向数组元素的指针变量 整型指针 p &a[0] a <例> int a[10]; int *p=&a[2]; p++; *p=1; 1 <例>int a[10]; int *p1=&a[2]; int *p2=&a[5]; 则:p2-p1=?

  36. 8.2 通过指针引用数组 <例>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]  数组名是地址常量 p++,p-- () a++,a-- () a+1, *(a+2) ()

  37. a 0 5 1 8 p p 2 7 3 6 4 2 5 7 6 3 8.2 通过指针引用数组 <例>注意指针变量的运算 例 void main() { int a []={5,8,7,6,2,7,3}; int y,*p=&a[1]; y=(*--p)++; printf(“%d ”,y); printf(“%d”,a[0]); } 6 输出:5 6

  38. 输入输出数组的全部元素 8.2 通过指针引用数组 方法2:指针法 方法1:下标法 main() { int a[10]; int i; for (i=0; i<10; i++) scanf("%d", &a[i] ); for (i=0; i<10; i++) printf("%d ", a[i] ); } main() { int a[10]; int *p, i; for (p=a; p<(a+10); p++) scanf("%d", p); for (p=a; p<(a+10); p++) printf("%d ", *p); } p指向了哪里? a+i *(a+i)

  39. 0 1 2 3 4 5 6 7 8 9 i j j j i j i i i j 3 7 9 11 0 6 7 5 4 2 2 4 5 7 6 0 11 9 7 3 8.3.4 用数组名作函数参数 void convert(int x[], int n) { int t,i=0,j=n-1; for(i=0;i<j; i++,j--) { t=x[i]; x[i]=x[j]; x[j]=t; } } main() {int i,a[10]={3,7,9,11,0,6,7,5,4,2}; convert(a,10); printf("reverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } <例>将数组a中的n个整数按相反顺序存放 x a

  40. a a p q a数组 p 2 a[0] 3 p p p 4 a[1] 7 5 a[2] 9 7 a[3] 11 6 a[4] 0 0 6 a[5] 11 7 a[6] 9 5 a[7] q q q 7 4 a[8] q q 2 3 a[9] 8.3.4 用数组名作函数参数 void convert(int *p, int n) { int t,*q; for(q=p+n-1; p<q; p++, q--) { t=*p; *p=*q; *q=t; } } main() { int i,a[10]={3,7,9,11,0,6,7,5,4,2}; convert(a,10); printf("reverted:\n"); for(i=0;i<10;i++) printf("%d,",a[i]); printf("\n"); } p<q;

  41. int a[10]; a 8.3.5 通过指针引用多维数组 • 一维数组的理解: 若定义int a[10]; 总结: (1)数组名a表示数组的首地址,即a[0]的地址; (2)数组名a是地址常量 (3)a+i是元素a[i]的地址 (4)a[i]  *(a+i)

  42. 17 1 9 19 11 3 21 13 5 23 15 7 8.3.5 通过指针引用多维数组 • 二维数组的理解: 若定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a={a[0], a[1], a[2]}; a= 可以理解为:二维数组是一个特别的一维数组 请高度集中

  43. 17 1 9 19 11 3 21 13 5 15 23 7 8.3.5 通过指针引用多维数组 • 二维数组的理解: 若定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a={a[0], a[1], a[2]}; a[0] a[1] a= 其中,a[0], a[1], a[2]分别又是一个一维数组的数组名 a[2] 请高度集中

  44. a[0][0] 0 6 a[0][1] 7 1 a[0] a[0][2] 8 2 a[0][3] 9 3 a[1][0] 10 4 a[1][1] 5 11 a[1] a[1][2] a[1][3] a[2][0] a[2][1] a[2] a[2][2] a[2][3] 8.3.5 通过指针引用多维数组 • 二维数组的理解: 若定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a={a[0], a[1], a[2]}; 等价 *a *(a+1) 等价 a= *(a+2) 等价 数组名是该数组的首地址

  45. a[0][0] 0 6 a[0][1] 7 1 a[0] a[0][2] 8 2 a[0][3] 9 3 a[1][0] 10 4 a[1][1] 11 5 a[1] a[1][2] a[1][3] a[2][0] a[2][1] a[2] a[2][2] a[2][3] 8.3.5 通过指针引用多维数组 • 二维数组的理解: 若定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a={a[0], a[1], a[2]}; *a a 由于可以将a数组理解为一个特别的一维数组,故: *(a+1) a+1 *(a+2) a+2

  46. a[0][0] 0 6 a[0][1] 7 1 a[0] a[0][2] 8 2 a[0][3] 9 3 a[1][0] 10 4 a[1][1] 11 5 a[1] a[1][2] a[1][3] a[2][0] a[2][1] a[2] a[2][2] a[2][3] 8.3.5 通过指针引用多维数组 • 二维数组的理解: 若定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a={a[0], a[1], a[2]}; a *a 对于二维数组,可以得到这样一个结论: *(a+1) a+1 a+i a[i]  *(a+i) a+i+j a[i]+j  *(a+i)+j *(a+2) a+2 但是

  47. a[0][0] 6 0 a[0][1] 7 1 a[0] a[0][2] 2 8 a[0][3] 9 3 a[1][0] 4 10 a[1][1] 11 5 a[1] a[1][2] a[1][3] a[2][0] a[2][1] a[2] a[2][2] a[2][3] 8.3.5 通过指针引用多维数组 • 二维数组的理解: 若定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a={a[0], a[1], a[2]}; a *a 二维数组相当于二级指针! “加1”逐个数组元素移动 *(a+1) a+i: 行指针 *(a+i) : 列指针 a+1 *(a+2) a+2 “加1”跨越一个一维数组

  48. a[0][0] 0 6 a[0][1] 7 1 a[0] a[0][2] 8 2 a[0][3] 9 3 a[1][0] 10 4 a[1][1] 11 5 a[1] a[1][2] a[1][3] a[2][0] a[2][1] a[2] a[2][2] a[2][3] 8.3.5 通过指针引用多维数组 • 二维数组的理解: 若定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}}; a={a[0], a[1], a[2]}; a *a 二维数组相当于二级指针! *(a+1) a+i: 行指针 *(a+i) : 列指针 a+1 a+i=*(a+i)=&a[i][0] *(a+2) a+2 **(a+i): a[i][0]

  49. a *a a[0] a[0][0] a[0][1] a[0][2] a[0][3] *(a+1) a+1 a[1] a[1][0] a[1][1] a[1][2] a[1][3] *(a+2) a+2 a[2] a[2][0] a[2][1] a[2][2] a[2][3] 8.3.5 通过指针引用多维数组 • 二维数组的理解: 总结: int a[3][4]; a+i:行指针 *(a+i)  a[i] :列指针 a+i+j: &a[i+j][0] *(a+i) +j : &a[i][j] *(*(a+i)+j): a[i][j]

  50. 2000 2002 2004 2006 a a[0]+2 a[0]+1 a[0] a[0]+3 a+1 (2000) a[0][0] a[0][1] a[0][2] a[0][3] a+2 (2008) a[1][0] a[1][1] a[1][2] a[1][3] (2016) a[2][3] a[2][0] a[2][1] a[2][2] 8.3.5 通过指针引用多维数组 (1)行指针:a+i &a[i],指向行,第i行首地址。 (2)列指针:a[i]*(a+i)  &a[i][0],为第i行第0列的元素地址,指向列。

More Related