C语言第九章 指针在数组中的应用

在上一篇文章里,介绍了一些指针的基本内容及其应用。接下来,会介绍指针和数组之间的关联以及应用。

9.1 数组名的理解

在之前数组的文章中,我们介绍过,数组名代表的是数组首元素的地址,但这有两个例外

• sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节

c 复制代码
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("arr[0] = %p\n", &arr[0]);
	printf("arr    = %p\n", arr);
	printf("%d\n", sizeof(arr));
	return 0;
}

运行结果

• &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)

c 复制代码
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0] + 1);
	printf("arr = %p\n", arr);
	printf("arr+1 = %p\n", arr + 1);
	printf("&arr = %p\n", &arr);
	printf("&arr+1 = %p\n", &arr + 1);
	return 0;
}

运行结果

通过上面的代码,可以观察到,&arr[0]和&arr[0]+1之间相差了4个字节,而&arr和&arr+1之间相差了40个字节。这就是&arr和&arr[0]之间的区别,虽然看上去取出来的都是地址,但在应用时是不一样的。

除此之外,任何地方使用数组名,都代表的是数组首元素的地址。

9.2 指针与一维数组

下面来介绍一下指针在一维数组中的应用,如何利用指针去对一维数组进行操作。

9.2.1 使用指针访问数组

由于数组名代表的是数组首元素的地址,可以直接将数组名作为地址赋值给一级指针变量,再通过指针之间的运算及解引用操作符,去对其进行访问

c 复制代码
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
		printf("%d ", *(p + i));
	return 0;
}

运行结果

9.2.2 一维数组传参的本质

学完指针之后,可以来讨论一下一维数组传参的本质。在上文中提到,数组名的本质就是数组首元素的地址,那么在进行传参时,传入的也是数组首元素的地址。通过下面的代码可以观察到。在test函数中,由于传参传入的是数组首元素的地址,所以用sizeof并没有获取到整个数组的大小,而仅仅是数组首元素的大小

c 复制代码
#include <stdio.h>
void test(int* arr)
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

运行结果

总结:一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

9.3 指针数组

下面介绍一下指针数组的概念。指针数组其实通过它的名字可以看出,它的本质是一个数组,里面存放的内容是指针。定义方式如下

c 复制代码
int* arr[5];  //数组中的5个元素都是指向整型变量的指针
char* str[10];  //数组中的10个元素都是指向字符变量的指针

通过下面的图可以更加直观的理解指针数组

下面我们使用指针数组来模拟实现一个二维数组。注意,这里是模拟实现二维数组,并不是二维数组在传递参数时的真正的形式。由于指针数组中每个变量都是指向同一变量类型的指针,所以我们可以让这些指针指向不同的数组,并将这些指针变量存到数组指针中,这样就模拟实现了一个二维数组,代码实现如下

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

通过下面的图可以更加直观的理解上述代码

9.4 指针与二维数组

本节来窥探指针与二维数组之间的关系及应用。先介绍数组指针,并讲述其与二维数组的关系。

9.4.1 数组指针

数组指针与指针数组虽然名字上很相似,但两者有很大差别。上文说到,指针数组的本质是数组,而数组指针的本质是一个存放整个数组的地址的指针变量。它的定义格式如下,其中int代表指向数组的类型,(*p)表示p是一个指针变量,后面的[5]表示指向数组的变量元素个数

c 复制代码
int arr[5] = { 1,2,3,4,5 };
int(*p)[5] = &arr;  //指向arr数组的指针变量
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[3] = { arr1,arr2,arr3 };  //指针数组,里面存放的都是指针变量
int* (*p2)[3] = parr;  //指向指针数组的指针变量

使用数组指针遍历数组的方式如下,由于该变量存放的是整个数组的地址,对其解引用后就得到了数组,之后再利用下标访问的方式就可以遍历数组了,下面代码中的两种写法都是可以实现目标的

c 复制代码
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
		//printf("%d ", *((*p) + i));  //这种写法也是可以的
		printf("%d ", (*p)[i]);
	return 0;
}

9.4.2 二维数组传参的本质

有了数组指针的知识,就可以窥探二维数组传参的本质了。在之前将二维数组作为参数传给一个函数时,写法是这样的

c 复制代码
void test(int a[3][5], int row, int col);

但在本人数组那篇文章介绍二维数组时提到过,二维数组的本质其实就是将几个一维数组存放到一个数组中,就成了二维数组。上文提到过,数组名是数组首元素的地址,而二维数组的数组名也同样如此,那么二维数组的首元素是什么呢?其实它就是那个指向一维数组的数组指针,传参时的写法也可以写成下面这样

c 复制代码
void test(int (*p)[5], int row, int col);

这种写法才是二维数组传参的本质,传入了二维数组首元素的地址,并通过这一地址可以进而访问其余元素,使用数组指针遍历二维数组的写法如下,其中arr+i表示二维数组中的数组指针,对其解引用后就找到了对应的一维数组,之后利用下标访问的方式即可遍历整个二维数组

c 复制代码
#include <stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
			//printf("%d ", *(*(arr + i) + j));  这种写法也是可以的
			printf("%d ", (*(arr + i))[j]);
		printf('\n');
	}
	return 0;
}

运行结果

9.5 字符指针与字符数组

字符指针用来存放字符变量的地址,并通过这一指针,可以完成对变量的各种修改操作,使用方式如下

c 复制代码
#include <stdio.h>
int main()
{
	char a = 'b';
	char* pa = &a;  //用字符指针存放变量a的地址
	*pa = 'c';  //通过对指针变量解引用改变变量a的存储内容
	printf("%c", a);
	return 0;
}

除了这种使用方式之外,我们也可以让字符指针指向字符串或者字符数组。由于字符串中的每个字符都是连续存放的,我们可以使用字符指针指向该字符串的首字母,再根据其连续存放的特点,就可以找到整个字符串,直到'\0'为止。所以下面的代码写法并不是用字符指针存放了整个字符串,而是存放首字母的地址,再通过字符串连续存放的特点找到整个数组

c 复制代码
#include <stdio.h>
int main()
{
	char* p = "effective";
	printf("%s\n", p);  //以%s的方式打印整个字符串
	//p[0] = 'a';  //不能对常量字符串进行任何的修改
	return 0;
}

需要注意的是,这里的p变量默认存放的是常量字符串,也就是说,对这个字符指针指向的字符串只能进行"读"的操作,而不能进行任何修改操作,否则程序无法正常运行。所以严谨的写法如下

c 复制代码
const char* p = "effective";

而使用字符数组存放字符串时,是可以进行任何的读写操作的。这也是字符指针存储字符串和使用字符数组存储字符串的区别。

下面来看一段代码,读者可以猜一猜它的运行结果

c 复制代码
#include <stdio.h>
int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";
	const char* str3 = "hello world.";
	const char* str4 = "hello world.";
	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;
}

根据上文的知识可以知道,字符指针存放的是常量字符串,str3和str4都指向了同一个常量字符串,没有必要开辟两块不同的空间。而每个字符数组是单独开空间存储相应的内容,只是在上面的程序中,存储的内容是一样的,但其实它们的地址是不一样的,运行结果如下

9.6 二级指针

指针变量也是变量,只要是一个变量,就有它自己的地址。存储指针变量的地址,需要用二级指针。具体的代码使用如下

c 复制代码
#include <stdio.h>
int main()
{
	int a = 5;
	int* pa = &a;
	int** ppa = &pa;  //用二级指针变量存储一级指针的地址
	**ppa = 10;  //通过二级指针改变变量a
	printf("%d\n", a);
	return 0;
}

通过下面的图示可以更好的理解二级指针

由此可以衍生出三级指针,四级指针的概念,三级指针存放二级指针变量的地址,四级指针存放三级指针变量的地址。不过二级指针的使用范围就已经比较狭隘了,三级,四级指针使用的非常少,这里就不再赘述了。

相关推荐
jiao000011 小时前
数据结构——队列
c语言·数据结构·算法
铁匠匠匠1 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计
C-SDN花园GGbond1 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
Navigator_Z3 小时前
数据结构C //线性表(链表)ADT结构及相关函数
c语言·数据结构·算法·链表
菜菜想进步4 小时前
内存管理(C++版)
c语言·开发语言·c++
知星小度S4 小时前
C语言——自定义类型
c语言·开发语言
cleveryuoyuo4 小时前
二叉树的链式结构和递归程序的递归流程图
c语言·数据结构·流程图
科研小白_d.s5 小时前
vscode配置c/c++环境
c语言·c++·vscode
暮色_年华5 小时前
嵌入式C语言自我修养:C语言的模块化的编程思想
c语言
大母猴啃编程7 小时前
数据结构---非线性--树
c语言·数据结构·学习·算法·青少年编程