type
status
date
slug
summary
tags
category
icon
password
😀
在子函数形参中传递数组名时只会传递数组的首地址,而不是整个数组。

1.子函数形参中传递数组的本质

先看一段代码:
 
我们都知道,参数的传递是把实参的副本传递给形参,但数组较例外。 在子函数形参中传递数组名时只会传递数组的首地址,而不是整个数组(毕竟假如数组有1万个元素,全部拷贝一份给形参实在是过于浪费内存空间),函数在后面需要用到数组元素时再根据首地址和下标去找。
当实参将arr数组首元素地址传递到函数时,形参用指针进行接收,想使用:sizeof(arr)/sizeof(arr[0]) 来求数组大小是行不通的,因为此时的sizeof(arr)并不是整个数组的大小了,表示的是求这个arr指针的字节大小。因此sizeof(arr)/sizeof(arr[0])这个表达式在函数内部会等价sizeof(int*)/sizeof(int),它计算的是指针大小与单个int元素大小的比值,而不是数组的元素数量。
综上,想要规避掉这个错误,我们一般都是在主函数/调用的地方中求出数组的具体长度,然后将长度作为参数传递给子函数。
 
那么这个时候有的同学就会问了,两次传入的都是数组的首地址,为什么主函数中就可以,自定义函数中就不行呢?
 

2.数组名不完全等同于指针

再看一段代码:
也就是说数组名在某些情况下是不等于指针的,只是在一些情况下会退化为指针。
 
首先我们要知道,单纯的数组名,不是指针。
 
数组名是一个标识符,它标识出我们之前申请的一连串内存空间,而且这个空间内的元素类型是相同的——即数组名代表的是一个内存块及这个内存块中的元素类型 。
 
只是在大多数情况下数组名会“退化”(C标准使用的decay和converted这两个词)为指向第一个元素的指针。 而指针不是一种聚合类的数据结构,它保存着某一种类型的对象的地址(void*除外),也说它指向这个对象。我们可以通过这个地址访问这个对象。用一个图来解释,其中a代表了整个我们声明的内存块,p仅仅指向了一个char类型的对象。
notion image
 
于是我们翻阅C99标准可知:
数组名只有在
  1. sizeof运算符
    1. 取址&运算符
        • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
          • int arr[]={1,2,3,4,5};
            &arr+1
            arr+1=&arr[0]+1
            &arr是指向整个数组的指针,因此,如果我们将&arr移动1个位置,它将移动一整个数组的地址,指向下一个包含5个元素的块。
            arr是指向数组第一个元素的指针。因此,如果我们将arr移动1个位置,它将指向第二个元素。
            如果数组基地址为00D5F940,则&arr+1将为00D5F940+(5*4),即00D5F954
            如果数组基地址是00D5F940 ,则arr+1将是00D5F940+4,即00D5F944
            notion image
    1. 字符串常量初始化的数组Str[]=“abcdef”
    这三种情况下不会发生退化(array decay) 其余情况下调用数组名,都会退化成指向数组首地址的指针
     
    而指针是用来记录另一个对象的地址,所以指针的内存大小就等于计算机内部地址总线的宽度。
    对一个地址来取大小呢,如果是32位系统的话即为4,如果是64位系统的话为8,所以呢,在函数中sizeof获取的是指针的长度而不是数组的长度
    指针变量的sizeof值与指针所指的对象没有任何关系。
    • 结论:也就是说在c语言中,数组名在函数的调用中退化成了一个指针,对函数的参数使用sizeof,sizeof获取的结果就是指针的大小,而不是数组本身的大小。
     

    3.练习

    写一个函数,实现一个整形有序数组的二分查找

    🤗 总结归纳

    数组名在函数的调用中退化成了一个指针,传递时只会传递数组的首地址,对函数的参数使用sizeof,sizeof获取的结果就是指针的大小,而不是数组本身的大小。

    📎 参考文章

    C语言中将数字0~9与对应字符相互转换快速理解C语言指针和数组的关系
    Loading...