你们的魔鬼来了~~~~
大部分学C的人都非常害怕遇到指针,我也不例外,但是摸清楚指针里的窍门,指针理解起来可能就没有那么困难了!
所以一定要好好看看这篇博客,超级无敌爆炸详细!!!!
与指针相关的基础知识
1、指针是什么?
我们将内存分为小小的内存单元,每个内存单元为一个字节,又为每个字节规定一个编号,这个编号就是地址,书面上的指针就是地址。而指针变量(也就是口语中所说的指针)就是用来存放地址(指针)的变量。(后文的指针均指指针变量)
内存 | 编号 |
一个字节 | 0xFFFFFFFF |
一个字节 | 0xFFFFFFFE |
一个字节 | 0xFFFFFFFD |
....... | ....... |
....... | ....... |
一个字节 | 0x00000001 |
一个字节 | 0x00000000 |
其中指针变量在32位系统下占4个字节,在64位系统下占8个字节。
2、指针类型
指针变量的类型分为整形指针、字符指针、数组指针等等。但它们都有一个共同点:在32位系统下占4个字节,在64位系统下占8个字节。
此时我们就有个疑问了,不同类型的指针变量在内存中所占的字节数明明一样,为什么还要划分类型加以区别呢?
原因如下:
- 指针的类型决定了指针在被解引用时所访问的权限。
int a = 0x11223344; int *pa = &a; *pa = 0; //此时a=0x00000000 a = 0x11223344; char *pa2 = (char*)&a; *pa2 = 0; //此时a=0x11223300
- 指针的类型决定了指针向前或者向后退一步走的“距离”。
#include<stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int *p = arr; //这是一个整形指针
int(*p2)[5] = &arr; //这是一个数组指针
printf("%p %p\n", p, p2);
p++; //让p+1
p2++; //让p2+1
printf("%p %p\n", p, p2);
}
结果显示整形指针变量+1相当于让地址+1*4;而数组指针变量+1相当于让地址+1*sizeof(arr)
其实指针变量+/-整数=指针+/-sizeof(指针变量类型)
3、野指针
野指针就是指针指向的位置是不可知的。
野指针的成因:
- 指针未初始化
- 指针越界访问
- 指针指向的空间被释放(解决方法:当指针指向的空间被释放时,及时把指针置为NULL)
4、常指针和指向常量的指针
int const * p / const int * p : 指向常量的指针 p指向的对象不可以通过p来改变,但是p的内容即p指向的对象可以改变。(这里的常量只是指不能通过p改变所指向的内容,而不是规定只能指向常量,也可以是变量)
int * const p : 常指针,p的内容即p指向的对象不能改变,但是可以通过p改变p所指向的对象
const int * const p : 指向常量的常指针,p的内容即p指向的对象不可以改变,也不可以通过p改变p所指向的对象。
指针的具体分类
1、字符指针
之前我们提到了,指针变量的类型决定了指针解引用时访问的权限,那么字符指针解引用时只能访问一个字节。
#include<stdio.h>
int main()
{
const char * p = "hello world"; //是把一个完整的字符串放到p指针变量中吗?
printf("%c\n", p); //p实际上存储的是常量字符串的首字符
printf("%s\n", p); //因此,从p所存储的地址处开始打印,会打印出“hello world”
return 0;
}
注意:“hello world”是一个常量字符串,存储在常量区,是不可以被改变的,因此最好定义p为const char *(指向常量的指针)。
2、数组指针
int *p1[10] ; // 指针数组,p1与“[ ]”结合,故p1为数组,指针数组本质为数组
int (*p2)[10] ; // 数组指针,p2与“ * ”结合,故p2为指针,数组指针本质为指针
注意:"[ ]"的优先级高于“*”,所以数组指针要用括号括起来。
数组指针是指向整个数组的指针。如:int (*p2)[10] 说明p2是指向含有10个int型元素的数组。
#include<stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int(*p)[5] = &arr; //p是一个指向含有5个整形元素的数组的数组指针,&arr是取整个数组的地址,arr是数组首元素的地址
int * (*pp)[5]; //pp是一个指向含有5个整形指针的数组的数组指针
int(*ppp[10])[5]; //ppp是一个含有10个整形元素的数组,其中每一个元素都是指向含有5个元素的数组的数组指针
}
数组指针、指针数组的应用 :
#include<stdio.h>
//打印二维数组
//方案一(二维数组):
void Print1(int a[3][5])
{
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%-2d ", a[i][j]);
}
printf("\n");
}
}
//方案二(指针数组):
void Print2(int *p[5])
{
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%-2d ", *(*(p + i) + j)); //也可以写成p[i][j]
}
printf("\n");
}
}
//方案三(数组指针):
void Print3(int(*p)[5])
{
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%-2d ", *(*(p + i) + j));//也可以写成p[i][j]
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
int data1[5] = { 1,2,3,4,5 };
int data2[5] = { 6,7,8,9,10 };
int data3[5] = { 11,12,13,14,15 };
int *arr2[3] = { data1,data2,data3 };
Print1(arr);
printf("\n");
Print2(arr2);
printf("\n");
Print3(arr);
return 0;
}
3、函数指针
定义函数指针变量
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int test(char*str)
{
}
int main()
{
int(*p)(int, int) = Add;//也可以写成=&Add,p就是一个指向Add函数的函数指针
int(*c)(char*) = &test;//c是一个指向test函数的函数指针
}
利用函数指针调用函数
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int test(char*str)
{
}
int main()
{
int(*p)(int, int) = Add;//也可以写成=&Add,p就是一个指向Add函数的函数指针
int(*c)(char*) = &test;//c是一个指向test函数的函数指针
int ret=(*p)(2,3); //或者int ret=p(2,3) 这就是用函数指针调用函数
}
再来给大家介绍两个非常有趣的代码
代码一: (*( void(*)( ) ) 0)( ) ;
代码二: void (*signal(int,void(*)(int)))( int );
首先解读代码一:void(*)( )是一个函数指针,( void(*)( ) ) 0 是把0这个整形强制转换成函数指针,则0就是一个返回类型为void,且无参数的函数的地址,最后调用0地址处的这个函数。
其次解读代码二:signal是一个函数名,两个形参类型分别为int型和void(*)(int)型,而把signal以及其形参这部分删去,剩下的就是函数的返回类型,返回类型为指向返回类型为void型,形参类型为int型的函数的函数指针。
函数指针数组
顾名思义,就是由函数指针组成的数组。
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int op,x,y;
int(*p[4])(int, int) = { add,sub,mul,div };
printf("**************************************\n");
printf("************简易计算器**************\n");
printf("******1、加法*********2、减法******\n");
printf("******3、乘法*********4、除法******\n");
printf("*************************************\n");
printf("*************************************\n");
printf("请输入你想进行操作: ->");
scanf("%d", &op);
if (op >= 1 && op <= 4)
{
printf("请输入你想要进行运算的两个操作数:->");
scanf("%d%d", &x, &y);
int ret = p[op](x, y);
printf("结果为%d\n", ret);
}
else
printf("输入有误\n");
return 0;
}
4、指向函数指针数组的指针
#include<stdio.h>
int main()
{
int(*p)(int, int); //函数指针
int(*parr[5])(int, int)={Add,Sub,Mul,Div}; //函数指针数组
int(*(*pparr)[5])(int, int)=&parr;//指向函数指针数组的指针,注意括号,别写成int(*(*pparr))[5](int, int);
for (int i = 0; i < 5; i++)
{
int ret=(*pparr)[i](2, 3);
}
return 0;
}
5、回调函数
回调函数是什么?
回调函数是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
通俗一点说,我们定义了A函数,但是我们不直接调用A函数,而是把A函数的地址作为参数传递给B函数(B函数的形参肯定是函数指针,用来接收A函数的地址),然后我们在B函数内部,通过调用函数指针来调用A函数,那么我们称A函数为回调函数。
#include<stdio.h>
void test()
{
printf("hello\n");
}
void printf_hello(void(*p)( ))
{
p();
}
int main()
{
printf_hello(test);
return 0;
}
下面举的例子更能体现回调函数的优越性
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
void cal(int(*p)(int, int))
{
printf("请输入两个操作数:->");
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", p(x, y));
}
int main()
{
int op;
printf("**************************************\n");
printf("************简易计算器**************\n");
printf("******1、加法*********2、减法******\n");
printf("******3、乘法*********4、除法******\n");
printf("*************************************\n");
printf("*************************************\n");
do
{
printf("请输入你想进行操作: ->");
scanf("%d", &op);
switch (op)
{
case1:
cal(add);
break;
case2:
cal(sub);
break;
case3:
cal(mul);
break;
case4:
cal(div);
break;
default:
printf("输入错误\n");
break;
}
} while (op);
return 0;
}
内容差不多就结束了,喜欢这篇博客就多多点赞吧!有问题可以在评论区留言,我会及时回复的~~~~,下篇博客见,bye~