今天我们来见识一下c语言里让万千少年少女从入门到入坟的一道大门槛——指针
目录
1.指针是什么?
2.指针和指针类型
3.野指针
4. 指针运算
5. 指针和数组
6. 二级指针
7. 指针数组
指针理解的2个要点:
1. 指针是内存中一个最小单元的编号,也就是地址
2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
我们知道内存是电脑上的存储设备,它有4G、8G、16G等等,我们的程序在运行时会加载到内存里,也会使用内存空间。
在内存里,一个内存单元的大小是一个字节, 而每一个内存单元都对应一个编号,这个编号就叫做地址,而地址又有一种叫法叫做指针。
我们写一个程序,比如int a = 5;然后我们&a
此时,&a就对应着0x0012ff40,我们把&a放入到一个变量里
int* pa = &a;
pa是用来存放地址的,pa就被叫做指针变量,因为a是int类型,所以pa就是int*类型,这个*就告诉我们pa的类型是指针,我们可以写个程序来看看
那么指针变量怎么使用呢?指针变量的使用就是对他进行解引用操作,比如:
地址是如何编址的呢?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电 平(低电压)就是(1或者0); 那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空间进行编址。
同样的方法,我们可以计算出64位机器的,他有64根地址线,这里就不再计算。
此时我们就可以得出如下结论:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
在64位机器上,如果有64个地址线,一个指针变量的大小是8个字节,才能存放一个地址。
总结一下就是:指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节。
我们知道变量有多种类型,比如int,float,double,char等等,那么指针有没有类型呢?有没有一种通用类型的指针,叫做ptr,可以用来存放所有的指针呢?答案是没有,指针既然有不同的类型,那就肯定有它自己存在的道理。
我们来看一段代码
我们可以看到,我们对pa解引用,将a的值改为0,在内存里a的值也确实变为了0。那我们用一个char*类型的指针来存放a的地址,会发生什么呢?
我们发现,a的值里的44变为了00,这是为什么呢?首先我们要知道,地址是以16进制展示的,但数据存储是2进制的,一个16进制位是4个二进制位,而8个二进制位占一个字节,所以a里边的44占用一个字节,33占用一个字节,22,11也同样占用一个字节,而char类型占用1个字节,我们发现在对pc解引用时只改变了一个字节,也就是44变为了00,而int*类型的指针解引用时访问了4个字节。
我们发现:
指针类型其实是有意义的
1. 指针类型决定了,指针进行解引用操作的时候,一次性访问几个字节,访问权限的大小
如果是char*的指针,解引用访问1个字节
如果是int*的指针,解引用访问4个字节
float* ----------------- 4个字节
2. 指针类型决定指针的步长(指针+1到底跳过几个字节)
字符指针+1,跳过1个字节
整型指针+1,跳过4个字节
如果我们想把a完全变为0,我们可以使用for循环循环4次,即可把4个字节全变为0。其他类型的指针以此类推。
指针类型的意义:指针类型的不同,提供了不同视角去观看和访问指针,比如char*类型指针一次访问一个字节,+1跳过一个字节,int*指针一次访问4个字节,+1跳过4个字节。
总结:指针+-整数
对于指针加减整数,我们看个例子,比如:int* p; p+4对应着sizeof(int)*4对应16个字节
指针的解引用
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的非常危险的,野指针的形成有三种原因:
1. 指针未初始化
2. 指针越界访问
3. 指针指向的空间释放。
要想规避野指针,我们可以
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
如果我们创建一个指针不知道它该指向哪里,那就让它指向NULL,NULL的本质就是0 ,是专门用来初始化指针的。
4.1 指针+-整数
我们看这样一段代码
数组名是首元素地址,我们让p等于数组首元素地址,然后通过*p(解引用)的方法将数组元素初始化为1, 然后我们再用解引用的方法打印数组的元素
在内存里是这样的:
4.2 指针-指针
我们看这样一段代码
我们先看看它们在内存是怎么画的
指针相减的绝对值,是两个指针之间元素的数量,如果上面代码是&arr[4]-&arr[0],那么结果就是4,ps:这里大家可能会疑惑为什么arr[5]可以使用,这是因为:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。也就是说,我们可以写出与arr[5]比较的代码,而不能写与arr[-1]比较的代码,注意,这里是比较大小,不是访问,我们仍然不能越界访问,有的编译器可能对于比较arr[-1]不会报错,但是这是错误的,因为不是所有的编译器都可以,c语言标准只规定可以与arr[5]这样的代码进行比较。
知道了指针-指针的规则,我们就可以写一个strlen函数(之前我们使用过循环和递归解决)
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
我们先看这样一段代码
可见数组名和数组首元素的地址是一样的。数组名是首元素地址(两种情况除外,我之前有过详细讲解,大家感兴趣可以看一看)
(2条消息) 数组传参究竟是怎么一回事?_KLZUQ的博客-CSDN博客_传参 数组
1.指针和数组是不同的对象
指针是一种变量,用来存放地址,大小为4/8字节
数组是一组相同类型元素的集合,可以存放多个元素,数组大小取决于数组类型和元素个数
2.数组的数组名是首元素地址,地址可以放在指针变量中,可以通过指针访问数组
概念:指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是二级指针 。
#include
int main() {
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
ppa就是一个二级指针,ppa前面的第一个*代表它是一个指针,*前面的int*代表了ppa所指向的类型。
ppa是指针变量的地址,而不是地址的地址
对于ppa,*ppa访问的是pa,而**ppa访问的是a。
既然有二级指针,同理也就是有三级、四级指针等等,不过我们几乎不使用。
指针数组,从名字来看,我们知道有整形数组,浮点型数组,字符数组等等,那么指针数组也是一个数组,只不过里边存放的元素是指针而已。
那么指针数组有什么用呢?我们来看这样一个例子:
我们可以用指针数组来模仿一个三行三列的数组。
在内存里大概就是这样子的,数组arr里存放了a1,a2,a3三个数组的地址,三个地址指向三个数组。这里再解释一下为什么arr[i][j]就可以直接访问数组的元素,比如我们访问a1里的元素,我们使用的是arr[0][j]的方法,arr[0][j]只是展示给我们看的,而编译器会把它转变为*(arr[0]+j)这种形式,与我们之前说过的通过指针访问数组是同一个道理。
以上就是初阶指针的全部内容,希望大家可以有所收获。
如有错误,还请指正。