C语言详解指针和数组.ppt
再论指针和数组,附录一,预习检查,链表单元有哪几个部分组成如何申请链表单元,及释放链表单元实现单链表插入的基本语法简述一下快速排序基本理论要点,课程目标,本章概述指针与数组什么时候相同C语言为什么把数组参数当作指针C语言的多维数组,及如何创建动态数组。本章目标 掌握指针什么时候和数组相同,以为容易混淆的原因掌握多维数组的内存布局。使用指针向函数传递多维数组参数使用指针返回多维数组使用指针创建和使用动态数组重点 指针和数组混淆的原因指针传递多维数组参数难点 指针和数组混淆的原因创建和使用动态数组,本章结构,指针与数组不相同,再论指针和数组,指针数组和数组指针,指针与数组相同,函数指针和指针函数,怎样使用数组,指针运算,1 再论指针和数组,指针与数组的不相同 指针与数组的相同 怎样使用指针 指针运算 函数指针和指针函数指针数组和数组指针,1.1 指针与数组的不相同,数组和指针是如何访问的 数组访问指针数据 使声明与定义相匹配 数组和指针的其他区别,1.1.1数组和指针是如何访问的,申明区别extern int*x;声明x是个int型的指针 extern int y y是个int型数组,长度尚未确定 地址和内容的区别,1.1.1数组和指针是如何访问的,数组下标引用特点地址在编译时可知 直接进行操作 例:数组:char a9“abedefgh”;.取值:c=ai,编译器符号表具有一个地址9980,运行步骤:取i的值,将它与9980相加取地址(9980i)的内容。,图A,1.1.2 数组访问指针数据,指针访问特点必须首先在运行时取得它的当前值 间接进行操作 例:指针:char*p取值:c=*p,编译器符号表有一个符号p,它的地址为4624,运行步骤:取地址4624的内容,就是5081 取地址5081的内容。,5081,4642,图B,1.1.2 数组访问指针数据,数组访问指针特点对内存进行直接的引用转化为间接引用例:数组:char a9“abedefgh”;.取值:c=ai,编译器符号表有一个符号p,它的地址为4624,运行步骤:1.取地址4624的内容,即5081。2.取得i的值,并将它与5081相加。3.取地址508l+i的内容。,5081,4642,5081+i,图C,1.1.2 数组访问指针数据,指针访问特点char*p=“abcdefgh”;p3 dchar a=”abcdefgh”;a3 d访问特点取得符号表中P的地址,提取存储于此处的指针。把下标所表示的偏移量与指针的值相加,产生一个地址。访问上面这个地址,取得字符。,1.1.3 数组和指针的其他区别,1.2 指针与数组的相同,什么时候指针与数组相同 混淆的原因数组和指针规则为什么C语言把数组形参当作指针 数组与指针归纳总结,1.2.1 什么时候指针与数组相同,数组运用特性数组声明外部数组(external array)的声明 数组的定义 函数参数的声明 运用特性作为函数参数的数组名可以通过编译器转换为指针使用数组时,数组可以写成指针,可以互换,1.2.1 什么时候指针与数组相同,数组与指针编译器处理时是不同的 一个数组就是一个地址 一个指针就是一个地址的地址 在运行时的表示形式也是不一样的 可能产生不同的代码,1.2.2 数组和指针混淆的原因,分析:,char my _array10char*my_ptr;.j=strlen(my_array);J=strlen(my_ptr);printf(”s s”,my_ptr,my_array);,1.2.2 数组和指针混淆的原因,数组和指针是相同的规则表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针1。下标总是与指针的偏移量相同在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针,1.2.3 数组和指针规则,“表达式中的数组名”就是指针 C语言把数组下标作为指针的偏移量“作为函数参数的数组名”等同于指针,1.2.3.1“表达式中的数组名”就是指针,数组下标的引用一个指向数组的起始地址的指针加上偏移量”下标值的步长调整到数组元素的大小 整型数的长度是4个字节,那么ai+1和ai在内存中的距离就是4(而不是1)例:访问ai:int a10;int*p;Int i=2;,p=a;pi;,p=a;*(p+i);,p=a+i;*p;,访问数组第i个元素的三张方式,1.2.3.1“表达式中的数组名”就是指针,数组的引用不能用指向该数组第一个元素的指针规则数组作为sizeof()的操作数一显然此时需要的是整个数组的大小,而不是指针所指向的第一个元素的大小。使用&操作符取数组的地址。数组是一个字符串(或宽字符串)常量初始值。,1.2.3.2 C语言把数组下标作为指针的偏移量,数组访问模式分析数组访问,1.2.3.2 C语言把数组下标作为指针的偏移量,数组访问模式分析指针备选方案1,1.2.3.2 C语言把数组下标作为指针的偏移量,数组访问模式分析指针备选方案1,1.2.3.2 C语言把数组下标作为指针的偏移量,数组访问模式分析指针备选方案,1.2.4 为什么C语言把数组形参当作指针,数组运用特性数组声明外部数组(external array)的声明 数组的定义 函数参数的声明,1.2.4 为什么C语言把数组形参当作指针,出于效率的考虑?传值调用与传址调用 C语言形参特性非数组形式的数据实参均以传值形式 拷贝整个数据拷贝整个数组,在时间上还是在内存空间上的开销都非常大 所有的数组在作为参数传递时都转换为指向数组起始地址的指针,而其他的参数均采用传值调用 函数的返回值绝不能是一个函数数组,而只能是指向数组或函数的指针,1.2.4 为什么C语言把数组形参当作指针,例:,展示了对一个下标形式的数组形参进行访问所需要的几个步骤。Func(char p);c=pi Func(char*p);c=pi编译器符号表显示p可以取址,从堆栈指针sp偏移14个位置运行时步骤1:从sp偏移14个位置找到函数的活动记录,取出实参。步骤2:取i的值,并与5081相加。步骤3:取出地址(508i)的内容。,1.2.4 为什么C语言把数组形参当作指针,数组,指针实参的一般用法,1.2.5 数组与指针归纳总结,用ai这样的形式对数组进行访问总是被编译器“改写”或解释为像*(a+1)这样的指针访问。指针始终就是指针。它绝不可以改写成数组。在特定的上下文中,也就是它作为函数的参数(也只有这种情况),一个数组的声明可以看作是一个指针。作为函数参数的数组(就是在一个函数调用中)始终会被编译器修改成为指向数组第一个元素的指针。当把一个数组定义为函数的参数时,可以选择把它定义为数组,也可以定义指针。不管选择哪种方法,在函数内部事实上获得的都是一个指针。定义和声明必须匹配,1.3 怎样使用数组,多维数组向函数传递一个多维数组从函数返回一个数组,1.3.1 多维数组,多维数组特性多维数组内存布局如何分解多维数组 如何对数组进行初始化,1.3.1.1 多维数组特性,定义和引用多维数组惟一的方法就是使用数组的数组 注意:ijk 与I,j,k多维数组看作是一种向量 多维数组的定义声明一个1020的多维字符数组 char carrot1020;或者声明一种看上去更像“数组的数组”形式:typedef char vegetable20;vegetable carrot10;不论哪种情况,访问单个字符都是通过carrotij的形式,编译器在编译时会把它解析为*(*(carrot+i)+j)的形式,1.3.1.2 多维数组的内存布局,多维数组内存布局pea12的内存表示:线性存储表达式为:*(*(pea+i)+j),Pea0,Pea1,Pea1,Pea1,Pea0123,Pea12,1.3.1.3 如何分解多维数组,分解特点:多维数组是如何分解为几个单独的数组的多维数组每一个单独的数组都可以看作是一个指针 不能把一个数组赋值给另一个数组 多维数组分解int apricot235 sizeof(apricot)区域sizeof(apricoti)sizeof(apricotij)sizeof(apricotijk),1.3.1.4 如何对数组进行初始化,嵌套的花括号进行初始化多维数组如short cantaloupe25=10,12,3,4,一5,31,22,6,0,-5,;如int rhubarb3=0,0,0,1,1,1,;,1.3.1.4 如何对数组进行初始化,建立指针数组进行初始化多维数组如如,只有字符串常量才可以初始化指针数组 指针数组不能由非字符串的类型直接初始化 int*weights=1,2,3,4,5,6,7,8,9,10;,char vegetables 9=“carrot”,“celery”,“corn”,“cilantro”,“crispy fried patatoes”,char*vegetables=“carrot”,“celery”,“corn”,“cilantro”,“crispy fried patatoes”,1.3.1.4 如何对数组进行初始化,建立数组进行初始化多维数组如:,int row_1=1,2,3,4,5,-1;*一1是行结束标志*int row_2=6,7,-1;int row_3=8,9,10,-1;int*weight=row_1,row_2,row_3;,1.3.2 向函数传递一个多维数组,方法1模式:my_function(int my_array1020);特点:最简单的方法 作用最小的 例子,int a33=1,1,1,2,2,2,3,3,3;/函数定义void Func(int array33);,Main()/函数调用 Func(a33);,1.3.2 向函数传递一个多维数组,方法2模式:my_function(int my_array20);例:方法3(指针传递模式)模式:my_function(char*my_array),int a33=1,1,1,2,2,2,3,3,3;/函数定义void Func(int*array);,Main()/函数调用 Func(a);,1.3.3 从函数返回一个数组,怎样返回一个数组一个指向任何数据结构的指针 一个指向数组的指针 例:,int(*pal()20;int(*pal()20/*声明一个指向包含20个int元素的数组的指针*/int(*pear)20;pear=calloc(20,sizeof(int);if(!pear)longjmp(error,1);return pear;,阶段小节,数组在什么时候和指针相同数组与指针混淆原因是什么数组当作函数传递的好处是什么如何向一个函数传递一维数组,1.4 指针运算,什么是间接引用 最多可以使用几层指针 void指针与空指针 指针运算,1.4.1 什么是间接引用,间接引用:指向变量或内存中的对象的指针 指针就是对对象值的间接引用 一个间接引用的例子,#include Int main()int i;int*p;i=5;p=/*see FAQ XVI.4*/,1.4.2 最多可以使用几层指针,一个指针时最多可以包含几层间接引用 至少可以有12层如:最多可以使用多少层指针而不会使程序变得难读 不要使用两层以上的指针 程序运行时最多可以有几层指针 无限层,int i=0;int*ip0l=,1.4.2 最多可以使用几层指针,例:一个有无限层间接引用的循环链表,/*Would run forever if you didnt limit it to MAX*/#include struct circ_list char value 3;struct circ_list*next;struct circ_list suffixes=th,#define MAX 20main()int i=0;struct circ_list*p=suffixes;while(i value);+i;p=p-next;,1.4.3 void指针与空指针,什么是空指针 什么是void指针 NULL总是被定义为0吗,1.4.3.1 什么是空指针,空指针 并不指向任何对象指针 值是NULL,NULL可能是0,0L或(void*)0 绝对不能间接引用一个空指针,1.4.3.1 什么是空指针,空指针的用法 用空指针终止对递归数据结构的间接引用 用空指针作函数调用失败时的返回值 用空指针作警戒值,1.4.3.2 什么是void指针,void指针通用指针或泛指针 不属于任何类型 常常用作函数指针 内存操作 内存操作 例子char*strepy(charstrl,const char*str2);char*strncpy(char*strl,const char*str2,size_t n);void*memcpy(void*addrl,void*addr2,size_t n);,1.4.3.3 NULL总是被定义为0吗,NULL 与 NULL不是被定义为0,就是被定义为(void*)0,if(/*/)pNULL;else p/*something else*/;/*/if(p0),1.4.4 指针运算,两个指针可以相减吗 把一个值加到一个指针上意味着什么 两个指针可以相加吗,1.4.4.1 两个指针可以相减吗,如果两个指针向同一个数组,它们就可以相减,其为结果为两个指针之间的元素数目 如果两个指针不是指向一个数组,它们相减就没有意义 指针相减的结果是某种整类型的值 ptrdiff_t,1.4.4.1 两个指针可以相减吗,指针的相减运算,#include#include struct stuff char namel6;struct stuff array=The,quick,brown,fox,jumped,over,the,lazy,dog.,;,main()struct stuff*p0=/*see FAQ XVI.4*/,1.4.4.2 把一个值加到一个指针上意味着什么,当把一个整型值加到一个指针上后,该指针指向的位置就向前移动了一段距离 这段距离对应的字节数等于该值和该指针所指向的对象的大小的乘积,1.4.4.3 两个指针可以相加吗,两个指针是不能相加的 如:p=(p+p2)-p1;正确的语句应该是:pp+(p2-p1);对此例来说,使用下述语句更好:p+p2-p1;,1.5 函数指针和指针函数,指针函数 函数指针函数指针的用法,1.5.1 指针函数,定义:指带指针的函数,即本质是一个函数 语法:返回类型标识符*返回名称(形式参数表)函数体 特点:返回类型可以是任何基本类型和复合类型 返回一个指针变量的值 可以把整个函数看成一个变量,1.5.1 指针函数,例子:,函数定义:,#include“stdio.h”float*find();main()static float score4=60,70,80,90,56,89,34,45,34,23,56,45;float*p;int i,m;printf(Enter the number to be found:);scanf(%d,/*定义指针函数*/float*find(float(*pionter)4,int n)float*pt;pt=*(pionter+n);return(pt);,1.5.2 函数指针,定义:指向函数的指针变量 语法:数据类型标志符(*指针变量名)(参数)特点:是指针变量 指向类型为函数 可用该指针变量调用函数,1.5.2 函数指针,例子:,函数定义:,void main()int(*ptr)();int a,b,c;ptr=max;scanf(%d,%d,/*定义函数*/int max(int x,int y)return(xy?x:y);,1.6 函数指针的用法,定义函数指针类型/定义一个原型为int Fun(int a);的函数指针typedef int(*PTRFUN)(int aPara);,函数指针变量的定义 PTRFUN pFun;/pFun 为函数指针变量名int(*pFun2)(int a);/pFun2也是函数指针变量名,1.6 函数指针的用法,函数指针作为函数的参数传递 定义回调函数定义回调者函数使用回调,int CallBack(int a)return+a;,void Caller(PTRFUN cb)/void Caller(int(*cb)(int)/也可这样申明 int nPara=1;int nRet=cb(nPara);,void Test()Caller(CallBack);/直接使用回调函数 PTRFUN cb=CallBack;/int(*cb)(int);cb=CallBack;int nRet1=cb(99);/nRet1=100;,阶段小节,指针在什么时候可以实现相加减空指针与void指针指针函数的申明和赋值函数指针的传递和调用指针数组的定义和初始化,本章总结,指针与数组不相同,再论指针和数组,指针数组和数组指针,指针与数组相同,函数指针和指针函数,怎样使用数组,指针运算,本节主要讲述了指针和数组的互访及其区别,讲述了指针和数组的相同点,以及分析他们之间的混淆原因,简单讲述了多维数组,并深入讲述数组和函数的应用,深入具体的讲述指针的运算,了解指针函数和函数指针的区别,并深入讲述函数指针的用法,了解指针数组和数组指针的区别,实验项目,题目写一个排序函数,要求实用两种参数传递模式。并采用函数指针调用模式实现的排序函数,并输入排序的最终结果实验目的回顾上章节的排序算法应用;数组的参数传递和数组及指针互用操作;函数指针的实现和调用;实验分析定义排序函数,采用传指针和传数组两种方式;用typedef定义函数指针;调用函数指针;输出最终的排序结果;,