目录
1. 指针的运算
2. 野指针和悬空指针
2.1 野指针
2.2 悬空指针
3. void类型指针
4. 二级指针和多级指针
4.1 命名规则
4.2 作用
4.2.1 二级指针可以操作一级指针记录的地址
4.2.2 利用二级指针获取变量中记录的数据
1. 指针的运算
文章开始前可以先了解:C语言指针·入门用法超详解-CSDN博客
通过上一章我们初步了解了指针的用法,知道指针的类型是:
其中指针中数据类型的作用是获取字节数据的个数,正常情况下变量p只能获取到0x001的内存地址,而int型数据占4字节,因此:
那么我们可以思考一下,若是:
int a=10;
int* p=&a;
p+1
假设a的地址为:0x01,那么p+1指向的地址会是什么呢?
会是0x02还是0x05呢?
正确答案应该是:0x05,这是为什么呢?
步长:指针移动一次的字节个数
因为,指针的加法操作实际上是将指针向后移动若干个存储单元,而不是简单的数学加法,上面我们也提到了正常情况下变量p只能获取到0x01的内存地址,而int型数据占4字节,而p+1就是指针移动一步,一步在这里是四个字节,因此p+1,最终会移动到0x05的位置。我们拿代码验证一下:
#include <stdio.h>
int main()
{
/*
* 指针的运算
* 步长:指针移动一次,走了多少字节
* char:1
* short:2
* int:4
* long:4
* long long:8
*/
//加法:指针往后移动了N步P+N
//减法:指针往前移动了N步P-N
int a = 10;
int* p = &a;
printf("%p\n", p);
printf("%p\n", p + 1);
printf("%p\n", p + 2);
printf("%p\n", p - 1);
printf("%p\n", p - 2);
return 0;
}
注意:
指针有意义的操作:
指针跟整数进行加、减操作(每次移动N个步长)
指针跟指针进行减操作(间隔步长)
#include <stdio.h>
int main()
{
/*
* 有意义的操作:
指针跟整数进行加、减操作(每次移动N个步长)
指针跟指针进行减操作(间隔步长)
无意义的操作:
指针跟整数进行乘除操作
原因:此时指针指向不明
指针跟指针进行加、乘、除操作
*/
//前提条件:保证内存空间是连续的
//数组
int arr[] = { 1,2,3,4,5,6,7,8,9 };
//获取0索引的内存地址
int* p1 = &arr[0];
//指针跟整数进行加、减操作(每次移动N个步长)
//通过内存地址获取数据
printf("%d\n", *p1);
printf("%d\n", *(p1 + 1));
//获取5索引的内存地址
int* p2 = &arr[5];
//p2 - p1间隔了多少步长
printf("%d\n", p2 - p1);
printf("%p\n", p1);
printf("%p\n", p2);
return 0;
}
指针无意义的操作:
指针跟整数进行乘除操作
原因:此时指针指向不明
指针跟指针进行加、乘、除操作
2. 野指针和悬空指针
2.1 野指针
野指针:指针指向的空间未分配。
#include <stdio.h>
int main()
{
/*
野指针:指针指向的空间未分配
悬空指针:指针指向的空间已分配,但是被释放了
*/
//野指针:指针指向的空间未分配
int a = 10;
int* p1 = &a;
printf("%p\n", p1);
printf("%d\n", *p1);
//p2为野指针
int* p2 = p1 + 10;
printf("%p\n", p2);
printf("%d\n", *p2);
return 0;
}
此时运行程序,虽然也能得到p2的地址,但是要知道,这块地址我们并没有给他分配,他虽然能找到地址,但是若是随意修改数据,在正常使用中,可能会修改别的函数的数据,导致最终运行错误:
2.2 悬空指针
悬空指针:指针指向的空间已分配,但是被释放了。
#include <stdio.h>
int* method();
int main()
{
/*
野指针:指针指向的空间未分配
悬空指针:指针指向的空间已分配,但是被释放了
*/
//悬空指针:指针指向的空间已分配,但是被释放了
int* p3 = method();
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("%p\n", p3);
printf("%d\n", *p3);
return 0;
}
int* method()
{
int num = 10;
int* p = #
return p;
}
会发现此时代码数据错误,正常p3应当显示10:
这里可以调用static来使用:
#include <stdio.h>
int* method();
int main()
{
/*
野指针:指针指向的空间未分配
悬空指针:指针指向的空间已分配,但是被释放了
*/
//悬空指针:指针指向的空间已分配,但是被释放了
int* p3 = method();
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("拖点时间\n");
printf("%p\n", p3);
printf("%d\n", *p3);
return 0;
}
int* method()
{
static int num = 10;
int* p = #
return p;
}
3. void类型指针
特殊类型:void* p
不表示任何类型,只能记录地址值,不能获取到变量里面的数据,也不能进行加减的计算。
特点:无法获取数据,无法计算,但是可以接收任意地址数据。
在我们使用指针的过程中会发现,不同类型的指针之间是不能相互赋值的,否则会发生编辑错误:
#include <stdio.h>
int main()
{
//void类型指针
//定义两个变量
int a = 10;
short b = 20;
//定义两个指针
int* p1 = &a;
short* p2 = &b;
//输出打印
printf("%d\n", *p1);
printf("%d\n", *p2);
//不同类型的指针之间是不能相互赋值的
char* p3 = p1;
printf("%d\n", *p3);
//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
char* p4 = (char*)p1;
printf("%d\n", *p4);
return 0;
}
这里的指针间进行复制那是因为编译器在编译过程中,对p1进行了强制类型转换,p3的实际流程,如p4所示:
为了解决这一问题我们可以将数据类型定义为void,不过这也会导致,不能printf输出数据,因为void不能获取到变量里面的数据,也不能进行加减的计算,否则编译器会报错:
#include <stdio.h>
int main()
{
//void类型指针
//定义两个变量
int a = 10;
short b = 20;
//定义两个指针
int* p1 = &a;
short* p2 = &b;
//输出打印
printf("%d\n", *p1);
printf("%d\n", *p2);
//不同类型的指针之间是不能相互赋值的
char* p3 = p1;
printf("%d\n", *p3);
//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
char* p4 = (char*)p1;
printf("%d\n", *p4);
//void类型指针打破上面的观念
//void没有任何类型,好处可以接收任意类型指针记录内存地址
void* p5 = p1;
void* p6 = p2;
//缺点:不能获取到变量里面的数据,也不能进行加减的计算
//printf("%d\n", *p5);
//printf("%d\n", *p6);
//printf("%d\n", *p5 - 1);
//printf("%d\n", *p6 + 1);
return 0;
}
那么我们引用void有什么用呢?
例如,我们用于调用函数进行交换数据,我们可以编写如下代码:
#include <stdio.h>
void swap(void* p1, void* p2);
int main()
{
//void类型指针
//定义两个变量
int a = 10;
short b = 20;
//定义两个指针
int* p1 = &a;
short* p2 = &b;
//输出打印
printf("%d\n", *p1);
printf("%d\n", *p2);
//不同类型的指针之间是不能相互赋值的
char* p3 = p1;
printf("%d\n", *p3);
//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
char* p4 = (char*)p1;
printf("%d\n", *p4);
//void类型指针打破上面的观念
//void没有任何类型,好处可以接收任意类型指针记录内存地址
void* p5 = p1;
void* p6 = p2;
//缺点:不能获取到变量里面的数据,也不能进行加减的计算
//printf("%d\n", *p5);
//printf("%d\n", *p6);
//printf("%d\n", *p5 - 1);
//printf("%d\n", *p6 + 1);
//调用函数交换数据
int c = 100;
int d = 200;
swap(&c, &d);
printf("c=%d,d=%d\n", c, d);
return 0;
}
//函数:用来交换两个变量记录数据
void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
此时可以实现数据的交换:
但是若是此时我想使用long long型数据呢?数据会发生报错(我在DevC++上运行报错,在VS上运行正确,因为VS在这里编辑器给他自动强制类型转换了):
#include <stdio.h>
void swap(void* p1, void* p2);
int main()
{
//void类型指针
//定义两个变量
int a = 10;
short b = 20;
//定义两个指针
int* p1 = &a;
short* p2 = &b;
//输出打印
printf("%d\n", *p1);
printf("%d\n", *p2);
//不同类型的指针之间是不能相互赋值的
char* p3 = p1;
printf("%d\n", *p3);
//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
char* p4 = (char*)p1;
printf("%d\n", *p4);
//void类型指针打破上面的观念
//void没有任何类型,好处可以接收任意类型指针记录内存地址
void* p5 = p1;
void* p6 = p2;
//缺点:不能获取到变量里面的数据,也不能进行加减的计算
//printf("%d\n", *p5);
//printf("%d\n", *p6);
//printf("%d\n", *p5 - 1);
//printf("%d\n", *p6 + 1);
//调用函数交换数据
long long c = 100L;
long long d = 200L;
swap(&c, &d);
printf("c=%lld,d=%lld\n", c, d);
return 0;
}
//函数:用来交换两个变量记录数据
void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
那么我们如何才能进行不同类型都能调用该函数呢?可以使用void类型,
void swap(void* p1, void* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
但是如果使用void又会出现新的问题就是:void不能获取到变量里面的数据,也不能进行加减的计算,那么要如何解决呢?
这里将void*类型的指针p1和p2分别转换为char*类型的指针pc1和pc2。这是因为char类型是 C 语言中的基本数据类型,且大小为1字节,所以可以通过char*指针逐字节访问内存。
同时函数void swap(void* p1, void* p2, int len)加上一个len用于表示字节数,要是int型len就等于4,long long就是等于8(在x64环境下):
void swap(void* p1, void* p2, int len)
{
//把void类型的指针,转换char类型的指针
char* pc1 = p1;
char* pc2 = p2;
char temp = 0;
//以字节为单位,一个字节一个字节的进行转换
for (int i = 0; i < len; i++)
{
temp = *pc1;
*pc1 = *pc2;
*pc2 = temp;
pc1++;
pc2++;
}
}
完整程序代码:
#include <stdio.h>
void swap(void* p1, void* p2, int len);
int main()
{
//void类型指针
//定义两个变量
int a = 10;
short b = 20;
//定义两个指针
int* p1 = &a;
short* p2 = &b;
//输出打印
printf("%d\n", *p1);
printf("%d\n", *p2);
//不同类型的指针之间是不能相互赋值的
char* p3 = p1;
printf("%d\n", *p3);
//这里编译器为我们进行了强制类型转换,上面的过程相当于如下
char* p4 = (char*)p1;
printf("%d\n", *p4);
//void类型指针打破上面的观念
//void没有任何类型,好处可以接收任意类型指针记录内存地址
void* p5 = p1;
void* p6 = p2;
//缺点:不能获取到变量里面的数据,也不能进行加减的计算
//printf("%d\n", *p5);
//printf("%d\n", *p6);
//printf("%d\n", *p5 - 1);
//printf("%d\n", *p6 + 1);
//调用函数交换数据
int c = 100;
int d = 200;
swap(&c, &d, 4);
printf("c=%d,d=%d\n", c, d);
return 0;
}
//函数:用来交换两个变量记录数据
/*
void swap(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
*/
//修改一下函数,更具有通用性
//因为以上函数,若是主函数调用的话,只能int类型的数据
void swap(void* p1, void* p2, int len)
{
//把void类型的指针,转换char类型的指针
char* pc1 = p1;
char* pc2 = p2;
char temp = 0;
//以字节为单位,一个字节一个字节的进行转换
for (int i = 0; i < len; i++)
{
temp = *pc1;
*pc1 = *pc2;
*pc2 = temp;
pc1++;
pc2++;
}
}
4. 二级指针和多级指针
4.1 命名规则
以二级指针为例,上一章最初我们了解了什么是指针?
C语言指针·入门用法超详解-CSDN博客
那么既然普通变量就有指针,指针也是一个变量为什么不能有一个指针继续指向他呢?那么我们可以了解到:
既然指针也有他的指针,那么指针的指针要怎么命名呢?
其和指针的命名规则是一样的也是:
数据类型 * 变量名;
但是需要注意的是:指针的数据类型要跟指向空间中的数据的类型保持一致。
首先对于指针p,其指向数据a的地址,需要和a数据类型保持一致,那么指针的命名就是:
int * p;
数据类型 标记 变量名
然后,二级指针指向的是指针的数据类型,此时指针空间内存储的不是数据,而是a的地址,他的数据类型是int*,也就是说二级指针的命名:
int* * p;
数据类型 标记 变量名
4.2 作用
4.2.1 二级指针可以操作一级指针记录的地址
上代码:
#include <stdio.h>
int main()
{
//定义变量
int a = 10;
int b = 20;
//定义一级指针
int* p = &a;
printf("a的地址:%p\n", &a);
printf("b的地址:%p\n", &b);
printf("修改前p的地址:%p\n", p);
//定义二级指针
int** pp = &p;
*pp = &b;
printf("修改后p的地址:%p\n", p);
return 0;
}
可以发现p的地址被修改了,简单点来说:初始时,p指向变量a的地址。然后,通过pp这个二级指针,修改了p的指向,使其指向了变量b的地址。因此,最后输出显示p的地址发生了变化,从指向a的地址变为指向b的地址。
4.2.2 利用二级指针获取变量中记录的数据
#include <stdio.h>
int main()
{
//定义变量
int a = 10;
int b = 20;
//定义一级指针
int* p = &a;
//定义二级指针
int** pp = &p;
printf("修改前获取变量里面的数据:%d\n", **pp);
*pp = &b;
printf("修改后获取变量里面的数据:%d\n", **pp);
return 0;
}
C语言指针·入门用法超详解-CSDN博客
指针_时光の尘的博客-CSDN博客