数组传参不理解?(数组传参的本质)

简介: 数组传参不理解?(数组传参的本质)

   在我们编写程序时,经常需要传递参数给函数,其中一种常见的参数类型就是数组。数组作为一种数据结构,可以存储多个相同类型的数据元素,并按照一定的顺序排列。在函数中传递数组参数,可以方便地对数组进行操作处理。但是,数组传参也有其特殊和需要注意的地方。在文中,我将向大家讲解数组作为参数传递时的一些问题和解决方法。学习之前我们要先了解数组的一些基础知识,也是对前期数组知识的补充。

一维数组与二维数组

本文将重点针对对一维数组和二维数组进行讲解。

前几期中我对一维数组的创建及初始化已进行了讲解,先对二维数组进行简单介绍。

二维数组

二维数组我们可以理解为一个数组里放了多个一维数组

例如:arr[4][5]:可以理解为arr[4][5]中存放了四个数组,分别名为arr[0],arr[1],arr[2],arr[3]每个数组大小都是5;

arr[0]:arr[0][0],arr[0][1],arr[0][2],arr[0][3],arr[0][4]

arr[1]:arr[1][0],arr[1][1],arr[1][2],arr[1][3],arr[1][4]

arr[2]:arr[2][0],arr[2][1],arr[2][2],arr[2][3],arr[2][4]

arr[3]:arr[3][0],arr[3][1],arr[3][2],arr[3][3],arr[3][4]

二维数组和一维数组在内存中存储都是连续存放的。

首先我们先写一个小程序对二维数组进行初始化

#include<stdio.h>
int main() {
    int arr[2][3];
    int i = 0;
    for (i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            scanf("%d", &arr[i][j]);
        }
    }
    for (i = 0; i < 2; i++) {
        int j = 0;
        for (j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

通过调试时我们可以发现相邻的两个元素之间地址相差4(这里表示地址使用的是16进制,每个元素相差4个字节),并且是连续的。随着数组下标的增长,元素的地址也在有规律的增长(由低地址到高地址)。

二维数组的创建

二维数组的创建与一维数组很相似,当然我们也可以创建多种类型的数组

char arr[2][3];
int arr1[3][4];
double arr2[2][4];

二维数组的初始化

有以下几种初始化形式。

int arr[3][4]={1,2,3,4};
int arr1[3][4]={{1,2},{3,4}};
int arr2[][4]={{2,3},{4,5}};

二维数组的两个[],分别代表的是行和列,前两个数组创建时是一个三行四列的数组,并且在数据元素不够时,会进行补0操作。

注意:二维数组的行可以省,但列不行,计算机会根据初始化时的数据自动计算行的值,而对于列,规定列的大小是为了确定后续计算机进行补0的个数。

数组越界

数组的下标是有范围的。

数组下标规定从0开始,如果数组有n个元素,那么最后一个数组下标就是n-1。

所以数组下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

C语言本身是不做数组下标的越界检查,编译器也不一定会报错,但编译器不报错就并不意味着程序就是正确的。

所以在写代码时,程序员最好自己做越界检查。

如上图,编译器没有报错,最后一个数字因为数组越界访问输出结果是随机值。

当然二维数组的行和列也可能存在越界访问

数组作为函数参数

在写代码时,会将数组作为参数传给函数,比如:实现一个冒泡排序的函数

先解释一下冒泡排序的原理以升序为例:相邻两个数字进行比较,较大数与后一个数位置交换

最终直到最大数到最后的位置(此过程需要比较9次)

这个过程为一趟,而第二趟就只需要交换8次,第三趟需要交换7次,需要交换的次数依次减少,一共需要9趟(交换次数也就是9!)由于我示例数据是降序,要排列为升序,需要交换的次数是最多的,在乱序数据中,交换次数只会比示例交换次数少。

代码实现:

for (int j = 0; j < sz-1; j++) //sz是数组元素个数
  {
    for (int i = 0; j < sz - 1 - i; i++) 
    {
      if (arr[i] > arr[i + 1]) 
      {
        t = arr[i];   //t是作为交换的中间媒介
        arr[i] = arr[i + 1];
        arr[i + 1] = t;
      }
    }
  }

写一个冒泡排序函数该如何实现呢?

void bubble(int arr[10]) {
  int t;
    int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
  for (int j = 0; j < sz-1; j++) 
  {
    for (int i = 0; j < sz - 1 - i; i++) 
    {
      if (arr[i] > arr[i + 1]) 
      {
        t = arr[i];
        arr[i] = arr[i + 1];
        arr[i + 1] = t;
      }
    }
  }
}
int main()
 {
  int arr[10];
  for (int i = 0; i < 10; i++) {
    scanf("%d", &arr[i]);
  }
bubble(arr);
  for (int i = 0; i < 10; i++) {
    printf("%d ", arr[i]);
  }
  return 0;
}

这段代码可以实现冒泡排序吗?当结果运行起来我们会发现,结果并没有预期那样排序,这是为什么呢?

在调用函数时bubble(arr),arr作为数组进行传参,数组传参,传递的实质是首元素的地址,而void bubble(int arr[10])这里的arr[10]本质上也是指针,在函数中我们使用sizeof计算数组大小也仅仅只是形参接收的首元素地址。

通过调试我们会发现这里的sz的值是1(在64位操作系统结果是2,64位操作系统中,指针变量占8个字节除以arr[0]整形所以结果是2)。

或许有同学会疑惑arr是数组首元素地址那sizeof(arr)为什么就可以计算数组长度。

这里额外补充一下

数组名的理解

数组名该怎么理解?

在通常情况下数组名就是首元素的地址。但是有两个意外

1.sizeof(数组名),数组名单独放在sizeof()内部,这里的数组名表示整个数组,计算的是数组大小

2.&数组名,这里的数组名也是表示整个数组,取出的是整个数组的地址

除此之外所有遇到的数组名都表示数组首元素的地址。

这里通过代码给大家更清晰的呈现:

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9 };//%p用来打印地址
  printf("%p\n", arr);
  printf("%p\n", &arr[0]);
  printf("%p\n", &arr);
  printf("%p\n", &arr+1);
  printf("%p\n", arr+1);
  return 0;
}

运行结果如下:

我们发现前3个结果相同,这是因为整个数组的地址也是从首元素地址开始的,将&arr+1,和arr+1我们就会发现结果不同。&arr+1结果与首元素地址相差28(这里28是16进制转化为10进制就是40。)正好相差一个数组的大小,arr+1与首元素地址相差4。

区别在于:arr+1跳过的是一个数组元素,而&arr+1跳过的是整个数组。

那如何完成冒泡排序函数呢?

也很简单,只需在主函数里求好数组元素个数,传到函数中(上述情况说明:数组元素是数组时没法在函数中计算大小,这里也仅限数组元素是数字,字符串数组可以),就可以解决这个问题。

代码修改后:

void bubble(int arr[10],int sz) {
  int t;
  for (int j = 0; j < sz-1; j++) 
  {
    for (int i = 0; j < sz - 1 - i; i++) 
    {
      if (arr[i] > arr[i + 1]) 
      {
        t = arr[i];
        arr[i] = arr[i + 1];
        arr[i + 1] = t;
      }
    }
  }
}
int main() {
  int arr[10];
  for (int i = 0; i < 10; i++) {
    scanf("%d", &arr[i]);
  }
  int sz = sizeof(arr) / sizeof(arr[0]);
  bubble(arr, sz);
  for (int i = 0; i < 10; i++) {
    printf("%d ", arr[i]);
  }
  return 0;
}

这里在给大家补充一点:计算数组元素个数有sizeof(数组名)/sizeof(arr[0]),和strlen(数组名)两种,

char str[]="hello world"
printf("%d %d",sizeof(str),strlen(str));//输出结果是:12 11

sizeof(str):获取数组的总大小,12个元素,每个元素占1个字节,因此总共是12个字节

strlen(str): 获取字符串中有效字符的个数,不算'\0',因此总共11个有效字符。此外strlen是只能计算字符串长度。在函数中可以使用strlen计算字符串数组的大小(当然字符串数组传进去的也是数组首元素地址)。

好了本期内容到这里就结束了,希望对你有所帮助,感谢观看!

相关文章
|
6月前
|
存储 编译器 C语言
函数指针&&数组指针&&数组传参的本质&&字符指针(进阶篇)
函数指针&&数组指针&&数组传参的本质&&字符指针(进阶篇)
126 0
|
9天前
|
存储
如何将数组元素中的函数作为参数传递给其他函数?
通过以上方法,可以灵活地将数组元素中的函数作为参数传递给其他函数,以满足各种不同的编程需求,实现更加强大的功能和更简洁的代码结构。
81 51
|
3月前
【函数】数组做函数参数
【函数】数组做函数参数
|
6月前
|
存储 C语言 索引
指针详解(const、指针运算、数组名的理解、传址调用和传值调用、一维数组的本质​)(一)
指针详解(const、指针运算、数组名的理解、传址调用和传值调用、一维数组的本质​)(一)
|
11月前
二维数组传参的本质
二维数组传参的本质
37 0
二维数组传参的本质
|
6月前
|
存储 Java 索引
【Java数组】数组定义 | 初始化赋值 | 数组练习题
【Java数组】数组定义 | 初始化赋值 | 数组练习题
|
11月前
一维数组传参的本质
一维数组传参的本质
47 0
|
存储 C语言 C++
【指针的进阶(1)】指针的类型、数组传参和指针传参
【指针的进阶(1)】指针的类型、数组传参和指针传参
60 0
|
存储 索引
函数与数组
函数(function),数学术语。其定义通常分为传统定义和近代定义,函数的两个定义本质是相同的,只是叙述概念的出发点不同,传统定义是从运动变化的观点出发,而近代定义是从集合、映射的观点出发。
数组作为函数参数传参&数组名到底代表什么意义?
数组作为函数参数传参&数组名到底代表什么意义?