指针的介绍

下面是阿鲤对指针更深学习的一些总结,再此分享,若有错误请指出,若有不懂评论区见哦
我将按照以下目录向大家介绍指针的细节

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 指针和数组面试题的解析
    ps:如果觉得下面内容有难度的话,请先看阿鲤的这篇文章https://blog.csdn.net/belongHWL/article/details/92136822

一:字符指针
字符指针就是指向字符的指针,其类型为char*

eg1:
int main()
{
	char *str = "BelongAl";
	printf("%s\n", str);
	system("pause");
	return 0;
}

假设BelongAl的地址是0x1100ffc2,那么如图;
字符指针str里存的是“BelongAl”的地址(也就是B的地址),所以str指向了“BelongAl”;
在这里插入图片描述

输出结果:
在这里插入图片描述

eg2:
eg1中说到了字符指针中存放的是首元素的地址,那么请看以下代码;

int main()
{
	char str1[] = "hello bit";
	char str2[] = "hello bit";
	char *str3 = "hello bit";
	char *str4 = "hello bit";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述
结果显示str1不等于str2 但是 str3等于str4
这是因为指针在访问时只会访问内存中的只读区,且系统在分配内存时只会给相同字符串相同的地址,所以str3和str4相等。(只读所以字符指针不可修改)

二:指针数组

是存放指针的数组

eg1:

int main()
{
	int arr1[] = { 1,2,3};
	int arr2[] = { 4,5,6};
	int arr3[] = { 7,8,9 };
	int arr4[] = { 10,11,12 };
	int *arr[4] = { &arr1,&arr2,&arr3,&arr4 };
	printf("%d\n", *(arr[1] + 1));
	printf("%d\n", *(*(arr + 1) + 1));
	system("pause");
	return 0;
}

*arr[4]是指针数组,它里面存放的是数组的地址
在这里插入图片描述
输出结果:

在这里插入图片描述
三:数组指针
数组指针是指针不是数组,是指向数组的指针。

比如int(*p)[5] 首先p与*结合,说明了p是一个指针变量,然后指向的是一个大小为十的数组,所以数组指针是指向数组的指针  

知道了清楚了什么是数组指针,接下来说说数组指针是怎样使用的;
下面这个例子是把数组指针使用在了二维数组中;
eg1:

void print_arr1(int arr[3][5],int row,int col)
{
	int i = 0;
	int j = 0;
	for(i=0;i<row;i++)
	{
		for (j = 0; j < col; j++)
	    {
			printf("%-3d ", arr[i][j]);
	    }
	   printf("\n");
   }
}
void print_arr2(int(*arr)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%-3d ", arr[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 };
	print_arr1(arr, 3, 5);
	print_arr2(arr, 3, 5);
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

在这段代码中void print_arr1和void print_arr2 的功能是一样的,而void print_arr1所接受的参数是
int arr[3][5]而void print_arr2接受的是int(*arr)[5],在c中,数组名代表的是数组首元素的地址,而在二维数组中,数组名代表二维数组第一行的地址,而每行又是一个一维数组, 所以可以用数组指针来接受它;如下图
如下图。
在这里插入图片描述
总结:数组指针就是指向数组,常在二维数组中使用。

四:数组传参和指针传参

在写代码时候我们往往需要把数组或指针传给函数,传参的方法有很多种,那么怎样正确的传参是很重要的。
下面请看一段代码
eg1:(一维数组的传参)

void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
	int arr[10] = { 0 };
	int *arr2[20] = { 0 };
	test(arr);
	test2(arr2);
	system("pause");
	return 0;
}

上述传参均正确,其中int *arr2[20]是指针数组,它里面存放的是int*类型,所以可以使用二阶指针接收

eg2:(二维数组的传参)

   void test(int arr[3][5])//正确
{}
void test(int arr[][])//错,不能省略列,必须知道一行有多少元素
{}
void test(int arr[][5])//正确,可以省略行
{}
void test(int *arr)//错误
{}
void test(int *arr[5])//错误,指针数组,存放五个地址的数组
{
	printf("%d\n",*(arr+5));
}
void test(int (*arr)[5])//正确,处理每行五个长度的数组//数组指针,指向长度为5的数组
{}
void test(int **arr)//错误,最终只能将二维数组当作一维数组(会发生内存冲突)
{}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,0,0,0,0,0,0 };
	test(arr);
	system("pause");
	return 0;
}

eg3(一级指针的传参)

 void show(int *p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int *p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	show(p, sz);
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
总结:当函数参数是一级指针时,只能接受一维数组,如果是二维数组,可按照一维数组输出
eg4:(二级指针的传参)

void test(int **ptr)
{
	printf("n = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int *p = &n;
	int **pp = &p;
	test(pp);
	test(&p);
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
那麽当函数参数是二级指针时,他都可以接受什么类型,请看下面一段代码

void test(char **ptr)
{
	printf("n = %d\n", **ptr);
}
int main()
{
	char c = 'b';
	char *pc = &c;
	char **ppc = &pc;
	char * arr[10];
	test(&pc);
	test(ppc);
	test(arr);//不可以传
	system("pause");
	return 0;
}

五:函数指针
在讨论函数指针之前,我们先看一下这段代码:
eg1:

void test()
{}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

从运行结果可以看出,函数名就是函数的地址,那么函数的地址怎样保存起来呢?请看下面代码;
eg2:

void test()
{
	printf("BelongAl");
}
int main()
{
	void(*pfun)()= test;
	pfun();
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述
由运行结果可以看出,pfun就是函数指针,它存放了函数的地址。
接下来我给大家介绍两端有趣的代码(代码来源c陷阱和缺陷);

eg3:
int main()
{
	//void(*pfun)()= test;
	//pfun();
	(*(void(*)())0)();
	void(*signal(int, void(*)))(int);
	system("pause");
	return 0;
}
(*(void(*)())0)();

首先void()():这是一个函数指针,指向一个返回值是void,无参的函数;然后我们把它看成pf
(pf*)0:这是将0强制转换成函数指针,且他无参,返回值是void,令pf = (pf*)0
(*pf)():pf此时就是一个调用函数:这个调用将在0的地址进行调用,且返回值为void,无参。

void (*signal(int , void(*)(int)))(int); 

Signal是一个函数指针,他有两个参数,分别是int型的和void(*)(int)型;返回值是函数指针,
六:函数指针数组
什么是函数指针数组?
答:函数指针数组,就是存放函数地址的数组;而函数指针数组是这样定义的,
int(*parr[10])() parr先和[ ]结合证明了他是数组,而数组里存的是int(*)()的。
而对函数指针数组的使用就是转移表:如下:
eg1:(转移表)

设计一个加减乘除的计算器,体现出函数指针数组的使用
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 menu()
{
	printf("*****************************\n");
	printf("****** 1.add **** 2.sub *****\n");
	printf("****** 2.mul **** 4.div *****\n");
	printf("*****************************\n");
}
int main()
{
	int(*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };
	int input = 0;
	int x = 0, y = 0;
	int ret = 0;
	int k = 0;
	do
	{
		printf("请选择正确的操作方式:");
		scanf("%d", &input);
		menu();
		if (input < 5 && input >0)
		{
			printf("请输入两个操作数");
			scanf("%d", &x);
			scanf("%d", &y);
			ret = arr[input](x, y);
			printf("%d\n", ret);
		}
	} while (input);
	system("pause");
	return 0;
}

7:指向函数指针数组的指针

在我理解,你可以把 指向函数指针数组的指针看成”二级指针“,而“一级指针”便是函数指针数组;
eg1:

void Test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	void(*pfun)(const char*) = Test;
	void(*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	void(*(*ppfunArr)[10])(const char*) = &pfunArr;
	return 0;
}

代码解释图:

在这里插入图片描述
这就是 指向函数指针数组的指针 ,而对函数指针数组的应用就是——回调函数;
下面这个qsort(快排函数)就是使用了回调函数;
eg2:
qsort函数是有四个参数的分别为:
1: void *base 参数是对接受需要排序的数组
2: size_t num 参数是接受数组的总长度
3: size_t width 参数是接受数组一个单位的长度
4: int(_cdcel *compare)(const void *elem1,const void *elem2) 比较相邻两个元素的大小,该参数需要程序员自己设计;

void Show(int *arr, int len)
{
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%5d", arr[i]);
	}
	printf("\n");
}

void Swap(const void *elem1, const void *elem2,int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *((char*)elem1+i);
		*((char*)elem1 + i) = *((char*)elem2 + i);
		*((char*)elem2 + i) = tmp;
	}
}

void Myqsort(void *base, int len,
		int size, 
	int( *Int_cmp)(const void *elem1, const void *elem2))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < len - 1; i++)
	{
		for (j = 0; j < len - 1 - i; j++)
		{
			if (Int_cmp( (char*)base+j*size, (char*)base + (j+1) * size)>0)
			{
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int Int_cmp(const void *elem1, const void *elem2)
{
	return *((int*)elem1) - *((int*)elem2);
}

int main()
{
	int arr[10] = { 3,5,32,9,7,45,4,655,45,8 };
	Show(arr, sizeof(arr) / sizeof(arr[0]));
	Myqsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), Int_cmp);
	Show(arr, sizeof(arr) / sizeof(arr[0]));
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述
8:指针和数组面试题的解析
以下是我收集的8道指针方面的面试题,并做了简单的讲解,如果有错误欢迎大家提出,如果大家有看不懂的地方,评论区见哦
1:

int main()
{
	int a[5] = { 1,2,3,4,5 };
	int *ptr = (int *)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述

解析:

 a代表了数组首元素的地址所以*(a+1) 等于2 ; 这道题中int *ptr = (int *)(&a + 1)中的&a代表了整个数组的地址,给&a+1等同于加了整个数组的长度,所以*(ptr - 1) 等于5。

//
//

2:

struct Test
{
	int Num;
	char *pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);
	printf("%p\n",(unsigned long) p + 0x1);
	printf("%p\n",(unsigned int*)p+0x1);
	printf("%p\n", (unsigned char*)p + 0x1);
	printf("%p\n", (unsigned char**)p + 0x1);
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述
解析:

这道题里的结构体struct Test 的大小为20个字节,故printf("%p\n", p + 0x1)输出为00100014(16进制满16进一);
接下来的printf("%p\n",(unsigned long) p + 0x1)将p强制转化成了unsigned long型,变成了一个数字,所以就,直接加1;
接下来的printf("%p\n",(unsigned int*)p+0x1)将p强制转化成了unsigned int*型,变成了一个指向int型的指针,所以加4;
接下来的printf("%p\n", (unsigned char*)p + 0x1)将p强制转化为了 unsigned char*型,变成了一个指char型的的指针,所以加1;
接下来的printf("%p\n", (unsigned char**)p + 0x1)将p强制转化为了unsigned char**型,变成了一个指向char*类型的指针,所以加4。

//
//

3:

int main()
{
	int a[4] = { 1,2,3,4 };//数组存放由低地址到高地址
	int *ptr1 = (int*)(&a + 1);
	int *ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述

解析:

   int *ptr1 = (int*)(&a + 1),&a代表整个数组的大小,所以加1,直接加到了数组的最后,所以ptr[-1] = *(ptr - 1) = 4;
    此题牵扯到了数组的存储,数组在运行时会存储到栈里,且由低地址向高地址存储,存储方式如下: 01000000 02000000 03000000 04000000 ([小端存储),而在题中的(int*)((int)a + 1)中的
    (int)a,强制把a从地址转化成了int型的数字,所以加1,就直接加1字节,把a地址的指向从 01000000 加成了 指向00000020,而因为为小端存储,所以以十六进制输出时就直接输出了2000000;

//
//

4:

int main()
{
	int a[3][2] = { (0,1),(2,3),(4,5) };
	int *p;
	p = a[0];
	printf("%d", p[0]);
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述

解析:

这道题的考点是逗号表达式,逗号表达式的执行结果是保留最后一个表达式,所以此二维数组就变成了{{1,3},{5,0},{0,0}};而p = a[0]中,p[0]等于*(*(a+0)+0) == 1。

//
//

5:

int main()
{
	int a[5][5];
	int(*p)[4];
	p = (int(*)[4])a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	system("pause");
	return 0;
}

输出结果:
在描述
解析:

这道题中的考点是	p = (int(*)[4])a 等同于*(*(a+4)+0), 而&p[4][2] 等同于*(*(a+4)+2))
而他的类型是 int(*) [4]所以每次加都是加四列,在加2列最后加到的位置是[3][3] 所以
 &p[4][2] - &a[4][2] = -4(%d输出),当以%p输出时要对-4求反码,补码

//
//

6:

int main()
{
	int aa[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
	int *ptr1 = (int*)(&aa + 1);
	int *ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述
解析:

(int*)(&aa + 1)是对整个数组加1;
(int*)(*(aa + 1))是对数组的第一行整体加一;

//
//

7:

int main()
{
	char *a[] = { "work","at","alibaba" };
	char**pa = a;
	pa++;
	printf("%s\n", *pa);
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述
解析:

pa指向a,a指向数组的地址,所以pa++  =》 a++ 等于 *(a+1)等于 at;

//
//

8:

int main()
{
	char *c[] = { "ENTER","NEW","POINT","FIRST" };
	char **cp[] = { c + 3,c + 2,c + 1,c };
	char***cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *--*++cpp+3);
	printf("%s\n", *cpp[-2]+3);
	printf("%s\n", cpp[-1][-1]+1);
	system("pause");
	return 0;
}

输出结果:
在这里插入图片描述
解析:

本题里的c指向{ "ENTER","NEW","POINT","FIRST" }的地址,cp指向{ c + 3,c + 2,c + 1,c }的地址,cpp指向cp
所以:

printf("%s\n", **++cpp)里面的   **++cpp 等于   *(*(cpp+1)+0) 等于*(c+2)	所以输出为
POINT
printf("%s\n", *--*++cpp+3`)里面的*--*++cpp+3 是在第一步cpp++的基础上执行的 所以
 *--*++cpp+3 等于 *(--(c+1))+3 等于*c+3 , 所以输出为ER
printf("%s\n", *cpp[-2]+3)此时的cpp也是在前两位的基础上执行的,所以*cpp[-2]+3 等于*(c+3)+3
,所以输出为ST
printf("%s\n", cpp[-1][-1]+1)此时的cpp也是在前三位的基础上执行的,所以cpp[-1][-1]+1等于
*(*(cpp-1)-1)+1 = *(*(c+3 - 1)-1)+1 所以输出为ER
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值