✨hello,愿意点进来的小伙伴们,你们好呐!✨
🐻🐻系列专栏:【C语言基础】
🐲🐲本篇内容:浅学指针基础
🐯🐯作者简介:一名大一即将升大二的三非编程小白
前言
指针是什么?
内存空间怎么管理呢?
切割成内存单元 – 1 byte(字节) – (最小单元)
每一个内存单元都有z自己独有的编号,这个编号就叫做地址(地址也称为指针)
指针其实就是地址,地址就是指针
指针就是内存单元的编号。
指针所占的内存大小
我们一般口中说的指针其实就是指针变量,指针变量本质上就是存放地址的变量,因此指针本质上也是一个地址,如 &a 是取出第一个字节的地址,该地址其实是一个数值。我们可以通过这个地址来找到一个内存单元。
int main() {
int a = 10;//a 是整型变量,占4个字节的内存空间。
int* pa = &a;
//&a 是取出第一个字节的地址,该地址其实是一个数值。我们可以通过这个地址来找到一个内存单元。
//pa 是一个指针变量用来存放地址的,
//然后我们经常口头语的将 pa 说成指针。
//本质上指针就是地址,
//口语中说的指针,其实是指针变量,指针变量就是一个变量,指针变量就是用来存放地址的变量。(**存放在指针中的值都被当做地址处理)
//总结:
//指针变量存放地址的,地址是唯一标示一块地址空间
//指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8 个字节 (32 位 为32条地址线)
return 0;
}
总结:
1.指针变量存放地址的,地址是唯一标示一块地址空间
2.指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8 个字节 (32 位 为32条地址线)。例如:
该代码运行环境位 X86 (32位),所以该环境下的指针内存大小都为 4 个字节
指针和指针类型
指针也是有类型的,指针类型有什么意义呢?
int main() {
int a = 0x11223344;
/*int *p = &a;
*p = 0;*/
//也可以强制类型转换
char* pc = (char*)&a;//int*
*pc = 0;
//结论:
//1.指针类型决定了指针在被解引用的时候访问了几个字节
//2.如果是 int* 的指针,解引用的时候访问了 4 个字节
//3.如果是 char* 的指针,解引用的时候访问了 1 个字节
//推广到所有类型
return 0;
}
> 结论:
1.指针类型决定了指针在被解引用的时候访问了几个字节
2.如果是 int 的指针,解引用的时候访问了 4 个字节
3.如果是 char 的指针,解引用的时候访问了 1 个字节
推广到所有类型**
例1:
pa 的指针类型为 int* – 该类型能访问 4 个字节的内存空间
pc 的指针类型为 char* – 该类型可以访问 1 个字节的内存空间
pa + 1 指在 pa 后面的另一个元素的地址。
pc + 1 指在 pc 后面的另一个元素的地址。
由上面的例子看出,pa + 1 跳过了 4 个字节, pc + 1跳过了 1 个字节。
可以得出:
由此可见,指针类型决定了指针 ± 1操作的时候跳过多少个字节
决定了指针的步长
因为指针类型 决定了解引用的时候在内存中访问了多少个字节。(访问权限)
然后一定要注意:
指针变量是 4 个字节
和指针变量的在内存中访问的内存空间不同(解引用)
不要将指针变量内存和指针变量的在内存中访问的内存空间搞混。
🐞🐞指针变量的编号是 4 个字节,但是他在内存中可以访问多少个字节的内存空间,要看指针类型🐞🐞
例2:
int main() {
int a = 0;
int* pi = &a;//pi 解引用访问四个字节, pi + 1 跳过 4 个字节
float* pf = &a;//pf 解引用访问四个字节, pi + 1 跳过 4 个字节
//int* 和 float* 是不是可以通用
//不可以
//两个指针变量内存中存放的值不同类型
return 0;
}
虽然说,两个指针类型的访问内存空间大小相同,但是不可以将两个该指针类型的指针变量通用,因为它们在地址中存放的值是不同类型的。
野指针
🐞🐞野指针指的是一个指针指向的位置是未知的,不确定的。
例1:没有初始化
int main() {
int* p;
//p 没有初始化,就意味着没有明确的指向,
//一个局部变量没有初始化,放的是随机值:0xcccccccc
//
*p = 10;
//这时候 *p 是非法访问内存 , 这里的 p 就是野指针。
//因为 0xcccccccc 是空间随机赋值的,并不是我们自己初始化的值
return 0;
}
因为指针变量 p 没有初始化值,所以在内存中该指针指向一个随机值,这种情况下 *p 就是非法访问内存。
🐝🐝p 就称为野指针。
例2:数组下标越界
int main() {
int arr[10] = { 0 };
int* p = arr;//arr是首元素地址,p 相当于 arr[0];
int i = 0;
for (i = 0; i <= 10; i++){
*p = i;
//当指针指向的范围超出数组arr的范围时,p就是野指针
p++;
}
return 0;
}
arr是首元素地址,p 相当于 arr[0] , 在for循环中,指针 p 已经指向arr数组外的一个地址,指向访问超过了数组的访问,这时候 p 就是野指针
例3:函数栈帧的销毁
函数栈帧的销毁的销毁属于比较难理解的一个原因,在这里我举个简单的例子:
int* test() {
int a = 10;
return &a;
}
int main() {
int* p = test();
//test 将 a 的地址返回过来给 p
// 但是 p 就是一个野指针
//因为 a 是局部变量,当 p 接收到 a 的地址后
//a 这个局部变量就会销毁 , 所以 p 访问不到 a 的地址
//所以 p 就是野指针
return 0;
}
🐛🐛test 将 a 的地址返回过来给 p ,
因为 a 是局部变量,当 p 接收到 a 的地址后,
a 这个局部变量就会销毁 , 所以 p 访问不到 a 的地址,
🐛🐛所以 p 就是野指针.
所以在知道了野指针的形成条件后,我们以后在写代码的过程中要注意一些事项,养成好习惯
1.
🐠🐠 我们要明确的初始化变量,
🐠🐠就算不知道初始化多少也要赋值为 NULL
int a = 10;
int* p = &a;
int* p2 = NULL;//
*p2 = 100;
//然后因为 p2 是 NULL,所以我们无法访问到该地址
//因此要做个约定
//在访问该地址时先判断:
if (p2 != NULL) { // 判断 p2 是否为空指针,不过对于全局变量栈帧的销毁没有有效的防范
printf("%d\n", *p2);//结果为 10 ,是因为虽然 a 被销毁了,但是原本 a 的地址还是存放 10
//但是不一定每一次都是 10
}
野指针的防范
如何避免野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放即设置 NULL
4.避免返回局部变量
5.指针使用前检查有效性
指针运算
减法
指针可以进行相减运算(指针 - 指针)
int main() {
int arr[10] = { 0 };
printf("%d\n", arr[9] - arr[0]);
printf("%d\n", arr[0] - arr[9]);
//指针减指针得到的绝对值是指针和指针之间元素的个数
//不是所有指针都可以相减
//要指向同一个空间的指针相减才有意义
//指针相加无意义
int arr2[5] = 0;
char ch[2] = 0;
printf("%d\n", arr2[4] - ch[1]);//无意义,会报错
return 0;
}
指针减指针得到的绝对值是指针和指针之间元素的个数
不是所有指针都可以相减
要指向同一个空间的指针相减才有意义,如:printf("%d\n", arr2[4] - ch[1]);//无意义,会报错
指针相加也无意义
知道了指针可以相减后,我们可以用一种新的方法求字符串长度
int my_strlen(char* ch) {
char* start = ch;
while (*ch != '\0') {
ch++;
}
return ch - start;//两个指针相减
}
int main() {
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
加法
指针的加法只能自增,如:
int main() {
int arr[5];
int* vp;
for (vp = &arr[0]; vp < &arr[5];) {//指针关系判断 &arr[5] 不算指针越界,虽
//然该地址不属于arr 但是内存中是有这个地址的
*vp++ = 0;//指针加法
//*vp = 0;
//vp++;
}
return 0;
}
指针的自增在上文已经提过
指针关系运算
int main() {
int arr[5];
int* vp;
for (vp = &arr[5]; vp > &arr[0];) {
*--vp = 0;
}
for (vp = &arr[5 - 1]; vp >= &arr[0]; vp--) {
*vp = 0;
}
//标准规定:
//允许数组元素与指向数组最后一个元素的后面的指针相比,
//但是不允许与指向数组第一个元素前面的指针相比
return 0;
}
指针的大小比较
vp >= &arr[0]
,比较的是指针的大小(地址在内存中的先后顺序),
标准规定:
允许数组元素与指向数组最后一个元素的后面的指针相比,但是不允许与指向数组第一个元素前面的指针相比
指针和数组
数组是一组相同类型元素的集合
指针变量是一个变量,存放的是地址
int main() {
int arr[10] = { 0 };
//arr是首元素地址
//&arr[0]
int* p = arr;
//通过指针来访问数组
for (int i = 0; i < 10; i++) {
printf("%d\n" , *(p + i));
}
//arr[i] -> *(arr + i) 在内存中是这样子访问的
for (int i = 0; i < 10; i++) {
printf("%p ----- %p\n", &arr[i] ,(p + i));
}
return 0;
}
p 指向 arr 的首元素,p + i 就可以访问arr数组里面的元素