1.指针是什么?
结论:指针是地址,口语中所说的指针通常指指针变量
指针可以理解为内存中最小存储单元的编号,通过指针我们可以完成各种操作,如赋值。我们在编程时,每创建一个变量,完成一次运算,都会被电脑所记录,每个变量都会向内存申请空间,而每字节大小的空间都有相应的编号。

而指针变量就是通过创建一个变量把地址存储起来
int main()
{
int a = 10;
int* pa = &a;
printf("%p\n", &a);
return 0;
}

2.指针变量和指针类型
如果需要创建一个指针来存储地址,对于不同的数据类型,需要创建不同数据类型的指针
int main()
{
int* pa;
char* pb;
//创建不同类型的指针
printf("%zu\n", sizeof(pa)); //结果为8
printf("%zu\n", sizeof(pb)); //结果为8
return 0;
}
但是通过 sizeof 这个操作符可以计算出不同数据类型的指针所占的内存一致,那我们何不直接规定一个 type* 来承接所有数据类型的地址呢?
原因有二,1.不同的数据类型的指针在访问数据时每一步所跳过的距离不一样
2.指针类型决定了解引用的权限有多大
让我们来先解释第一点
int main()
{
int a = 3;
int* pi = &a;
char* pc = &a; //创建不同的指针类型用于比较不同
printf("%p\n", &a);
printf("%p\n", pi);
printf("%p\n", pc);
printf("%p\n", pi + 1);
printf("%p\n", pc + 1);
return 0;
}

可以看到,在打印地址时,不同的指针类型并没有差异,但将二者都+1 后,char * 的指针的编号只增加了1,而 int* 则加了4,这就是第一点不同。
对于第二点
int main()
{
int a = 0x11223344;;
int* pi = &a;
char* pc = (char*)&a;
//a在内存中为 44 33 22 11
*pc = 0; //被改为 00 33 22 11 只能修改一个
*pi = 0; // 00 00 00 00 可以全部修改
return 0;
}
3.野指针
3.1野指针成因
1.指针未初始化
int main()
{
int* p;
*p = 2; //err
return 0;
}
对一个未初始化的指针,进行赋值。映射到现实生活中,这件事的离谱程度简直就像你在大街上,随便找了一个酒店房间,进去睡了一晚上
2.指针越界
int main()
{
int arr[10] = { 0 };
for (int i = 0; i <= 10; i++) //err
{
arr[i] = 0;
}
return 0;
}
由于数组的下标是从0开始的,这个代码一共访问了11个元素,这就造成了指针越界,第11个地址为随机值
3.指针指向的地址已被回收
int* test()
{
int a = 3;
return &a;
} //出了这个函数以后,a作为局部变量,向内存申请的空间被回收
int main()
{
int* pa = test();
*pa = 20; //err
return 0;
}
上面这个 错误案例就不那么容易被识别,大家在写代码时要特别注意
3.1 野指针规避方案
3.1.1 指针初始化
在写代码时,我们可以养成习惯去初始化指针,就像我们初始化变量一样
#include<stdio.h>
int main()
{
int* pa = NULL;
return 0;
}
指针的初始化只需在包含头文件<stdio.h> 的前提下,初始化为NULL即可
3.1.2检查有效性
在无法或难以判断的情况下,我们不妨为我们的代码加上一层保险
#include<stdio.h>
int main()
{
int* p;
if (*p != NULL)
{
p + 1;
}
return 0;
}
4. 指针运算
4.1 指针 - 整数
int main()
{
int arr[5] = { 0 };
int* p = arr;
for (int i = 0; i < 5; i++)
{
*p = 1;
*p++;
}
//for循环完成后 arr[5] = {1,1,1,1,1}
return 0;
}
通过观察发现指针与整数的运算,并非简单加和,而是让指针指向下一个数据,看到这里,不知道你是否会产生这样的疑惑
指针的本质不是地址吗?为什么让它与整数相加,会产生这样的结果
首先,能产生这样的疑惑非常好,需要注意的是,我们在这里创建了一个指针变量,变量是允许加和的,而在C语言的语法中,指针变量 +1 后,就移向下一个数据。
4.2 指针 - 指针
int my_strlen(char* s)
{
char* p = s; //为首元素地址创建副本
while (*p != '\0')
{
p++; //计算有第多少个元素
}
return p - s;
}
int main()
{
char ch[] = { "abc" };
int len = my_strlen(ch); //自己实现strlen函数
printf("%d\n", len); //结果为 3
return 0;
}
可以看到我们可以通过指针来计算数组中的元素个数,你可能会感到疑惑,两个地址相减,怎么会得到这样的结果,这就是 王八的屁股 - 龟腚(规定)
4.3 指针的关系运算
int main()
{
int arr[5] = { 0 };
int* i; //不能写&arr,因为arr代表的是这个数组的地址
for (i = &arr[5]; i > &arr[0];)
{
*--i = 1;
}
//arr[5] = { 1,1,1,1,1 }
return 0;
}
你可能对于上面代码的写法感到不习惯,为什么要从最后往前赋值呢?
那下面这个版本就是从前往后,尽管在大多数编译器上都是支持编译的,但是C语言的标准是并不允许这种写法代码
int main()
{
int arr[5] = { 0 };
int* i;
for (i = &arr[0]; i < &arr[5]; *i--)
{
*i = 1;
}
for (int j = 0; j < 5; j++)
{
printf("%d ", arr[j]);
} //err
return 0;
}
如果你的电脑上安装了 VS2022 你可以试着运行一下,你就会发现这样的警告


5.指针和数组
读到这里,相信你对指针有了一定的了解,那我们就可以数组用指针来访问数组
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = arr;
for (int i = 0; i < 5; i++)
{
printf("%d ", *p + i); //打印arr下标为i的元素
}
return 0;
}
这里的 arr 表示的是首元素的地址,详见“C语言数组从入门到精通”我的这篇博客
6.二级指针
指针变量作为一个变量,电脑一定会有相应的地址,那如果我们想读取它的地址,就涉及到了二级指针,二级指针应该怎么表示呢?
int main()
{
int a = 2;
int* pa = &a; //数据类型* 名称
int** ppa = &pa;
return 0;
}
指针的表示方式就是 数据类型* 名称 ,而二级指针需要取出一级指针的地址,一级指针的数据类型为 int* ,所以二级指针的表示形式就是 int* * ppa (数据类型)(表示该变量为指针)
大家有什么不懂的欢迎留言问我😊
1205

被折叠的 条评论
为什么被折叠?



