前言
在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 是一个指针数组,它的每个元素都是一个指向整型数组的指针。通过指针数组,我们可以模拟出二维数组的效果,但需要注意的是,每一行并不是连续的。
七、总结
通过今天的探讨,我们深入理解了指针的多种用法,从数组到二维数组,指针都扮演了重要的角色。数组名本质上是首元素的地址,通过指针可以高效地访问数组元素。一维数组传参的本质是传递首元素的地址,而二级指针和指针数组则为我们提供了更灵活的操作方式。
希望这篇文章能帮助你更好地理解指针的奥秘。如果你对指针还有其他疑问,欢迎在评论区留言,我们一起探讨!