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