各种类型的指针

你们的魔鬼来了~~~~

大部分学C的人都非常害怕遇到指针,我也不例外,但是摸清楚指针里的窍门,指针理解起来可能就没有那么困难了!

所以一定要好好看看这篇博客,超级无敌爆炸详细!!!!

与指针相关的基础知识

1、指针是什么?

我们将内存分为小小的内存单元,每个内存单元为一个字节,又为每个字节规定一个编号,这个编号就是地址,书面上的指针就是地址。而指针变量(也就是口语中所说的指针)就是用来存放地址(指针)的变量。(后文的指针均指指针变量)

 内存编号
一个字节0xFFFFFFFF
一个字节

0xFFFFFFFE

一个字节

0xFFFFFFFD

..............
..............
一个字节0x00000001
一个字节0x00000000

其中指针变量在32位系统下占4个字节,在64位系统下占8个字节

2、指针类型

指针变量的类型分为整形指针、字符指针、数组指针等等。但它们都有一个共同点:32位系统下占4个字节,在64位系统下占8个字节。

此时我们就有个疑问了,不同类型的指针变量在内存中所占的字节数明明一样,为什么还要划分类型加以区别呢?

原因如下:

  1. 指针的类型决定了指针在被解引用时所访问的权限。
    int a = 0x11223344;
    int *pa = &a;
    *pa = 0;  //此时a=0x00000000
    a = 0x11223344;
    char *pa2 = (char*)&a;
    *pa2 = 0;   //此时a=0x11223300
  2. 指针的类型决定了指针向前或者向后退一步走的“距离”。
#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、野指针

野指针就是指针指向的位置是不可知的。

野指针的成因:

  1. 指针未初始化
  2. 指针越界访问
  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~

  • 15
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值