C语言之详解数组【附三子棋和扫雷游戏实战】(二)

简介: C语言之详解数组【附三子棋和扫雷游戏实战】(二)

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:扫雷游戏

由于篇幅较长,这里我另外写一篇文章来详解扫雷小游戏

相关文章
|
11天前
|
存储 C语言 开发者
C语言实战 | Flappy Bird游戏
【7月更文挑战第4天】Flappy Bird是由越南开发者制作的简单却极具挑战性的游戏,玩家需控制小鸟穿越水管障碍。游戏涉及角色初始化、显示和更新。小鸟和水管结构体存储数据,使用变量和数组。初始化小鸟和水管,显示背景、小鸟和水管,更新小鸟位置及碰撞检测。代码示例展示了小鸟和水管的状态管理,当小鸟与管道碰撞或触地时,游戏结束。游戏的成功在于其独特的虐心体验。
21 0
C语言实战 | Flappy Bird游戏
|
4天前
|
C语言
C语言实战 | 弹跳的小球
【7月更文挑战第6天】使用C语言实现了一个小球(小方块)在屏幕上斜向移动并反弹的程序。当小球碰到边界时,其运动方向会发生改变。代码分为三部分,分别处理初始化、主循环和更新位置及边界检测。变量drow和dcol控制移动方向,遇到边界时会反转它们的值。
13 3
C语言实战 | 弹跳的小球
|
9天前
|
存储 编译器 C语言
|
12天前
|
存储 大数据 文件存储
C语言实战 | 用户管理系统重构
【7月更文挑战第3天】在大数据背景下,云存储成为关键。案例展示了如何创建一个用户管理系统,包含登录和注册功能,确保数据持久化。通过文件存储,即便程序重启,用户信息仍能被保留,实现登录状态的延续。代码图片省略。
16 2
C语言实战 | 用户管理系统重构
|
12天前
|
C语言 数据安全/隐私保护 UED
C语言实战 | 用户管理系统
【7月更文挑战第2天】近期推出的青少年防沉迷系统采用统一标准,管控未成年人上网时段、时长及内容。用户管理系统是其基础,包含登录和注册功能。代码示例展示了用户管理的流程,通过保存用户信息到文件实现持久化,避免重复注册,确保在限制游戏时间的同时提供更好的用户体验。
15 1
C语言实战 | 用户管理系统
|
5天前
|
存储 编译器 C语言
C语言实战 | “贪吃蛇”游戏
【7月更文挑战第5天】在C语言实战中,本文档介绍了如何构建一个简单的“贪吃蛇”游戏。游戏的核心是控制蛇移动并增长,当吃掉食物时,蛇的身体变长。数据结构使用固定大小的数组表示蛇的位置,变量存储食物位置和蛇的长度。初始化后,利用非阻塞式`getKey()`函数实现WASD键盘控制蛇的运动方向。虽然蛇的边界检测和吃食物后的增长尚未详细说明,但提到了这些问题作为练习留给读者解决,并预告将在后续章节讨论模块化编程以简化复杂代码。
16 0
C语言实战 | “贪吃蛇”游戏
|
2天前
|
安全 C语言
C语言8 数组与字符串
C语言8 数组与字符串
5 0
|
7天前
|
存储 编译器 定位技术
结构体数组在C语言中的应用与优化策略
结构体数组在C语言中的应用与优化策略
|
2天前
|
C语言
C语言5 字符输出函数和格式输出函数
C语言5 字符输出函数和格式输出函数
6 1
|
4天前
|
算法 编译器 C语言
深入浅出C语言—【函数】下
深入浅出C语言—【函数】下