目录
- 1.内存和地址
- 1.1 内存
- 1.2 对编址的理解
- 2.指针变量和地址
- 2.1 取地址操作符(&)
- 2.2 指针变量和解引用操作符(*)
- 2.2.1 指针变量
- 2.2.2 拆解指针类型
- 2.2.3 解引用操作符
- 2.3 指针变量的大小
- 3. 指针变量类型的意义
- 3.1 指针的解引用
- 3.2 指针+ -整数
- 3.3 void* 指针
- 4. 指针运算
- 4.1 指针 + - 整数
- 4.2 指针 - 指针
- 4.3 指针的关系运算
1.内存和地址
1.1 内存
说起内存,我们可以举一个生活例子,用宿舍楼找房间的例子。
在一栋有很多房间的宿舍楼里,如果房间没有编号,找人就只能一间一间找,效率很低。为了更快找到目标房间,可以按照楼层给房间编号,例如:
一楼:101、102、103……
二楼:201、202、203……
这样一来,只要知道房间号,就能快速定位房间。
对应到计算机中:
宿舍楼可以理解为内存,
房间可以理解为内存中的存储单元,
房间号就相当于内存地址。
所以,内存地址的作用就是帮助计算机快速找到数据存放的位置。
对应到计算机中,计算机上的CPU(中央处理器)在处理数据时,需要的数据是放在内存中读取的,处理后的数据是放在内存中读取的,电脑的内存一般是8GB/16GB/32GB等,电脑将内存划分为一个一个的内存单元,每个内存单元的大小取1个字节。
注意:一个比特位可以存储一个二进制位的位1或者0。
1Byte=8bit1KB=1024Byte1MB=1024KB1GB=1024MB1TB=1024GB1PB=1024TB每个内存单元就像一个学生宿舍,里面可以存放数据。
一个字节有8 个比特位,就像一个宿舍住 8 个人,每个人代表 1 个比特位。
每个内存单元都有一个编号,这个编号就像宿舍门牌号。CPU 通过这个编号,就能快速找到对应的内存位置。
在计算机中,内存单元的编号叫做地址。
在 C 语言中,地址又叫做指针。
内存地址的编号,地址,指针三者之间等价。
1.2 对编址的理解
对编址的理解,要先理解,在计算机内有许多的硬件单元,硬件单元是要互相协同工作的,相互之间能够进行数据传递,计算机中用线连起来,对编址的理解就要了解地址总线。
CPU 访问内存时,会先给出一个地址。内存根据这个地址找到对应位置,再把数据交给 CPU。
地址是通过地址总线传递的。
地址总线由多根线组成,每根线只能表示0 或 1。
如果有 32 根地址线,就可以表示:232个不同地址
也就是说,CPU 最多可以找到 232个内存单元。
编址就是给内存中的每个存储单元分配一个唯一编号。
如果实在不理解,看下图:
2.指针变量和地址
2.1 取地址操作符(&)
在C语言中,变量创建的本质就是向内存中申请空间。
上述代码中,创建了整型变量a, 向内存中申请了4个字节,用来存放整数10,每个字节都有地址。
上述的代码就是创建了整型变量a,内存中申请4个字节,⽤于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:
//因为是在X64平台下,所以有16位,如果在X86平台下,就是8位0x0000003C588FF8C40x0000003C588FF8C80x0000003C588FF8CC0x0000003C588FF8D0讲解了这么多,要想得到a的地址,就要学习取地址操作符(&)
intmain(){inta=11;&a;//& -> 取出a的地址printf("%p\n",&a);return0;}&a 取出的是a所占4个字节中地址较小的字节的地址,虽然整型占4个字节,我们只需知道第一个字节,顺藤摸瓜访问就可以访问到4个字节的数据。
2.2 指针变量和解引用操作符(*)
2.2.1 指针变量
我们通过取地址操作符(&)拿到变量的地址是一个数值,这个数值有时候是需要存储起来的,方便后期进行使用,我们就可以将地址存放在指针变量中。
例如:
intmain(){inta=10;int*pa=&a;//取出a的地址并存储到指针变量pa中return0;}指针变量也是一种变量 , 指针变量是用来存放地址的,存放在指针变量中的值都会理解成地址。
2.2.2 拆解指针类型
指针变量pa的类型是int*,我们应该这样来理解指针变量:
2.2.3 解引用操作符
我们将地址通过指针变量保存起来,要通过解引用操作符*来进行访问。解引用操作符*的作用是:通过指针中保存的地址,找到该地址对应的变量。
2.3 指针变量的大小
指针变量是用来存放地址的,地址有多大,指针变量就需要多大的空间。
32 位平台指针大小通常是4 字节,64 位平台指针大小通常是8 字节。
结论:
- 32位平台下(X86)环境下地址是32个bit位,指针变量的大小是4个字节
- 64位平台下(X64)环境下地址是64个bit位,指针变量的大小是8个字节
- 注意:指针变量的大小和类型是无关的,只要是指针类型的变量,在相同平台下,大小都是相同的。
3. 指针变量类型的意义
3.1 指针的解引用
//代码1intmain(){inta=0x11223344;int*pa=&a;*pa=0;return0;}//代码2intmain(){inta=0x11223344;char*pa=&a;*pa=0;return0;}
结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能访问几个字节)。
比如:char*的指针解引用就只能访问一个字节,而int*的指针的解引用就可以访问四个字节。
3.2 指针+ -整数
intmain(){inta=0x11223344;int*pa=&a;char*pc=&a;printf("pa = %p\n",pa);printf("pa + 1 = %p\n",pa+1);printf("pc = %p\n",pc);printf("pc + 1 = %p\n",pc+1);return0;}结论:指针类型决定了指针向前一步或者向后一步走多大(步长)。
3.3 void* 指针
void*指针就是一种"通用指针",他可以保存任意类型变量的地址,例如:int、char、float,可以理解为无意义的指针(或者叫泛型指针),但是,void*类型的指针不能直接运算或取值,使用前要先转成具体类型指针。
intmain(){inta=10;int*p=&a;char*pa=&a;return0;}
上述代码中,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警
告,是因为类型不兼容,如果使用void*就没问题。
下面一段代码:
intmain(){inta=10;charb='w';int*p=&a;void*pa=&a;void*pb=&b;&pa=100;&pb='x';return0;}VS编译代码的结果:
从上述中我们可以看到,void*类型的指针可以接受不同的地址,但是不能直接进行指针的运算。
一般void*类型的指针是使用在函数参数的部分,用来接收不同数据的地址,这样的设计可以实现泛型编程的效果,使得一个含函数用来处理多个数据。
4. 指针运算
指针运算一般分为三种,分别是:
- 指针+ -整数
- 指针-指针
- 指针的关系运算
4.1 指针 + - 整数
这里需要回顾一下数组的知识点,如果忘记的话点击链接就能跳转了:深入浅出数组。
数组在内存中是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就能找到后面的所有元素。
intarr[10]={1,2,3,4,5,6,7,8,9,10};//第一种写法intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};int*p=&arr[0];//指针变量p取数组arr中的首元素for(inti=0;i<10;i++){printf("%d ",*(p+i));//*(p + i) 就是指针变量指向数组下标为i所对应元素的地址}return0;}//第二种写法intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};int*p=&arr[0];//指针变量p取数组arr中的首元素for(inti=0;i<10;i++){printf("%d ",*p);//先打印p所指向对象的指针p++;//p = p + 1}return0;}4.2 指针 - 指针
指针减指针得到的是指针与指针之间元素的个数。也可以换一种方式来理解,就是指针 + - 一个整数得到的还是指针,将指针放在等号的同一边,就变成了指针减指针。前提:两个指针指向同一块空间。
intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};printf("%d\n",&arr[9]-&arr[0]);return0;}
介绍完指针减指针之后,我们来模拟实现一下strlen函数。
回顾一下strlen函数,相关介绍:strlen
size_tmy_strlen(char*p){char*start=&p[0];//存储数组首元素的地址while(*p!='\0')//当指针变量指到\0时,循环结束p++;returnp-start;//指针减指针}intmain(){charstr[20]="abcdef";// [a b c d e f \0]size_tlen=my_strlen(str);//传数组名,传的就是数组的地址printf("%zu\n",len);}我在配合一张图片,让知识理解的更深。
4.3 指针的关系运算
常见的关系运算有以下操作符:
>>=<<===!=指针就是地址,地址就是内存单元的编号,编号是一个数值,随着下的标增长,地址是由小到大变化的, 所以指针与指针之间可以用来进行关系比较。
intmain(){intarr[]={1,2,3,4,5,6,7,8,9,10};int*p=arr;//将数组的地址存给指针变量pintsz=sizeof(arr)/sizeof(arr[0]);//计算的是数组的大小//注意:因为数组在内存中是连续存放的,所以&arr[sz]编译不会报错,因为在内存中arr[sz]//的地址是已经存在的如果是直接使用arr[sz],不加取地址操作符,就构成了越界访问while(p<&arr[sz])//运用了指针的关系运算{printf("%d ",*p);p++;}return0;}完