C语言之详解数组【附三子棋和扫雷游戏实战】(一):https://developer.aliyun.com/article/1426998
四、数组作为函数参数
1、冒泡排序函数的错误设计
- 然后让我们来看看错误的冒泡排序
void PrintArray(int* a, int n) { for (int i = 0; i < n; ++i) { printf("%d ", a[i]); } printf("\n"); } void BubbleSort(int a[10]) { int n = sizeof(a) / sizeof(a[0]); for (int i = 0; i < n - 1; ++i) { for (int j = 0; j < n - 1 - i; ++j) { if (a[j] > a[j + 1]) { int t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; } } } } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); PrintArray(arr, sz); BubbleSort(arr); PrintArray(arr, sz); return 0; }
- 为什么会发生这样的情况呢?我们通过DeBug来调试看看
- 这里n应该为10而不是1
接下来就来介绍一下为什么是1而不是10
2、数组名意味着什么?
- 对于数组名而言,当我们将一个数组作为函数的参数进行传递的时候,传入的仅仅这个数组的
首元素地址
,而并不是把整个数组作为参数传递过去
情况1:sizeof(数组名)
sizeof(数组名)求解的是整个数组的字节大小
int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; printf("%d\n", sizeof(arr));
- 可以看到输出的结果为【40】,我们刚才说到数组名指的是首元素地址,刚才在【冒泡排序】中计算
sizeof(a)
得出的结果为4,但是这个为什么是40呢 sizeof(数组名)
计算的就是整个数组的大小,因为arr数组中有十个元素,一个整型元素占4个字节,所以整个数组的大小即为40
情况2:&数组名
&数组名为整个数组的地址
printf("%p\n", &arr[0]); printf("%p\n", arr); printf("%p\n", &arr);
- 三个打印出来的结果都是一样的,对于第一个
arr[0]
指的是首元素,&arr[0]
指的便是首元素的地址;对于arr
来说也是一样为首元素地址 - 也是一样为首元素地址
- 而对于&arr来说,指的则是整个数组的地址,它和数组首元素地址是一样的,所以三者地址相同
小结一下
- &数组名:数组名表示整个数组。取出的是整个数组的地址
- sizeof(数组名):数组名表示整个数组。求解的是整个数组的大小,单位是字节
- 除此之外见到数组名全部都为该数组的首元素地址
3、冒泡排序函数的改进
- 通过上面的分析可以知晓出错的地方是在数组的个数,所以我们在排序外面计算完再把这个数组的大小传进去就行
void bubble_sort(int arr[], int sz) { int i = 0; //确定冒泡排序的趟数 for (i = 0; i < sz - 1; i++) { //假设数组是有序的 int flag = 1; //一趟冒泡进行多少对比较 int j = 0; for (j = 0; j < sz - 1 - i; j++) { //交换 if (arr[j] < arr[j + 1]) { int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; flag = 0; } } // 这一趟没交换就说明已经有序,后续无序排序了 if (flag == 1) { break; } } } void print_arr(int* arr, int sz) { for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); } } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); print_arr(arr, sz); printf("\n"); bubble_sort(arr, sz); print_arr(arr, sz);
4、数组地址与指针
数组地址偏移量与指针偏移量
- 在C语言中,我们可以通过指针来访问数组的元素,利用指针进行数组元素的遍历和访问。首先,我们将数组的首元素地址赋给一个指针变量,然后通过逐步向后移动指针来访问数组的各个元素。
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[0];
- 现在,指针变量
p
中存放着数组arr
的首元素地址。要通过这个指针变量访问后面的所有元素,我们可以使用循环,通过p + i
的方式来获取元素的地址,然后通过解引用操作*(p + i)
来访问元素的值。
for (int i = 0; i < 10; ++i) { printf("%d ", *(p + i)); } printf("\n");
- 在循环中,
*(p + i)
表示访问数组中第i
个元素的值。这种方式可以适用于任何一维数组,因为一维数组在内存中是一块连续的存储空间,通过指针的偏移可以依次访问数组的所有元素。
- 通过将数组的首元素地址赋值给指针变量 p,然后逐个递增指针,每次递增一个元素的大小(在这里是4个字节,假设是int类型数组),第 i 个元素的地址即为 p + i。当我们需要访问这个地址的内容时,通过对指针进行解引用 *(p + i),就能够获取数组中第 i 个元素的值。这种方式可以灵活地遍历数组中的所有元素,而不需要直接使用数组下标。在循环中,这个过程被用来打印数组中的十个元素。
指针变量与数组名的置换
- 回到我们的【数组名 == 首元素地址】,那么
int* p = &arr[0]
可以写成int* p = arr
- 也就是把我这个arr赋值给了p,所以我们在使用arr的时候可以换成p,使用p的时候可以换成arr
- 在C语言中,数组名(如
arr
)表示该数组的首元素地址。 - 当首元素地址向后偏移
i
个位置时,就到达了下标为i
的元素所在的位置。通过对其进行解引用,就可以获取下标为i
的元素。这可以表示为*(arr + i)
。 - 对于数组访问操作符
[]
,它有交换律。将arr[i]
转换为*(arr + i)
时,括号中的操作数可以进行交换,变成*(i + arr)
。 - 进一步推导,
*(i + arr)
也可以写成i[arr]
。 - 因此,
*(arr + i)
可以等价于arr[i]
,同时也可以写成i[arr]
。
- 那么
*(i + arr)
是否可以写成i[arr]
呢
此刻我们再进行代码演示一下~~
- 那这里也可以写成
p[i]
小结一下
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; for (int i = 0; i < 10; ++i) { printf("%d ", arr[i]); } printf("\n\n\n"); for (int i = 0; i < 10; ++i) { printf("%d ", *(arr + i)); } printf("\n\n\n"); for (int i = 0; i < 10; ++i) { printf("%d ", *(p + i)); } printf("\n\n\n"); for (int i = 0; i < 10; ++i) { printf("%d ", p[i]); } printf("\n\n\n"); return 0; }
- arr[i] == *(arr + i) == *(p + i) == p[i]
五、数组的应用实例1:三子棋
由于篇幅较长,这里我另外写一篇文章来详解三子棋小游戏
六、数组的应用实例2:扫雷游戏
由于篇幅较长,这里我另外写一篇文章来详解扫雷小游戏