网站设计重要性,网站后台管理系统摘要怎么写,网站建设的7种流程图,宁夏考试教育网站一、前言二、变量与指针的本质三、指针的几个相关概念四、指向不同数据类型的指针五、总结一、前言如果问C语言中最重要、威力最大的概念是什么#xff0c;答案必将是指针#xff01;威力大#xff0c;意味着使用方便、高效#xff0c;同时也意味着语法复杂、容易出错。指针… 一、前言二、变量与指针的本质三、指针的几个相关概念四、指向不同数据类型的指针五、总结一、前言如果问C语言中最重要、威力最大的概念是什么答案必将是指针威力大意味着使用方便、高效同时也意味着语法复杂、容易出错。指针用的好可以极大的提高代码执行效率、节约系统资源如果用的不好程序中将会充满陷阱、漏洞。这篇文章我们就来聊聊指针。从最底层的内存存储空间开始一直到应用层的各种指针使用技巧循序渐进、抽丝剥茧以最直白的语言进行讲解让你一次看过瘾。说明为了方便讲解和理解文中配图的内存空间的地址是随便写的在实际计算机中是要遵循地址对齐方式的。二、变量与指针的本质1. 内存地址我们编写一个程序源文件之后编译得到的二进制可执行文件存放在电脑的硬盘上此时它是一个静态的文件一般称之为程序。当这个程序被启动的时候操作系统将会做下面几件事情把程序的内容(代码段、数据段)从硬盘复制到内存中创建一个数据结构PCB(进程控制块)来描述这个程序的各种信息(例如使用的资源打开的文件描述符...);在代码段中定位到入口函数的地址让CPU从这个地址开始执行。当程序开始被执行时就变成一个动态的状态一般称之为进程。内存分为物理内存和虚拟内存。操作系统对物理内存进行管理、包装我们开发者面对的是操作系统提供的虚拟内存。这2个概念不妨碍文章的理解因此就统一称之为内存。在我们的程序中通过一个变量名来定义变量、使用变量。变量本身是一个确确实实存在的东西变量名是一个抽象的概念用来代表这个变量。就比如我是一个实实在在的人是客观存在与这个地球上的道哥是我给自己起的一个名字这个名字是任意取得只要自己觉得好听就行如果我愿意还可以起名叫鸟哥、龙哥等等。那么我们定义一个变量之后这个变量放在哪里呢那就是内存的数据区。内存是一个很大的存储区域被操作系统划分为一个一个的小空间操作系统通过地址来管理内存。内存中的最小存储单位是字节(8个bit)一个内存的完整空间就是由这一个一个的字节连续组成的。在上图中每一个小格子代表一个字节但是好像大家在书籍中没有这么来画内存模型的更常见的是下面这样的画法也就是把连续的4个字节的空间画在一起这样就便于表述和理解特别是深入到代码对齐相关知识时更容易理解。(我认为根本原因应该是大家都这么画已经看顺眼了~~)2. 32位与64位系统我们平时所说的计算机是32位、64位指的是计算机的CPU中寄存器的最大存储长度如果寄存器中最大存储32bit的数据就称之为32位系统。在计算机中数据一般都是在硬盘、内存和寄存器之间进行来回存取。CPU通过3种总线把各组成部分联系在一起地址总线、数据总线和控制总线。地址总线的宽度决定了CPU的寻址能力也就是CPU能达到的最大地址范围。刚才说了内存是通过地址来管理的那么CPU想从内存中的某个地址空间上存取一个数据那么CPU就需要在地址总线上输出这个存储单元的地址。假如地址总线的宽度是8位能表示的最大地址空间就是256个字节能找到内存中最大的存储单元是255这个格子(从0开始)。即使内存条的实际空间是2G字节CPU也没法使用后面的内存地址空间。如果地址总线的宽度是32位那么能表示的最大地址就是2的32次方也就是4G字节的空间。【注意】这里只是描述地址总线的概念实际的计算机中地址计算方式要复杂的多比如虚拟内存中采用分段、分页、偏移量来定位实际的物理内存在分页中还有大页、小页之分感兴趣的同学可以自己查一下相关资料。3. 变量我们在C程序中使用变量来“代表”一个数据使用函数名来“代表”一个函数变量名和函数名是程序员使用的助记符。变量和函数最终是要放到内存中才能被CPU使用的而内存中所有的信息(代码和数据)都是以二进制的形式来存储的计算机根据就不会从格式上来区分哪些是代码、哪些是数据。CPU在访问内存的时候需要的是地址而不是变量名、函数名。问题来了在程序代码中使用变量名来指代变量而变量在内存中是根据地址来存放的这二者之间如何映射(关联)起来的答案是编译器编译器在编译文本格式的C程序文件时会根据目标运行平台(就是编译出的二进制程序运行在哪里是x86平台的电脑还是ARM平台的开发板)来安排程序中的各种地址例如加载到内存中的地址、代码段的入口地址等等同时编译器也会把程序中的所有变量名转成该变量在内存中的存储地址。变量有2个重要属性变量的类型和变量的值。示例代码中定义了一个变量int a 20;
类型是int型值是20。这个变量在内存中的存储模型为我们在代码中使用变量名a在程序执行的时候就表示使用0x11223344地址所对应的那个存储单元中的数据。因此可以理解为变量名a就等价于这个地址0x11223344。换句话说如果我们可以提前知道编译器把变量a安排在地址0x11223344这个单元格中我们就可以在程序中直接用这个地址值来操作这个变量。在上图中变量a的值为20在内存中占据了4个格子的空间也就是4个字节。为什么是4个字节呢在C标准中并没有规定每种数据类型的变量一定要占用几个字节这是与具体的机器、编译器有关。比如32位的编译器中char: 1个字节short int: 2个字节int: 4个字节long: 4个字节。比如64位的编译器中char: 1个字节short int: 2个字节int: 4个字节long: 8个字节。为了方便描述下面都以32位为例也就是int型变量在内存中占据4个字节。另外0x112233440x112233450x112233460x11223347这连续的、从低地址到高地址的4个字节用来存储变量a的数值20。在图示中使用十六进制来表示十进制数值20转成16进制就是0x00000014所以从开始地址依次存放0x00、0x00、0x00、0x14这4个字节(存储顺序涉及到大小端的问题不影响文本理解)。根据这个图示如果在程序中想知道变量a存储在内存中的什么位置可以使用取地址操作符如下printf(a 0x%x \n, a);
这句话将会打印出a 0x11223344。考虑一下在32位系统中指针变量占用几个字节4. 指针变量指针变量可以分2个层次来理解指针变量首先是一个变量所以它拥有变量的所有属性类型和值。它的类型就是指针它的值是其他变量的地址。 既然是一个变量那么在内存中就需要为这个变量分配一个存储空间。在这个存储空间中存放着其他变量的地址。指针变量所指向的数据类型这是在定义指针变量的时候就确定的。例如int *p; 意味着指针指向的是一个int型的数据。首先回答一下刚才那个问题在32位系统中一个指针变量在内存中占据4个字节的空间。因为CPU对内存空间寻址时使用的是32位地址空间(4个字节)也就是用4个字节就能存储一个内存单元的地址。而指针变量中的值存储的就是地址所以需要4个字节的空间来存储一个指针变量的值。示例int a 20;
int *pa;
pa a;
printf(value %d \n, *pa);
在内存中的存储模型如下对于指针变量pa来说首先它是一个变量因此在内存中需要有一个空间来存储这个变量这个空间的地址就是0x11223348其次这个内存空间中存储的内容是变量a的地址而a的地址为0x11223344所以指针变量pa的地址空间中就存储了0x11223344这个值。这里对两个操作符和*进行说明取地址操作符用来获取一个变量的地址。上面代码中a就是用来获取变量a在内存中的存储地址也就是0x11223344。*这个操作符用在2个场景中定义一个指针的时候获取一个指针所指向的变量值的时候。int pa; 这个语句中的表示定义的变量pa是一个指针前面的int表示pa这个指针指向的是一个int类型的变量。不过此时我们没有给pa进行赋值也就是说此刻pa对应的存储单元中的4个字节里的值是没有初始化的可能是0x00000000也可能是其他任意的数字不确定printf语句中的*表示获取pa指向的那个int类型变量的值学名叫解引用我们只要记住是获取指向的变量的值就可以了。5. 操作指针变量对指针变量的操作包括3个方面操作指针变量自身的值获取指针变量所指向的数据;以什么样数据类型来使用/解释指针变量所指向的内容。5.1 指针变量自身的值int a 20;这个语句是定义变量a在随后的代码中只要写下a就表示要操作变量a中存储的值操作有两种读和写。printf(a %d \n, a); 这个语句就是要读取变量a中的值当然是20a 100;这个语句就是要把一个数值100写入到变量a中。同样的道理int *pa;语句是用来定义指针变量pa在随后的代码中只要写下pa就表示要操作变量pa中的值printf(pa %d \n, pa); 这个语句就是要读取指针变量pa中的值当然是0x11223344pa a;这个语句就是要把新的值写入到指针变量pa中。再次强调一下指针变量中存储的是地址如果我们可以提前知道变量a的地址是 0x11223344那么我们也可以这样来赋值:pa 0x11223344;思考一下如果执行这个语句printf(pa 0x%x \n, pa);打印结果会是什么上面已经说过操作符是用来取地址的那么pa就表示获取指针变量pa的地址上面的内存模型中显示指针变量pa是存储在0x11223348这个地址中的因此打印结果就是pa 0x11223348。5.2 获取指针变量所指向的数据指针变量所指向的数据类型是在定义的时候就明确的也就是说指针pa指向的数据类型就是int型因此在执行printf(value %d \n, *pa);语句时首先知道pa是一个指针其中存储了一个地址(0x11223344)然后通过操作符*来获取这个地址(0x11223344)对应的那个存储空间中的值又因为在定义pa时已经指定了它指向的值是一个int型所以我们就知道了地址0x11223344中存储的就是一个int类型的数据。5.3 以什么样的数据类型来使用/解释指针变量所指向的内容如下代码int a 30000;
int *pa a;
printf(value %d \n, *pa);
根据以上的描述我们知道printf的打印结果会是value 30000十进制的30000转成十六进制是0x00007530内存模型如下现在我们做这样一个测试char *pc 0x11223344;
printf(value %d \n, *pc);
指针变量pc在定义的时候指明它指向的数据类型是char型pc变量中存储的地址是0x11223344。当使用*pc获取指向的数据时将会按照char型格式来读取0x11223344地址处的数据因此将会打印value 0(在计算机中ASCII码是用等价的数字来存储的)。这个例子中说明了一个重要的概念在内存中一切都是数字如何来操作(解释)一个内存地址中的数据完全是由我们的代码来告诉编译器的。刚才这个例子中虽然0x11223344这个地址开始的4个字节的空间中存储的是整型变量a的值但是我们让pc指针按照char型数据来使用/解释这个地址处的内容这是完全合法的。以上内容就是指针最根本的心法了。把这个心法整明白了剩下的就是多见识、多练习的问题了。三、指针的几个相关概念1. const属性const标识符用来表示一个对象的不可变的性质例如定义const int b 20;
在后面的代码中就不能改变变量b的值了b中的值永远是20。同样的如果用const来修饰一个指针变量int a 20;
int b 20;
int * const p a;
内存模型如下这里的const用来修饰指针变量p根据const的性质可以得出结论p在定义为变量a的地址之后就固定了不能再被改变了也就是说指针变量pa中就只能存储变量a的地址0x11223344。如果在后面的代码中写p b;编译时就会报错因为p是不可改变的不能再被设置为变量b的地址。但是指针变量p所指向的那个变量a的值是可以改变的即*p 21;这个语句是合法的因为指针p的值没有改变(仍然是变量c的地址0x11223344)改变的是变量c中存储的值。与下面的代码区分一下int a 20;
int b 20;
const int *p a;
p b;
这里的const没有放在p的旁边而是放在了类型int的旁边这就说明const符号不是用来修饰p的而是用来修饰p所指向的那个变量的。所以如果我们写p b;把变量b的地址赋值给指针p就是合法的因为p的值可以被改变。但是这个语句*p 21就是非法了因为定义语句中的const就限制了通过指针p获取的数据不能被改变只能被用来读取。这个性质常常被用在函数参数上例如下面的代码用来计算一块数据的CRC校验这个函数只需要读取原始数据不需要(也不可以)改变原始数据因此就需要在形参指针上使用const修饰符short int getDataCRC(const char *pData, int len)
{short int crc 0x0000;// 计算CRCreturn crc;
}
2. void型指针关键字void并不是一个真正的数据类型它体现的是一种抽象指明不是任何一种类型一般有2种使用场景函数的返回值和形参;定义指针时不明确规定所指数据的类型也就意味着可以指向任意类型。指针变量也是一种变量变量之间可以相互赋值那么指针变量之间也可以相互赋值例如int a 20;
int b a;
int *p1 a;
int *p2 p1;
变量a赋值给变量b指针p1赋值给指针p2注意到它们的类型必须是相同的a和b都是int型p1和p2都是指向int型所以可以相互赋值。那么如果数据类型不同呢必须进行强制类型转换。例如int a 20;
int *p1 a;
char *p2 (char *)p1;
内存模型如下p1指针指向的是int型数据现在想把它的值(0x11223344)赋值给p2但是由于在定义p2指针时规定它指向的数据类型是char型因此需要把指针p1进行强制类型转换也就是把地址0x11223344处的数据按照char型数据来看待然后才可以赋值给p2指针。如果我们使用void *p2来定义p2指针那么在赋值时就不需要进行强制类型转换了例如int a 20;
int *p1 a;
void *p2 p1;
指针p2是void*型意味着可以把任意类型的指针赋值给p2但是不能反过来操作也就是不能把void*型指针直接赋值给其他确定类型的指针而必须要强制转换成被赋值指针所指向的数据类型如下代码必须把p2指针强制转换成int*型之后再赋值给p3指针int a 20;
int *p1 a;
void *p2 p1;
int *p3 (int *)p2;
我们来看一个系统函数void* memcpy(void* dest, const void* src, size_t len);
第一个参数类型是void*这正体现了系统对内存操作的真正意义它并不关心用户传来的指针具体指向什么数据类型只是把数据挨个存储到这个地址对应的空间中。第二个参数同样如此此外还添加了const修饰符这样就说明了memcpy函数只会从src指针处读取数据而不会修改数据。3. 空指针和野指针一个指针必须指向一个有意义的地址之后才可以对指针进行操作。如果指针中存储的地址值是一个随机值或者是一个已经失效的值此时操作指针就非常危险了一般把这样的指针称作野指针C代码中很多指针相关的bug就来源于此。3.1 空指针不指向任何东西的指针在定义一个指针变量之后如果没有赋值那么这个指针变量中存储的就是一个随机值有可能指向内存中的任何一个地址空间此时万万不可以对这个指针进行写操作因为它有可能指向内存中的代码段区域、也可能指向内存中操作系统所在的区域。一般会将一个指针变量赋值为NULL来表示一个空指针而C语言中NULL实质是 ((void*)0) 在C中NULL实质是0。在标准库头文件stdlib.h中有如下定义#ifdef __cplusplus#define NULL 0
#else #define NULL ((void *)0)
#endif
3.2 野指针地址已经失效的指针我们都知道函数中的局部变量存储在栈区通过malloc申请的内存空间位于堆区如下代码int *p (int *)malloc(4);
*p 20;
内存模型为在堆区申请了4个字节的空间然后强制类型转换为int*型之后赋值给指针变量p然后通过*p设置这个地址中的值为14这是合法的。如果在释放了p指针指向的空间之后再使用*p来操作这段地址那就是非常危险了因为这个地址空间可能已经被操作系统分配给其他代码使用如果对这个地址里的数据强行操作程序立刻崩溃的话将会是我们最大的幸运int *p (int *)malloc(4);
*p 20;
free(p);
// 在free之后就不可以再操作p指针中的数据了。
p NULL; // 最好加上这一句。
四、指向不同数据类型的指针1. 数值型指针通过上面的介绍指向数值型变量的指针已经很明白了需要注意的就是指针所指向的数据类型。2. 字符串指针字符串在内存中的表示有2种用一个数组来表示例如char name1[8] zhangsan;用一个char *指针来表示例如char *name2 zhangsan;name1在内存中占据8个字节其中存储了8个字符的ASCII码值name2在内存中占据9个字节因为除了存储8个字符的ASCII码值在最后一个字符n的后面还额外存储了一个\0用来标识字符串结束。对于字符串来说使用指针来操作是非常方便的例如变量字符串name2:char *name2 zhangsan;
char *p name2;
while (*p ! \0)
{printf(%c , *p);p p 1;
}
在while的判断条件中检查p指针指向的字符是否为结束符\0。在循环体重打印出当前指向的字符之后对指针比那里进行自增操作因为指针p所指向的数据类型是char每个char在内存中占据一个字节因此指针p在自增1之后就指向下一个存储空间。也可以把循环体中的2条语句写成1条语句printf(%c , *p);
假如一个指针指向的数据类型为int型那么执行p p 1;之后指针p中存储的地址值将会增加4因为一个int型数据在内存中占据4个字节的空间如下所示思考一个问题void*型指针能够递增吗如下测试代码int a[3] {1, 2, 3};
void *p a;
printf(1: p 0x%x \n, p);
p p 1;
printf(2: p 0x%x \n, p);
打印结果如下1: p 0x733748c0
2: p 0x733748c1
说明void*型指针在自增时是按照一个字节的跨度来计算的。3. 指针数组与数组指针这2个说法经常会混淆至少我是如此先看下这2条语句int *p1[3]; // 指针数组
int (*p2)[3]; // 数组指针
3.1 指针数组第1条语句中中括号[]的优先级高因此与p1先结合表示一个数组这个数组中有3个元素这3个元素都是指针它们指向的是int型数据。可以这样来理解如果有这个定义char p[3]很容易理解这是一个有3个char型元素的数组那么把char换成int*意味着数组里的元素类型是int*型(指向int型数据的指针)。内存模型如下(注意三个指针指向的地址并不一定是连续的)如果向指针数组中的元素赋值需要逐个把变量的地址赋值给指针元素int a 1, b 2, c 3;
char *p1[3];
p1[0] a;
p1[1] b;
p1[2] c;
3.2 数组指针第2条语句中小括号让p2与*结合表示p2是一个指针这个指针指向了一个数组数组中有3个元素每一个元素的类型是int型。可以这样来理解如果有这个定义int p[3]很容易理解这是一个有3个char型元素的数组那么把数组名p换成是*p2也就是p2是一个指针指向了这个数组。内存模型如下(注意指针指向的地址是一个数组其中的3个元素是连续放在内存中的)在前面我们说到取地址操作符用来获得一个变量的地址。凡事都有特殊情况对于获取地址来说下面几种情况不需要使用操作符字符串字面量作为右值时就代表这个字符串在内存中的首地址数组名就代表这个数组的地址也等于这个数组的第一个元素的地址;函数名就代表这个函数的地址。因此对于一下代码三个printf语句的打印结果是相同的int a[3] {1, 2, 3};
int (*p2)[3] a;
printf(0x%x \n, a);
printf(0x%x \n, a);
printf(0x%x \n, p2);
思考一下如果对这里的p2指针执行p2 p2 1;操作p2中的值将会增加多少答案是12个字节。因为p2指向的是一个数组这个数组中包含3个元素每个元素占据4个字节那么这个数组在内存中一共占据12个字节因此p2在加1之后就跳过12个字节。4. 二维数组和指针一维数组在内存中是连续分布的多个内存单元组成的而二维数组在内存中也是连续分布的多个内存单元组成的从内存角度来看一维数组和二维数组没有本质差别。和一维数组类似二维数组的数组名表示二维数组的第一维数组中首元素的首地址用代码来说明int a[3][3] {{1,2,3}, {4,5,6}, {7,8,9}}; // 二维数组
int (*p0)[3] NULL; // p0是一个指针指向一个数组
int (*p1)[3] NULL; // p1是一个指针指向一个数组
int (*p2)[3] NULL; // p2是一个指针指向一个数组
p0 a[0];
p1 a[1];
p2 a[2];
printf(0: %d %d %d \n, *(*p0 0), *(*p0 1), *(*p0 2));
printf(1: %d %d %d \n, *(*p1 0), *(*p1 1), *(*p1 2));
printf(2: %d %d %d \n, *(*p2 0), *(*p2 1), *(*p2 2));
打印结果是0: 1 2 3
1: 4 5 6
2: 7 8 9
我们拿第一个printf语句来分析p0是一个指针指向一个数组数组中包含3个元素每个元素在内存中占据4个字节。现在我们想获取这个数组中的数据如果直接对p0执行加1操作那么p0将会跨过12个字节(就等于p1中的值了)因此需要使用解引用操作符*把p0转为指向int型的指针然后再执行加1操作就可以得到数组中的int型数据了。5. 结构体指针C语言中的基本数据类型是预定义的结构体是用户定义的在指针的使用上可以进行类比唯一有区别的就是在结构体指针中需要使用-箭头操作符来获取结构体中的成员变量例如typedef struct
{int age;char name[8];
} Student;Student s;
s.age 20;
strcpy(s.name, lisi);
Student *p s;
printf(age %d, name %s \n, p-age, p-name);
看起来似乎没有什么技术含量如果是结构体数组呢例如Student s[3];
Student *p s;
printf(size of Student %d \n, sizeof(Student));
printf(1: 0x%x, 0x%x \n, s, p);
p;
printf(2: 0x%x \n, p);
打印结果是size of Student 12
1: 0x4c02ac00, 0x4c02ac00
2: 0x4c02ac0c
在执行p操作后p需要跨过的空间是一个结构体变量在内存中占据的大小(12个字节)所以此时p就指向了数组中第2个元素的首地址内存模型如下6. 函数指针每一个函数在经过编译之后都变成一个包含多条指令的集合在程序被加载到内存之后这个指令集合被放在代码区我们在程序中使用函数名就代表了这个指令集合的开始地址。函数指针本质上仍然是一个指针只不过这个指针变量中存储的是一个函数的地址。函数最重要特性是什么可以被调用因此当定义了一个函数指针并把一个函数地址赋值给这个指针时就可以通过这个函数指针来调用函数。如下示例代码int add(int x,int y)
{return xy;
}int main()
{int a 1, b 2;int (*p)(int, int);p add;printf(%d %d %d\n, a, b, p(a, b));
}
前文已经说过函数的名字就代表函数的地址所以函数名add就代表了这个加法函数在内存中的地址。int (*p)(int, int);这条语句就是用来定义一个函数指针它指向一个函数这个函数必须符合下面这2点(学名叫函数签名)有2个int型的参数;有一个int型的返回值。代码中的add函数正好满足这个要求因此可以把add赋值给函数指针p此时p就指向了内存中这个函数存储的地址后面就可以用函数指针p来调用这个函数了。在示例代码中函数指针p是直接定义的那如果想定义2个函数指针难道需要像下面这样定义吗int (*p)(int, int);
int (*p2)(int, int);
这里的参数比较简单如果函数很复杂这样的定义方式岂不是要烦死可以用typedef关键字来定义一个函数指针类型typedef int (*pFunc)(int, int);
然后用这样的方式pFunc p1, p2;来定义多个函数指针就方便多了。注意只能把与函数指针类型具有相同签名的函数赋值给p1和p2也就是参数的个数、类型要相同返回值也要相同。注意这里有几个小细节稍微了解一下在赋值函数指针时使用p a;也是可以的使用函数指针调用时使用(*p)(a, b);也是可以的。这里没有什么特殊的原理需要讲解最终都是编译器帮我们处理了这里的细节直接记住即可。函数指针整明白之后再和数组结合在一起函数指针数组。示例代码如下int add(int a, int b) { return a b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }int main()
{int a 4, b 2;int (*p[4])(int, int);p[0] add;p[1] sub;p[2] mul;p[3] divide;printf(%d %d %d \n, a, b, p[0](a, b));printf(%d - %d %d \n, a, b, p[1](a, b));printf(%d * %d %d \n, a, b, p[2](a, b));printf(%d / %d %d \n, a, b, p[3](a, b));
}
这条语句不太好理解:int (*p[4])(int, int);先分析中间部分标识符p与中括号[]结合(优先级高)所以p是一个数组数组中有4个元素然后剩下的内容表示一个函数指针那么就说明数组中的元素类型是函数指针也就是其他函数的地址内存模型如下如果还是难以理解那就回到指针的本质概念上指针就是一个地址这个地址中存储的内容是什么根本不重要重要的是你告诉计算机这个内容是什么。如果你告诉它这个地址里存放的内容是一个函数那么计算机就去调用这个函数。那么你是如何告诉计算机的呢就是在定义指针变量的时候仅此而已五、总结我已经把自己知道的所有指针相关的概念、语法、使用场景都作了讲解就像一个小酒馆的掌柜把自己的美酒佳肴都呈现给你但愿你已经酒足饭饱如果以上的内容太多一时无法消化那么下面的这两句话就作为饭后甜点为您奉上在以后的编程中如果遇到指针相关的困惑就想一想这两句话也许能让你茅塞顿开。指针就是地址地址就是指针。指针就是指向内存中的一块空间至于如何来解释/操作这块空间由这个指针的类型来决定。另外还有一点嘱咐那就是学习任何一门编程语言一定要弄清楚内存模型内存模型内存模型祝您好运【原创声明】作者道哥(公众号: IOT物联网小镇)知乎道哥B站道哥分享掘金道哥分享CSDN道哥分享END推荐阅读专辑|Linux文章汇总专辑|程序人生专辑|C语言我的知识小密圈关注公众号后台回复「1024」获取学习资料网盘链接。欢迎转发在看评论~