深度理解指针(3)

hello,各位小伙伴们在上期的最后我们了解到了指针数组,是用来存储指针的数组。这期我们将会学习深度理解指针(3)有关指针的内容,仍然与数组分不开,让我们踏上此次列车来进行新的旅途吧!

目录

 字符指针

经典例题

数组指针

二维数组传参本质 

 函数指针变量的创建

小试牛刀 

typedef关键字

函数指针数组


 字符指针

我们通常会这样使用字符指针:

#include<stdio.h>
int main ()
{
    char ch = 'a';
    char*pch = &ch;
    &ch = 'w';
    return 0;
}

但是还会有一种特殊的使用方式:

#include<stdio.h>
int main ()
{
    char*ptr = "hello liyaoyao";
    printf("%s",ptr);
}

 这是什么意思呢?如果难以理解不妨看一下下面与之类似的:

#include<stdio.h>
int main ()
{
    char arr[] = "hello liyaoyao";
    char*ptr = arr;
    //arr和ptr的类型相同,同时指向的对象也相同可以互相替换
    //printf("%s",arr);
    printf("%s",ptr);
}

 看来这段代码之后也许小伙伴们心里已经清楚了。char*ptr = "hello liyaoyao";是将字符串的首地址赋给ptr,同时后面为常量字符串是不可以被修改的。但如果以创建一个字符数组的形式,字符串的内容可以被修改,小伙伴们可以试试哦。(程序会崩掉)!

经典例题

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

 想一想结果会是什么呢?答案会在最后揭晓!

数组指针

存放整型地址的变量为int*,存放字符地址的变量为char*,那么存放数组地址的变量为什么呢?

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

数组指针变量的格式为:

#include<stdio.h>
int main ()
{
    int arr[10] = { 0 };
    int (*parr)[10] = &arr;
}//再次提醒:arr为数组首元素地址,&arr[0]也为数组首元素地址,arr为整个数组的地址。

解释:p先跟*结合说明p是一个指针变量,然后指针指向的是大小为10的整型数组,所以p是一个指针,指向一个数组,叫做数组指针变量。

二维数组传参本质 

在前期的扫雷游戏中我们就已经大概了解到了二维数组的传参问题。通常情况下我们会使用下标的形式对二维数组进行访问。

#include<stdio.h>
void Print(int arr[][5],int row,int col)
{
    int i = 0;
    for(i = 0;i < 3;i++)
    {
        int j = 0;
        for(j = 0;j < 5;j++)
        {
            printf("%d",arr[i][j]);
        }
    }
}

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

在函数实参部分arr是二位数组的数组名,表示二维数组首元素地址,我们不禁会想首元素到底是谁,地址又是哪个呢?

我们可以这样理解:二维数组其实是一维数组的数组,那二维数组的首元素地址应该是第一行数组的地址。根据前面介绍的数组指针,我们可以通过修改形参部分来对其进行接收。

函数定义部分:

void Print(int (*p)[5],int row,int col)
{
    int i = 0;
    for(i = 0;i < row;i++)
    {
        int j = 0;
        for(j = 0;j<col;j++)
        {
            //printf("%d",*(*(p+i)+j));
            //printf("%d",(*(p+i))[j]);
            printf("%d",p[i][j]);
        } 
    }
}

 函数指针变量的创建

根据前面的学习我们了解到变量有地址,数组有地址,那么函数是否存在地址呢?

答案是存在!

#include<stdio.h>
int Sub(int x,int y)
{
    return x - y;
}
int main ()
{
    printf("%p\n",Sub);
    printf("%p\n",&sub);
}//两者的输出结果是相同的

对函数取地址有两种使用方式:1,函数名作为函数地址     2,取地址函数名作为函数的地址。

创建函数指针变量与数组指针变量的创建类似。

//……
int function(int x,int y)
{
    …………
}
int main()
{
    int (*p)(int x,int y) = &function;
}

 函数指针变量的使用:

//……
int function(int x,int y)
{
    ///
}
int main ()
{
    int(*p)(int x,int y); 
    int ret = function(2,3);
    ret = (*p)(2,3);
    ret = p(2,3);
}//三种使用方式均可以

小试牛刀 

 让我们来看两个有意思的代码并尝试理解它:

1、

#include<stdio.h>
int main ()
{
    (*(void(*))0)();
    return 0;
}

我们试着从0开始入手,0的前面有一个括号,括号里面为void(*),在一个数字的前面加上括号就是强制类型转化,即将0强制类型转化成void(*)类型,即0从一个整型转化成了函数的地址(变成了函数指针变量),同时这个函数没有参数。

该段代码出自C陷阱与缺陷。 

2、 

#include<stdio.h>
int main ()
{
    void(*signal(int , void(*)(int)))(int);
    return 0;
}

这句代码是函数的声明,signal是函数的名字,signal函数有两个参数一个是int类型,另一个是void(*)(int)类型,即函数指针类型,指向的参数类型是int类型,返回值是void。同时该函数指针指向的参数类型是int类型,返回值是void。

typedef关键字

typedef旨在类型的重定义,将复杂的参数类型定义为简单类型。

//…………
//指针的重定义
typedef int* prt;
int a = 0;
//int *pa = &a;
ptr pa = &a;

//…………
//指针数组的重定义
typedef int *ptr_t[10] ;
//int *arr[10];
ptr_t arr;

//…………
//数组指针的重定义
typedef int (*ptr_t1)[2];
int arr[2] = {0};
//int (*parr)[2] = &arr;
ptr_t1 parr[2] = &arr;

这样重定义虽然在书写的时候变得简单了,但对于变量类型的理解做出了阻碍,在具体写代码时要看情况合理使用。

函数指针数组

前面我们讲到了指针数组(整型指针数组、字符指针数组),下面我们来学习一下函数指针数组。

定义: 

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

 

#include<stdio.h>
int Add(int x,int y)
{
    //
}
int Sub(int x,int y)
{
    //
}
int main ()
{
    int (*ptr[2])={Add,Sub};
    return 0;
}

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

模拟实现一下计算器的+、-、*、\的功能。

通常情况下我们会这样使用:

#include<stdio.h>
Print_menu()
{
	printf("**************************************\n");
	printf("*******   1.  add      2.sub    ******\n");
	printf("*******   3.  mul      4.div    ******\n");
	printf("************     0.exit     **********\n");
	printf("**************************************\n");

}
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()
{
	Print_menu();
	int input = 0;
	int x = 0;
	int y = 0;
	
	do
	{
		int ret = 0;
		printf("请选择>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d", ret);
			break;
		case 2:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);

			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			break;
		default:
			printf("输入错误,请重新输入:");
		}

	} while (input);
	return 0;
}

但我们发现在switch语句中每一种情况的格式是一样的,只有函数不一样,多次书写会造成代码冗余的情况,我们可以使用函数指针数组来进行简化!

主函数部分:

int main()
{
	Print_menu();
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	int (*ptr[5])(int x, int y) = { 0,Add,Sub,Mul,Div };
	do
	{
		printf("请选择>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个整数:");
			scanf("%d %d", &x, &y);
			ret = (*ptr[input])(x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("计算结束!");
		}
		else
		{
			printf("输入错误请重新输入!\n");
		}
	} while (input);
	return 0;
}

 使用函数指针数组可以将这几个传参相同的函数地址存放在一个数组中,在每次使用时,直接调用即可!

 

经典例题答案公布:

str1 and str2 are not same
str3 and str4 are same

这里的判断部分比较的都是首元素地址,str1和str2都是创建一个新的数组,与数组内容无关,str1与str2在内存中的地址不同所以会输出not same。str3与str4都是字符指针变量,将一常量赋给str1和str2,因为常量内容是一样的,所以不需要在额外申请一段空间来进行存储,所以str1与str2所代表的首元素地址是相同的。

 OK这期就到这里啦!下期我们继续学习指针的内容有关回调函数和qsort函数的内容,拜拜。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值