《2022年C语言指针完全指南 .pdf》由会员分享,可在线阅读,更多相关《2022年C语言指针完全指南 .pdf(12页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、C语言指针完全指南(上)2008-05-30 12:49 1、目标掌握指针的声明、定义和使用;掌握动态内存分配的方法;掌握指针及数组名之间的差别;掌握函数的值传递机制以及函数返回多值的方法。2、预备知识C 语言普通变量的声明和使用,包括局部变量和全局变量的特点;C 语言中数组的声明及使用;C 语言中函数的声明、定义及调用。3、问题的提出3.1、我们知道 C 语言中的函数调用过程中,函数参数是通过值传递来实现的。比如说有如下的函数:void Swap(int nLeft,int nRight)int nTemp=nLeft;nLeft=nRight;nRight=nTemp;该函数的目的是希望把
2、两个整型数通过函数的两个参数传递进来,并交换这两个数。我们也许会像下面这样调用这个函数:int nLeft=100;int nRight=50;Swap(nLeft,nRight);printf(“nLeft=%d,nRight=%dn”,nLeft,nRight);在最后一个输出的语句中,我们期望它输出的是nLeft=50,nRight=100,但是实际上输出的却是 nLeft=100,nRight=50,也就是函数的调用并没有对这两个变量的值起任何作用。造成这样结果的原因就是因为C 语言的函数中采用的是值传递的机制。调用过程是这样的,在函数调用前的两个变量nLeft、nRight(对函数来
3、说是全局变量)按顺序赋值给函数的两个参数nLeft、nRight(对函数来说是局部变量),在函数内部操作的过程中使用的两个变量nLeft、nRight 正是该函数的两个参数(即局部变量),而与函数调用前声明的两个变量(即全局变量)没有关系。在函数调用结束后但是返回前,函数两个局部变量的值的确是交换了(我们可以在其中插入一条输出语句来验证),但是在函数返回后,函数的局部变量被销毁,其值再也找不到。在整个函数的执行过程中,两个全局变量只起到了给函数两个参数赋初值的作用,函数的执行期间也没有修改这两个全局变量的值,所以得出的是上述的结果。那么如果要实现这样的功能的时候该怎么办呢?3.2、C 语言的函
4、数声明的时候可以声明具有一个返回值,函数返回的时候可以通过 return语句返回一个值给调用者。比如下面的函数返回一个布尔值以表明函数的执行成功与否:bool ReadFile()bool bSuccess=false;.名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 12 页 -./根据其中执行结果修改bSuccess的值.return bSuccess;这个函数的目标是从文件中读内容,返回值表明是否从文件中读出了内容。然而,在函数声明的过程中不能把函数声明为返回多个值的函数,如果一个函数需要返回多个值的时候,比如说我既要知道函数执行成功与否,还要知道执行成功后从文件中读了多少
5、内容,且这些内容是什么,那该怎么办呢?3.3、如果我们在写一个比较通用一些的程序,这个程序能完成一个班学生期末成绩的录入、输出以及排序等的功能。因为要求这是一个比较通用的程序,也就是说这个程序究竟是为哪个班所用是不知道的,但不管它为哪个班所用,程序都应该能正确在执行它应该完成的功能。一个简单的问题是,每个班的人数是不一样的,那么怎么知道需要多少的空间来保存该班的数据呢?我们当然可以声明一个很大的数组,但是究竟需要多大才是合适呢?如果有一个班的人数刚好就比你声明的这个数组要大,那么程序就不能正常工作了。4、解决办法类似的问题还有很多。针对这样的一些问题,C 语言提供了一个解决办法,就是用指针。4
6、.1、什么是指针简单地说,指针就是地址。这里的地址是指的内存地址,但是与我们日常生活中的地址很相似,它说明了某一个对象(对程序来说是数据,对生活中的对象可以是人,也可以物等)所在的地方。我们通常会向某人询问他的住址,然后把这个地址记录在纸上。在程序的世界里,也可以找到这样的模型。如下面的程序段所示:1int nSomething=100;2int*pSomethingAddress=&nSomething;其中 1 声明了一个整型变量nSomething并赋初值 100,2 声明了一个整型指针变量 pSomethingAddress,并让指向 nSomething。与现实世界模型相对应的计算机
7、世界模型是:nSomething相当于一个人所在的房子,其值 100 相当于这个人,其地址&nSomething 就是这个房子的地址,变量pSomethingAddress相当于记录地址的纸,其值(现在是&nSomething,它是由程序动态确定的)就是记录在纸上的地址。我们可以利用下图来理解:4.2、指针变量的声明在 C 语言中,声明一个指针变量与声明其它的变量相似,只需要在对应的变量类型后面加一个“”号。如声明一个整型指针变量如前面所述。你可以声明任何类型的指针变量,包括C 语言内置的变量类型和自定义类型,如结构、联合或枚举类型,声明的方法同上。假如有一个结构类型Student定义如下:s
8、truct Studentchar szName50;名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 12 页 -char szGender2;int nAge;那么声明一个 Student类型的指针变量如下:struct Student*pStudent;指针变量的命名与其它变量的命名一样是任意的,但要求符合C语言的命名习惯。一般来说,为了程序的可读性,我们总以字母p 开头来声明指针变量名,这样我们一看到这个变量的时候就知道这是一个指针变量,减少出错的可能性。在这里要说明一下,所谓某一类型的指针变量是这样理解的,该变量是一个指针,该指针指向该类型变量的内存首地址,该指针也可以为
9、空指针,即暂时(也许永远)不指向任何变量,但如果将来某一时刻为其分配变量,则它将指向该类型的变量。也可以这样想,该变量保存了一个地址(记住是地址,而不是一般的值,也许某一变量的值与该地址的值相等,但不相干),该地址是该类型的某一变量的首地址。以上只说明了指针变量的声明,还没有为该变量的赋值。习惯上我们在声明一个指针变量的同时也为其赋初值。如果我们已经明确知道该指针指向的变量,我们可以直接把该变量的地址赋给该指针变量。取得某一变量的首地址的方法是在该变量的名字前加上“”符号。这样我们就可以这样声明一个指针变量:struct Student stJordan;struct Student*pJor
10、dan=&stJordan;stJordan是 Student类型的变量,pJordan是 Student类型的指针变量,声明的同时给它赋初值指向 stJordan。如果在声明指针变量的时候我们不知道该指针该指向哪一个变量,那么我们习惯上把它置空,即NULL 值。如:int*pAccount=NULL;struct Student*pStudent=NULL;这样在以后的某一时刻引用该变量时,需要判断该指针变量是否有效,即是否已经指向属于应用程序内存空间中的该类型的某一变量或内存空间(可以是数组或动态分配的内存空间,关于动态分配内存在以下介绍)。4.3、指针变量的使用在声明了指针变量以后,就可
11、以利用该指针变量来对所指向的对象进行操纵,当然在进行操纵前必须保证所操纵的对象是有效的,即该指针不为空,否则会引起“内存访问冲突”的错误,导致程序崩溃。判断一个指针是否有效的办法是判断它是否为空指针:if(pSomePoint!=NULL)/指针有效else名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 12 页 -/指针无效然而这样的判断方法需要有一个前提保证的,即是前面所说的在声明指针时如果没有明确指向一个已知的变量,则将其置空,在不再需要使用这个指针时也将其置空。如果该指针指向一块动态分配的内存空间,在释放后也应该将其置空。在这个前提下,前面判断才是正确的。对一个有效的指针
12、,引用该指针所指向的对象的办法是在该指针变量前用“”号,如下所示:int nAccount=100;int*pAccount=&nAccount;/pAccount 指向 nAccount(*pAccount)=500;/nAccount=500(*pAccount)=(*pAccount)+500;/nAccount=1000各运算符的运算顺序按C 语言的规定,但是最好的加上括号明确指明各个运算的顺序。这里可以看到(*pAccount)与 nAccount等价。对于指向结构变量的指针的使用也相似,按以上pJordan的定义,我们可以这样来访问其结构成员:strcpy(pJordan-szNa
13、me,”Jordan”);strcpy(pJordan-szGender,”男”);pJordan-nAge=24;其中的“-”运算符表示取该结构指针所指向的结构对象的成员变量(即域)。另一种与之等价的办法是:strcpy(*pJordan).szName,”Jordan”);strcpy(*pJordan).szGender,”男”);(*pJordan).nAge=24;在这里(*pJordan)与 stJordan等价。对于指针,除了可以进行上述的各种运算以外,还可以自加(+)、自减(-)、加一个整数、减一个整数和两个指针相减等的操作。与对整型数的自加和自减操作一样,指针的自加和自减操作
14、与指针的加一和减一操作相同,自加与自减运算符出现在指针变量的前面或后面对指针变量的影响与该运算符出现在整型变量的前面或后面对整型变量的影响一样。指针变量可以与一整型数相加或相减。指针变量 p 与整型变量 n相加 p+n 表示指针变量 p 的值增加 n*sizeof(pointer_type),其中的 pointer_type表示该指针变量的类型,而 p-n 表示指针变量 p 减少 n*sizeof(pointer_type)。两个指针变量不可以相加,因为其相加的结果是没有意义的,但是相同类型的两个指针变量可以相减。两个指针变量的差的意义是:(p2-p1)*sizeof(pointer_type
15、)表示两个指针变量之间的内存空间的字节数。要注意的是,并不是任何的两个指针变量之间都可以进行相减操作。指针变量的自加、自减、与整数的加减运算以及两个指针变量的差的运算通常用于利用指针访问一片连续的内存空间的场合,比如利用指针遍历数组,给动名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,共 12 页 -态分配的内存空间赋值或从动态分配的内存空间中取值。4.4、指针与动态内存分配利用指针对静态分配变量的操纵只是指针作用很小的一部分,指针另一个很大的用处在于对动态分配内存的操纵。程序在运行时刻才能决定需要多大的内存空间来保存执行操作的数据,这时候程序需要为其分配相应大小的一块内存空间,并把
16、其首地址保存在一个指针变量中,使用完后再释放该内存空间归还系统。C 语言中动态分配内存的函数是:void*malloc(size_t size);其中的 size表示需要分配的内存空间的字节数,如果成功,返回值为指向已分配的内存空间的首地址的指针,否则为 NULL,表示没有足够的内存空间。返回值为 void 型的指针,当需要其它类型的指针时需要进行强制转换。比如需要分配一块内存空间以保存100 个 double型的数,相应的程序段是:double*pDouble=(double*)malloc(100*sizeof(double);如果一个班有 n 个学生,n是在运行时由用户输入的一个整型值,
17、则程序需要为其分配一块保存这n 个学生信息的内存空间,相应的程序段是:struct Student*pStudent=(struct Student*)malloc(n*sizeof(struct Student);分配空间的目的是为了保存数据的,那么怎样向已经分配的空间存储数据呢?办法之一是逐一访问该连续的内存空间中的对象,并为这些对象赋值。对上面的 pDouble 我们可以这样做:int i=0;double*pTemp=pDouble;srand(unsigned)time(NULL);for(;i 100;i+,pTemp+)(*pTemp)=rand();我们也可以把该连续的内存空间
18、看作是一个数组,指针变量名就为数组名。这样我们可以这样来对该连续的内存空间赋值:int i=0;srand(unsigned)time(NULL);for(;i 100;i+)pDoublei=rand();上述两个程序段完成的功能是一样的。对于结构型的变量也是一样的,只是在访问内存空间里的每一个结构对象时,需要对该结构对象的每一个域进行赋值,稍微复杂一些。取数操作与赋值操作相反,但原理是一样的。比如对已经排好序的学生信息进行输出操作,需要访问连续内存空间中的每一个Student对象,并取出每一个域值进行输出。可以利用以下的程序段:int i=0;struct Student*pTemp=pS
19、tudent;名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 12 页 -for(;i szName,pTemp-szGender,pTemp-nAge);同样的可以利用以下的程序段:int i=0;for(;i szName,”Jordan”);strcpy(pJordan-szGender,”男”);pJordan-nAge=24;其中的“-”运算符表示取该结构指针所指向的结构对象的成员变量(即域)。另一种与之等价的办法是:strcpy(*pJordan).szName,”Jordan”);strcpy(*pJordan).szGender,”男”);(*pJordan).n
20、Age=24;在这里(*pJordan)与 stJordan等价。对于指针,除了可以进行上述的各种运算以外,还可以自加(+)、自减(-)、加一个整数、减一个整数和两个指针相减等的操作。与对整型数的自加和自减操作一样,指针的自加和自减操作与指针的加一和减一操作相同,自加与自减运算符出现在指针变量的前面或后面对指针变量的影响与该运算符出现在整型变量的前面或后面对整型变量的影响一样。指针变量可以与一整型数相加或相减。指针变量p 与整型变量n 相加 p+n 表示指针变量 p 的值增加n*sizeof(pointer_type),其中的 pointer_type 表示该指针变量的类型,而p-n 表示指针
21、变量p 减少 n*sizeof(pointer_type)。两个指针变量不可以相加,因为其相加的结果是没有意义的,但是相同类型的两个指针变量可以相减。两个指针变量的差的意义是:(p2-p1)*sizeof(pointer_type)表示两个指针变量之间的内存空间的字节数。要注意的是,并不是任何的两个指针变量之间都可以进行相减操作。指针变量的自加、自减、与整数的加减运算以及两个指针变量的差的运算通常用于利用指针访问一片连续的内存空间的场合,比如利用指针遍历数组,给动态分配的内存空间赋值或从动态分配的内存空间中取值。名师资料总结-精品资料欢迎下载-名师精心整理-第 8 页,共 12 页 -4.4、
22、指针与动态内存分配利用指针对静态分配变量的操纵只是指针作用很小的一部分,指针另一个很大的用处在于对动态分配内存的操纵。程序在运行时刻才能决定需要多大的内存空间来保存执行操作的数据,这时候程序需要为其分配相应大小的一块内存空间,并把其首地址保存在一个指针变量中,使用完后再释放该内存空间归还系统。C 语言中动态分配内存的函数是:void*malloc(size_t size);其中的size 表示需要分配的内存空间的字节数,如果成功,返回值为指向已分配的内存空间的首地址的指针,否则为NULL,表示没有足够的内存空间。返回值为void 型的指针,当需要其它类型的指针时需要进行强制转换。比如需要分配一
23、块内存空间以保存100 个 double 型的数,相应的程序段是:double*pDouble=(double*)malloc(100*sizeof(double);如果一个班有n 个学生,n 是在运行时由用户输入的一个整型值,则程序需要为其分配一块保存这 n 个学生信息的内存空间,相应的程序段是:struct Student*pStudent=(struct Student*)malloc(n*sizeof(struct Student);分配空间的目的是为了保存数据的,那么怎样向已经分配的空间存储数据呢?办法之一是逐一访问该连续的内存空间中的对象,并为这些对象赋值。对上面的pDouble
24、我们可以这样做:int i=0;double*pTemp=pDouble;srand(unsigned)time(NULL);for(;i 100;i+,pTemp+)(*pTemp)=rand();我们也可以把该连续的内存空间看作是一个数组,指针变量名就为数组名。这样我们可以这样来对该连续的内存空间赋值:int i=0;srand(unsigned)time(NULL);for(;i 100;i+)pDoublei=rand();上述两个程序段完成的功能是一样的。对于结构型的变量也是一样的,只是在访问内存空间里的每一个结构对象时,需要对该结构对象的每一个域进行赋值,稍微复杂一些。取数操作与赋
25、值操作相反,但原理是一样的。比如对已经排好序的学生信息进行输出操名师资料总结-精品资料欢迎下载-名师精心整理-第 9 页,共 12 页 -作,需要访问连续内存空间中的每一个Student 对象,并取出每一个域值进行输出。可以利用以下的程序段:int i=0;struct Student*pTemp=pStudent;for(;i szName,pTemp-szGender,pTemp-nAge);同样的可以利用以下的程序段:int i=0;for(;i n;i+)printf(“%s t%d”,pStudenti.szName,pStudenti.szGender,pStudenti.nAge
26、);上述的两个程序段的功能也是一样的。当对相应的数据操作完成后,需要释放相应的内存空间,否则会发生内存泄漏。C 语言中释放内存空间的函数是:void*free(void*memblock);memblock 指向待释放的内存空间首地址。比如释放上述分配的pDouble 和 pStudent 的程序段为:free(pDouble);pDouble=NULL;free(pStudent);pStudent=NULL;一个好的习惯是在调用free 释放了内存空间后,马上把相应的指针变量置空。这不是一个必须的原则,仅仅是一个好的习惯而已。因为前面已经讨论过判断指针是否有效的办法是判断指针是否为空指针,
27、当调用free 释放了相应的内存空间后,相应指针变量的值仍然保持原来的值,它不会自动变为空,我们称处于这种状态的指针为野指针。如果我们不人为的把指针变量置为空或为它重新分配有效的内存空间,它所指向的就是无效的内存空间,但是我们判断不出来。程序员也常常忘记了这个内存空间已经被释放,在以后的某个时刻又重新利用,这常常导致程序的崩溃,而且这个原因不容易查到。养成一个好的习惯,可以大大减少使用指针出错的可能性。4.5、指针与数组指针也常常应用于数组的遍历。对于一个数组,数组名标识了该数组(也是一块连续的名师资料总结-精品资料欢迎下载-名师精心整理-第 10 页,共 12 页 -内存空间)的首地址,因此
28、数组名本身也是一个指针,但是数组名仅仅是一个指针常量,而不是一个指针变量。当用指针变量对数组进行遍历时,需要首先声明一个与数组元素相同类型的指针变量,并让其指向数组中的某一元素。当指针在数组中前后移动时,就可以访问数组中的元素。在使用的过程中,需要注意指针不要移出数组的边界。另外需要注意的是,数组名是一个指针常量,因此它不支持自加和自减操作,因为自加和自减振作会修改变量自身的值,而常量的值是不可以改变的。但是数组名可以加上某一个整数,并把结果赋给另一个指针变量。显然,为了程序的正确运行,这个整数不应该超出数组元素的个数,否则在对这个指针变量访问的过程中也会出错。数组名也可参与指针的减运算操作。
29、对数组访问的例子如下:int sgnArray20;int*pArray=sgnArray;/pArray=&sgnArray0 int*pTen=sgnArray+10;/pTen=&sgnArray10 int nCount=pTen sgnArray;/nCount=10 5、问题的解决我们利用上述的知识解决第三节提出的几个问题。对于3.1,我们把函数改写成以下的原型并重新定义:void Swap(int*pLeft,int*pRight)int nTemp=(*pLeft);(*pLeft)=(*pRight);(*pRight)=nTemp;调用该函数时也作相应的改动:int nLe
30、ft=100;int nRight=50;Swap(&nLeft,&nRight);printf(“nLeft=%d,nRight=%d”,nLeft,nRight);这样得出的结果就是所希望的结果。为什么呢?在函数调用的过程中,仍然是值传递,但是这一次函数传递的是变量的地址值,在函数内部通过“”运算符取出相应地址的内容并进行交换,达到了交换两个变量值的目的。对于 3.2 的问题,与3.1 的问题有同样的解法。当需要返回多个值的时候,一个值可以通过函数的return 语句返回,而其它的值可以通过函数的参数返回,而函数的参数应该是相应类型的指针变量。3.3 的问题需要使用动态内存分配,在程序运行时刻由用户输入一个班的人数,然后动态分配足够的内存空间以保存用户将要输入的数据。其中空间的分配、访问以及释放在4.4名师资料总结-精品资料欢迎下载-名师精心整理-第 11 页,共 12 页 -节已经讨论,在这不作详细的说明。名师资料总结-精品资料欢迎下载-名师精心整理-第 12 页,共 12 页 -