[C语言]指针简介
前言指针是C语言中的精髓意味着学好指针才能发挥出C语言的强大作用。要看一个程序员用C的能力强不强就要看其对指针的理解到不到位。指针数据存储在内存中。为了高效地访问数据内存中的每个字节都被赋予一个唯一的地址。通过该地址我们可以定位到对应的数据并进行相应操作。取地址操作符、指针变量取地址操作符。用来取出变量存储在内存中的地址指针变量指针变量可以用来存储取出来的地址如下图中的变量p1就是指针变量变量的类型是int*也就是指向整形的指针。inta0;int*pAa;// a 取变量 a 的地址 pA类型为int* 的指针变量pA的意思是point a。return0;指针变量本身也是一个变量说明变量pA本身也占用字节指针变量在32位操作环境下大小为4字节而在64位操作环境下大小则为8个字节。不管指针指向什么数据在同一环境下它存储地址所需要的字节数都是固定的。上述int*是指向整形的指针变量还有各种其他的指针变量如char**和double*以及void*无类型指针通用指针…。解引用操作符得到了数据的存储地址那么要怎么获得指针指向的实际数据呢*解引用操作符解读地址的内容以读取数据或者是修改数据。例如#includecstdiointmain(){charaa;// 创建了一个字符变量 a并将字符的值初始化为 achar*pAa;printf(%c\n,*pA);//}运行结果a指针的加减运算指针变量可以存储的地址也可以进行加减运算那么指针进行加减操作有什么意义呢下面请看一段代码和运行结果示例代码#includestdio.hintmain(){inta0;int*pAa;printf(%d\n,*pA);printf(这是p1的地址: %p\n,pA);printf(这是p11的地址: %p\n,pA1);printf(这是p12的地址: %p\n,pA2);putchar(\n);charba;char*pBb;printf(%c\n,*pB);printf(这是p2的地址: %p\n,pB);printf(这是p21的地址: %p\n,pB1);printf(这是p22的地址: %p\n,pB2);return0;}运行结果0 这是p1的地址: 004FF9DC 这是p11的地址: 004FF9E0 这是p12的地址: 004FF9E4 a 这是p2的地址: 004FF9C7 这是p21的地址: 004FF9C8 这是p22的地址: 004FF9C9在内存中int和char类型分别占用4字节和1字节的内存大小。 当我们对其类型的指针变量进行加减操作时指针会按照其类型的大小进行位移如int*的指针加上1就会从当前地址位移4个字节char*指针加上1就会位移一个字节。如果我们指针p指向的是一个数组的起始元素地址通过数组元素在内存中存储是连续的特性可以通过加和减来定位数组中的每个元素的地址计算偏移量时要小心避免越界。数组名的理解指针可以指向数组的元素下面我们来研究一下指向数组的指针该如何使用吧。下面来看具体例子说明数组变量名代码示例#includecstdiointmain(){intarr[10]{0};// arr 的类型: int[10]printf(%-20s %-14s %-10s\n,变量,地址(16进制),地址10进制);printf(%-20s %-14p %-10d\n,arr:,arr,(int)arr);printf(%-20s %-14p %-10d\n,arr[0]:,arr[0],(int)arr[0]);printf(%-20s %-14p %-10d\n,arr:,arr,(int)arr);printf(%-20s %-14p %-10d\n,arr[0] 1:,arr[0]1,(int)(arr[0]1));printf(%-20s %-14p %-10d\n,arr 1:,arr1,(int)(arr1));printf(-------------------------------------\n);printf(%-20s %-14s\n,变量,大小);printf(%-20s %-14zu\n,sizeof(arr):,sizeof(arr));printf(%-20s %-14zu\n,sizeof(arr[0]):,sizeof(arr[0]));return0;}运行结果变量 地址(16进制) 地址10进制 arr: 006FF7A8 7337896 arr[0]: 006FF7A8 7337896 arr: 006FF7A8 7337896 arr[0] 1: 006FF7AC 7337900 arr 1: 006FF7D0 7337936 ------------------------------------- 变量 大小 sizeof(arr): 40 sizeof(arr[0]): 4运行结果显示数组名其实是一个地址它不仅代表数组首元素的地址还可以代表整个数组。数组名只有在两种情况下才代表整个数组的地址。第一种情况arr这个操作是将整个数组的地址取出来其值是数组的首元素地址观察上面的运行结果中arr、arr1 1和arr 1的十进制地址发现arr1 1相比arr的偏移量是 4一个int变量的大小而arr 1跳相比arr的偏移量是 40一个数组的大小也就是10个int的大小。第二种情况arr1放在sizeof()的括号里头时代表的是整个数组计算的内存是整个数组占用的内存大小。既然数组名是首元素的地址我们可以尝试用指针来访问数组的每一个元素而不用下标操作符“[]”来操作符下面我们来看代码的具体实现。#includecstdiointmain(){intarr[10]{10,9,8,7,6,5,4,3,2,1};int*parr;size_t sizesizeof(arr)/sizeof(arr[0]);// 此表达式可以计算数组中单个元素的大小for(inti0;isize;i){printf(%d ,*(pi));}putchar(\n);for(inti0;i10;i){printf(%d ,p[i]);}return0;}运行结果10 9 8 7 6 5 4 3 2 1 10 9 8 7 6 5 4 3 2 1想要获取数组中的任意一个元素可以用*(p i)的方式或p[i]的方式获取。换句话说*(p1i)和p1[i]是同一个意思。循环里变量i每增加一次我们的指针就指向下一个元素的起始位置然后根据指针类型进行解引用操作就可以访问数组的每个元素。指针数组指针数组就是指针的数组这个数组存放的变量是指针变量。我们通过指针数组模拟二维数组的示例代码来理解指针数组。示例代码#includecstdio//指针数组模拟实现二维数组intmain(){//初始化三个一维数组intarr1[5]{1,2,3,4,5};intarr2[5]{6,7,8,9,10};intarr3[5]{11,12,13,14,15};//指针数组//arr数组存放的变量类型是int*的指针//存放的是一维数组的首元素地址int*arr[3]{arr1,arr2,arr3};//遍历数组模拟一个三行五列的二维数组for(inti0;i3;i){for(intj0;j5;j){printf(%2d ,*(arr[i]j));}//arr[i]可写成*(arri)printf(\n);}return0;}运行结果1 2 3 4 5 6 7 8 9 10 11 12 13 14 15我们所创建的指针数组里三个元素存放的是指针变量每个指针变量指向三个一维数组的起始位置当我们读取数组中第一个元素其实是得到第一个一维数组的首元素地址我们对其 i指针偏移并进行解引用就可以访问到每一个元素了。我们可以将上面代码的for循环中的i5条件改为i20,这样做虽然是不允许的但是为了深入探讨问题我们可以试一试。修改的代码for(intj0;j20;j)// 循环条件 j 5 改为 j 20{printf(%2d ,*(arr[i]j));}运行结果1 2 3 4 5 -858993460 230655617 13630200 6234595 1 17117464 17120824 1 17117464 17120824 13630292 6234154 230655757 6230051 6230051 6 7 8 9 10 -858993460 -858993460 1 2 3 4 5 -858993460 230655617 13630200 6234595 1 17117464 17120824 1 11 12 13 14 15 -858993460 -858993460 6 7 8 9 10 -858993460 -858993460 1 2 3 4 5 -858993460我们会发现访问完一维数组中的五个元素后后面访问到的元素中有莫名其妙的值也有访问完的上一个一维数组存储的值。这是使用指针数组模拟二维数组与直接开辟二维数组的区别。开辟空间时是依据从高地址向低地址的顺序开辟的当我们继续向高地址进行访问时就有可能访问到之前开辟的一维数组的元素。直接创建的二维数组在内存中是连续的而我们用指针数组和一维数组模拟的二维数组在内存中并不是连续的。字符指针我们来看一段代码#includecstdio// 字符指针和数组指针intmain(){charstr1[20]{I love you!};printf(%s\n,str1);constchar*str2I love you!;printf(%s\n,str2);return0;}看到这里你是不是以为第二种方式是将字符串“I love you”传入了字符指针里了其实它是将字符串中首元素地址“I”传入了str2中只要找到字符串数组的首元素地址就可以将字符串打印出来了\0为字符串的结束标志。数组指针数组指针就是指针指向的内容是一个数组(细品这句话)。下面我们用二维数组传参的方式来认识一下数组指针。#includecstdiointmain(){//数组指针 指向数组intarr[3][5]{{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};int(*pArr)[5]arr;//这里放入的是二维数组首元素即一维数组的地址for(inti0;i3;i){for(intj0;j5;j){// pArr指向的是一个一维数组// (pArr i) 理解为指向数组指针中的第 i 个一维数组类型为 (*pArr)[5]// *(pArr i) 解引用第 i 个一维数组指向一维数组的首元素类型为 int*// *(pArr i) j) 理解为指向一维数组中的第 j 个元素的起始位置类型为 int*// *(*(pArr i) j) 解引用第 j 个元素的起始位置获取到 int 元素printf(%2d ,*(*(pArri)j));}putchar(\n);}printf(-----------------等价的访问方式-----------------\n);for(inti0;i3;i){for(intj0;j5;j){// *(*(parr1i)j)printf(%2d ,pArr[i][j]);}putchar(\n);}return0;}运行结果1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -----------------等价的访问方式----------------- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15代码中我们声明了一个数组指针即int (*pArr)[5]。因为下标操作符[]比解引用操作符优先级更高防止被理解为int*类型的指针数组所以我们需要给指针*pArr加上括号来保证其所代表的是指针而不是数组。指针pArr指向的是int [5]类型的数组。所谓的二维数组我们可以理解为一个存储了一维数组的数组也就是二维数组中每个元素即为一个一维数组所以二维数组的数组名代表了第一个整个一维数组的地址即二维数组名指向二维数组的第一行元素一个一维数组的起始地址.我们将二维数组的第一个元素的地址存入数组指针pArr中当我们进行pArr i操作时数组指针就会指向二维数组的下一个元素下一个一维数组。当我们对这一个元素的地址进行解引用的时候我们就得到了这一个元素中的第一个元素该元素就是int的地址元素的元素降维此时的*pArr i指向的就是一维数组中的首个int元素的地址当我们再进行 j操作时指针就会往后移动j个元素解引用后得到第这个一维数组的第j个元素。使用这样的方法这样我们就能用数组指针遍历二维数组里每一个元素了并将其打印出来。函数指针基本用法函数指针也就是指向函数的指针我们可以通过函数指针来调用函数。函数由函数名、参数、返回值组成其中函数名不仅仅是函数名还代表着函数的地址。我们可以通过函数名和函数名的方法得到函数的地址这两种方法都是一样的没有什么区别。下面我们通过代码来看看函数指针是如何声明和使用的吧示例代码#includecstdio//函数指针intmax(inta,intb){returnab?a:b;//返回a和b中的最大值}intmain(){int(*p)(int,int)max;// 函数指针inta3,b4;printf(%d和%d的最大值是max(a, b) %d。\n,a,b,max(a,b));printf(%d和%d的最大值是p(a, b) %d。\n,a,b,p(a,b));return0;}运行结果3和4的最大值是max(a, b) 4。 3和4的最大值是p(a, b) 4。在上述代码中我们声明了一个函数指针p它指向的函数类型的返回值是int型传入的形参是两个int的数据int, int)。在后面的代码中我们用指针的方式调用了函数max来求出两个数的最大值即max(a, b)。如果不用常规函数名方式调用函数也可以用函数指针也就是p(a, b)。容易观察到函数名起始就是一个地址我们将函数地址存入函数指针中我们就可以通过函数指针的方式来调用该函数了。结合 qsort 排序任意类型的数组函数指针的一个用法c 标准库中有一个快速排序的函数叫做qsort如果想要对自定义类型的数组如自己定义的结构体的数组进行排序那么就要告诉qsort排序规则。首先要自己定义一个函数根据比较方法来返回比较的结果然后把这个比较规则的函数的函数指针传给qsort供其内部调用这样就可以实现对任意类型的数据进行排序了代码示例#includestdio.h#includestdlib.h#includestring.h// 定义学生结构体typedefstruct{charname[20];intage;floatscore;}Student;// 按年龄升序排序intcompareByAge(constvoid*a,constvoid*b){Student*s1(Student*)a;Student*s2(Student*)b;returns1-age-s2-age;// 升序}// 按分数降序排序intcompareByScoreDesc(constvoid*a,constvoid*b){Student*s1(Student*)a;Student*s2(Student*)b;// 分数高的排前面降序if(s1-scores2-score)return-1;if(s1-scores2-score)return1;return0;}// 按姓名升序排序intcompareByName(constvoid*a,constvoid*b){Student*s1(Student*)a;Student*s2(Student*)b;returnstrcmp(s1-name,s2-name);// 字符串比较}// 打印学生信息voidprintStudents(Student*students,intn){for(inti0;in;i){printf(%-10s\t%d岁\t%.1f分\n,students[i].name,students[i].age,students[i].score);}}intmain(){// 初始化学生数组Student students[]{{Max Caulfield,20,85.5},{Chloe Price,18,92.0},{Rachel Amber,22,78.5},{Kate Marsh,19,88.0},{Warren Graham,21,95.5}};intnsizeof(students)/sizeof(students[0]);printf( 原始数据 \n);printStudents(students,n);// 按年龄排序qsort(students,n,sizeof(Student),compareByAge);printf(\n 按年龄升序 \n);printStudents(students,n);// 按分数降序排序qsort(students,n,sizeof(Student),compareByScoreDesc);printf(\n 按分数降序 \n);printStudents(students,n);// 按姓名排序qsort(students,n,sizeof(Student),compareByName);printf(\n 按姓名升序 \n);printStudents(students,n);return0;}运行结果 原始数据 Max Caulfield 20岁 85.5分 Chloe Price 18岁 92.0分 Rachel Amber 22岁 78.5分 Kate Marsh 19岁 88.0分 Warren Graham 21岁 95.5分 按年龄升序 Chloe Price 18岁 92.0分 Kate Marsh 19岁 88.0分 Max Caulfield 20岁 85.5分 Warren Graham 21岁 95.5分 Rachel Amber 22岁 78.5分 按分数降序 Warren Graham 21岁 95.5分 Chloe Price 18岁 92.0分 Kate Marsh 19岁 88.0分 Max Caulfield 20岁 85.5分 Rachel Amber 22岁 78.5分 按姓名升序 Chloe Price 18岁 92.0分 Kate Marsh 19岁 88.0分 Max Caulfield 20岁 85.5分 Rachel Amber 22岁 78.5分 Warren Graham 21岁 95.5分转移表我们可以创建一个指针数组用来存放我们的函数的地址然后通过不同的条件调用数组中的不同函数这种方式的调用就叫转移表。如下是简单的创建函数指针数组实现简易的转移表代码示例代码#define_CRT_SECURE_NO_WARNINGS1#includecstdiointmax(inta,intb){returnab?a:b;// 返回a和b中的最大值}intmin(inta,intb){returnab?a:b;// 返回a和b中的最小值}intmain(){int(*p[3])(int,int){NULL,max,min};inta,b,choice;do{printf(请输入两个整数: \n);scanf(%d %d,a,b);printf(请输入你的操作: 1---求最大值 2---求最小值\n);scanf(%d,choice);if(choice1)printf(%d和%d的最大值为: ,a,b);elseif(choice2)printf(%d和%d的最小值为: ,a,b);else{printf(错误请输入正确的操作\n);return-1;}printf(%d\n,p[choice](a,b));printf(是否继续? 继续---Y, 退出---N\n);getchar();// 用于接收换行符}while(Ygetchar());return0;}运行结果请输入两个整数: 520 1314 请输入你的操作: 1---求最大值 2---求最小值 1 520和1314的最大值为: 1314 是否继续? 继续---Y, 退出---N Y 请输入两个整数: 520 1314 请输入你的操作: 1---求最大值 2---求最小值 2 520和1314的最小值为: 520 是否继续? 继续---Y, 退出---N N以上就是我对于指针的理解
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2439293.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!