深入理解指针(2)

目录

1、字符指针变量

1-1 字符指针变量的一般使用

2、数组指针变量

2-1 数组指针变量是什么?

2-2 数组指针变量初始化

2-3 数组指针变量的使用

3、二维数组传参的本质

3-1 二维数组传参

3-2 分析

4、函数指针变量

4-1 函数指针变量的创建

4-1-1 函数指针变量的形式

4-2 函数指针变量的使用

4-3 两段有趣的代码

4-3-1 第一段代码

4-3-2 第二段代码

4-3-3 typedef关键字

5、函数指针数组

5-1 函数指针数组变量的形式 

5-2 函数指针数组的使用

6、转移表

6-1 方案1——利用函数指针变量调用相关函数

6-2 方案2——利用函数指针数组调用相关函数

7、总结


1、字符指针变量

在指针类型中我们知道有一种指针类型是字符指针类型——char *

创建一个字符数组,用什么接收字符数组的地址呢?

方法如下:

char arr[] = "abcdef";
char* p = arr;

其中字符数组arr的内容是可变的。

并且指针p存放字符a(首字符)的地址。

1-1 字符指针变量的一般使用

样例代码:

//字符指针——指向的是字符
#include<stdio.h>
int main()
{
	char a = 'A';
	char* p = &a;
	*p = 'Z';
	printf("%c\n", a);
	return 0;
}

输出结果:

还有一种使用情况如下所示:

样例代码:

//还有一种为如下
#include<stdio.h>
int main()
{
	const char* pstr = "ABCDEF";
	printf("%s\n", pstr);//把字符串首字符的地址放到pstr中
	return 0;
}

输出结果:

并且常量字符串与之不同!!

比如

char* p = "abcd";

字符串的内容是 a b c d \0

指针p存放字符a的地址

同时常量字符串的内容时不可以修改的

为了加深字符指针变量存放字符串首字符的地址,看看下面的代码吧。

const char *p="hello world!";

对于这段代码,很容易理解为将字符串“hello world!"放到指针p里,但实际上指针p只存放字符串首字符的地址!!

看一道经典的笔试题吧。

代码如下:

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const 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");

	return 0;
}

输出结果:

解释:

总结:相同的常量字符串,没必要保留2份,因为常量字符串不能被修改。所以大家共用一份是可以的,这样也能节省空间。


2、数组指针变量

2-1 数组指针变量是什么?

之前学习了指针数组,知道了指针数组是一种数组,数组存放的是指针/地址!!

那么数组指针变量是数组还是指针呢?

答案是:是指针,数组指针变量的本质是指针。

不同类型的指针变量

字符指针变量

形式:char *p含义:p是指向字符的变量
整型指针变量形式:int *p含义:p是指向整型的变量
浮点型指针变量形式:float *p含义:p是指向浮点型的变量
双精度浮点型指针变量形式:double *p含义:p是指向的双精度浮点型的变量

那么数组指针变量:是能够指向数组的指针变量,存放的是数组的地址。

形式比如:

int arr[10]={0};

int *p=&arr;

数组指针变量:是能够指向数组的指针变量,存放的是数组的地址。

指针数组:是数组,是存放指针的数组。

指针数组的形式

例如:int *p[ 10 ]——p是指针数组——存放指针的数组

数组指针的形式

例如:int (*p)[ 10 ]——p是数组指针——指向的是数组

int (*p)[ 10 ]

注意:p先和*结合说明p是一个指针变量,然后指向的是一个大小是10整型的数组,所以p是一个指针,指向的是数组,因此p叫数组指针。

注:[ ]的优先级高于*号的,所以必须加上()保证*与p先结合起来

总结数组指针的形式:数组元素的类型 + (*+变量名)+[ 数组元素的个数 ]

例如

int (*p)[10]=&arr;

数组指针的类型:

int (*)[10]

2-2 数组指针变量初始化

数组指针变量是用来存放数组的地址的,之前我们学习了&数组名可以拿到数组的地址,那么如果要存放个数组的地址,就得存放在数组指针变量里!!

int (*p)[10]=&arr;

样例代码:

//数组指针——指向的是数组
//数组指针--在二维数组中应用广泛
#include<stdio.h>
int main()
{
	int set[10] = { 2,4,6,8,10,12,14,16,18,20};
	int(*pset)[10] = &set;
	int sz = sizeof(set) / sizeof(set[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", (*pset)[i]);
	}
	return 0;
}

输出结果:

2-3 数组指针变量的使用

探究一下数组指针变量+-整数与地址的关系吧。

代码如下:

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int  * p1=	arr;
	//	  int*  int*
	//判断类型的方法是去除变量名,即是相关的类型
	printf("%p\n", p1);
	printf("%p\n", p1 + 1);//+1操作跳过一个整型(4个字节)大小
	int (*p2)[10]= & arr;
	//数组元素的类型 (*变量名)[数组元素个数]
	//int (*)[10]	int (*)[10]
	printf("%p\n", p2);
	printf("%p\n", p2+1);//+1操作跳过一个整型数组(40个字节)大小
	//当然具体数值大小还与操作系统有关
	return 0;
}

输出结果:


3、二维数组传参的本质

二维数组的数组名也是数组首元素的地址,那么是谁的地址呢?

其实在二维数组中可以将第一行视为首元素,那么首元素的地址就是第一行的地址,第一行的地址就是一维数组的地址,它的类型是数组指针类型。

其实可以将二维数组想象成一维数组!!

回顾一下:

数组名就是数组首元素的地址,但有两个特殊情况:

1、&数组名——取出的是整个数组的地址,只要是&+数组名那么取出的就是整个数组的地址,&数组名是取出数组的地址,数组名是数组首元素的地址,两个地址的数值的起始值是一样的!!!

2、sizeof(数组名)——计算的是整个数组的大小

3-1 二维数组传参

// 在二维数组中应用
#include<stdio.h>
void test(int(*p)[5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			//printf("%d ", *(*(p + i) + j));
			printf("%d ", p[i][j]);//*(p+i)==p[i]	*(*(p + i) + j))==p[i][j]
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,1,1,1,1},{2,2,2,2,2},{3,3,3,3,3} };
	test(arr, 3, 5);
	return 0;
}

输出结果:

3-2 分析

由数组名是首元素的地址,可知二维数组的数组名是第一行的地址,是一维数组的地址,第一行的一维数组的类型是int [5];所以第一行的地址的类型是数组指针类型 int ( * )[ 5 ],那么也就是说二维数组传参的本质是传递了地址,传递的是第一行这个一维数组的地址,那么形参可以写成指针的形式。

函数部分可以写成:

void test(int arr[3][5], int x, int y)

结论:

二维数组传参的本质:二维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式!!


4、函数指针变量

4-1 函数指针变量的创建

数组指针本质是指针,指向的是数组的地址,

函数指针本质也是指针,指向的是函数的地址,换言之存放的是函数的地址。

也就是说我们要将函数的地址存放起来,就得创建函数指针变量

其中函数是有地址的,并且函数名与&函数名都是函数的地址,两者无任何区别!!

#include<stdio.h>
int add(int x,int y)
{
	return x * y;
}
int main()
{
	int a = 0;
	int b = 0;
	int ret = add(a, b);
	printf("add= %p\n", add);
	printf("&add=%p\n", &add);
	return 0;
}

输出结果:

4-1-1 函数指针变量的形式

指针指向函数的返回类型 +(*变量名)(指针指向函数的参数类型和个数的交代)/(函数参数1的类型,函数参数2的类型,……)

例如:

int (*p)(int,int)=add;

代码中p是专门用来存放函数的地址,p就是函数指针变量

4-2 函数指针变量的使用

以前我们通过函数的调用使用函数,现在通过函数指针调用指针指向的函数,来使用函数

实现代码如下:

//函数指针——指向的是函数
#include<stdio.h>
int pow(int x, int y)
{
	return x * y;
}
int main()
{
	int (*p)(int,int) = pow;
	printf("%d\n", p(5, 3));//写法1
	printf("%d\n", (*p)(5, 3));//写法2	*可写可不写
	return 0;
}

输出结果:

4-3 两段有趣的代码

4-3-1 第一段代码

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

分析过程:
对于函数来说包含函数声明和函数定义
void (*)()——函数指针类型,
 实际地址是编号,默认0是整型。
( void (*)()  )的含义是将0强制类型转换为void (*)()类型,将0转换成这样函数void (*)()的地址,0地址处放着这样的函数。
即0地址处放着一个函数函数返回类型是void,参数是void;
(* ( void (*)() )   0 ) ()  第一颗*表示调用函数,最后的()是因为函数的参数是void。

  

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

这段代码实际上是函数调用。

代码含义总结:

1、void ( * ) ( ) ——函数指针变量的类型

2、( void ( * ) ( )  ) ——强制类型转换

3、( void ( * ) ( )  )0——将0强制类型转换为 void ( * ) ( ) 的函数指针类型

4、这就意味着我们假设0地址处放着无参,返回类型是void的函数

5、最终是调用0地址处放的这个函数。
    即将0强制类型转换成void (*)()类型的函数指针,然后调用0地址处的这个函数。

4-3-2 第二段代码

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

回忆一下函数的构成——返回类型+函数名(参数)

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

 其中signal是函数指针变量。

分析如下:

  1.      signal-函数名
  2.     第一个参数类型是int
  3.     第二个参数类型是void(*) (int)——函数指针类型--该指针可以指向一个函数,指向的函数参数是int,函数的返回类型是void,
  4.     返回类型:void (*) (int)——函数指针类型--该指针可以指向一个函数,指向的函数参数是int,函数的返回类型是void,
  5.     可以理解为:void (*) (int) signal(int,void(*) (int)),但不能这样编写

实际上这段代码是一次函数声明!!那么如何简化这段代码呢,那就得使用typede关键字了!!

4-3-3 typedef关键字

 typedef关键字可以对普通类型重命名

代码如下:

typedef unsigned char num;
#include<stdio.h>
int main()
{
	char nums = 0;//以前
	num nums=0;//现在
	return 0;
}

 typedef关键字可以对指针类型重命名

代码如下:

//对指针类型重命名
typedef int* num;
#include<stdio.h>
int main()
{
	int* nums = NULL;//以前
	num nums=NULL;//现在
	return 0;
}

 typedef关键字可以对数组指针重命名

代码如下:

// 重定义数组指针类型
typedef int(*parr_t)[10];
//parr_t等价于int(*)[10]
#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
	//这里的p是变量名,
	//类型是int(*)[10]——数组指针类型
	parr_t num = &arr;
	//这里parr_t是类型
	return 0;
}

 typedef关键字可以对函数指针重命名

代码如下:

//对函数指针重命名产生新的类型pf_t
void print(char* s)
{

}
typedef void (*pf_t)(char*);
#include<stdio.h>
int main()
{
	//void (*pf)(char*)=print;
	pf_t p2 = print;
	return 0;
}

 那么第二段代码就可简化为

typedef void(*pf) (int)//pf就是函数指针类型
void (*signal(int, void(*) (int))) (int);
简化为pf signal2 (int, pf);

重定义类型除了typedef关键字外还有define关键字,但它们有所差异。

分析一下,这段代码吧!

代码如下:

#include<stdio.h>
typedef int* ptr;//类型重定义
#define PTR int*
int main()
{
	ptr p1, p2;
	//这里p1,p2都是指针变量
	//替换为 int * int *
	PTR p3, p4;
	//这里p3是指针变量,p4整型变量
	//替换为 int* int
	return 0;
}

通过调试可是#define的使用次数为一次,而typedef的使用次数是无数次!!


5、函数指针数组

数组是存放相同类型数据的存储空间,之前学习了指针数组

比如:

int *arr[5];

我们可知数组的每个元素都是int*

我们之前学习了指针的知识,可以类比学习函数指针数组!!

我们知道指针数组——arr是指针数组,存放的是指针

二级指针变量是存放一级指针变量的地址

那么函数指针数组可以理解为函数指针的数组——存放相同类型的函数指针(地址)的数组

例如:如果我们有3个函数的地址要存放,那么就得使用函数指针的数组

5-1 函数指针数组变量的形式 

函数的返回类型+(*变量名[ 数组元素个数 ])(函数参数1的类型,函数参数2的类型,……)

例如:int (*p[5])(int,int)=add;

注:p先和[ ]结合,说明p是数组,数组的内容是int (*)(int,int)类型的函数指针。

5-2 函数指针数组的使用

函数指针数组的样例代码:

#include<stdio.h>
int add(int x, int y)
{
	return x + y;
}
int sub(int x ,int y)
{
	return x-y;
}
int mul(int x, int y)
{
	return x *y;
}
int div(int x, int y)
{
	return x / y;
}
int main()
{
	
	int (*arr[4])(int, int) = { add,sub,mul,div };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\n", arr[i](3, 4));
	}
	return 0;
}

输出结果:


6、转移表

实现一个计算机进行加减乘除运算

6-1 方案1——利用函数指针变量调用相关函数

解决问题:

1、代码冗余

2、防止如果扩展功能,代码量大大增加。

思路:将相似的代码封装成一个函数,再调用。

调用函数除了直接调用还可以将函数的地址传给函数指针变量来接收。

图解:

#include<stdio.h>
int add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}
int mul(int x, int y)
{
	return x * y;
}
int div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("********************************\n");
	printf("*******	  1.add  2.sub  ********\n");
	printf("*******	  3.mul  4.div  ********\n");
	printf("*******	  0.exit        ********\n");
	printf("********************************\n");
}
void calc(int(*pf)(int,int))
{
	int x = 0;
	int y = 0;
	int z = 0;
	printf("请输入操作数:\n");
	scanf("%d%d", &x, &y);
	z = pf(x, y);//函数指针调用,调用函数
	printf("%d\n", z);
}
int main()
{
	int x = 0;
	int y = 0;
	int z = 0;
	int input = 0;
	do
	{
		menu();
		printf("请选择-->\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(add);
			break;
		case 2:
			calc(sub);
			break;
		case 3:
			calc(mul);
			break;
		case 4:
			calc(div);
			break;
		case 0:
			printf("退出计算机\n");
			break;
		default:
			printf("请重新输入操作数\n");
			break;
		}
	} while (input);
	return 0;
}

6-2 方案2——利用函数指针数组调用相关函数

核心:如果要把多个相同类型的函数指针存放在一个数组中,这个数组就是函数指针数组。

关键处:

int (*pf[5])(int, int) = {0,add,sub,mul,div};

这里pf就是函数指针数组。

思路:这样输入值对应的功能与数组中存放元素的下标对应起来,通过数组下标找到函数的地址。

pf[input](x,y)——访问数组下标为input对应的函数,再传递相关的参数。

图解:

这里pf的作用类似于”跳板“。

完整代码实现

 #include<stdio.h>
int add(int x, int y)
{
	return x + y;
}
int sub(int x ,int y)
{
	return x-y;
}
int mul(int x, int y)
{
	return x *y;
}
int div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("********************************\n");
	printf("*******	  1.add  2.sub  ********\n");
	printf("*******	  3.mul  4.div  ********\n");
	printf("*******	  0.exit        ********\n");
	printf("********************************\n");
}
int main()
{
	int x = 0;
	int y = 0;
	int z = 0;
	int input = 0;
	int (*pf[5])(int, int) = {0,add,sub,mul,div};
	do//					  0  1   2   3    4
	{
		menu();
		printf("请选择-->\n");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数\n");
			scanf("%d%d", &x, &y);
			z = pf[input](x, y);
			printf("%d\n", z);
		}
		else if (input == 0)
		{
			printf("退出计算机\n");
			break;
		}
		else
		{
			printf("请重新选择-->\n");
		}

	} while (input);
	return 0;
}

7、总结

指针类型的总结

字符指针变量

形式:char*+变量名,理解:指针p存放字符a(首字符)的地址。
数组指针变量

形式:

数组指针的形式:数组元素的类型 + (*+变量名)+[ 数组元素的个数 ]

理解:是能够指向数组的指针变量,存放的是数组的地址。
指针数组变量  

形式:例如:int *p[ 10 ]——p是指针数组——存放指针的数组理解:是数组,是存放指针的数组。
函数指针变量形式:指针指向函数的返回类型 +(*变量名)(指针指向函数的参数类型和个数的交代)/(函数参数1的类型,函数参数2的类型,……)理解:函数指针本质也是指针,指向的是函数的地址,换言之存放的是函数的地址。
函数指针数组形式:  函数的返回类型+(*变量名[ 数组元素个数 ])(函数参数1的类型,函数参数2的类型,……)理解:函数指针的数组——存放相同类型的函数指针(地址)的数组

二维数组传参的本质:二维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式!!

以及两段有趣的代码,利用函数指针数组和函数指针变量实现转移表!!

制作不易一键三连吧,老铁们!!,别再下次一定了!!

  • 45
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值