C语言---深入理解指针(3)

目录

1 字符指针变量

2 数组指针变量

2.1 什么是数组指针变量

2.2 数组指针变量的初始化 

3 二维数组传参的本质 

4 函数指针变量 

4.1 两个有趣的代码 

4.2 typedef关键字 

5 函数指针数组

6 函数指针数组的应用---计算器的实现 

6.1 计算器的一般实现 

 6.2 利用函数指针数组实现

6.3 一般实现的改进


1 字符指针变量

在指针的类型中有一种指针类型为字符指针char*,一般使用:

#include 
int main()
{
	char ch = 'a';
	char* pc = &ch;
	*pc = 'a';
	return 0;
}

 还有一种使用方式如下:

#include 
int main()
{
	const char* pstr = "hello world";
	printf("%s\n", pstr);
	return 0;
}

代码const char* pstr = "hello world";一些初学者就会误认为是把字符串hello world放入字符指针pstr里,但其实是把字符串hello world的首字符h的地址放到了pstr中

C语言---深入理解指针(3)_第1张图片一道有关字符串相关的题目: 


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

打印出什么结果? 

 输出结果:

 C语言---深入理解指针(3)_第2张图片

这里的str3和str4指向相同的常量字符串,C语言中把 常量字符串存储到单独的一个内存区域,多个指针指向同一个字符串时,指向的内存是相同的。而相同的常量字符串初始化不同数组时就会申请不同的内存块。所以str1和str2不同,str3和str4相同。

2 数组指针变量

2.1 什么是数组指针变量

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

1 int *p1[10];//指针数组

2 int (*p2)[10]p;//数组指针

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

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

2.2 数组指针变量的初始化 

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

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

其中p是数组指针变量名,10是p指向数组的元素个数,int是p指向的数组的数据类型。

将p去掉,int(*)[10]就是数组指针变量的数据类型。

3 二维数组传参的本质 

有了对数组指针的理解,就可以理解二维数组传参的本质。

如果不利用指针,我们将二维数组传参给一个函数,一般这样写:

#include 
void test(int arr[3][5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	test(arr, 3, 5);
	return 0;
}

二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组每一个元素是一个一维数组,第一个元素就是第一行。所以,二维数组的数组名就代表第一行的地址,根据上面代码的例子,第一行一维数组的类型是int[5],第一行地址类型就是int(*)[5],意味着二维数组传参本质上也是传递了地址,传递的是第一行一维数组的地址,代码如下:

#include 
void test(int (*p)[5], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ",*(*(p+i)+j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
	test(arr, 3, 5);
	return 0;
}

4 函数指针变量 

 函数指针变量是用来存放函数地址的,可以通过地址来调用函数。

函数是否有地址呢?可以编写代码测试一下:

void test()
{
	printf("hello world");
}
#include 
int main()
{
	printf("test: %p\n", test);
	printf("&test:%p\n", &test);
	return 0;
}

输出结果:

C语言---深入理解指针(3)_第3张图片

所以函数也是有地址的,函数名就是函数的地址。如果要将函数的地址存放起来,就要创建函数指针变量,函数指针变量的创建和数组指针变量的创建类似:

int Add(int x, int y)
{
    return x + y;
}
int (*pf)(int, int) = Add;//x和y可以省略
int (*pf)(int x, int y) = Add;

其中pf是函数指针变量名,(int x,int y)是交代指向的函数参数类型,int是pf指向函数的返回类型。pf函数指针的数据类型是int (*) (int x,int y)。 

 函数指针变量的使用:

#include 
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf)(int, int) = Add;
	printf("%d", pf(3, 3));
	return 0;
}

4.1 两个有趣的代码 

代码1:(*(( void (*) () )0)) ();

解释:

 void (*) () ---函数指针类型

(( void (*) () )---强制类型转换

(( void (*) () )0---将0强制类型转换成为 void (*) ()的函数指针类型,意味着0这个地址放着无参数,返回类型是void的函数。

(*(( void (*) () )0)) ();最终调用0地址存放的函数

 代码2:void (*signal(int, void(*)(int)))(int);

解释:

 signal(int, void(*)(int))---signal是函数名,int和void(*)(int)是参数,如果把signal(int, void(*)(int))去掉,剩下的void (*)(int)是函数返回值类型。*必须放在名字的旁边。代码的意义是声明一个为signal(int, void(*)(int))的函数,返回类型是void (*)(int)。

4.2 typedef关键字 

typedef是用来类型重命名的,可以将复杂的类型简单化,举例:

 1 typedef unsigned int uint

//将unsigned int重命名为uint

指针类型的重命名:

typedef int* ptr_t 

数组指针类型的重命名:例如将一个数据类型为int(*)[5]重命名为parr_t:

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

 函数指针类型的重命名:例如将一个数据类型为void(*)(int)重命名为pf_t:

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

5 函数指针数组

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

函数指针数组的定义:

int (*parr)();

数组里的内容是int (*)()类型的函数指针。

6 函数指针数组的应用---计算器的实现 

6.1 计算器的一般实现 

编写一个计算器的代码时,一般会这么写:

//计算器实现  普通方法
#include 
void menu()
{
	printf("*******1.加法*******\n");
	printf("*******2.减法*******\n");
	printf("*******3.乘法*******\n");
	printf("*******4.除法*******\n");
	printf("*******0.退出*******\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()
{
	int input = 1;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();//菜单
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Add(x, y);
			printf("结果为:%d\n", 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:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

 6.2 利用函数指针数组实现

int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };//定义一个函数指针数组

//计算器实现 函数指针数组
#include 
void menu()
{
	printf("*******1.加法*******\n");
	printf("*******2.减法*******\n");
	printf("*******3.乘法*******\n");
	printf("*******4.除法*******\n");
	printf("*******0.退出*******\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()
{
	int input = 1;
	int x = 0;
	int y = 0;
	int ret = 0;
	int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		menu();//菜单
		printf("请选择:>");
		scanf("%d", &input);
		if (input > 0 && input < 5)
		{
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = (*p[input])(x,y);
			printf("结果为:>%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出程序\n");
			break;
		}
		else
		{
			printf("输入错误,请重新输入:>\n");
			break;
		}
	} while (input);
	return 0;
}

6.3 一般实现的改进

当我们使用一般方法写一个计算器时,case后面的代码重复率过高,输入输出操作造成冗余,可以把调用的函数地址以参数形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这种方法也叫做利用回调函数:

//计算器实现  普通方法 方法二
#include 
void menu()
{
	printf("*******1.加法*******\n");
	printf("*******2.减法*******\n");
	printf("*******3.乘法*******\n");
	printf("*******4.除法*******\n");
	printf("*******0.退出*******\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;
}
void calc(int (*p)(int,int))
{
	int x, y;
	printf("请输入两个操作数:>");
	scanf("%d%d", &x, &y);
	int ret = p(x, y);
	printf("结果为:%d\n", ret);
}
int main()
{
	int input = 1;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();//菜单
		printf("请选择:>");
		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;
}

以上就是有关指针的部分内容了,如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力,后续会继续更新指针的剩余知识。

你可能感兴趣的:(C语言,c语言)