【C语言】指针剖析(3)

发布于:2024-07-03 ⋅ 阅读:(17) ⋅ 点赞:(0)

©作者:末央&

©系列:C语言初阶(适合小白入门)
©说明:以凡人之笔墨,书写未来之大梦

在这里插入图片描述

一、字符指针

字符指针的使用方式一般都是:

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

值得注意的是还有一种使用方式是:

int main()
{
 const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
 printf("%s\n", pstr);
 return 0;
}

代码 const char* pstr = “hello bit.”; 特别容易让同学以为是把字符串 hello bit 放
到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。

在这里插入图片描述

理解上面的概念后我们再来看一道题

#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");
 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 and str2 are not same
str3 and str4 are same

在这里插入图片描述

注意:常量字符串与普通字符串最大的区别是,常量字符串是不可被修改的字符串,既然不能被修改,那么在内存中没有必要存放两个一模一样的字符串,所以在内存中相同的常量字符串只有一个。

二、数组指针

概念

之前我们已经学习了指针数组,知道指针数组实际上是一个数组,他的每个元素是指针类型。反之,数组指针实际上就是一个指针,一个指向数组的指针。
在这里插入图片描述
区别之前我们应该知道,*号的优先级是比[ ]低的所以第一行是先与[ ]结合则是一个数组,每个元素类型是int *,第二行不同的是在用()把 *p2括了起来可以知道()和[ ]的优先级一样,但是根据结合性是从左到右所以先与()结合,则包含了 * 则是一个指针,指向一个数组.所以第二行是数组指针

类型补充

如果我们想知道一个变量的类型就可以把变量名去掉就是他的类型。例如:

int a;		//去掉a他的类型就是int
int *arr[10]	//去掉arr他的类型就是int* [10]

这里要注意的是数组的类型分别体现了数组元素的类型(数组的类型和数组元素类型不一样)和数组元素个数的概念。
在这里插入图片描述
一个指针变量除去了变量名和 * ,便是指针指向的内容的类型。

int a = 10;
int* p = &a;//除去变量名(p)和*,便是P指向的内容(a)的类型->int
int (*p)[10];//去掉 * 和变量名指向内容的类型就是int [10];

三、二维数组传参的本质

实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组
在这里插入图片描述

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀
维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类
型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

#include<stdio.h>
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("\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;
}

四、函数指针

函数的地址

函数指针既然是指针,就一定离不开地址。那么函数的地址是什么呢?我们不妨测试以下代码:

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

在这里插入图片描述
那么我们可以知道函数名就是函数的地址,当然也可以通过&函数名来获得函数的地址

函数指针的初始化

那么我们现在有如下一个函数

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

我们可以定义一个函数指针把他的地址存储起来。我们结合前面数组指针的例子,可以知道:
在这里插入图片描述
知道这三点我们再来创建函数指针类型,只要是指针就必须要先与 * 号结合,而括号的优先级高于 * 所以我们要用括号把* 括起来,而又因为我们要指向的函数类型是int (int, int) 对应上面第三点,所以我们可以写为int (*p)(int,int);对应上面第二条,指针变量的类型又是int ( * )(int,int)
在这里插入图片描述
知道了怎么创建函数指针类型,那么我们就可以创建一个函数指针来存储上面的函数了。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;	//这里还可以&Add
	return 0;
}

注意在数组中&数组名和数组名是不一样的意思,但是在函数中&函数名和函数名都是表示函数的地址。

函数指针的使用

我们可以通过函数指针来使用函数
方法一:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 20;
	int(*p)(int, int) = &Add;
	int ret = (*p)(a, b);//解引用找到该函数
	printf("%d\n", ret);
	return 0;
}

既然初始化的时候是用&Add赋值函数指针,那么我们调用的时候就可以用*把&抵消,得到Add直接进行传参
在这里插入图片描述

方法二:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 20;
	int(*p)(int, int) = Add;
	int ret = p(a, b);//不用解引用
	printf("%d\n", ret);
	return 0;
}

我们这次在赋值的时候直接把函数名给了函数指针,前面说到函数名和&函数名都表示函数的地址,我们可以不解引用就直接传参。
在这里插入图片描述

五、typedef关键字修饰指针

typedef关键字的主要作用就是修改类型标识符的名字。例如:

int a = 10;
typedef int INT;
INT b = 10;

这里的INT就等价与int
那么是否可以修饰指针呢?

int a = 10;
int* pa = &a;
typedef int* INT;

INT b = &a;
printf("%d",*b);

很明显是可以的
但是对于数组指针和函数指针类型标识符的修改有点差别
在这里插入图片描述

六、函数指针数组

概念

同理解指针数组,函数指针数组实际上也是一个数组。他的每个元素是一个函数指针。

int* pa[4];

将数组名和[ ]类型去掉就是数组元素的类型

很明显这个数组的元素类型是int*

同理函数指针数组的元素类型应该是一个函数指针类型。我们看前面的一个函数指针如下:

int(*p)(int, int) 

去掉变量名就是变量类型:int(*)(int,int);再加上函数指针数组是一个数组所以要与[ ] 先结合
所以就是 int( * pp[4])(int,int)

函数指针数组的创建只需在函数指针创建的基础上加上[ ]即可。
比如,你要创建一个函数指针数组,这个数组中存放的函数指针的类型均为int(*)(int,int),如果你要创建一个函数指针为该类型,那么该函数指针的写法为int(*p)(int,int),现在你要创建一个存放该指针类型的数组,只需在变量名的后面加上[ ]即可,int(*pArr[10])(int,int)。

函数指针的使用(模拟计算器)

#include<stdio.h>
void menu()
{
	printf("|-----------------------|\n");
	printf("|     1.Add   2.Sub     |\n");
	printf("|     3.Mul   4.Div     |\n");
	printf("|        0.exit         |\n");
	printf("|-----------------------|\n");
}//菜单
double Add(double x, double y)
{
	return x + y;
}//加法函数
double Sub(double x, double y)
{
	return x - y;
}//减法函数
double Mul(double x, double y)
{
	return x*y;
}//乘法函数
double Div(double x, double y)
{
	return x / y;
}//除法函数
int main()
{
	int input = 0;
	double x = 0;//第一个操作数
	double y = 0;//第二个操作数
	double ret = 0;//运算结果
	double(*pArr[])(double, double) = { 0, Add, Sub, Mul, Div };
	//函数指针数组-转移表,这里首元素是0是为了方便用户选择
	int sz = sizeof(pArr) / sizeof(pArr[0]);//计算数组的大小
	do
	{
		menu();
		printf("请输入:>");
		scanf("%d", &input);
		if (input == 0)
			printf("退出程序\n");
		else if (input > 0 && input < sz)
		{
			printf("请输入两个操作数:>");
			scanf("%lf %lf", &x, &y);
			ret = pArr[input](x, y);
			printf("ret=%lf\n", ret);
		}
		else
			printf("选择错误,请重新选择!\n");
	} while (input);//当input不为0时循环继续
	return 0;
}

代码中函数指针数组第一个元素为0是为了对应菜单选择模块,程序用数组把每个函数的地址都存储起来,再通过input变量来控制要访问的函数和程序的终止。