传参方式
-
值传递,指函数调用时,为形参分配存储单元,并将实参的值复制到形参,函数调用结束后,形参所占内存单元 被释放,值消失。特点是:形参和实参各占不同的内存单元,函数对形参值的改变不会改变实参的值,这就是参数的单向传递规则。
-
地址传递,是指在函数调用是,将实参数据的存储地址作为参数传递给形参。其特点是:形参和实参占用同样的内存单元,函数中对形参值的改变也会改变实参的值。因此,函数参数的地址传递方式可以实现调用函数与被调函数之间的双向数据传递。
一维数组传参:
C语言中,当一维数组作为函数参数的时候,编译器总把它解析成一个指向其首元素首地址的指针。
因为如果要拷贝整个数组,无论是在时间上还是在空间上,其开销都是非常大的。更重要的是,在绝大部分情况下,你其实并不需要整个数组的拷贝。所以为了节省时间和空间,提高程序运行速率,于是就有了上述规则。
二维数组传参:
我们可以把a[3][4]理解为一个一维数组a[3],其每个元素都是一个含有4个char类型数据的数组。上面的规则”C语言中,当一维数组作为函数参数的时候,编译器总把它解析成一个指向其首元素首地址的指针。“‘
这里同样适用。也就是说,我们可以把这个函数申明改为void(char(*p)[4])
,这里的()
绝对不能省略。这样才能保证编译器把p解析为一个包含4个char 类型数据元素的数组,即一维数组a[3]的元素。
同样,作为参数,一维数组[]
内的数字完全可以省略void fun(char a[])[4]
,不过二维数组的维数却不可省略。
数组参数 | 等效的指针参数 |
---|---|
数组的数组 chara[3][4] | 数组的指针 char (*p)[10] |
指针数组 char *a[5] | 指针的指针 char **p |
C语言中,当一维数组作为函数参数的时候,编译器总把它解析成一个指向其首元素首地址的指针,这条规则并不是递归的,也就是说只有一维数组才是如此,当数组超过一维时,将一维改写为指向数组的首元素的首地址后,后面的维再也不可改写。
指针数组和数组指针
指针数组和数组指针的内存布局
初学者总是分不出指针数组与数组指针的区别。其实很好理解:
- 指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身
决定。它是“储存指针的数组”的简称。 - 数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节,
至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
下面到底哪个是数组指针,哪个是指针数组呢:
A),int *p1[10];
B),int (*p2)[10];
每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。
- “[]”的优先级比“*”要高。
p1 先与“[]”结合,构成一个数组的定义,数组名为 p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含 10 个指向 int 类型数据的指针,即指针数组。 - 至于 p2 就更好理解了,在这里“() ”的优先级比“[]”高,“*”号和 p2 构成一个指针的定义,指针变量名为 p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚 p2 是一个指针,它指向一个包含 10 个 int 类型数据的数组,即数组指针。我们可以借助下面的图加深理解:
说实话,指针这个东西真的是你以为你学懂了它,然后他又会猝不及防的让你城墙失守,你发现你好像真的不认识。当他和数组在一起的时候,emmm…但是,所有的东西你只要能认识到它的本质,形变神不变。指针其实就是分配了一块内存,而这块内存中放的是他所指向数据的地址,(指针的类型就是它所指的数据的数据类型,它本身不管所指向的数据类型是啥,他的大小是不变的。linux X86-64下sizeof等于8)而数组名称其实是没有实际含义,只不过是我们分配了一块空间,然后给它叫了个名字而已。