轻松拿下指针(4)

文章目录

一、字符指针变量

二、数组指针变量    

                2.1 数组指针是什么    

                2.2 数组指针变量初始化

三、二维数组传参的本质

四、函数指针变量

    4.1 什么是函数指针变量

    4.2 函数指针变量的使用

       4.3 typedef 关键字

五、转移表


一、字符指针变量

在指针的类型中我们知道有⼀种指针类型为字符指针 char* ,一般使用如以下代码:
int main()
{
 char ch = 'w';
 char *pc = &ch;
 *pc = 'w';
 return 0;
}

我们再来看下面这两段代码:

1. char arr[] = "abcdefg"
   char*p = arr;


2. char* p = "abcdefg";

发现这其实不就是一个意思吗,答案是不一样的,

(1)代码1中,字符数组char arr[]中的内容(a b c d  e f g /0)是可以修改的;

(2)而代码2中,”abcdefg“是常量字符串,这里的赋值是将字符串中首字符的地址赋值给p,常量字符串(a b c d  e f g /0)不能被修改。

《剑指offer》中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下:
#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");  1
 else
 printf("str1 and str2 are not same\n");  2
 
 if(str3 ==str4)
 printf("str3 and str4 are same\n");  3
 else
 printf("str3 and str4 are not same\n");  4
 
 return 0;
}

当我们执行后的结果会是语句2和语句3:

(1)这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域(只读数据区),当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。
(2)但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

二、数组指针变量

2.1 数组指针是什么
  • 指针数组 - 是数组,存放的是指针(地址);
  • 数组指针 - 是数组还是指针?
类比:
  • 字符指针 - char* - 指向字符的指针 - 字符指针变量中存放字符变量的地址:
        char ch = ‘w’;
        char* pc = &ch;
  • 整型指针 - int* - 指向整型的指针 - 整型指针变量中存放整型变量的地址:
        int a = 10;
        int* p = &a;
  • 数组指针 - 指向数组的指针 - 数组指针变量中存放数组的地址(&数组名);

下面代码哪一个是数组指针变量,p1 p2分别是什么:

1. int *p1[ 10 ];(指针数组)
2. int (*p2)[ 10 ];(数组指针)
解释:p先和*结合,说明p是⼀个指针变量,然后指针指向的是⼀个⼤⼩为10个整型的数组。所以             p是⼀个指针,指向⼀个数组,叫数组指针。
注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。
2.2 数组指针变量初始化
int main()
{
	int arr[10] = {0};
	int (*p) [10] = &arr;//p就是数组指针,p中存放的是数组的地址
   
   return 0;
}

通过调试发现&arr 和 p 的类型是完全一致的。

这里在回顾补充一些知识:

    arr -- int*            arr+1 跳过4个字节
    &arr[0]  -- int*       &arr[0]+1跳过4个字节
    &arr -- int(*)[10]     &arr+1 跳过40个字节

三、二维数组传参的本质

过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:
void Pirnt(int arr[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++) 
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	Pirnt(arr, 3, 5);
	return 0;
}
这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
  • 一维数组传参:
(1)数组名是首元素的地址;
(2)一维数组在传参的时候,其实传递的是首元素的地址。(函数的参数可以写成数组,也可以写成指针)
二维数组传参:
(1)二维数组的数组名也是首元素的地址
(2)二维数组的数组名就是他的第一行的地址(第一行是一个一维数组,传过去的就是第一行这个一维数组的地址)
(3)二维数组可以理解成为一维数组的数组,二维数组的每一行可以看作是一个一维数组,所以二维数组其实是一个一维数组的数组,二维数组的首元素就是他的第一行。
 所以打印数组的函数参数形式可以写成这样:
         
void Pirnt(int (*arr)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(arr + i) + j));//*(arr + i) == arr[i]
			//arr[i]是第i行的数组名,数组名又表示数组首元素的地址,arr[i]表示是&arr[i][0]
		}
		printf("\n");
	}
}

(二维数组传参,形参的部分可以写成数组,也可以写成指针形式)

四、函数指针变量

4.1 什么是函数指针变量

我们知道,变量有地址,数组也有地址,那么函数是否有地址呢?

我们来测试一下:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int* pa = &a;//整型指针变量

	int arr[5] = {0};
	int (*parr)[5] = &arr;//parr 是数组指针变量
	//arr &arr
	
	//&函数名和函数名都是函数的地址,没有区别
	printf("%p\n", &Add);
	printf("%p\n", Add);
 
return 0;
}

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	
	int (*pf)(int,int) = &Add;//pf 函数指针变量
	int (* pf)(int, int) = Add;//pf 函数指针变量
	int ret2 = (*pf)(4, 5);
	printf("%d\n", ret2);

	int ret = Add(4, 5);
	printf("%d\n", ret);


	int (*pf)(int x, int y) = &Add;//pf 函数指针变量
	//int (*)(int,int) 函数指针类型

	return 0;
}
4.2 函数指针变量的使用
#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = Add;
	printf("%d", pf(3, 5));
	return 0;
}

下面我们来看两端有趣的代码

  • (1)    (        *(void (*)() )0        )    ();
  • (2)        void (*signal( int , void (*)( int )))( int );
   (1) void (*)() :函数指针类型;( void (*)())0:强制类型转换;
     意思是:是一次函数调用,调用0地址处的那个函数,0地址处放的这个函数是没有参数,返回类型是void。
(2)首先要清楚:
//函数定义:
int Add(int x, int y)
{
	return x + y;
}

//函数调用:
Add(int, int);

//函数定义:
int Add(int, int);

所以:上面代码(2)是一次函数声明,函数的名字叫:signal ,signal 函数的参数有两个,第一个参数是的类型是 int ,第二个参数的类型是一种函数指针 void(*)(int),该函数指针指向的函数参数是 int,返回类型是 void;signal函数的返回类型也是一个函数指针,类型是 void(*)(int)

 ,该函数指针指向的函数参数是 int ,返回类型是 void。

4.3 typedef 关键字
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
(1)  unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
typedef unsigned int uint;
//将unsigned int 重命名为uint

(2)指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:

typedef int* ptr_t;

(3)数组指针和函数指针稍微有点区别: ⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:

typedef int(*parr_t)[5]; //新的类型名必须在*的右边

(4)函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:

typedef void(*pfun_t)(int);//新的类型名必须在*的右边

所以,我们来看刚刚那两段有趣的代码,可以将 void(*)(int) 类型重命名为pf-t:

typedef void(*pf_t)(int);//新的类型名必须在*的右边

//简化两段代码:
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);

五、转移表

在了解转移表之前,首先要了解一个新的名词:函数指针数组;

把函数的地址存到一个数组中,那这个数组就叫函数指针数组:

  • int (*parr1[n])();

那函数指针数组有什么用呢,接下来我们来实现一个计算器的的例子说明:

(1)首先定义一些 加Add 减Sub 乘Mul 除Div 函数

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;
}

(2)分别将函数的地址放进pf指针中,并且用函数指针数组存放这些函数的地址


int (*pf1)(int, int) = Add;
int (*pf2)(int, int) = Sub;
int (*pf3)(int, int) = Mul;
int (*pf4)(int, int) = Div;


int (* pf[4])(int, int) = {Add, Sub, Mul, Div};
                         // 0    1    2    3

(3)接下来我们把结果打印出来测试一下

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 (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;

	
	int (* pf[4])(int, int) = {Add, Sub, Mul, Div};
	                       //   0    1    2    3

	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[i](8, 4);
		printf("%d\n", ret);
	}
	return 0;
}

(4.1)接下来就实现一个完整的计算器

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 input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个整数:\n");
			scanf("%d %d", &x, &y);
			 ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个整数:\n");
			scanf("%d %d", &x, &y);
			 ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个整数:\n");
			scanf("%d %d", &x, &y);
			 ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个整数:\n");
			scanf("%d %d", &x, &y);
			 ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
		}
	}while (input);
	return 0;
}

(4.2)使用函数指针数组 - 转移表

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		if (input > 0 && input < 5)
		{
			printf("请输入两位操作数:\n");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d", ret);
			break;
		}
		else if (input == 0)
		{
			printf("游戏结束,退出游戏\n");
			break;
		}
		else
		{
			printf("选择错误,请重新选择\n");
		}
	} while (input);
	return 0;

}

未完待续~~

  • 35
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值