第十八章 指针进阶(2)(秒懂数组指针与指针数组)

C语言学习之路

第一章 初识C语言
第二章 变量
第三章 常量
第四章 字符串与转义字符
第五章 数组
第六章 操作符
第七章 指针
第八章 结构体
第九章 控制语句之条件语句
第十章 控制语句之循环语句
第十一章 控制语句之转向语句
第十二章 函数基础
第十三章 函数进阶(一)(嵌套使用与链式访问)
第十四章 函数进阶(二)(函数递归)
第十五章 数组进阶
第十六章 操作符(详解及注意事项)
第十七章 指针进阶(1)
第十八章 指针进阶(2)


文章目录

  • C语言学习之路
  • 前言
  • 一、字符指针
    • 什么是字符指针?
    • 字符指针的使用
      • (1)一般使用情况
      • (2)其余情况
  • 二、指针数组
  • 三、数组指针
    • 1、什么是数组指针?
    • 2、如何创建一个数组指针?
    • 3、数组地址深度解析:
    • 3、数组指针的用途
      • 数组指针在二维数组中的应用
  • 总结


前言

在C语言中,指针是非常难并且非常灵活的一个板块,本章将继续对指针的应用进行深挖。


一、字符指针

什么是字符指针?

字符指针就是记录字符型数据的指针,它的访问权限是1个字节,每次的偏移量也是1个字节。

字符指针的使用

(1)一般使用情况

int main()
{
	//利用字符指针记录字符变量的地址
	char ch='a';
	char *pc=&ch;
	//利用字符指针指向字符常量的地址
	char *pc1='a';
	return 0;
}

但是需要注意的是,当我们将字符指针指向一个字符型的常量的时候,我们无法修改这个常量,即我们无法通过解引用的方式去修改字符常量,为了避免出错,我们可以采用常量指针的方式修饰指向字符常量的指针const char *p='a'

(2)其余情况

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

除了利用字符指针指向一个字符外,我们还可以利用一个字符指针指向一个字符串,但是并非将整个字符串都存储在指针p中,而是将一个字符串中的首字符的地址存储在该常量指针中。
我们看下面的题目:

int main()
{
	char str1[]="hello\n";
	char str2[]="hello\n";
	char*p1=str1;
	char*p2=str2;
	const char*p3="hello\n";
	const char*p4="hello\n";
	if(p1==p2)
	{
		printf("str1 and str2 is seam.\n");
	}
	else
	{
		printf("str1 and str2 is not seam.\n");
	}
	if(p3==p4)
	{
		printf("p3 and p4 is seam.\n");
	}
	else
	{
		printf("p3 and p4 is not seam.\n");
	}
	return 0;
}

我们执行上述的代码:
第十八章 指针进阶(2)(秒懂数组指针与指针数组)_第1张图片
我们发现,str1和str2并不相同,这是非常好理解的,并且该结果是符合我们的认知的,但是我们发现,当我们利用两个指针指向两个字符串常量(这两个常量的内容相同),其最终结果是,两个指针指向的是同一块内存空间,其实这是一种优化,由于字符串常量我们无法通过指针的方式进行修改,所以没有必要去创建两个相同内容的常量,故二者指向的内存空间是相同的。

二、指针数组

指针数组的本质是数组,在指针进阶(1)中我们有所涉及,这里为了帮助大家进行复习回顾,再次进行讲解。

int main()
{
	int arr1[]={1,2};
	int arr2[]={3,4};
	int arr3[]={5,6};
	int*arr_pointer[]={arr1,arr2,arr3};
	return 0;
}

上述代码就是指针数组的一个基本使用,其实就是我们创建了一个数组,只不过这个数组中的元素是指针。
第十八章 指针进阶(2)(秒懂数组指针与指针数组)_第2张图片
我们可以利用指针数组和一维数组模拟一个二维数组:

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

三、数组指针

1、什么是数组指针?

顾名思义,数组指针的本质是一个指针。我们之前学过整型指针,字符指针等,这些指针都是记录整型数据、字符数据等的地址。同理,数组指针就是记录数组地址的指针。

2、如何创建一个数组指针?

int main()
{
	int arr[10]={0};
	int (*p)[10]=&arr;
	return 0;
}

创建数组指针的语法:数据类型 (* 指针变量的名称)[ 元素个数]=数组的地址
那么我们在创建一个数组指针的时候,为什么要加上一个小括号呢?这就涉及到了之前所讲述的操作符深度解析的一章中,所说的操作符的优先级问题。
我们看下列的图示:
第十八章 指针进阶(2)(秒懂数组指针与指针数组)_第3张图片

由于操作符中的[]的优先级高于*所以arr优先和[]结合,二者结合后意味着arr是一个数组,而这个数组中每个元素的类型是整型指针。
第十八章 指针进阶(2)(秒懂数组指针与指针数组)_第4张图片
为了解决优先级的问题,我们可以先让arr和*结合,那么此时就代表着arr是一个指针,那么我们如何观察一个指针的类型呢?这里告诉大家一个小技巧,当我们将*指针名去除后,剩下的部分就是这个指针的类型,比如int*p我们去掉*p后剩下int,说明这是一个int类型的指针。
同理,我们再次回到数组指针中,我们拿掉*arr后,剩下的部分是int()[10]而这是一个整型数组。所以这个指针就是一个数组指针。

3、数组地址深度解析:

我们知道数组名代表的是第一个元素的首地址,而整个数组的地址在数值上和首元素的地址是相同的,那么数组名什么时候代表首元素地址,什么时候代表整个数组的地址呢?二者又有什么区别呢?
一般以下两种情况代表整个数组的地址:

  • sizeof()中的数组名代表整个数组的地址。
  • &arr代表的是整个数组的地址。
    那么整个数组的地址和数组首元素的地址又有什么区别呢?
int main()
{
	int arr[10]={0};
	printf("%p\n",arr);
	printf("%p\n",&arr[0]);
	printf("%p\n",&arr);
	
	printf("%p\n",arr+1);
	printf("%p\n",&arr[0]+1);
	printf("%p\n",&arr+1);
	return 0;
}

第十八章 指针进阶(2)(秒懂数组指针与指针数组)_第5张图片

我们发现,二者的数值相同的,但是当我们向后偏移一个单位的时候,首元素地址的偏移长度是一个元素的大小,但是当整个数组的地址向后偏移一个单位的时候,我们发现偏移量是整个数组。这是为什么呢?
其实,当我们对数组指针有了了解后,我们不难发现两个地址的指针类型是完全不同的,一个是Int类型的指针,一个是Int数组类型的指针,而指针类型的不同也就标志着访问权限和偏移量的不同。所以两个地址在偏移时会出现不同的结果。

3、数组指针的用途

我们先看下面的代码:

void print_array(int(*p)[10], int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%d ", (*p)[i]);
	}
}
int main()
{
	int arr[10] = { 0 };
	int len = sizeof(arr) / sizeof(int);
	for (int i = 0; i < len; i++)
	{
		arr[i] = i;
	}
	int(*p)[10] = &arr;

	print_array(p,len);
	return 0;
}

第十八章 指针进阶(2)(秒懂数组指针与指针数组)_第6张图片
我们发现,此时我们成功地打印出了数组中的内容,但是我们肯定对这个打印数组的函数中的代码感到困惑:
我们先来解决一下,数组指针解引用的问题:
一个数组指针的数据类型是数组,也就是说当我们对一个数组进行解引用操作的时候,会得到一个某类型的数组,而一个数组的数组名记录的是首元素的地址。所以此时就会出现一个非常奇怪的现象,我们打印p和*p的时候,最终得到的结果都是首元素的地址,但是这两个地址仅仅是在数值上是相同的,在含义上并不相同,p打印出来的地址代表整个元素,其指针类型是数组,而对其进行解引用操作后得到的地址含义是指首元素地址,其指针类型是整型。
有了以上的理解后,我们就能非常容易地理解上述函数中的代码内容:

(*p)[i]

这行代码就是先将数组指针解引用,变成首元素的地址,此时解引用后的地址类型是整型,然后再通过[]进行元素的打印。
但是上述代码看起来是非常别扭的,在打印一维数组的时候并不推荐。

数组指针在二维数组中的应用

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

在刚才的基础上,理解上述代码就非常简单了,其实简单的来讲就是通过第一次解引用,将数组首元素地址从整型数组类型转化成整型类型,再通过解引用对数组中的元素进行访问。
我们现在可以联想一下之前所讲述的数组传参的问题:
在一维数组中:
void test(int arr[])
void test(int*arr)
这两者的意思是相同的,因为一维数组的数组名就是首元素的地址,这个地址所对应的指针的指针类型是整型。
那么二维数组中:
void test(int arr[2][3])
void test(int (*p)[3])
这两者的意思是相同,二维数组的数组名记录的是第一行数组的地址,这个地址对应的指针的指针类型是数组指针。


总结

今天我们主要讲解了两方面:一是数组指针,二是指针数组。希望大家能区分好二者的区别,并且掌握二者的应用。

你可能感兴趣的:(C语言学习之路,c语言)