字符指针、数组指针、指针数组以及指针传参——C语言

1.字符指针

我们先来看两组代码:

#include <stdio.h>
int main()
{
	char* p1 = "abcdef";
	printf("%s", p1);
	return 0;
}
#include <stdio.h>
int main()
{
	char arr[] = "abcdef";
	printf("%s", arr);
	return 0;
}

运行的结果都为

第二组是我们平时常用的写法,那么第一组为什么会能和第二组打印出相同的效果呢?

我们看第一组的代码:我么把常量字符串 “abcdef” 赋给了 char* 型指针变量 p1 ,那么 p1 里面存的地址是什么?我们可以通过下面这个代码来求解:

#include <stdio.h>
int main()
{
	char* p1 = "abcdef";
	printf("%c", *p);
	return 0;
}

 运行的结果为a。

这串代码的意思是,我把常量字符串 “abcdef” 赋给了 char* 型指针变量 p1 ,然后直接对其解引用打印,屏幕上显示 a。

由此我们得出结论:把常量字符串直接赋给指针变量,指针变量里面存放的是常量字符串的首元素地址。这与第二组的第二条语句十分相似(数组名表示首元素地址)。

好了,现在我们可以理解那两组代码了:哦,原来啊,printf后面写的都是首元素地址啊!

但是,为什么拿到首元素地址,并且还不用解引用就能直接打印出字符串了?这就与 %s 自身有关了,其原理大概就是,%s 会对通过你提供的首元素地址找到字符串,然后一直遍历到 "\0" 。

上面我经常提到一个名词——常量字符串。

什么是常量字符串?

我们先定一下这个“常量字符串”的属性——常量,既然它是常量,那它可以被更改吗?

#include <stdio.h>
int main()
{
    //常量不能更改,我们用const保护
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

	char ch1[] = "abcdef";
	char ch2[] = "abcdef";

	if (p1 == p2)
		printf("p1 == p2\n");
	else
		printf(" p1 != p2\n");
	if (ch1 == ch2)
		printf("ch1 == ch2\n");
	else
		printf("ch1 != ch2\n");
	return 0;
}

 大家可以猜一猜运行结果,真正的结果会令你大跌眼镜。

可以发现运行结果,p1 == p2 ;ch1 != ch2 。

这是什么原因?

事实上,p1,p2存放的都是常量字符串 “abcdef” 的首元素地址,这个字符串是放在只读数据区的。我们只要在内存中找到这个字符串,把它的首元素地址分别拿给 p1 ,p2 就好了。

 

但是对于数组来说,每次创建都是独立开辟一块空间,每块空间的地址是不一样的,所以虽然空间里面的东西一样,但是空间(即地址)不一样。

2.指针数组

指针数组,我们需要注意,它的本质是一个数组

好,本质是一个数组,那么我们可以回想一下数组的定义:同一种类型元素的集合叫做数组

指针数组指针数组,它的元素类型应该是指针类型,即用来存放指针的数组。

#include <stdio.h>

int main()
{
    //指针数组的定义例子
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* p[3] = { arr1,arr2,arr3 };
	return 0;
}

 我们强调过很多次了,数组名表示的是首元素地址。可以看到代码段的第四条语句,我把每个数组的首元素地址都放在一个 int* 类型的数组中。数组里面的元素类型都是 int* 的。

接下来我们谈谈指针数组的用法。

#include <stdio.h>

int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* p[3] = { arr1,arr2,arr3 };
	
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *p[i]);
	}
	return 0;
}

这组代码我们是可以打印数组里面的元素的,而数组里面的各个元素又是每个数组的首元素地址,那么运行结果是:

 但如果仅仅是这样,我们何必大费周章的用指针的形式来打印呢?所以这并不是指针数组的正常用法。

其实,我们可以运用指针数组来实现二维数组。

#include <stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* p[3] = { arr1,arr2,arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(p[i] + j));
		}
		printf("\n");
	}
	return 0;
}

这组代码运行的结果是:

可以发现我们只是加了一层循环,并对指针做了一些改动,就能实现二维数组的打印。

现在我们对 *(p[i]+j) 作出解释:

那 p[i]+j 又是什么意思?因为是两层循环,所以当 i 为 0 的时候,j会的值会从 0 到 4 之间++ 。 

那我们知道指针的运算:指针加减一个数,能够跳过指针类型对应的字节大小。

我们把 p[i]+j 用括号括起来 (p[i]+j)再对其解引用 *(p[i]+j) 就可以访问到每个数组的元素。

 3.数组指针

我们先来谈一谈它的属性。

首先我们举几个例子:

int*        整型指针

char*        字符指针

float*        浮点型指针

那数组指针?对啦,数组指针的实质是指针,是指向数组的指针

但是,上面我们一直再说,数组名是首元素的地址,这怎么会是数组呢?

这时候我们就忽略了一个符号,那就是 &

也就是 arr 和 &arr 两个性质不一样,那我们怎么证明?

我们有一组代码:

#include <stdio.h>

int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };

	printf("%p\n", arr1);
	printf("%p\n", &arr1);

	printf("%p\n", arr1 + 1);
	printf("%p\n", &arr1 + 1);
	return 0;
}

我们对数组其地址打印,分别观察arr打印什么地址,&arr打印什么地址;再观察arr1+1打印什么地址,&arr1+1打印什么地址。

我们对前两个打印结果分析:arr1 和 &arr1 的地址一样,证明这两个表示的都是首元素的地址。

我们再对后两个打印结果分析:

arr1+1后跳过 0C-08=4 个字节,跳过了一个元素。

&arr1+1后跳过 30-08=28,转化为十进制为 40,也就是跳过了40个字节,40个字节整个数组的大小,意思就是跳过了整个数组。

由此可见,arr1 仅仅是首元素的地址,而&arr1 表示的是整个数组的地址,只是用其首元素地址来表示而已。

好,了解了区别之后我们看一组代码:

#include <stdio.h>
int main()
{
	int arr[2][3] = { 1,2,3,3,2,1 };
	int arr[][] * p = &arr;//这个表达式正确码?
	return 0;
}

这个表达式正确吗?按照我们以前所学的 整型指针(int*),字符指针(char*)等等,我们主观意识认为这个写法是没有错误的,不是数组指针吗?那我就给你写个数组类型。其实不然,这是不符合语法规定的。

正确的写法应该是:

#include <stdio.h>
int main()
{
	int arr[3] = { 1,2,3};
	int (*p)[3] = &arr;
	printf("%d",  *(*p+1));
	return 0;
}

这个写法是C语言标准规定的。

 所以在代码中的printf语句中,*(*p+1)代表:

p 解引用 *p 得到数组名,即首元素地址,*p+1 则代表往后跳过一个整数类型,即指向了数组元素中的 2,再对其解引用 *(*p+1) 就能得到 2。

4.指针传参

 我们从这个代码的角度出发:

#include <stdio.h>

void print1(int(*p)[3], 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");
	}
}

void print2(int(*p)[3], 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[2][3] = { 1,2,3,3,2,1 };
	print1(arr,2,3);
	print2(&arr, 2, 3);
	return 0;
}

可以看到,print1和print2用了不同的方法传参,但都用了同一种方法接收参数。这不得不提到二维数组的特性,但因为本篇文章是专门这对指针的,这里不再赘述。关于数组的博客我会跟进。

我们需要总结的是,当我们传递参数的时候,传递的是什么类型,对应的形参参数也应该是同种类型就像我们这一组代码,传递的是形参分别是 arr数组首元素地址(实际上也是一个数组)、arr数组地址

猜你喜欢

转载自blog.csdn.net/weixin_59913110/article/details/125067812