810 likes | 918 Views
第八章. 使用指针. 为什么引入指针. 指针为函数提供修改变量值的手段 指针为 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++)
E N D
第八章 使用指针
为什么引入指针 • 指针为函数提供修改变量值的手段 • 指针为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++) { scanf(“%d”, &a[i]); sum+=a[i]; } ave=sum/num; printf(“average:%d”, ave); } 数组固定大小 *a 浪费内存空间 a=(int *)malloc(num*sizeof(int)); 动态内存
指针的故事 • 铁杆C/C++程序员最挚爱的武器:指针 • 指针造就了C/C++的高效和强大 • 很多不可能的任务由指针完成 • main() • {char*a="main(){char*a=%c%s%c;printf(a,34,a,34);}"; • printf(a,34,a,34);}
指针的故事 • “该程序执行了非法操作,即将关闭” • 这种错误几乎全是由指针和数组导致的 • 黑客攻击服务器利用的bug绝大部分都是指针和数组造成的
内容提要 • 指针的概念; • 难点:对指针数据类型的理解 • 数组的下标法引用和指针法引用; • 难点:二维数组的地址和指针概念 • 利用字符指针存取字符串; • 难点:字符数组和字符指针的区别与联系 • 指针数组应用; • 难点:指向数组的指针与指针数组的区别
关于指针的原则 • 学习原则 • 一定要学会 • 其实通常的应用很简单 • 与使用变量几乎无异 • 使用原则 • 永远要清楚每个指针指向了哪里 • 永远要清楚指针指向的位置是什么
8. 1 地址和指针的概念 • 内存 • 地址 • 寻址方式 • 指针变量 • 求地址和访问地址运算符
内存(Random Access Memory)与地址(Address) 8. 1 地址和指针的概念 • 计算机内的存储部件,所有指令和数据都保存在内存 • 速度快,但是掉电即失 • 可以随机访问 • 只要指名要访问的内存单元的地址,就可以立即访问到该单元 • 地址是一个无符号整数,其字长一般与主机相同 • 内存中的每个字节都有唯一的一个地址 • 地址按字节编号,按类型分配空间
8. 1 地址和指针的概念 内存中每个字节(存储单元)有一个编号-----地址 double k; int i; 内存 1 …... 2000 i 2004 k 编译时为其分配内存单元 指针:可根据变量地址找到变量的存储单元,地址起到指示性作用,一个变量的地址称为该变量的指针 …...
…... …... 8. 1 地址和指针的概念 • 指针变量:专门存放变量地址的变量叫~ int *i_pointer; int i; 指针 2000 整型变量i 10 变量的内容 2004 变量i_pointer 变量的地址 2000 i_pointer=&i; 指针变量
…... …... 8. 1 地址和指针的概念 • 指针变量:专门存放变量地址的变量叫~ 指针 2000 整型变量i 10 指向 2004 变量i_pointer i=10; *i_pointer=10; 2000 指针变量 • 直接访问:按变量地址存取变量值 • 间接访问:通过存放变量地址的变量去访问变量
8. 1 地址和指针的概念 保险柜A KEY1 直接访问 KEY1 保险柜B 间接访问 KEY2 KEY1 KEY2
8. 1 地址和指针的概念 • 求地址和访问地址运算 (1) 求地址运算(&) *, &互为逆运算 (2)访问地址运算(*) 对用户——“直接”访问 i=100; 对内存——“间接”访问 int *p; p=&i; *p=100; 含义:取指针所指向变量的内容 单目运算符 优先级: 2 结合性:自右向左 p-----指针变量,它的内容是地址量 *p----指针的目标变量,它的内容是数据 &p---指针变量占用内存的地址 p=&i=&*p i=*p =*&i
指针变量的定义 一般形式: 数据类型 *指针名; 8.2 变量的指针和指向变量的指针变量 <例> int *p1,*p2; float *q ; char *name; 合法标识符 指针的目标变量的数据类型 表示定义指针变量 不是‘*’运算符 注意: (1)int *p1, *p2;与 int *p1, p2; (2)指针变量名是p1,p2 ,不是*p1,*p2 (3)指针变量只能指向定义时所规定类型的变量 (4)指针变量定义后,变量值不确定,应用前必须先赋值
8.2 变量的指针和指向变量的指针变量 • 指针变量的初始化 一般形式:数据类型 *指针名=初始地址值; <例>int i; int *p=&i; <例> int i, *p; p=&i; <例>int i; int *p=&i; int *q=p; 用已初始化指针变量作初值
指针变量必须先赋地址值,再使用 <例>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
8.2 变量的指针和指向变量的指针变量 p指向内存 第10号 存储单元 • 指针变量必须先赋地址值,再使用 <例>main( ) { int i=10; int *p; p=i; *p=20; printf(“%d,%d\n”,i,*p); } TC编译系统下,运行结果:10,20 VC编译系统下,运行时会报错:
..... 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); } 随机值 运行时报错
…... 整型变量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); }
8.2.3 指针变量作为函数参数 • 实参和形参结合的两种方式 • 值传递方式 • 方式:函数调用时,为形参分配单元,并将实参的值复制到形参中;调用结束,形参单元被释放,实参单元仍保留并维持原值 • 特点: • 形参与实参占用不同的内存单元 • 单向传递
实参和形参结合的两种方式 • 值传递方式 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); } <例> 将数从大到小输出
<例> 交互两数。 实参和形参结合的两种方式 • 值传递方式 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的值没有交换。
8.2.3 指针变量作为函数参数 • 实参和形参结合的两种方式 • 值传递方式 • 地址传递方式 • 方式:函数调用时,将数据的存储地址作为参数传递给形参 • 特点: • 形参与实参占用同样的存储单元 • “双向”传递 • 实参和形参必须是地址常量或变量 数组名或指针
地址传递 <例>交换两数。 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
<例>交换两数。 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
<例>交换两数。 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
(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; }
<练>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
<练>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
<练>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
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; 数组名是表示数组首地址的地址常量
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)
有何感觉? 8.2 通过指针引用数组 我明白了,一维数组中p=a时,p和a都可以相互替换。 有一个地方不能替换,知道是什么地方吗? p++不能用a++替换!! 为什么? 因为a是常量,5++即5=5+1是个低级错误!
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=?
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) ()
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
输入输出数组的全部元素 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)
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
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;
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)
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= 可以理解为:二维数组是一个特别的一维数组 请高度集中
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] 请高度集中
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) 等价 数组名是该数组的首地址
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
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 但是
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”跨越一个一维数组
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]
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]
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列的元素地址,指向列。