【C语言】深入理解指针(二):从数组到二维数组的指针魔法

发布于:2025-03-23 ⋅ 阅读:(27) ⋅ 点赞:(0)

前言

在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);
    return 0;
}

在这里插入图片描述

运行结果会发现,&arr[0] 和 arr 打印出来的地址是一样的。这说明数组名 arr 本质上就是数组首元素的地址。
但是,这里有两个例外情况:
sizeof(数组名):此时数组名表示整个数组,计算的是整个数组的大小,单位是字节。
&数组名:此时数组名表示整个数组,取出的是整个数组的地址,而不是首元素的地址。
我们再来看两个代码示例来验证:

#include <stdio.h>
int main()
{
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    printf("sizeof(arr) = %zd\n", sizeof(arr)); // 输出整个数组的大小
    printf("&arr = %p\n", &arr); // 输出整个数组的地址
    return 0;
}

在这里插入图片描述

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("&arr    = %p\n", &arr);

	return 0;
}

在这里插入图片描述

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

在这里插入图片描述

通过例子,我们可以清楚地看到数组名的两种特殊情况。

二、使用指针访问数组

既然数组名是首元素的地址,那么我们就可以通过指针来访问数组中的元素。来看一个代码示例:

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = arr; // 将数组名赋值给指针变量
    for (i = 0; i < sz; i++)
    {
        scanf("%d", p + i); // 使用指针访问数组元素
    }
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i)); // 使用指针访问数组元素
    }
    return 0;
}

在这里插入图片描述

在这个例子中,我们通过指针 p 来访问数组 arr 的元素。p + i 表示的是数组中第 i 个元素的地址,而 *(p + i) 则表示的是第 i 个元素的值。这说明,数组元素的访问本质上是通过指针来实现的。

三、一维数组传参的本质

数组可以传递给函数,但数组传参的本质是什么呢?我们来看一个代码示例:

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

在这里插入图片描述

运行结果会发现,在函数内部,sz2 的值并不是数组的实际大小,而是 1。这是因为数组传参的本质是传递数组首元素的地址,而不是整个数组。在函数内部,arr 是一个指针变量,sizeof(arr) 计算的是指针变量的大小,而不是数组的大小。
所以,一维数组传参的本质是传递数组首元素的地址。函数的形参部分可以写成数组的形式,也可以写成指针的形式,但本质上都是指针。

四、冒泡排序的实现

冒泡排序是一种简单的排序算法,它的核心思想是两两相邻的元素进行比较。我们来看一个冒泡排序的实现代码:

#include <stdio.h>
void bubble_sort(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz - 1; i++)
    {
        int j = 0;
        for (j = 0; j < sz - i - 1; j++)
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}
int main()
{
    int arr[] = {3, 1, 7, 5, 8, 9, 0, 2, 4, 6};
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, sz);
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

在这里插入图片描述

优化代码:

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		int flag = 1;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = 0;
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

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

在这里插入图片描述

这个代码实现了冒泡排序的基本逻辑。它通过两层循环,逐个比较相邻的元素,并在必要时交换它们的位置,最终实现数组的排序。

五、二级指针的奥秘

二级指针是一个指向指针的指针。它可以帮助我们更灵活地操作指针。我们来看一个二级指针的代码示例:

#include <stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;
    int** ppa = &pa;
    printf("a = %d\n", a);
    printf("pa = %p\n", pa);
    printf("ppa = %p\n", ppa);
    printf("*pa = %d\n", *pa);
    printf("**ppa = %d\n", **ppa);
    return 0;
}

在这里插入图片描述

在这个例子中,pa 是一个指向 a 的指针,而 ppa 是一个指向 pa 的指针。通过二级指针,我们可以更灵活地操作指针变量。

六、指针数组的魔法

指针数组是一个数组,它的每个元素都是一个指针。我们可以用指针数组来模拟二维数组。来看一个代码示例:

#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};
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("%d ", parr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

在这个例子中,parr 是一个指针数组,它的每个元素都是一个指向整型数组的指针。通过指针数组,我们可以模拟出二维数组的效果,但需要注意的是,每一行并不是连续的。

七、总结

通过今天的探讨,我们深入理解了指针的多种用法,从数组到二维数组,指针都扮演了重要的角色。数组名本质上是首元素的地址,通过指针可以高效地访问数组元素。一维数组传参的本质是传递首元素的地址,而二级指针和指针数组则为我们提供了更灵活的操作方式。

希望这篇文章能帮助你更好地理解指针的奥秘。如果你对指针还有其他疑问,欢迎在评论区留言,我们一起探讨!


网站公告

今日签到

点亮在社区的每一天
去签到