第六章 指针 指针的概念 指针的运算 指向变量的指针 指向数组的指针 指向函数的指针 二级指针 主讲:李祥 时间:2015年10月
2.1 2.2 2.3 2.4 2.5 2.5 2.5 指针的概念 指针的运算 指向变量的指针 指向数组的指针 指向函数的指针 指针数组与二级指针 2.5 指针与const限定符
6.1 指针的概念 大部分初学者都会觉得指针的概念难以理解,这是因为大家没有从计算机的存储原理上去认识指针。在学习指针之前不妨先来了解一下什么是地址以及变量的地址。 在程序运行过程中,数据都是保存在内存中的,内存是以字节为单位的连续存储空间,每个字节都有一个编号,这个编号称为地址。变量也是有地址的,每个变量在生存期内都占据一定数量的字节,这些字节在内存中是连续的,其第一个字节的地址就称为该变量的地址。 在C语言中有一种特殊类型的变量,专门用于存放其他变量的地址,这种变量称为指针变量,通常简称指针。 在使用指针变量时,和普通变量一样也必须先定义后使用。其定义的语法格式如下:
6.1 指针的概念 上述语法格式中,数据类型和变量名都是大家所熟悉的,其中的“*”是专门用于定义指针的标识符,用于标识定义的变量是指针变量,而不是普通的变量。下面通过一段代码来演示如何定义一个指针变量,以及如何使用指针变量存放变量的地址,示例代码如下: 在上面的这段代码中,首先定义了一个整型变量i和一个字符变量c,然后使用“&”运算符分别获得变量i和c的内存地址,并分别使用一个整型指针变量和一个字符指针变量来记住地址。其中指针变量pi用于记住变量i的内存地址,而指针变量pc用于记住变量c的内存地址。 数据类型 * 指针变量名; int i; char c; int * pi = &i; char * pc = &c;
6.1 指针的概念 为了让读者更好的理解指针变量在内存中存储结构,下面通过一个图来演示,如图6-1所示: 图6-1 变量在内存中的存储结构
6.1 指针的概念 在图6-1中,编号0x804a020、0x804a024、0x804a010和0x804a014代表的都是内存中的地址,i代表的是一个整型变量,c代表的是字符变量,pi代表的是指向i的整型指针变量,pc代表的是指向c的字符指针变量。箭头代表了指针变量pi,pc和变量i,c之间的指向关系(即指针变量指向变量)。不难看出,指针变量实际上存储的并不是具体的值,而是变量的内存地址。
6.2 指针的运算 在前面的第2章中讲解过许多运算符,有算术运算符、逻辑运算符等。在程序中,可以使用这些运算符对各种数据类型的变量进行运算。指针作为一种特殊的数据类型同样也可以参与运算,与其他数据类型不同的是指针的运算都是针对内存中的地址来实现的,本节将介绍指针运算所要用到的运算符以及指针如何进行运算。 6.2.1 取址运算符 在程序中定义一个变量时会在内存中开辟一个空间用于存放该变量的值,每一个为变量分配的内存空间都有一个唯一的编号,这个编号就是变量的内存地址。取址运算符的作用就是用于取出指定变量在内存中的地址,取址运算符使用“&”符号来表示,其语法格式如下: & 变量
6.2 指针的运算 从语法格式可以看出取址运算符的用法非常简单,接下来通过一个具体案例来学习一下如何获取变量的内存地址,如例6-1所示: 运行结果如图6-2所示: 图6-2 运行结果 #include <stdio.h> void main() { int num =100; //int类型变量num printf("num = %d\n", num); // 输出变量的值 printf(“&num = %p\n”, &num);// 输出变量的 内存地址 }
6.2 指针的运算 例6-1中通过取址运算符“&”获取变量num的内存地址并进行输出。 其中,第4行代码定义了一个变量num并赋值为100,第5行代码通过printf()函数输出变量num的值 ,第6行代码使用取址运算符“&”获得num变量的地址,并输出到控制台。值得注意的是在输出取址运算获得的地址时,需要使用“%p”作为格式输出符。
多学一招 指针变量间的赋值: 取址运算符不仅可以获取变量在内存中的地址,还可以进行赋值操作。需要注意的是,在赋值操作时,赋值的指针变量与被赋值的指针变量数据类型必须相同,例如下面这段代码: 整型变量d的数据类型与指针变量e所指向的数据类型相同,因此,变量指针变量e可以指向变量d的地址。而指针变量e与f的数据类型也相同,所以可以将指针变量e赋值给f。 int d ,*e ,*f; e=&d; f=e;
6.2 指针的运算 6.2.2 取值运算符 上一个小节讲解的取址运算符的作用是根据给定的变量获得变量的内存地址。在C语言中针对指针运算还提供了一个取值运算符,其作用是根据一个给定的内存地址取出该地址对应变量的值。取值运算符使用“*”符号表示,其格式如下: 上面的格式非常简单,其中“*”表示取值运算符,“指针表达式”用于得到一个内存地址,两者结合使用就可以获得该内存地址对应变量的值。 为了让读者更好的理解取值运算符的使用,接下来通过一个案例来具体学习,如例6-2所示: * 指针表达式
6.2 指针的运算 例6-2: 运行结果如图6-3所示: 图6-3 运行结果 #include <stdio.h> void main() { int num = 100; //这里定义一个整型变量num printf("num = %d\n", num);//输出变量num的值 printf("&num = %p\n", &num); //输出变量 num的地址 printf("*&num = %d\n", *&num);//通过num地址读取num中的数据 }
6.2 指针的运算 从图6-3中可以看出,对num变量分别进行取址运算和取值运算并输出结果。在例6-2中,程序第5行正常输出变量num的值,第6行使用取址运算符获取变量num的内存地址并输出,第7行在取得变量num地址的基础上使用取值运算符获取该地址的内容值并输出。简单的说,在取值运算符*后面跟一个内存地址,就可以获取该内存地址的内容值。
6.2 指针的运算 6.2.3 指针的常用运算 指针作为一种数据类型在程序中也经常需要参与运算,除了上面提到的取址和取值运算,还包括指针与整数的加减、自增自减、同类指针相减运算等,下面将对指针的常用运算进行详细讲解。 指针变量与整数相加、减运算 指针变量可以与整数进行相加或相减操作,具体示例如下: 上面的示例中,p表示一个指针变量,n表示一个整数。表达式p+n表示p所指向的内存地址向后移动n个数据长度,p-n表示p所指向的内存地址向前移动n个数据长度,其数据长度为该整数类型的字节大小。 p+n,p-n
6.2 指针的运算 为了加深读者对指针变量与整数相加减操作的理解,通过一个图来表示上述操作,如图6-4所示: 图6-4 内存地址 图6-4中描述了指针变量p与p+1在内存中的情况,接下来通过一个具体案例来学习一下该运算在程序中如何实现,如例6-3所示: p+n,p-n
6.2 指针的运算 #include<stdio.h> void main() 例6-3: 运行结果如图6-5所示: 图6-5 运行结果 #include<stdio.h> void main() { int arr[5] = {1,2,3,4,5}; int *p1 = &arr[0]; printf("p1的地址为:%p\n",p1); printf("p1+1=的地址为:%p\n", p1+1); printf("p1-1=的地址为:%p\n", p1 - 1); }
6.2 指针的运算 从图6-5中可以看出,指针变量p1通过加减整数1运算后输出的地址都是以十六进制表示的。将其转换为十进制进行比较,发现p1+1比p的值大4,而p1-1比p的值小4,而int类型的数据在内存中占用的就是4个字节。由此可见,指针变量的加减运算实质上是其指针在内存中移动数据类型所占据的字节数。 需要注意的是,只有指向连续的同类型数据区域,指针加、减整数才有实际意义。
6.2 指针的运算 指针表达式的自增、自减运算 指针类型变量也可以进行自增或自减运算,具体示例如下: 上面的示例中可分为自增和自减运算,根据自增和自减运算符的先后可以分为先增和后增运算,自增和自减运算符在前面已经讲解过,在这里使用方法一样,不同的是其增加或减少的是指内存地址的向前或向后移动。接下来通过一个案例来详细讲解,如例6-4所示: 6.2 指针的运算
6.2 指针的运算 例6-4: 运行结果如图6-6所示: 图6-6 运行结果 #include<stdio.h> void main() { int arr[5] = {1,2,3,4,5}; int *p1 = &arr[0]; int *p2 = &arr[3]; printf("p1的值为:%d\n",*p1); printf("++p1的值为:%d\n", *(++p1)); printf("p2的值为:%d\n", *p2); printf("--p2的值为:%d\n", *(--p2)); }
6.2 指针的运算 从图6-6中可以看出,在指针变量p1和p2进行自增运算后,分别对其进行取值操作,它们的值都向下移动了一个元素。p1指向的是数组第一个元素的值,进行自增操作后输出第二个元素的值。由此可以发现++p1与p+1的操作效果一样的,都是对内存地址移动一个数据类型大小的长度。 同类指针相减运算 同类指针类型可以进行相减操作,具体示例如下: 上面的示例中,m和n是两个指向同一类型的指针变量。同类指针进行相减运算其结果值为两个指针之间数据元素的个数。 m-n
6.2 指针的运算 接下来通过一个具体案例来学习一下,如例6-5所示: 例6-5: 运行结果如图6-7所示: 图6-7 运行结果 接下来通过一个具体案例来学习一下,如例6-5所示: 例6-5: 运行结果如图6-7所示: 图6-7 运行结果 #include<stdio.h> void main() { int arr[5] = {1,2,3,4,5}; int *p1 = &arr[0]; int *p2 = &arr[3]; printf("p1的地址为:%d\n",p1); printf("p2的地址为:%d\n", p2); printf("p2-p1=%d\n", p2-p1); }
脚下留心 空指针: 在没有对指针变量赋值时,指针变量的值是不确定的,可能系统会分配一个未知的地址,此时当使用此指针变量时可能会导致不可预料的后果甚至是系统崩溃。为了避免这个问题,通常给指针变量赋初始值为0,并把值为0的指针变量称为空指针变量。
6.3 指向变量的指针 6.3.1 指针变量的使用 上面的案例中已经用到过指针变量,下面再来认识一下它的语法格式,具体如下: 6.3.1 指针变量的使用 上面的案例中已经用到过指针变量,下面再来认识一下它的语法格式,具体如下: 上面的语法格式展示了定义指针变量的通用格式,为了便于大家理解和熟悉指针变量的定义,下面给出几个具体的示例: 在上面的示例中,分别创建了int、char、float三个类型的指针变量,其中pAge用于指向int类型的变量,pLetter用于指向char类型的变量,pScore用于指向float类型的变量。 指针所指向的变量类型 *指针变量名; int *pAge; char *pLetter; float *pScore;
6.3 指向变量的指针 例6-6: #include <stdio.h> 为了让初学者更好的学习指针变量,接下来通过一个案例来演示指针变量的使用,如例6-6所示: 例6-6: #include <stdio.h> void main(int argc, char* argv[]) { int nNum = 0x12345678; int * pNum; //定义一个指针变量 pNum = &nNum; //为指针变量赋值 printf("nNum Val:%x\n", nNum); printf("nNum Addr:%p\n", &nNum);//输出变量nNum的地址 printf("pNum Val:%p\n", pNum); printf("*pNum:%x\n", *pNum); //输出指针变量所对应的值 }
6.3 指向变量的指针 运行结果如图6-8所示: 图6-8 运行结果 在例6-6中,首先定义了一个整型变量nNum,然后定义了一个指向整型变量的指针变量pNum,通过取址运算符来取得nNum的地址,并将其赋给指针变量pNum。最后通过取值运算符*来读取pNum中地址指向的值,结果与变量nNum中的值相等,同为12345678。
6.3 指向变量的指针 6.3.2 指针变量作为函数参数 在C语言中,指针变量除了可以参与运算,还可以作为函数的参数来使用,它的作用是将一个变量的地址传送到另一个函数中,接下来通过一个具体的案例学习指针变量作为函数参数的用法,如例6-7所示: #include<stdio.h> void swap(int *a,int *b) //函数参数为指针类型 { int temp; temp=*a; *a=*b; *b=temp; } void main() { int a=10,b=20; printf("调用函数前变量a和b的值为:%d %d \n",a,b); swap(&a,&b); printf("调用函数后变量a和b的值为:%d %d \n",a,b);}
6.3 指向变量的指针 运行结果如图6-9所示: 图6-9 运行结果 从图6-9中可以看出,在swap()函数调用后变量a和b的值发生了交换。在程序中第2行代码定义一个swap()函数,该函数的参数类型都为指针类型,在main()函数中调用该函数时传入变量a和b的地址,对其取值运算后交换这两个变量的值。 需要注意的是,swap()函数必须写在main()函数的前面,否则需要在main()函数前声明swap()函数。
6.4 指向数组的指针 6.4.1 指向一维数组元素的指针 在第五章的学习中了解到数组可以通过索引的方式来访问数组中的每个元素。这是因为数组中的元素在内存中是连续的,而数组名默认指向数组在内存中的首地址,即第一个元素的地址。其实也可以用一个指针来指向一维数组,通过指针的移动来依次访问数组中的元素。 下面来认识一下如何定义一个指向一维数组的指针,其语法格式如下: 在上述语法格式中,指针的数据类型必须与一维数组中元素的数据类型相同。例如,定义一个指向int x[10]的指针变量,具体示例如下: 一维数组元素的数据类型 *指针变量名; int *pX = x;
6.4 指向数组的指针 为了让读者更好的掌握用指针变量来访问数组中的元素,接下来通过一个具体的案例来演示,如例6-8所示: 例6-8: 为了让读者更好的掌握用指针变量来访问数组中的元素,接下来通过一个具体的案例来演示,如例6-8所示: 例6-8: #include <stdlib.h> #include <stdio.h> void main(int argc, char* argv[]) { int X[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int i = 0; int nLen = 10; printf("数组X修改之前:\n"); for (i = 0; i < 10; i++) { printf("%d ", X[i]); } //用指针pTemp指向数组X int * pTemp = X; //通过指针pTemp访问数组中的元素,并修改元素的值 for (i = 0; i < nLen; i++) { pTemp[i] = pTemp[i] * 2; } printf("\n数组X修改之后:\n"); //通过指针移动来遍历输出X数组中的元素 { printf("%d ", *pTemp); pTemp++; } printf("\n"); }
6.4 指向数组的指针 运行结果如图6-10所示: 图6-10 运行结果 在例6-8中,使用指针变量实现了访问数组中元素的功能。其中,第14行代码定义了一个int类型的指针变量pTemp指向数组X,第16-19行代码通过for循环来移动指针,依次指向数组中的每个元素,并对数组中的元素进行修改。第24行代码通过for循环来移动指针访问数组中的元素,并输出数组中的每个元素。
6.4 指向数组的指针 值得一提的,在例6-8中,如果希望访问数组X中的某个元素,可以采用以下方式: 1. 通过数组名来访问X[i] 2. 通过指针pTemp访问X[i] 其实数组名X也是存放了数组在内存中的首地址,一维数组名就是一个指针,所以可以通过*(X+i)的形式来访问元素X[i]。和指针变量不同的是,数组名X中的地址是不可变的,而指针pTemp中的地址是可变的。 X[i] *(X+i) pTemp[i] *(pTemp+i)
6.4 指向数组的指针 上一小节讲解了如何使用指针指向一维数组并访问数组中的元素,本小节将针对指针指向二维数组的相关知识进行详细地讲解。 6.4.2 指向二维数组元素的指针 上一小节讲解了如何使用指针指向一维数组并访问数组中的元素,本小节将针对指针指向二维数组的相关知识进行详细地讲解。 一维数组中数组名代表该数组的首地址,这对二维数组也是通用的。二维数组实际上可以看做是一维数组,而数组中的每一个元素又是一维数组。二维数组d[3][5]的存储形式如图6-11所示: 图6-11 二维数组
6.4 指向数组的指针 从图6-11中可以看出,二维数组d可以看作由d[0]、d[1]、d[2]等元素组成的一维数组,而d[0]、d[1]、d[2]又都是一维数组名,也就是对应一维数组的首地址。即数组d中存放的元素是一维数组d[0]、d[1]、d[2]的首地址。如果要访问二维数组中的元素,首先需要使用数组指针指向二维数组,其语法格式如下: 在上述语法格式中,“*数组指针变量名”使用了一个括号括起来,这样做是因为“[]”的优先级高于“*”,如果不括起来编译就会将“数组指针变量名”和“[数组元素个数]”先进行运算,构成一个数组。此时就相当于在一个数组前加上了“*”,即定义了一个用来存放指针类型的数组,而不是定义指向数组的指针。 数组元素类型 (* 数组指针变量名)[数组元素个数];
6.4 指向数组的指针 例如指向int类型的二维数组d[3][5]的数组指针,具体代码如下: 上面的一行代码中,变量pX为指向二维数组d的数组指针,在定义该变量时将二维数组d赋值给pX。为了让读者更好的掌握数组指针的使用,接下来通过具体案例来演示一下,如例6-9所示: 运行结果如图6-12所示: 图6-12 运行结果 int (*pX)[5]=d;
6.4 指向数组的指针 例6-9: #include <stdlib.h> #include <stdio.h> void main() { int X[2][5] = { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }}; //定义数组指针用于指向二维数组X int(*pXElement)[5] = X; int m = 0; int n = 0; printf("二维数组元素如下:\n"); for (m = 0; m < 2; m++) { printf("第%d行的元素如下:\n", m + 1); for (n = 0; n < 5; n++) { //通过数组指针来访问为数组中的元素 printf("%d ", pXElement[m][n]); } printf("\n");
6.4 指向数组的指针 在例6-9中,使用数组指针实现了遍历二维数组中元素的功能。其中第7行定义了数组指针pXElement来指向二维数组X,在第11-20行代码,通过两个for循环遍历二维数组X中的元素,其中外层for循环用于来控制二维数组的维度,内存for循环以数组指针pXElement[m][n]的形式来访问数组X中的元素并输出。 值得一提的,在例6-9中,如果希望访问二维数组X中的某个元素,可以采用以下方式: 通过数组名访问元素X[m][n] X[m][n] *(X[m]+n) *(*(X+m)+n) (*(X+m))[n]
6.4 指向数组的指针 2. 通过指针pXElement访问元素X[m][n] 由于二维数组名也是一个数组指针,所以可以通过数组名以数组指针的形式来访问数组中的元素。数组名“X”表示数组的首地址,即第0行的首地址。“X[m]”表示数组第m行0 列元素的地址,“X[m]+n”则表示第M行第n个元素地址,“X+m”用于表示第m行的首地址。当获取到某个元素地址时,可以通过取值运算符“*”来获取当前元素的值。 需要注意的是,在二维数组中,表达式“X++”和“X[0]++”都是错误的因为它们都表示地址常量,不能被改变。 pXElement[m][n] *(pElement[m]+n) *(*( pElement+m)+n) (*(pElement+m))[n]
6.5 指向函数的指针 6.5.1 定义一个指向函数的指针 定义函数指针与定义一个函数类似,不同的是需要将函数名替换为“(* 函数指针名)”,并将函数中的参数名去掉。定义函数指针的语法格式如下: 在上述语法格式中,定义了一个函数指针。接下来通过一段代码来演示函数指针的定义,具体代码如下: 上述代码就是定义了一个名为pAdd的函数指针,pAdd指向函数的参数必须为int类型、返回值也必须为int类型。 函数返回类型 (* 函数指针名) ([[参数类型],[参数类型],……]); int (* pAdd)(int, int);
6.5 指向函数的指针 函数指针定义完成后,就可以使用该指针指向一个函数。假设定义好了一个求和运算的int add(int data1,int data2)函数,如果要使用pAdd函数指针指向该函数,就可以使用以下代码: 上述代码就是使用pAdd函数指针指向add(int data1,int data2)函数,如果要使用函数指针来调用这个函数,则可以通过以下代码来实现: 通过上面的讲解,大家对函数指针有了一定的了解,接下来通过一个案例来演示如何使用函数指针来调用一个函数,如例6-10所示: 运行结果如图6-13所示: 图6-13 运行结果 pAdd=add; (*pAdd)(1, 2);
6.5 指向函数的指针 例6-10: 在例6-10中,通过函数指针pAdd实现两个数求和的功能。第1-4行代码定义了一个add()函数,第8-9行代码定义了一个函数指针pAdd并使用该指针指向 add()函数,第10行代码通过pAdd函数指针调用add()函数,最后将程序的结果输出在控制台。 int add(int data1, int data2) { return data1 + data2; } void main(int argc, char * argv[]) { int c = 0; int(*pAdd)(int, int); //定义函数指针pAdd pAdd = add; //pAdd指向add()函数 c = (*pAdd)(1, 2); //通过pAdd调用add()函数 printf("1+2=%d\n", c); }
6.5 指向函数的指针 6.5.2 使用函数指针作为函数参数 在C语言中,函数指针也可以作为函数的参数,通过函数指针可以向函数传入某些特定的操作,接下来通过一个案例来演示这种特殊的用法,如例6-11所示: #include<stdio.h> int getnum(int x) { return x + 1; } //add()函数接收一个函数指针(*num)(int x)和两个int类型变量作为的参数 int add(int(*num)(int x), int a, int b) { return (*num)(a)+(*num)(b); //分别调用num函数获得返回值,将返回值相加 } void main() { int sum, (*p)(int x); //定义一个int类型的sum变量和函数指针变量 p = getnum;//将函数getnum()赋值给函数指针变量p sum = add(p, 1, 2);//调用函数add()返回相加的结果 printf("运行结果为:%d\n", sum);//输出运行结果 }
6.6指针数组与二级指针 上述示例代码中定义了一个名为p的指针数组,该数组中元素类型为int,长度为5。 6.6.1 指针数组的概念 6.6.1 指针数组的概念 在前面讲解数组的章节中提到过,数组是用来存放一系列相同类型的数据,当然数组也可以用来存放指针,这种用来存放指针的数组被称为指针数组,它要求存放在数组中指针的数据类型必须一致。指针数组的定义方式与普通数组类似,只需在类型前面加上“*”,具体示例如下: 上述示例代码中定义了一个名为p的指针数组,该数组中元素类型为int,长度为5。 在数组中经常会用到数组的遍历操作,对于指针数组也是一样。接下来通过一个具体案例来学习指针数组的遍历操作,如例6-12所示: int *p[5];
6.6指针数组与二级指针 例6-12: #include<stdio.h> //输出指针数组中元素的值 int print(int *arr[], int n) { if (!arr) { return 0; } int i = 0; for (i = 0; i < n; i++) { printf("%d\n", *arr[i]); } return 1; } void main(int argc, char * argv[]) { int a, b, c, d, e; a = 1; b = 2; c = 3; d = 4; e = 5; int *Arr[] = { &a, &b, &c, &d, &e };//定义一个int类型的指针数组 print(Arr, 5); //调用print()函数
6.6指针数组与二级指针 运行结果如图6-15所示: 图6-15 运行结果 在例6-12中,第3-15行代码定义了print()函数,该函数接收一个int类型的指针数组和一个int类型的变量作为参数,输出了指针数组中元素所指向变量的值。第24行代码定义了一个指针数组Arr用于存放指针变量。第25行代码调用了print()函数并将指针数组Arr传入该函数。 需要注意是的,数组指针和指针数组不是同一个概念,数组指针是指一个指向数组的指针,指针数组是指数组中的元素类型都是指针类型
6.6指针数组与二级指针 6.6.2 指针数组作为main()函数参数 在前面使用的main()函数都是无参的,实际上该函数也可以接收参数。main()函数是程序的入口,通常用来接收来自系统的参数。下面就来看一下main()函数的完整定义方式。 上述代码中可以看出,main()函数有两个参数,argc参数表示在命令行中输入的参数个数,argv参数是字符串指针数组,其各元素值为命令行中各字符串的首地址。数组第一个元素指向当前运行程序文件名的字符串。指针数组的长度即为参数个数,数组元素初值由系统自动赋予 为了让读者更好的掌握main()函数外部传参的使用方法,接下来通过一个具体案例来演示指针数组作为main()函数参数的使用,如例6-13所示: int main( int argc, char *argv[] );
6.6指针数组与二级指针 例6-13: 运行结果如图6-16所示: 图6-16 运行结果 #include <stdlib.h> #include <stdio.h> int main(int argc, char * argv[]) { int i = 0; printf("程序的参数列表如下:\n"); for (i = 0; i < argc; i++) { printf("%s ", argv[i]); } return 0; }
6.6指针数组与二级指针 6.6.3 二级指针 通常我们所说的指针实际上是指一个指针变量,该变量的值为一个内存地址用于与其他内存地址之间建立关联,该内存地址可以被定义为整型,浮点型,数组等类型,当然也可以是指针类型。指向指针的指针也称为二级指针。简单来说二级指针就是一个指针变量的值是另外一个指针变量的地址。 在前面学习中知道了,指针可以用来指向数组、字符串、函数,同样一个指针也可以指向另一个指针变量,这种指向指针的指针称为二级指针,二级指针的数据的类型必须与所指向的的指针的数据类型相同。 定义二级指针的语法格式具体如下: 数据类型 **指针名;
6.6指针数组与二级指针 下面定义一个二级指针并对其进行初始化,示例代码如下: 上述代码中定义了一个二级指针p2用来指向指针p1。为了让读者更好的掌握二级指针的使用,接下来将通过一个具体案例来学习,如例6-14所示: 例6-14: int a=100 , *p1; p1=&a; int **p2=&p1 #include <stdlib.h> #include <stdio.h> void main(int argc, char * argv[]) { int m = 10; int * pM = &m; //指针pM存放的是变量m的地址 int ** ppM = &pM;//二级指针ppM存放的是指针pM的地址 printf("m=%d\n", m); printf("*pM=%d\n", *pM); printf("**ppM=%d\n", **ppM); }
6.6指针数组与二级指针 运行结果如图6-17所示: 图6-17 运行结果 在例6-14中,第6行代码定义了一个pM指针并存放变量m的地址,第7行代码定了一个二级指针ppM来存放指针pM的地址。从运行结果可以看出表达式m、*pM、**ppM的值都是10,因为以上三种方式访问的都是变量m的值。
多学一招 malloc()函数和free()函数: malloc()函数用于动态分配堆内存,free()函数用于释放堆内存。这两个函数通常都是配合一起使用的。为了让读者更好的掌握这两个函数的使用,接下来通过一个动态创建数组的案例来学习一下malloc()和free()函数的使用,如例6-15所示: 运行结果如图6-18所示: 图6-18 运行结果 从图6-18可以看出,数组元素被正确输出了,而在程序中没有给数组赋初始值,正常情况下如果没有赋初始值直接输出结果会报错,而这个程序没有报错的原因是因为程序第9行使用了malloc()函数为数组提前开辟了内存空间,在运行时可以动态的给数组赋值。
多学一招 例6-15: #include <stdio.h> void main() { int u, v; // 定义二维数组的长和宽 printf("第一维为:"); scanf("%d", &u); printf("第二维为:"); scanf("%d", &v); int** array = (int**)malloc(sizeof(int*)* u); //先创建第一维 for (int i = 0; i < u; i++) { //在内层循环中动态创建第二维 array[i] = (int*)malloc(sizeof(int)* v); for (int j = 0; j < v; j++) { array[i][j] = 0; //给数组元素复制 printf("%d ", array[i][j]); } } free(array); printf("\n"); }
6.7指针与const限定符 在开发一个程序时,为了防止数据被使用者非法篡改,此时可以使用const限定符。const限定符修饰的变量在程序运行中不能被修改,因此在一定程度上可以提高程序的安全性和可靠性。 const限定符通常与指针配合使用,与指针配合使用时有三种形式,具体方式如下: 常量指针 常量指针的作用是使当前指针所指向变量的值在程序运行时不能被修改,其语法格式如下: 上述示例代码中,变量num为普通变量,当在指针变量p的数据类型前使用const限定符修饰后,p所指向的变量num将不能被改变。 const 数据类型 * 指针变量名
6.7指针与const限定符 指针常量 指针常量其实就是一个常量,该指针存放的地址不能被改变,其语法格式如下: 定义指针常量的示例代码如下: 上述示例代码中指针P使用const限定符修饰,将变量num的地址赋值给指针常量P,那么指针变量p的地址不能被改变。 数据类型 * const 指针变量名 int num=10; int * const P=#
6.7指针与const限定符 指向常量的常指针 指向常量的常指针就是指针所指向的地址不能被改变,且所指向地址中的值也不能被改变,其语法格式如下: 上述语法格式中,在“指针变量名”和“数据类型”前都使用const限定符修饰,那么该指针的值为常量,且该指针指向的变量也变为常量 定义指向常量的常指针的示例代码如下: 上述语法格式中定义了一个指向常量的常指针p,并将变量num的地 址赋值给变量p。此时,变量p的值不能被改变,且变量num的值也不 能被改变。 const 数据类型 * const 指针变量名 int num=10; const int * const P=#
本章小结 本章首先讲解了指针的概念,然后讲解了指针的运算、指向变量的指针、指向数组的指针、指向字符串的指针、指向函数的指针,最后讲解了指针数组与二级指针以及const限定符的使用。通过本章的学习,读者应该能够熟练掌握指针的运算以及指针的用法。指针是C语言的核心内容,希望大家能够好好实践本章的例子,加深对指针的理解。
谢 谢!