第9章指针本章的学习重点:◆指针的定义◆指针与地址的关系◆指针与数组◆指针与函数◆指针与字符串◆指针与二维数组◆内存分配9.1指针的引入C语言中,有时希望通过某个参数去操控其他变量或者复杂结构类型,为满足这一需求,便引入了指针。使用指针变量可以构造和查找各种数据类型和数据结构,能够直接查找并存取内存中所存储的数据,从而编出精练而高效的程序。9.1.1指针的定义C语言中,为了便于直接与硬件进行交互,有时需要在程序中访问这些内存单元,通常把能够标示内存单元的地址称为指针。C语言中通常使用指针变量来实现指针的应用。定义指针变量的格式为:类型说明符*变量名;或者将*号放于变量名前:类型说明符*变量名;例如:int*pointer1;//定义一个指向int型变量的指针float*poniter2;//定义一个指向float型变量的指针char*_charac;//定义一个指向char型变量的指针9.1.2指针的引用指针的引用是指如何对指针变量赋值以及如何使用指针变量。范例9.1VariableAddressPrint.c显示一个变量在计算机里的位置,即它在内存中的物理地址,使用取地址符&获取变量的地址,并打印在屏幕上。范例9.2PointerAndAddress.c使一个指针变量指向一个普通变量,分别将指针和变量的值显示出来,以对比他们的关系与差别。变量me和指针point_you的映射关系:范例9.3PointerChangeVariableValue.c利用指针修改变量的值,实现方法为首先使指针指向要修改的变量,然后使用解引用操作符*修改变量的值,使变量的值增1。me&me0point_you9.2指针和地址指针和地址即有联系又有区别,指针变量可以存储内存地址,但指针又不单单是固定地址的储藏室。在程序运行过程中,指针变量的值是可以任意变化的,也就是说指针在程序运行过程中具有动态读取内存地址的功能。9.2.1指针和地址的关系地址是对计算机内存进行的连续编号,指针是地址的操作者和使用者。如图所示为某段计算机内存示意图。1.指针与变量的作用域指针与其所指变量的作用域不同,将导致程序对指针的引用出现错误。范例9.4VariableFunctionArea.c指针指向不同类型的变量将产生不同的结果,将short型的指针指向一个int型变量,打印出指针值、变量地址值和变量值,分析打印的结果。ff1000aaccbb100110021009......地址数据9.2.1指针和地址的关系2.强制类型转换对指针作用域的影响当使用指针和变量地址索引同一内存区域的数据读取时,要使用强制类型转换来查找内存数据。将范例9.4中程序第7行做如下修改:printf(a=%x,*b=%x,&a=%x,b=%x\n,a,*(int*)b,&a,b);内存执行状态如图所示:3.强制类型转换对变量作用域的影响将范例9.4中程序第7行作如下修改:printf(a=%x,*&a=%x,*(short*)&a=%x,&a=%x,b=%x\n,a,*&a,*(short*)&a,&a,b);执行状态如右图所示:b785634120x0012ff7c&ab作用域(int*)b作用域*(int*)b0x12345678b785634120x0012ff7c&a*(short*)&a(short*)&a作用域0x5678&a作用域9.2.1指针和地址的关系4.使用指针修改内存单元数据指针和地址可以读取内存单元部分数据,也可以使用指针或取地址操作更改内存单元的部分数据。C语言的指针和地址具备对内存单元的写权限。范例9.5PointerChangeMemoryValue.c:演示使用指针修改内存数据,将指针指向非该指针类型的变量,通过符号*对指针所指内存进行读写,将读写前后的数值打印到屏幕上。变量b的执行状态如图所示:变量b的内存结构如图所示:b785634120x0012ff7c&a*b=0x6688b作用域*b=0x567888663412b作用域*b=0x66889.2.2指针和地址的区别指针和地址的区别在于:地址是一个表征计算机系统内存单元的常量,其值是不能变化的,而指针则是可以等于任何地址值的变量。范例9.6PoniterAdd1Self.c:指针变量的增和减不同于一般变量,它根据指针类型的不同而不同,设置一个short型指针,使其指向int型变量,改变该指针的值,查看它所指的内存区域中所存的数值。指针ap的内存变化如图所示:b785634120x0012ff7c&a*b=(short*)apb作用域ap作用域78563412b作用域ap0x0012ff7c&ab++ap作用域b0x0012ff7e9.2.2指针和地址的区别对指针变量作增减运算,其值的变化与该变量的类型有关,下表列举了几种常用指针类型作自增1运算后其值的变化。指针类型初始值增1运算后减1运算后内存字节跨度char0x0012ff7c0x0012ff7d0x0012ff7b1short0x0012ff7c0x0012ff7e0x0012ff7a2int0x0012ff7c0x0012ff800x0012ff784long0x0012ff7c0x0012ff800x0012ff784float0x0012ff7c0x0012ff800x0012ff784double0x0012ff7c0x0012ff840x0012ff7489.2.3void指针和空指针空指针(NULL)是不指向任何有效地址的指针,即空指针不指向任何变量地址。将指针变量赋值为NULL即指该指针变量为空指针,如定义指针变量语句:short*b=NULL;C语言中,NULL是标准库文件中定义的宏(宏的概念请参阅第12章),定义如下:#defineNULL((void*)0)1.void指针void是一个特殊的类型,其字面解释是“无类型”例如有下面定义和引用:void*a=NULL;//定义void型指针a,并赋处置NULLfloatb=0;//定义float型变量ba=&b;//使a指向b上述语句对指针a的定义和引用是正确的,C语言不允许使用void类型指针直接操作其所指内存单元的数据。9.2.3void指针和空指针范例9.7VoidPointer.c:void型指针应用广泛,它的特性是可以指向任何类型的变量。但是,void指针不能做增减运算,设置一个void型变量,验证其特性。2.空指针空指针即赋值为NULL的指针,NULL被定义为((void*)0)。其实,NULL的值是可以通过屏幕打印输出的,例如使用下面语句输出NULL:printf(“NULL=%d\n”,NULL);执行上述语句程序输出:NULL=09.3指针与数组虽然使用指针与使用数组都可以读写内存数据,但相比而言,指针则显得更加灵活和实用。9.3.1指针与数组首地址指针变量用于存储某内存单元的地址,而数组名也代表某内存变量的地址。C语言中,指针与数组的主要区别在于:指针变量是一种变量类型,其值可随意变化,而数组名则是一个常量,其值不能变化。范例9.8PointerPointArray.c:使一个指针指向数组首地址,利用指针增减的性质引用数组内的元素,并将首地址的值打印出来,验证两者是否相同。数组b的内存逻辑结构及计算机内存分配如图所示:a000000000x0012ff680x0012ff690x0012ff6a0x0012ff6b010000000x0012ff6c0x0012ff6d0x0012ff6e0x0012ff6f......040000000x0012ff780x0012ff790x0012ff7a0x0012ff7bb9.3.2指针与数组名的区别范例9.9PointerPointArrayFistAddr.c:数组名和指针类似,都可以表示内存中的某个地址,设置一个指针,通过这个指针引用已知的一个数组。范例9.10ArrayAddrasPointer.c:已知数组b中存放5名学生成绩,使用指针读取其中一名学生的成绩,并通过解引用操作符修改该学生的成绩值。指针a、c与数组b在内存中的存储示意图如下图所示:a5a005c000x0012ff6c0x0012ff6d0x0012ff6e0x0012ff6f3c0058000x0012ff700x0012ff710x0012ff720x0012ff734c000x0012ff740x0012ff75b*a*b*(b+3)c*c实训9.1——指针转换数组中字母大小写编写程序,使用指针判断维数为N的字符数组array中元素的值,使用数组地址偏移将元素中大写字母转为小写字母,并计算被转换元素个数。数组array定义如下:chararray[N]={'a','B','N','8','M','D',',','*','0','X','m','4','y','Z','!','t','U','T','k','@'};1.需求分析:需求1:使用指针判断数组array中元素的值需求2:使用数组地址偏移转换数组中为大写字母元素的值需求3:计算被转换元素的个数需求4:打印array实训9.1——指针转换数组中字母大小写2.技术应用对于需求1,使用指针a引用数组中元素,并依次读取数组元素的值。通过if语句判断是否属于大写字母,实现语句如下:if(('A'=*a)&&(*a='Z'))对于需求2,使用数组首地址array偏移,采用与指针类似的引用方式,修改数组中元素的值,实现语句如下:*(array+loop)=*a+'a'-'A';对于需求3,设置变量,对符合要求的元素,变量做递增运算,实现语句如下:if(('A'=*a)&&(*a='Z')){*(array+loop)=*a+'a'-'A';change_sum++;}对于需求4,转换前后分别打印字符数组,每5个字符一行,实现语句如下:for(loop=0;loopN;loop++){if(0==(loop)%5){printf(\n);}printf(%-4c,*a++);}9.4指针与函数指针与函数的关系主要包括:指针作函数参数、指针作函数返回值以及函数指针等。由于指针的灵活性以及函数的多样性和局部性,使得在程序开发中指针与函数的结合运用异常频繁。因此,掌握好指针与函数结合编程的知识,是一个好的程序员所具备的重要技能之一。9.4.1指针作函数参数指针作函数参数主要分为两种类型,一种是作函数形参,另一种是作函数实参。按照函数的级别可以分为:指针作子函数参数和指针作主函数参数。指针作函数形参的定义方式为:类型说明符函数名(类型说明符1*指针名1,类型说明符2*指针名2,…)指针作主函数参数的定义方式为:main(intargc,char*argv[])1.指针作子函数参数范例9.11PointerChangeTwovairableVal.c:在子函数中实现主函数中两个整型变量值的互换。指针可以读写内存中的数据,通过指针,在子函数中改变所指变量的值,实现交换主函数变量值的功能。9.4.1指针作函数参数函数调用时的值传递过程:(1)对于函数swap1(2)对于函数swap2(3)对于函数swap31234xyswap1(x,y)5678mn12345678tempmn567812341234xyswap2(&x,&y)5678pmpn0x10000x1010temppmpn0x10000x10100x10000x1010&x&y1234xyswap3(&x,&y)5678pmtemppn0x10000x1010&x&y*pm*pn5678xy1234pmpn0x10000x1010&x&y*pm*pn9.4.1指针作函数参数2.数组作函数参数数组作函数实参时,可以使用相同类型的数组作形参,也可以使用指针作形参。同理,当使用指针作实参时,可以使用相同类型指针作形参,也可以使用数组作形参,即数组和指针可以等价使用。范例9.12Pointe