【c语言】深入理解指针(2)

1. 字符指针变量

可以利用字符指针存放字符数组来间接存放字符串。

int main()
{
	char arr[10] = "abcdef";
	char* p = arr;
	printf("p  = %s\n", p);
	return 0;
}

那我们可不可以直接给字符指针存放字符串呢? 

int main()
{
	char* p1 = "abcdef";
	printf("p1 = %s\n", p1);
	return 0;
}

我们来看一下结果:

可以看到是可以直接给字符指针存放字符串,其实指针仅仅存放了字符串首字母的地址,%s会根据这个首字母的地址依次打印字符,当遇到空字符‘\0’则会停止打印,从而打印出了完整的字符串。

字符指针存放字符数组的字符串可修改,而存放字符串字面量不可修改,因为

  • 当字符指针指向一个字符数组时,该数组通常在栈上或堆上分配内存空间,这块内存是可以被修改的。
  • 当字符指针直接指向一个字符串字面量时,该字面量通常存储在程序的只读数据段,这部分内存是不可以修改的。

《剑指offer》中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下:

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

先来看str1和str2,在一篇文章中已经讲过数组名是数组首元素的地址,所以str1和str2分别是数组str1[]和数组的str2[]的首地址,而两个不同的数组是不会存放在同一个位置的,所以str1和str2是不相等的。

再来看str3和str4,str3和str4都是存放常量字符串的首地址,而当字符指针直接指向一个字符串字面量时,该字面量通常存储在程序的只读数据段,在只读数据段中是不会开辟两个空间存放相同的字面量的,也就是说*str3和*str4指向的"hello bit."是同一个字符串,所以str3和str4存放的地址是相同的。

2. 数组指针变量

2.2 数组指针变量是什么

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

指针数组与数组指针:

指针数组:

int* p1[10];

[]的优先级是比*的优先级高的,所以p1先和[]结合。p1[10]是一个数组,这个数组存放的是int*类型的元素,也就是存放着指针类型的元素,所以p1是一个指针数组。

数组指针:

int (*p2)[10];

()的优先级是比[]高的,所以*与p2结合,p2是指针变量,后面的[10]表示,p2指向一个存放着10元素的数组,int表示数组的每个元素的类型是int类型。

总的来说,指针数组是数组,数组指针数指针

 2.3 数组指针变量怎么初始化

数组名只是表示首元素的地址,而取地址数组名才是表示整个元素的地址,所以在给数组指针初始化的时候我们应该取地址数组名,才会存下整个数组的地址。

int arr[10] = { 0 };
int(*p2)[10] = &arr;

3. 二维数组的传参本质

数组名是数组首元素的地址,那二维数组中谁是首元素?

二维数组的首元素就是第一行,首元素的地址就是第一行的地址,第一行的地址是一个一维数组的地址,所以二维数组在传参的时候,如果需要用指针去接收,就要用一维数组指针。

void print(int(*p)[5], int a, int b)
{
	for (int i = 0; i < a; i++)
	{
		for (int j = 0; j < b; 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;
}

4. 函数指针变量

函数指针是指向函数的指针。

函数名代表的是函数的入口地址。我们只需要用函数指针存放这个入口地址就可以通过函数指针调用这个函数了,还有就是函数名与取地址函数名都是函数的入口地址,二者没有区别。

【c语言】深入理解指针(2)_第1张图片

函数指针存放函数地址:

void Add(int x,int y)
{
	return x + y;
}
int main()
{
	int(*p)(int, int) = Add;
	return 0;
}

 通过函数指针调用函数:

一般通过调用函数名就可以调用函数了,而函数指针存放着函数的地址,所以我们可以不解引用直接通过函数指针调用函数,当然了也是可以通过解引用函数指针来调用函数的。

【c语言】深入理解指针(2)_第2张图片

5. 函数指针数组

存放函数指针的数组是函数指针数组。 

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 (*p)(int, int) = Add;//p是函数指针变量
	int (*p1[])(int, int) = { Add,Sub,Mul,Div };//p1是函数指针数组
	return 0;
}

函数指针数组的调用: 

【c语言】深入理解指针(2)_第3张图片

6. 转移表

转移表在C语言中通常指的是函数指针数组

转移表(也称为跳转表或分支表)是编程中用于实现多路分支选择的一种数据结构。它通常用于优化程序的执行效率,尤其是在需要根据不同的输入值进行不同操作的场景中。

在C语言中,转移表可以定义为一个包含函数指针的数组。每个数组元素都指向一个函数,这些函数代表了不同的操作或处理逻辑。通过索引访问这个数组,程序可以“跳转”到相应的函数并执行。这种方式比使用多个if-else语句或者switch-case语句更加高效,因为它可以直接通过数组索引来定位和调用函数,而不需要逐个判断条件。

举例:计算器的一般实现:

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 menu()
{
	printf("***************************\n");
	printf("******1.加法    2.减法*****\n");
	printf("******3.乘法    4.除法*****\n");
	printf("***********0.退出**********\n");
	printf("***************************\n");

}
int main()
{
	int(*p[5])(int, int) = {0,Add,Sub,Mul,Div};
	int input;
	int x, y;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input > 0 && input < 5)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			printf("%d\n", p[input](x, y));
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("请重新选择\n");
		}
	} while (input);
	return 0;
}

你可能感兴趣的:(c语言,c语言,开发语言,学习)