目录
一、内存
1.1 ❥ 理解内存和地址的关系
我们可以举个例子:当我们回宿舍的时候,我们是如何找到自己所在的宿舍呢?
当然是根据门牌号来确定我们所在的宿舍。有了门牌号,就能提高效率,快速的找到房间。
这里的一个学生宿舍相当于一个内存单元,每个内存单元的大小取1个字节,1个字节里放8个比特位,就好比同学住的八人间,每个人是1个比特位。
每个内存单元都有一个编号,这个编号就相当于宿舍的门牌号,有了这个内存单元的编号,cpu就可以快速找到一个内存空间。
这里的门牌号,也就是内存单元的编号,也叫作地址。c语言中给地址起了个新名字,叫指针。
所以:内存单元的编号 == 地址 == 指针
1.2 ❥ 编址
二、指针变量
2.1 ❥ 取地址操作符(&)
在c语言中,创建变量其实就是向内存申请空间:
上述的代码就是创建了整型变量a,内存申请了4个字节,用于存放整数10,其中每个字节都有地址,上图中4个字节的地址分别是:
这里我们利用取地址操作符(&)来获取a的地址。
#include <stdio.h>
int main()
{
int a = 10;
printf("%p\n", &a);//打印的是首字节的地址,即:0x010FF9E4
return 0;
}
2.2 ❥ 指针变量和解引用操作符(*)
❤ 指针变量
那我们通过取地址操作符(&)拿到的地址是一个数值,比如:0x010FF9E4,这个数值有时候也是需要存储起来,方便后期再使用。那么我们把这样的地址值存放在指针变量。
int* p = &a;//将a的地址存放在指针变量p中
❤ 解引用操作符(*)
c语言中,我们可以通过解引用操作符来找到我们指针(地址)所指向的对象。
代码如下:
这里*p的意思是:通过p中存放的地址,找到p所指向的对象,*p就是p所指向的对象a
当然,也可以对*p(即变量a)进行修改,代码如下:
2.3 ❥ 指针变量的大小
- 不管什么类型的指针(地址),都是在创建指针变量,指针变量是用来存放地址的。
- 指针变量的大小取决于一个地址存放需要多大的空间(即指针变量的大小取决于地址的大小)。
- 32位机器上的地址:32bit位 - 4byte ,所以指针变量的大小是4个字节
- 64位机器上的地址:64bit位 - 8byte ,所以指针变量的大小是8个字节
- 指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的。
- 在32OS下,int*,char*,double*...都是4个字节。
总结:指针大小与数据类型无关,它与系统位数有关。
三、指针类型的意义
指针变量的大小和类型无关,只要是指针变量,在同一平台下,大小都是一样的,为什么还要有各种指针类型呢?
其实指针类型是有它存在的意义的,我们来看下指针类型存在哪些意义吧。
3.1 ❥ 指针的解引用
我们首先来看下面两段代码:对他们进行调试:
代码1
代码2
调试我们可以看到:代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0。
结论:指针的类型决定了,指针在被解引用时能有多大权限(一次能操作几个字节)。
例如:char*的指针解引用就只能访问1个字节,而int*的指针解引用能访问4个字节。
3.2 ❥ 指针+-整数
我们还是先来看一段代码:
#include <stdio.h>
int main()
{
int a = 10;
char* pa = (char*)&a;
int* pc = &a;
printf("%p\n", &a);
printf("%p\n", pa);
printf("%p\n", pa + 1);
printf("%p\n", pc);
printf("%p\n", pc + 1);
return 0;
}
运行结果可以发现:char* 类型的指针变量+1跳过1个字节;int* 类型的指针变量+1跳过了4个字节。
结论:指针的类型决定了指针在+-操作的时候,能够跳过几个字节。(决定了指针的步长)
3.3 ❥ 易错点
我们看如下代码:
#include <stdio.h>
int main()
{
int a = 0;
int* pa = &a;
float* pf = &a;
return 0;
发现:
pa解引用访问4个字节,pa+1跳过4个字节。
pf解引用访问4个字节,pf+1也是跳过4个字节。
那么int*和float*是不是就可以通用呢?
答案是:不能。
原因:因为两者的存储方式完全不同。
3.4 ❥ void*指针
void* —— 泛型指针,没有具体类型的指针。
这种类型的指针可以用来接收任意类型的地址。
如下面代码所示:
#include <stdio.h>
int main()
{
int a = 10;
void* pa = &a;
void* pc = &a;
return 0;
}
但是也有局限性:void*类型的指针不能直接解引用和进行指针的 + - 整数的运算,因为不知道访问(多大空间)多少个字节。
如下图所示:
void*类型的指针的用处:
一般void*类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样涉及可以实现泛型编程的效果。使得一个函数来处理多种类型的数据。
四、野指针
4.1 ❥ 野指针的成因
❤ 指针没有初始化
一个指针变量被声明但没有初始化时,其中的值将是不确定的,它可能指向任意的内存地址,包括重要的内存位置。
❤ 指针越界访问
❤ 指针指向的空间释放
当某个内存块已经被释放并返回给系统,但对应的指针仍然保留着指向之前的内存地址,那么这个指针就是野指针。
分为以下几个方面:
例如下面代码:
#include <stdio.h>
int* test()
{
int n = 100;//局部变量n进栈创建,出栈销毁
return &n;
}
int main()
{
int* p = test();
printf("%d\n", *p);
return 0;
}
4.2 ❥ 野指针可能导致的问题
4.3 ❥ 如何避免野指针的出现
补充:NULL是C语言中定义的一个标识符常量,值是0,0也是地址。该地址是属于系统内核的,用户程序是不能使用的,读写该地址会报错。
五、指针运算
5.1 ❥ 指针 +- 整数
因为数组在内存中是连续存放的,只要知道第一个元素的地址,就能找到后面的所有元素。
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i)); //p+i 这里就是指针+整数
}
return 0;
}
5.2 ❥ 指针 - 指针
指针 - 指针的绝对值得到的是指针和指针之间的元素个数。
注意:
5.3 ❥指针的关系运算(比大小)
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较。
代码1
代码2
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
六、二级指针
二级指针:用来存放指针变量的地址的指针(变量)
如下图所示:
对于二级指针的运算有:
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是pa
int b = 20;
*ppa = &b; //等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,找到的是 a
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;