【C语言进阶篇】指针进阶(一)- 字符指针&&指针数组&&数组指针

目录

  • 写在前面
  • 1. 字符指针
  • 2. 指针数组
  • 3. 数组指针
    • 3.1 数组指针的定义
    • 3.2 数组指针的作用 - 数组名 VS &数组名
    • 3.3 数组指针的使用
    • 3.4 个人理解补充

写在前面

在初阶指针的学习中,我们已经了解到了指针的部分内容:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
  4. 指针的几种运算。

在掌握了这些问题的基础上,我们就有学习进阶指针的基础了。

1. 字符指针

在声明指针时,我们声明的诸如int * p、char * p时,“ * ”表示p是一个指针变量,而intchar则是说明这个指针所指向的地址存储数据的类型。
对于字符指针char *,我们一般是这样运用:

 char ch = 'w';
 char *pc = &ch;

但我们还有一种新的使用方式:

 char* pstr = "hello world.";

这里是把一个字符串放到 pstr 指针变量里了吗? 简单一想,pstr撑死也就8个字节的空间,它也存不下这一个字符串啊。
所以,这里本质上是把字符串“hello world.
首字符的地址放到了pstr 中,换句话说,pstr 指向的其实是它首元素的地址。

【C语言进阶篇】指针进阶(一)- 字符指针&&指针数组&&数组指针_第1张图片
此时,这里的"hello world."就是一个常量字符串,怎么理解这个常量字符串呢?我们看下面一段程序的运行结果:
【C语言进阶篇】指针进阶(一)- 字符指针&&指针数组&&数组指针_第2张图片
此时代码是跑不起来的。就好比我用 const 修饰了一个变量,我还能改变这个变量的值吗?很蓝的啦。所以,我们应该能够理解这里的常量字符串。
理解了这个,我们再看下面一段代码:

int main()
{
    char str1[] = "hello world.";
    char str2[] = "hello world.";
    const char *str3 = "hello world.";//由于是常量字符串
    const char *str4 = "hello world.";//所以最好加上const
    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; 
}

会跑出来怎么个结果呢?Duang~
在这里插入图片描述
很奇怪啊,str1str2 为什么不相等,而 str3str4 又为什么相等了呢?
这可不是我能把握住的,这里面水很深,且听我细细狡辩。

对于 str1str2 ,我们给字符串赋值时要在栈上给它开辟一块空间,它们的内容是一样的没错,但我比较的是这两个字符串的内容吗?我们知道,字符串的本质是数组,而数组名就是数组首元素的地址,所以在比较时本质上是比较的两个字符串首字符的地址!而两个字符串都是变量字符串,所以开辟的两块空间是完全独立的,它俩能一样吗?显然不能!
对于 str3str4 ,由于他俩所指向的内容是完全相同的常量字符串,它们所指向的是存放在静态区的"hello world."这段字符。计算机也不傻,当它知道你俩指向的是完全相同的一个常量,还有必要给你俩单独开两块地吗?你俩挤一挤节省内存它不香吗?所以,str3str4 是相同的。

2. 指针数组

顾名思义,指针数组就是存放指针变量的数组。
比如:

int* arr1[10];  //整形指针的数组
char* arr2[4];  //一级字符指针的数组
char**arr3[5];  //二级字符指针的数组

怎么理解呢?下面画一幅图来展示一下:
【C语言进阶篇】指针进阶(一)- 字符指针&&指针数组&&数组指针_第3张图片
很明了,指针数组是一个数组,它的每个元素都是一个指针。

3. 数组指针

数组指针?指针数组?这俩听起来好像啊,但实际上却千差万别。
指针数组是什么?是数组!
数组指针是什么?是指针!

3.1 数组指针的定义

我们已经熟悉:

整形指针: int * pint - 能够指向整形数据的指针。
浮点型指针: float * pf - 能够指向浮点型数据的指针。
那数组指针应该是…能够指向数组的指针!

我们先来分辨一下下面两个指针:

int* p1[10];
int (*p2)[10];

从运算符的优先级角度去看待的话,[ ] 的优先级是要大于 * 的。
所以对于 p1 来讲,它首先是 p1[10],所以它其实是一个指针数组,存放指针的类型都是 int *。
p 先和 * 结合,说明 p 是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以 p 是一个指针,指向一个数组,叫数组指针。

3.2 数组指针的作用 - 数组名 VS &数组名

要了解数组指针的作用,我们首先要弄明白数组名&数组名之间的区别。
对于下面的数组:

int arr[10];

我们知道 arr 是数组名,数组名就是数组首元素的地址。
&arr 是什么呢?我们看下面一段代码:

int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr= %p\n", &arr);
	printf("arr+1 = %p\n", arr+1);
	printf("&arr+1= %p\n", &arr+1);
	return 0; 
}

结果如下:
在这里插入图片描述
有没有和你预想的一致呢?

我们看到,arr&arr 打印出来的结果是相同的,但 +1之后的结果却不同。
通过比较不难发现,+1 之后的结果正好差了40,正好是这个数组的大小。
所以,有没有一种可能,就是说 &arr 表示的是数组的地址,而不是数组首元素的地址?

自信一点,把可能去了,事实就是这样。
浅结一下:本例中 &arr 的类型是:int( * )[10] ,是一种数组指针类型。数组的地址+1会跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。

3.3 数组指针的使用

既然存在数组指针这么个东西,那就一定有它的价值。要发挥它的价值,就得先搞明白它是如何使用的。
对于一个标准的一维数组:

 int arr[10] = {1,2,3,4,5,6,7,8,9,0};

我们此前是这样去打印它:

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int i = 0;
	for (i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	return 0;
}

现在我学了数组指针,我可以把这个数组的地址存放给数组指针:

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

如果能通过指针遍历数组,那么就可以实现打印。
但这跟之前的指针还不太一样,不能直接对指针进行加减操作,所以不要眨眼:

p 是一个指针,它代表的是整个数组的地址;
*p 就是对 &arr 解引用,解引用之后就是数组首元素
数组名就是首元素的地址,所以 *p 就等价于首元素的地址;
所以对 *p 进行加减操作就可以遍历整个数组。

所以有下面的实现方式:

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,0};
	int(*p)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
		printf("%d ", *(*p + i));
	return 0;
}

现在情况有变, 数组突变为二维:

int arr[3][5] = { {1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7} };

现在该怎么做呢?
先把实现代码摆出来:

int main()
{
	int arr[3][5] = { {1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7} };
	int(*parr)[5] = arr;
	int i, j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
			printf("%d ", *(*(parr + i) + j));
		printf("\n");
	}
	return 0;
}

浅浅解释一下下:

这里明明是二维数组,但声明数组指针的时候为什么和一维数组一样呢?
首先我们知道数组名是数组首元素的地址,而二维数组的首元素就是它的首行!
所以就可以将这里的二维数组理解为有3个元素,每个元素都是有5个元素的一维数组。
parr 是一个指针,它代表了二维数组第一行整个数组的地址;
parr + 1 就是二维数组第二行的地址,以此类推。
所以,对 (parr + i) 整体解引用得到的就是数组第 i 行首元素的地址;
那么, ( * ( parr + i ) + j ) 整体就是数组第 i 行第 j 列所对应元素的地址;
最终,对它解引用,就得到了arr[ i ] [ j ]

如果理解的不错的话,那就有:

*(*(parr + i) + j) <==> parr[i][j] <==> arr[i][j]

所以,如果程序恰巧使用一个指向二维数组的指针,而且要通过该指针获取值时,最好用简单的数组表示法(arr[ i ][ j ]),而不是指针表示法( *( *(parr + i) + j) )

理一下思路:

parr               ←二维数组首元素的地址(每个元素都是内含5int类型元素的一维数组)
parr + i           ←二维数组的第i个元素(即一维数组)的地址
*(parr + i)        ←二维数组的第i个元素(即一维数组)的首元素(一个int类型的值)地址
*(parr + i) + j    ←二维数组的第i个元素(即一维数组)的第2个元素(也是一个int类型的值)地址
*(*(parr + i) + j) ←二维数组的第i个一维数组的第2int类型元素的值,即arr[i][j]

3.4 个人理解补充

这块内容确实有点抽象,不好理解,我个人在写这块内容的时候也是绕了很多弯子才绕明白。
其中有一点,说是“二维数组的首元素就是它的首行”。
这句话我百思不得其解,但我查阅一些书籍也确实是这样说的。
于是我试了试:
【C语言进阶篇】指针进阶(一)- 字符指针&&指针数组&&数组指针_第4张图片
我当时认为,如果 arr 指向的是首元素,也就是首行的话,那这个程序应该跑不起来才对啊。
在向大佬们请教的过程中突然想明白:arr 指向首行,那它应该能一次访问20个字节的空间,所以它指向对象的类型应该是 int ( )[5],这样它才有一次访问20个字节空间的权限。
而这里 arr 指向对象的类型却是 int ,一次只能访问4个字节!相当于把它强制类型转换了!
所以理解这一块的时候,一定要弄明白指针类型,弄明白指针指向的是数组还是单个数!

你可能感兴趣的:(C语言,c语言,c++,后端,经验分享)