指针详解(3)

指针详解(3)

字符指针变量

字符指针变量,使用来存放字符数据的地址,常用于存放单个字符或字符串。

int main()
{
    char str = 'h';
    char* ptr = &str;
    //*ptr 就等于 ‘h’
    return 0;
}

更常用的是使用字符指针存放字符串.

int main()
{
    
	char* pstr1 = "hello hehe";
	char str[] = "hello haha";
	return 0;
}

使用数组来存放字符串与使用字符指针存放字符串的区别:

  • 使用字符指针存放字符串实际上存放的是首元素的地址。
int main()
{
	char* pstr = "hello haha";
	printf("%c\n", *pstr);
	printf("%s\n", pstr);
	return 0;
}

在使用上,由于pstr存放的是字符串第一个字符的地址所以 对pstr解引用就可以打印第一个字符,打印整个操作符只需提供首元素的地址,使用%s就可以打印出来。

  • 使用字符指针存放字符串,又名常量字符串,是不可以被修改的。所以在使用时一般会加上const进行修饰,这样当试图对常量字符串进行修改系统就会直接报错,否则不加上const修饰时,当试图对常量字符串内容进行修改时只有在程序运行起来才会报错。
int main()
{
	const char* pstr = "hellow hehe";
	*pstr = 'a';//这里再编译器里直接报错了
	return 0;
}

在这里插入图片描述

一到剑指offer里关于字符串的题目:

分析程序运行后的结果为什么?

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

在这里插入图片描述

数组名代表数组收元素的值,而数组是在堆区上不同的位置开辟空间存放字符串,所以 str1 != str2.

这里str3和str4指向的是同一个常量字符串,C/C++会把常量字符串存放在一块单独的内存区域,多个指针指向同一个字符串时,它们存储的都是同一个字符串的地址。所以str3 == str3 而 str1 != str2

数组指针变量

理解数组指针变量:之前学过整形指针、字符指针 int* p; char* ps;,整形指针是用来存放整形变量的地址,字符指针是用来存放字符的地址,而数组指针同理,数组指针是用来存放数组的地址。

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

这里p1,p2分别是什么呢?

p2先和*结合,说明p2是一个指针变量,然后指针指向一个大小为10十个整形的数组,所以p2是一个指针,指向一个数组,为数组指针。是来存放数组的地址。

[]的优先级高于,这里必须加上(),来保证 p先和 * 结合。*

数组指针变量的使用

数组指针变量使用来存放数组地址的,通过& + 数组名获得数组的地址,而不是直接通过数组名赋值。

//变量声明
int (*p)[10]//初始化
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int (*p)[10] = &arr;//取出整个数组的地址

//使用*p)[i];//访问数组里的元素
  • 变量声明,变量声明很容易理解,类型加变量名,数组指针变量的类型是 int (*)[5];

去掉变量名剩余的就是它的类型,这是编译器给出的答案,相互应证。

在这里插入图片描述

  • 初始化,将数组指针初始化为一个已经存在的数组

  • 使用,数组指针存放的是数组地址,(*p),这步对变量进行解引用,就通过数组地址找到了数组arr,(*P) 等价 arr,然后使用索引就可以访问数组的元素了。

二维数组传参本质

之前说过,一维数组名是首元素地址,那二维数组的数组名又是什么呢?

二维数组的数组名也是数组首元素的地址,二维数组可以理解为,一维数组拼接在一起的数组。

void Print(int arr[3][5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; 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}};
	Print(arr, 3, 5);
	return 0;
}

这是一个打印二维数组的函数,在函数参数上,使用了数组的形式来接受,二维数组。我们说在一维数组传递参数时,实际上传递的是一维数组首元素的地址,所以可以使用指针来接受。同理,二维数组也可以这样操作。

而二维数组的数组名表示什么意思,又该使用什么类型的参数接受?

  • 二维数组可以怎么理解,它是一维数组拼接在一起的数组

  • 二维数组名表示的是首元素的地址,更准确的说,这里首元素指的是第一行数组的地址,也就是一个一维数组的地址。

    可以借助指针数组理解,这里使用三个一维数组模拟了二维数组,将每个一维数组的首元素的地址放在指针数组里,一共有三个一维数组,没个一维数组有五个元素,等价于 int arr[3][5]

    	int arr1[5] = { 0 };
    	int arr2[5] = { 0 };
    	int arr3[5] = { 0 };
    	int* parr[3] = {arr1, arr2, arr3};
    

这里,函数参数就可以使用,数组指针来接收

int arr[3][5] = {0};
int (*parr)[5] = arr;//二维数组首元素的地址,每一行有5个元素,所以数组指针中括号里的值为5

使用指针的形式接收二维数组就可以写为:

void Print(int (*parr)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", *(*(parr + 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}};
	Print(arr, 3, 5);
	return 0;
}

这里将二维数组写成指针的形式,*(*(parr + i) + j),数组的下标访问操作符,和括号内的数值可以这么理解,数值用来计算元素在内存中偏移量获取对应的地址,然后解引用,获得对应的值。

二维数组arr:

在这里插入图片描述

函数指针变量

函数也是有它对应的地址吗?我们不妨可以通过代码进行测试。

void test()
{
	printf("haha\n");
}
int main()
{
	test();
	printf("test  = %p\n", test);
	printf("&test = %p\n", &test);

	return 0;
}

在这里插入图片描述

这里可以发现,打印函数名以及取函数名的值打印,两者均能打印出地址来,不用想别的,这就是函数的地址。而函数名与对函数名取地址二者是等价的,都用来表示地址。

如果将函数的地址存放起来这就是函数指针变量。

函数指针变量在声明和定义时与数组指针类似,在声名函数指针变量它涉及了函数的返回类型,函数名,函数参数。

具体写法:int (*Pf)(int x, int y);

int add(int x, int y)
 {
 	return x+y;
 }
int main()
{
	int (*padd)(int, int) = add;//等价int (*add)(int x, int y);
    printf("%d", (*padd)(3, 2));
    printf("%d", padd(3, 2));
}

上述printf函数里展现了使用函数指针变量的方法,其中padd是需要加上 (*),还是不需要呢~,前文说明了,函数名等价于对函数名进行取地址(&add),而 (*padd)对变量进行解引用通过函数地址访问函数,所以(*padd)等价于add,函数指针变量名padd存放的是函数的地址 它于 &add是等价的,所以 padd 与 *padd 是等价的,在写法上就可以忽略 (*)

四者等价
add
&add
padd
*padd

函数指针数组

int* arr[5];//整形指针数组,用来存放整形指针的地址

char* arr2[5];//字符指针数组,用来存放字符指针的地址

而函数指针数组,则是用来存放函数指针的数组。

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

当需要使用的函数指针过多,我们可以想既然有用来存放整形指针的地址的数组,那也可以有存放函数指针的地址的数组,这样更方便对它们进行维护。

函数指针数组首先得是一个数组,然后才是一个函数指针类型。

int (*pf[4])(int, int) = {Add, Sub, Mul, Div};

下标0,对应加法函数Add;下标1,对应着减法函数Sub;下标2,对应着乘法函数;下标3,对应着除法函数。

 #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 (*pf[4])(int, int) = {Add, Sub, Mul, Div};
    int i = 0;
    for(i = 0; i < 4; i++)
    {
        int ret = pf[i](4, 2);
        printf("%d\n", ret);
    }
    return 0;
}
数组下标访问函数名
pf[0]Add
pf[1]Sub
pf[2]Mul
pf[3]Div

以上表格说明这左右两者均是等价得,pf[i](4, 2),后面的括号是为函数传递参数。

运行结果:

在这里插入图片描述

转移表

函数指针数组的用途:转移表

使用函数指针数组实现:整数的加法、减法、乘法、除法。

当不是使用函数指针数组时,写法如下:

#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;
}
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, y;
    int input = 1;
    int ret = 0;
    do
    {
		menu();
        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;
}

如何,这种写法是不是发现还可以进行优化,因为在每条case语句里的许多代码都是重复的,重复使用printf函数、scanf函数、对函数的调用。这里就可以借助函数指针数组来帮助我们完成简化的目的。

使用函数指针数组将四个函数存入数组里,通过下标就可以完成对这个函数的调用,这样通过输入菜单里的数字就可以完成对不同函数的调用,这样一来就不需要使用switch语句,重复敲许多代码。

这种使用转移表实现的功能,更加灵活,便于维护,可以更具我们的需求来不断完事计算器,不局限于 加、减、乘、除,而实现更多的计算机功能在主函数上并不会做出太大的改动。

int main()
{
	int x, y;
	int ret = 0;
	int input = 1
    int (*pf[5])(int, int) = {0, add, sub, mul, div};//转移表
    do
    {
		menu();
        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;
}
        

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

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值