1.内存和地址
在内存中,一个内存单元可以放8位数据,也就是8-bit,我们可以为内存单元进行编号,也就是我们所说的地址,在C语言中,我们可以认为 地址 == 指针,指针里存放的就是地址。
在32位的电脑中,有32根地址总线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么用二进制来进行表示就可以得到 2^32,也就是我们可以为内存编写的最大地址编号为2^32。64位同理可知。
2.指针变量
& 取地址操作符
在我们写代码的是时候,如果我们想要知道某个元素的地址,可以使用下图的方式
来取出整型a的地址;
int a = 0;
// &表示取出a的地址
int *p = &a; //*p 表示p是指针变量 int表示指针指向地址的内容是int类型
printf("%p", p); //打印a的地址
我们打印出来看下a的地址(这是以16进制表示的地址)
* 解引用操作符
如果我们已经知道一个元素的地址了,那该怎么看这个地址中存放的是什么呢?
int a = 0;
int* p = &a;
printf("%d", *p); //在p前面加 * 对p进行解引用得到指向地址的内容
打印出来看下,的确就是a的值。
指针变量的大小
在windows vs2022中有符号整型变量的的大小是4-byte 一个有符号整型变量所占的内存空间是32-bit,表示大小最大位 2 ^31 -1 (因为最高位用来表示正负不参与计数)。
以x86环境为例 有32根地址总线 可以表示2^32的内存编号 ,最大位2^32 - 1,明显一个指针变量占用了32-bit的空间,那么同样的大小也是4-byte
在x64环境下 有64根地址总线 大小是8-byte
指针变量的类型以及void *指针
我们一般把指针也称为地址,通常指针变量这么表示: 存放的数据的类型 *指针变量名 = 地址
如图
int a = 0;
// &表示取出a的地址
int *p = &a; //*p 表示p是指针变量 int表示指针指向地址的内容是int类型
char a = 'a';
char *p = &a;
拿怎么判断指针的数据类型呢,我们想想平时判断一个元素的数据类型,不就是去掉变量名,剩下的就是数据类型了嘛。指针也是一样的!
int a = 0; //去掉 a, 左侧剩下 int
int *p = &a; //去掉p 左侧剩下int *
那如果我们不知道元素数据类型或者元素类型不确定呢,我们可以使用void * 类型,它不表示任何数据类型的地址,也无法被解引用,但可以用它来存放数据的地址。
char a = 'a';
void *p = &a;
3.指针运算
指针数据类型的意义
不管是什么数据类型的指针,取出来的永远是元素所占第一个字节的地址,而不同的数据类型指针会影响从首元素起向后共操作几个字节。操作几个字节取决于指针变量去掉*后的数据类型所占内存大小。
以下是在解引用操作中的影响:
例如:int* 类型进行解引用的时候 从首地址往后总共操作4字节,最后得到int 的值;
同理:char* 类型进行解引用的时候 从首地址往后总共操作1字节,最后得到char 的值;
指针 +- 整数
同上,当指针加减整数的时候 加减几个字节也是取决于指针的数据类型的
新指针(新地址) = 指针(地址) + 整数 * 操作字节数
int a = 0;
int* p = &a;
printf("%p\n", p);
printf("%p\n", p + 1);
.
int* 类型的指针依次操作四个字节, 因此当 p + 1(这里的一可以想成是p往后走了1个整型大小)的时候, p往后走了四个字节,也就是p加上了4。
指针+-指针
用上面的公式进行简单推导,可以得到以下公式:
指针 - 指针 = 整数 (整数就是相隔几个指针存放的数据类型)
int a = 0;
int* p1 = &a;
int* p2 = p1 + 1;
printf("%p\n", p1);
printf("%p\n", p2);
printf("%d\n", p2 - p1);
printf("%d\n", p1 - p2);
4.野指针
危害
会导致程序报错,影响正常运行
成因
1.指针创建时没有初始化
int* p;
printf("%p", p);
运行一下不出意外的报错了。
2.指针指向的地址被释放
这大多是因为指针指向的地址指向的数据是局部变量,局部变量被销毁之后,这个地址就被释放了,不在用来存储之前放的那个局部变量了。
如何避免
如果在创建指针变量的时候不知道放什么,或者是指针暂时用不到了,可以放入NULL,NULL的意思是空地址,这时就不能对指针进行解引用等读写的行为,等有需要时,给指针存放新的地址,就可以正常使用啦!
5.二级指针
指针变量也是变量,也有自己的地址。
而二级指针,顾名思义,就是存放指针变量地址的指针。和之前指针的命名规则一样
int a = 0;
int* p = &a;
int** pp = &p; //这里的pp就是二级指针
6.数组传参本质
数组传参实际上传的是数组首元素的地址
int arr[] = { 0, 1, 2 };
int* p = arr;
7.指针数组
指针数组就是存放指针类型数据的数组
int a = 0;
int b = 1;
int c = 2;
int* pa = &a;
int* pb = &b;
int* pc = &c;
int* arr[] = { pa, pb, pc };
8.函数指针
首先我们先创建一个函数
void Add(int x, int y)
{
printf("%d", x + y);
}
然后我们再创建一个函数指针
写法 函数返回类型 (*指针变量名)(参数类型,参数类型) = 函数名(函数名也表示函数首元素地址)
void (*fun_p)(int , int ) = Add;
int x = 1;
int y = 2;
fun_p(x, y);
于是我们写了以下程序
void (*fun_p)(int , int ) = Add;
int x = 1;
int y = 2;
fun_p(x, y);
这里使用函数指针来调用函数时 * 符号可加可不加,刚刚说了函数名也就是函数地址,我们平时在调用函数的时候也是直接使用函数名调用的,所以直接使用函数指针变量名调用就可以啦!
over!!!!!!!