指针的进阶--从入门到入土

指针--变态加强版

  • 前言
  • 面试题
  • 字符指针
  • 指针数组
  • 数组指针
  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针
  • 面试题解析
  • 结语

前言

掌握指针这块知识的重要性不言而喻,当你觉得自己已经差不多掌握指针的时候,不妨看看下面8道面试题(题目从简单到困难,读者根据自己水平选择题目难度),答案在文末公布,如果你都做对了,那说明你对指针这一块知识的掌握还是非常不错的,当然了,如果你的表现还欠那么一点点,不妨看看这篇文章对指针的详解。

面试题

问下面8道面试题分别输出什么?

1.

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

2.

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

3.

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));
	 return 0;
}

4.

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]);
	 return 0;
}

5.

struct Test
{
	 int Num;
	 char *pcName;
	 short sDate;
	 char cha[2];
	 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	 printf("%p\n", p + 0x1);
	 printf("%p\n", (unsigned long)p + 0x1);
	 printf("%p\n", (unsigned int*)p + 0x1);
	 return 0;
}

6.

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);
	 return 0;
}//这里采用小端存储

7.

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

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);
	 return 0;
}

字符指针

字符指针是一种使用起来相对比较简单的指针,这里讲一讲其比较特殊的一种使用方法

int main()
{
	char*p="hello world!";
	return 0;
}

这是把字符串常量 “hello world!” 的首字符 ‘ h ’ 的地址存到指针变量 p 中,通过p就可以找到该字符串的首字符,继而找到整个字符串,而该字符串则是存到代码区中去了。
指针的进阶--从入门到入土_第1张图片

指针数组

指针数组是存放指针的数组,与我们平时使用的数组没有多大区别,只不过指针数组的元素是指针而已。

char* arr1[3];//一级字符指针数组
int* arr2[3];//一级整型指针数组
int** arr3[3];//二级整型指针数组

指针的进阶--从入门到入土_第2张图片

数组指针

数组指针是指向数组的指针。如指向一个大小为10个整型的数组的指针为:

int (*ptr)[10];

ptr先与*结合,表明ptr是指针变量,接着指向大小为10个整型的数组,如果没有括号则是定义指针数组。接下来我们可以看看以下代码表示什么意思?

int (*ptr[3])[5];

ptr先与[3]结合,表明ptr是数组名,接着与*结合,表明数组元素为指针类型,最后与[5]结合,表明这3个指针都是指向大小为5个整型的数组。
指针的进阶--从入门到入土_第3张图片

其实ptr就相当于指向一个三维数组,该数组有3行1列且高为5,即ptr[3][1][5]。

这里补充一下数组名的理解:
数组名都是表示首元素的地址,但有2个例外
1.sizeof(数组名)
此时数组名表示整个数组的地址,sizeof计算的是整个数组的大小,单位是字节。
2.&数组名
这里的数组名表示的是整个数组的地址,因而取地址符取出的是整个数组的地址。
对于数组 int arr[5] 来说,arr与&arr表示的地址是一样的,但他们的数据类型不一样,导致他们在+1时跳跃的距离不一样,如arr+1,地址跳跃了4(一个int类型的大小),而&arr+1则跳跃了20(4*5=20)。

函数指针

函数指针是指向函数的指针,其形式为:

函数返回值类型  (*函数指针变量名)(参数类型);

例:

//定义一个函数
int add(int x,int y)
{
	return x+y;
}

int main(void)
{
	int (*p)(int,int);//定义函数指针
	
	p=add;//1
	p=&add;//2
	//1和2这两种赋值方式没有什么区别
	
	int result=0;
	result=p(3,5);//3
	result=(*p)(3,5);//4
	//3和4这两种使用方式也没有什么区别

	return 0;
}

函数指针看上去是比较容易上手的,接下来我们看看两段有意思的代码:
1.

(*(void(*)())0)();

该代码是调用0地址处的函数。
2.

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

该代码是signal函数的声明,相当于

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

感兴趣的读者可以自行分析原因,这里不过多赘述。

函数指针数组

函数指针数组是存放函数指针的数组,其定义方式为:

函数返回类型 (*数组名[元素个数])(函数参数列表);

函数指针数组比较大的一个用途就是在转移表上。
如在两位整数的加减乘除运算中,要编写4个函数,输入1调用加函数,输入2调用减函数,以此类推,那我们就要用很多个if语句或case来选择调用哪个函数,但当我们使用函数指针数组时,代码就会大大简化。只需要一个intput接收输入的值,接着调用下标为intput的函数指针指向的函数就行了。

int (*p[4])(int int);
...//数组赋值
scanf("%d",&input);
int result=(*(p[input]))(1,7);//p为函数指针数组名,返回结果由result接收
//自己要安排好哪个函数在数组的什么位置

指向函数指针数组的指针

其定义形式为

函数返回类型 (**指针变量名)[所指向的函数指针数组的元素个数])(参数列表)
void (*pfun1[5])(int);//定义函数指针数组
void (*(*ppfun2)[5])(int)=&pfun1;//定义指向函数指针数组的指针并指向pfun1

倘如读者了解了数组指针和函数指针,理解这个应该不成问题。

面试题解析

面试题答案

  1.       1
    
  2.   	  2,5
    
  3.    	  10,5
    
  4.    	  fffffffc,-4
    
  5.    	  100020
       	  100001
       	  100004
    
  6.    	  4,2000000
    
  7.    	  at
    
  8.       POINT
       	  ER
          ST
          EW
    

分析:
1.

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

指针的进阶--从入门到入土_第4张图片

2.

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

指针的进阶--从入门到入土_第5张图片
3.

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));
	 return 0;
}

*(ptr1 - 1)的计算方法和第2题一样,这里就不讲了

指针的进阶--从入门到入土_第6张图片

4.

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]);
	 return 0;
}

指针的进阶--从入门到入土_第7张图片

5.

struct Test
{
	 int Num;
	 char *pcName;
	 short sDate;
	 char cha[2];
	 short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	 printf("%p\n", p + 0x1);
	 printf("%p\n", (unsigned long)p + 0x1);
	 printf("%p\n", (unsigned int*)p + 0x1);
	 return 0;
}

在64位机器下,这里结构体Test的大小经过计算是32个字节(64位机器下,指针的大小是8个字节),如果结构体的大小不会计算的,可以去看看我的另一篇关于如何计算结构体大小的文章
亦星编程
0x1是10进制的1
p是结构体指针,指向的是一个Test结构体的地址,p+0x1跳过一个结构体类型的大小,即要加32,最后以16进制打印就是0x100020

(unsigned long)p + 0x1中p被强制类型转化为无符号长整型(已经不是指针了,而是一个整数),整数0x100000加1就是0x100001,最后以16进制打印就是100001
unsigned int*)p + 0x1中,p被强制转换为无符号整型指针,p指向的是一个无符号整数的地址,加1跳过4个字节(一个无符号整型的大小),最后以16进制打印就是0x100004。

6.

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);
	 return 0;
}//这里采用小端存储

&a+1跳过整个数组,接着转化int*指针,故ptr[-1]就是4
指针的进阶--从入门到入土_第8张图片

7.

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

指针的进阶--从入门到入土_第9张图片

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);
	 return 0;
}

指针的进阶--从入门到入土_第10张图片

结语

其实就是指向内存某个地方的地址,尽管该地址处的数据不变,但由于指针类型不一样,读取数据的方式就不同,结果就不一样,如整型指针解引用会在该地址处向后读取4个字节的数据,而字符型指针解引用只会在该地址处向后读取一个字节的数据。以上就是本文的所有内容,如果有什么不对的地方,恳请指正,当然了,如果你觉得本文对你有帮助,不妨动动小手给博主一个点赞收藏加关注吧。

你可能感兴趣的:(c语言)