🐒博客名:平凡的小苏
📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情
目录
1. 指针是什么
2. 指针和指针类型
2.1指针类型的第一个意义
2.2指针类型的第二个意义
3. 野指针
3.1 野指针成因
3.2 如何规避野指针
4. 指针运算
4.1 指针+-整数
4.2 指针-指针
4.3 指针的关系运算
5. 指针和数组
6. 二级指针
7. 指针数组
1. 指针是什么
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;
}
总结:指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
00000000 00000000 00000000 0000000000000000 00000000 00000000 00000001...11111111 11111111 11111111 11111111
总结:指针变量是用来存放地址的,地址是唯一标示一个内存单元的。指针的大小在 32 位平台是 4 个字节,在 64 位平台是 8 个字节
2. 指针和指针类型
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
2.1指针类型的第一个意义
代码
#include<stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
我们来进行调试观看它的内存变化
我们可以看到存放的的确是11223344
当我们用整型int来存放a的地址的时候,这时候将他解引用将它修改为0时,修改的是四个字节所以都变为零
当我们改为char和short类型将会如何变化呢?
//演示实例
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
我们可以看到char类型的指针为一个字节,只能修改一个字节的值
总结:
指针类型决定了,指针解引用操作的时候,一次性访问几个字节,访问权限的大小
如果是char*的指针解引用访问1个字节
那么short*解引用则访问2个字节
2.2指针类型的第二个意义
代码演示:
#include<stdio.h>
int main()
{
int a = 0x11223344;
int * pa = &a;
char *pc = &a;
printf("%p\n", pa);
printf("%p\n", pa+1);
printf("%p\n", pc);
printf("%p\n", pc+1);
return 0;
}
总结:
指针类型决定指针的步长(指针+1跳过几个字节)
字符指针类型+1,跳过1个字节
整型指针+1,跳过4个字节
当我们想要一个一个字节访问则可以用char类型的指针
如果想要4个字节的访问,则用int类型的指针
3. 野指针
3.1 野指针成因
1. 指针未初始化
代码演示:
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
这里编译器都不允许我们通过
2. 指针越界访问
代码演示:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 12; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
printf("%d ", *p);
p++;
}
return 0;
}
这个时候就是越界访问了,访问的空间是不属于我们的
例子:
#include <stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int *p = test();
printf("%d\n", *p);
return 0;
}
这里我们调用函数,函数里面讲a取地址返回,但这个做法是错误的,因为函数一旦返回,a的生命周期就结束了,a的空间就已经释放掉了,这时候我们进行解引用p的话就属于非法访问了。但是第一次打印结果是对的,是因为编译器做了保存,假如先打印一个hehe,再打印解引用p那么打印出来的值就不是10了。
3.2 如何规避野指针
#include <stdio.h>
int main()
{
int *p = NULL;//指针置为空指针
//....
int a = 10;
p = &a;
if(p != NULL)//使用指针之前检查有效性
{
*p = 20;
}
return 0;
}
4. 指针运算
4.1 指针+-整数
代码演示:
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
4.2 指针-指针
代码演示:
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
注:
指针减指针前提:两个指针要指向同一块空间
指针减指针得到的是两个指针之间的元素个数
4.3 指针的关系运算
for(vp = &values[5]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[5-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
这里当vp已经指向下标为0时,vp还需要继续向前走一位,相当于向前越界了,应该避免这种写法
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
1.指针和数组时不同的对象
指针是一种变量,是存放地址的,大小是4/8字节的
数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决元素个数和元素的类型的
2.数组的数组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针来访问数组
我们看一个例子:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
可见数组名和数组首元素的地址是一样的。结论: 数组名表示的是数组首元素的地址
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
所以 p+i 其实计算的是数组 arr 下标为 i 的地址。那我们就可以直接通过指针来访问数组。
6. 二级指针
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
int** pa = &p;
return 0;
}
、画图演示
解读:首先解读指针,*pa表示是一个指针变量,用来存放a得地址,而a的类型是int,则*pa的前面是int。*ppa指的是指针变量,用来存放pa的地址,而pa的类型是int*,则*ppa的前面为int*。也就是二级指针,以此类推,可以三级指针,但是三级指针很少见。
int b = 20;
*ppa = &b;//等价于 pa = &b;
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
7. 指针数组
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int e = 50;
int* arr[5] = {&a, &b, &c, &d, &e};
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(arr[i]));
}
return 0;
}
解读:int*arr[5]是一个指针数组,存放的各个变量的地址,当我们需要遍历的时候,需要进行解引用,这里只为讲语法,用法很牵强! 并且该数组的的地址不一定连续
写一个比较有意义的指针数组,利用一维数组来模拟二维数组
#include<stdio.h>
int main()
{
//假设我想模拟出一个3行4列的数组
int a[] = { 1,2,3,4 };
int b[] = { 2,3,4,5 };
int c[] = { 3,4,5,6 };
int* arr[3] = { a, b, c };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
解读:数组名表示数组首元素的地址,arr[i][j]可以写成*(arr[i]+j),意思是arr[i]找到数组a的首元素地址,然后加 j 找到 数组里的各个元素,因为是地址,所以需要进行解引用