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

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

相关文章
|
1月前
|
C语言
扫雷游戏(用C语言实现)
扫雷游戏(用C语言实现)
96 0
|
30天前
|
存储 编译器 C语言
【c语言】数组
本文介绍了数组的基本概念及一维和二维数组的创建、初始化、使用方法及其在内存中的存储形式。一维数组通过下标访问元素,支持初始化和动态输入输出。二维数组则通过行和列的下标访问元素,同样支持初始化和动态输入输出。此外,还简要介绍了C99标准中的变长数组,允许在运行时根据变量创建数组,但不能初始化。
37 6
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
存储 C语言
C语言:一维数组的不初始化、部分初始化、完全初始化的不同点
C语言中一维数组的初始化有三种情况:不初始化时,数组元素的值是随机的;部分初始化时,未指定的元素会被自动赋值为0;完全初始化时,所有元素都被赋予了初始值。
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
10天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
27 6
|
30天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
36 10
|
23天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
29天前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
57 7
|
29天前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
30 4