指针知识梳理(3)

本文详细介绍了C++中的char*指针、数组(包括栈上的动态数组和常量区的字符串)、数组指针、二维数组传参的本质以及函数指针的概念,还展示了如何使用函数指针数组(转移表)实现简易计算器的功能。
摘要由CSDN通过智能技术生成

字符指针变量  

char*一般有两种写法

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main(){
	char ch = 'w';
	char* pc = &ch;
	*pc = 'w';
	return 0;
}

还有一种是

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main(){
	const char* pstr = "hello world";//这是把⼀个字符串放到pstr指针变量里了吗?
	printf("%s\n", pstr);
	return 0;
}

虽然容易被这样以为, 但本质上是把字符串hello world的首字符的地址放到了pstr

来看这道练习

 

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

想想答案再看运行结果

 

第一个不同是因为数组是在栈上开辟的是不同空间,按照这个形式往里面存入数据虽然是一样的,但是数组下标空间的地址是不一样的,所以元素地址肯定是不一样。数组的内容不是在字符常量区上而是在栈上,所以可以不加const。

第二个一样的是因为字符串存放在字符常量区中不会额外在开辟相同的字符串,指针变量指向的都是字符串首元素的地址。

数组指针变量

我们从前面知道了指针数组是一种数组,数组中存放的是指针

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

整形指针是指向整形的指针,整形指针变量存放的就是整形数据的地址

浮点型指针是指向浮点型的指针,浮点型指针变量存放的就是浮点型数据的地址

那么数组指针就是指向数组的指针,数组指针变量存放的就是数组的地址

int *p1[10];
int (*p2)[10];

上面哪个是数组指针变量? 

 int*p1[10]中,p1先和[]结合,先说明自己是一个数组,那前面的int*就是数组指向的元素的类型,表明这是一个指针数组

int(*p2)[10]中,p2先在括号中与*结合,说明自己是一个指针,指向的是一个int类型的数组,表明这是一个数组指针    

注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合    

那怎么获取数组的地址呢

如果要存放个数组的地址,就得存放在数组指针变量中

 我们可以看到&aar和p的类型是完全一致的

int (*p) [10] = &arr;
 |   |     |
 |   |     |
 |   |     p指向数组的元素个数
 |   p是数组指针变量名
 p指向的数组的元素类型

二维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下二维数组传参的本质了。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test(int q[3][5], int r, int c){
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++){
		for (j = 0; j < c; j++){
			printf("%d ", q[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} };
	test(arr, 3, 5);
	return 0;
}
这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么⼆维数组的⾸元素就是第一行,是个一维数组。

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第一行的地址,是⼀维数组的地址。根据上⾯的例⼦,第一行的一维数组的类型就是 int [5] ,所以第一行的地址的类型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第一行这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test(int(*p)[5], int x, int y){
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++){
        for (j = 0; j < y; j++){
            printf("%d ", *(*(p + i) + j));
            //等同于p[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} };
    test(arr, 3, 5);
    return 0;
}

函数指针变量

通过前面的类比,我们不难得出函数指针变量是存放函数地址的,是能通过地址调用函数的,那函数是否有地址呢

做个测试

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test(){
	printf("hehe\n");
}
int main(){
	printf("test: %p\n", test);
	printf("&test: %p\n", &test);
	return 0;
}

 

我们确实发现函数是有地址的,函数名是函数的地址,取地址(&)函数名也是函数的地址,函数的地址要保存到函数指针变量里面去,那函数指针变量要怎么写呢?

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test()
{
	printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{
	return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;

 函数类型解析:

int (*pf3) (int x, int y)
 |    |     ------------ 
 |    |          |
 |    |          pf3指向函数的参数类型和个数的交代
 |    函数指针变量名
 pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型
(*(void (*)())0)();
void (*signal(int, void(*)(int)))(int);

 分析这两句代码

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

首先我们分析0是什么类型?0是个int类型的

0前面是(void (*) () )0,(void (*) () )这个是函数指针类型,这个指针指向的函数没有参数,返回类型是void,所以(void (*) () )0 我们是把0强制转换成函数指针的类型,所以我们可以把0当做一个函数的地址,(*0)()这样就是去调用0地址处的函数

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

(*signal ( int, void(*)(int)) )首先*signal没有单独用括号扩起来,所以signal是先跟后面的结合,所以signal是一个函数,这个函数第一个函数时int,第二个参数是void(*)(int),

void(*)(int)是函数指针,该指针指向函数参数是int,返回类型是void,

我们把void (*signal(int, void(*)(int)))(int) 去掉signal ( int, void(*)(int)) 我们可以得到这个函数的返回类型是也是void(*)(int)这个函数指针,该指针指向函数参数是int,返回类似是void

所以这段代码是一段函数的声明

去掉函数名和参数列表剩下的就是函数的返回类型

typedef关键字


typedef是可以把复杂类型重命名的,使其简单化

如果你觉得unsigned int这个无符号整形类型太复杂了,那么我们就可以使用typedef来重命名类型

int  main(){
    typedef unsigned int uint;
    //所以unsigned int就等价于uint
    unsigned int a = 10;
    uint b = 20; //a和b的类型是一样的
    return 0;
}

但是对数组指针和函数指针类型重命名还是稍微有区别的

我们把数组指针int(*)[5],需要重命名为parr_t

int main(){
    //typedef int(*)[5] parrt_t 这个写法是错误的
    typedef int(*parrt_t)[5];
    int arr[5] = { 0 };
    parrt_t p1 = &arr;
    int(*p2)[5] = &arr;
    //p1和p2类型是等价的
    return 0;
}

函数指针重命名也是一样,我们想将int(*)(int)重命名为pf_t

int test(int a){
    return a + 1;
}
int main(){
    //typedef int(*)(int) pf_t;这个写法是错误的
    typedef int(*pf_t)(int) ;
    pf_t p1 = test;
    int(*p2)(int) = test;//p1和p2类型是一样
    return 0;

函数指针数组

数组是用来存放相同类型数据的存储空间,如果我们要把函数的地址存放在数组中,我们就需要用到函数指针数组

函数指针数组就是在函数指针的基础上做一点点改变

函数指针void(*p)(int, int),这颗*说明p是个指针,指向函数参数是2个int类型的,函数的返回类型是void

函数指针数void(*p[4])(int, int),这里p是首先和[]先结合的,说明的p是个数组,数组有4个元素,每个元素的类型都是void(*)(int, 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 x, y;
 int input = 1;
 int ret = 0;
 do
 {
 printf("*************************\n");
 printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
 printf(" 0:exit \n");
 printf("*************************\n");
 printf("请选择:");
 scanf("%d", &input);
 switch (input)
 {
 case 1:
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = add(x, y);
 printf("ret = %d\n", ret);
 break;
 case 2:
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = sub(x, y);
 printf("ret = %d\n", ret);
 break;
 case 3:
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = mul(x, y);
 printf("ret = %d\n", ret);
 break;
 case 4:
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = div(x, y);
 printf("ret = %d\n", ret);
 break;
 case 0:
 printf("退出程序\n");
 break;
 default:
 printf("选择错误\n");
 break;
 }
 } while (input);
 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;
}
int main()
{
 int x, y;
 int input = 1;
 int ret = 0;
 int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
 do
 {
 printf("*************************\n");
 printf(" 1:add 2:sub \n");
 printf(" 3:mul 4:div \n");
 printf(" 0:exit \n");
 printf("*************************\n");
 printf( "请选择:" );
 scanf("%d", &input);
 if ((input <= 4 && input >= 1))
 {
 printf( "输⼊操作数:" );
 scanf( "%d %d", &x, &y);
 ret = (*p[input])(x, y);
 printf( "ret = %d\n", ret);
 }
 else if(input == 0)
 {
 printf("退出计算器\n");
 }
 else
 {
 printf( "输⼊有误\n" ); 
 }
}while (input);
return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值