一.内存与地址1.1 内存在讲解内存和地址之前我想给大家举个例子在学校的宿舍楼你所在的宿舍楼没有门牌号你的朋友想去找你就只能一个一个房间的挨个找这样效率很低而且很费朋友。但是你想到了根据楼层等信息给每个寝室标记上各自的门牌号那么你只需要告诉你朋友你所在寝室的门牌号这样他就能很快的找到你。如果把上面的例子对照到计算机中又是怎么样呢我们知道计算机上CPU中央处理器在处理数据的时候需要的数据是在内存中读取的处理后的数据也会放回内存中那我们买电脑的时候电脑上内存是 8GB/16GB/32GB 等那这些内存空间如何高效的管理呢其实与我们所举的寝室楼的例子差不多内存会划分出一个个的内存单元每个内存单元的大小为一个字节。其中每个内存单元就相当于一个学生宿舍我们都知道一个字节的空间可以存放8个比特位那么我这么说应该能帮助大家更好地理解每个内存单元也都有⼀个编号这个编号就相当于宿舍房间的⻔牌号有了这个内存单元的编号CPU就可以快速找到⼀个内存空间。⽣活中我们把⻔牌号也叫地址在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫---指针。我们可以这样理解内存单元的编号地址指针。1.2 如何理解编址⾸先必须理解计算机内是有很多的硬件单元⽽硬件单元是要互相协同⼯作的。所谓的协同⾄少相互之间要能够进⾏数据传递。但是硬件与硬件之间是互相独⽴的那么如何通信呢答案很简单⽤线连起来。⽽CPU和内存之间也是有⼤量的数据交互的所以两者必须也⽤线连起来。不过我们今天关⼼⼀组线叫做地址总线。CPU访问内存中的某个字节空间必须知道这个字节空间在内存的什么位置⽽因为内存中字节很多所以需要给内存进⾏编址(就如同宿舍很多需要给宿舍编号⼀样)。计算机中的编址并不是把每个字节的地址记录下来⽽是通过硬件设计完成的。钢琴、吉他 上⾯没有写上“剁、来、咪、发、唆、拉、西”这样的信息但演奏者照样能够准确找到每⼀个琴弦的每⼀个位置这是为何因为制造商已经在乐器硬件层⾯上设计好了并且所有的演奏者都知道。本质是⼀种约定出来的共识硬件编址也是如此我们可以简单理解32位机器有32根地址总线每根线只有两态表示0,1【电脉冲有无】那么一根线就能表示2种含义2根线就能表示4种含义依次类推。32根地址线就能表示2^32种含义每一种含义都代表一个地址。地址信息被下达给内存在内存上就可以找到该地址对应的数据将数据再通过数据总线传入CPU内寄存器。二.指针变量与地址2.1 取地址操作符在C语言中我们使用来获得变量的地址每当我们创建了一个变量其实就是在向内存申请一块空间如下代码所示#includestdio.h int main() { int a 10; printf(%p, a); return 0; }在printf函数中要打印a的地址到屏幕上我们使用a前面的占位符也得跟着变变为%p专门用来指地址的。note地址在内存中的存储是连续的。a取出的是a所占4个字节中地址较⼩的字节的地址。我们知道了a的第一个字节的地址就可以顺藤摸瓜找出a的四个字节的地址。2.2 指针变量与解引用操作符*2.2.1 指针变量我们得到了a的地址现在想把这个地址存入到一个变量里去这时我们应该创建指针变量来存储地址。#includestdio.h int main() { int a 10; int* pa a;//取出a的地址存入到指针变量pa中 return 0; }指针变量也是⼀种变量这种变量就是⽤来存放地址的存放在指针变量中的值都会理解为地址。2.2.2 指针变量的类型我们之前学过变量是有类型的那么指针变量也是变量那指针变量的类型我们该怎么确定呢在上面的代码中pa的前面写的是int*其中*说明pa是一个指针变量而int说明这个指针变量指向的是一个整型变量。那如果a是一个字符型的变量呢#includestdio.h int main() { char a c; char* pa a;//取出a的地址存入到指针变量pa中 return 0; }以此类推pa的前面写的是char*其中*说明pa是一个指针变量而char说明这个指针变量指向的是一个字符型变量。2.2.3 解引用操作符*我们已经学会了将地址存入到指针变量中那么我们在以后要使用这个地址该怎么使用呢这时我们就要用到解引用操作符*。在我的理解中解引用就是追溯本源的意思为什么这么说呢#include stdio.h int main() { int a 100; int* pa a; *pa 0; return 0; }以上代码可以很好地解释pa是一个指针变量其中存储着a的地址那么对pa解引用*pa就是追溯pa的本源pa的本源是a。所以这里*pa0其实就是把0赋值给a。那么会有人想这样多麻烦为啥不直接写a0呢其实这里是把a的修改交给了pa来操作这样对a的修改就多了一种途径写代码就会更加灵活后期慢慢就能理解了。2.3 指针变量的大小变量总是有大小的指针变量也是变量那么指针变量的大小根据什么来看呢是指针变量所指的变量类型吗前面的内容我们了解到32位机器假设有32根地址总线每根地址线出来的电信号转换成数字信号后是1或者0那我们把32根地址线产生的2进制序列当做一个地址那么一个地址就是32个bit位需要4个字节才能存储。如果指针变量是用来存放地址的那么指针变量的大小就得是4个字节的空间才可以。同理64位机器假设有64根地址线一个地址就是64个二进制位组成的二进制序列存储起来就需要8个字节的空间指针变量的大小就是8个字节。让我们看看以下代码在不同环境下运行的结果#includestdio.h int main() { printf(%zd\n, sizeof(char*)); printf(%zd\n, sizeof(short*)); printf(%zd\n, sizeof(int*)); printf(%zd\n, sizeof(double*)); return 0; }x64环境下x86环境下所以指针变量只与当前的环境有关如果是32位的环境指针变量的大小就是4。如果是64位的环境指针变量的大小就是8。三.指针变量类型的意义指针变量的⼤⼩和类型⽆关只要是指针变量在同⼀个平台下⼤⼩都是⼀样的为什么还要有各种各样的指针类型呢3.1 指针的解引用对比下面两个代码//代码1 #includestdio.h int main() { int n 0x11223344; int* pi n; *pi 0; return 0; }//代码2 #includestdio.h int main() { int n 0x11223344; char* pc (char*)n; *pc 0; return 0; }调试我们可以看到代码1会将n的4个字节全部改为0但是代码2只是将n的第⼀个字节改为0。指针的类型决定了对指针解引⽤的时候有多⼤的权限⼀次能操作⼏个字节。⽐如char*的指针解引⽤就只能访问⼀个字节⽽int*的指针的解引⽤就能访问四个字节。3.2 指针加减整数看这个代码通过调试来观察地址的变化#includestdio.h int main() { int n 10; char* pc (char*)n; int* pi n; printf(%p\n, n); printf(%p\n, pc); printf(%p\n, pc 1); printf(%p\n, pi); printf(%p\n, pi 1); return 0; }代码运行结果如下通过代码我们知道pc是char*类型的指针变量pi是int*类型的指针变量。那么pc1只能跨过1个字节而pi1能跨过4个字节。这就是指针变量的类型差异带来的变化。指针1其实跳过1个指针指向的元素。指针可以1那也可以-1。所以指针的类型决定了指针向前或者向后⾛⼀步有多⼤距离。3.3 void*指针在指针类型中有⼀种特殊的类型是void *类型的可以理解为⽆具体类型的指针或者叫泛型指针这种类型的指针可以⽤来接受任意类型地址。但是也有局限性void*类型的指针不能直接进⾏指针的-整数和解引⽤的运算。例子#includestdio.h int main() { int a 10; int* pa a; char* pc a; return 0; }在上⾯的代码中将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警告如下图是因为类型不兼容。⽽使⽤void*类型就不会有这样的问题。使用void*的指针接收地址#includestdio.h int main() { int a 10; void* pa a; void* pc a; *pa 10; *pc 0; return 0; }这⾥我们可以看到void*类型的指针可以接收不同类型的地址但是⽆法直接进⾏指针运算。那么void*类型的指针到底有什么⽤呢⼀般void*类型的指针是使⽤在函数参数的部分⽤来接收不同类型数据的地址这样的设计可以实现泛型编程的效果。使得⼀个函数来处理多种类型的数据在以后我们会讲解。四.指针运算指针的基本运算有三种分别是1指针- 整数2指针-指针3指针的关系运算4.1 指针加减整数我们在学习数组时知道数组中的元素在内存中是连续存放的只要知道了第一个元素的地址我们就可以找到所有的元素。#includestdio.h int main() { int arr[10] { 1,2,3,4,5,6,7,8,9,10 }; int* p arr[0];//首元素地址存入p中 int i 0; int sz sizeof(arr) / sizeof(arr[0]); for (i 0; i sz; i) { printf(%d , *(p i));//pi 这⾥就是指针整数 } return 0; }我们可以通过这种指针的方法对数组进行遍历。4.2 指针减指针#include stdio.h int my_strlen(char* s) { char* p s; while (*p ! \0) p; return p - s; } int main() { printf(%d\n, my_strlen(abc)); return 0; }我们可以通过指针减指针对strlen函数进行模拟实现。4.3 指针的关系运算#include stdio.h int main() { int arr[10] { 1,2,3,4,5,6,7,8,9,10 }; int* p arr[0]; int sz sizeof(arr) / sizeof(arr[0]); while (p arr sz) //指针的⼤⼩⽐较 { printf(%d , *p); p; } return 0; }