[2] 数组和指针的前世今生 - 数组篇

来源:互联网 发布:大司马淘宝 编辑:程序博客网 时间:2024/05/23 01:16

本节主要说明为什么C语言要把数组形参作为指针传递给函数。

历史原因

  • 效率。在C语言中,所有非数组形式的数据实参都是传值调用。然而,如果拷贝整个数组,无论是时间还是空间的开销都有可能非常大,而且在绝大多数情况下,其实并不需要整个数组的拷贝。
    同样出于效率的目的,Pascal语言选择在传入函数的形参加入一个存储说明符,来说明是深拷贝还是浅拷贝。C语言则采用了另一种方法->即所有数组在作为参数传递的时候转换为指向数组首地址的指针,而其他参数则采用值传递,这样做就可以简化编译器,类似的,函数的返回值也不能是函数数组,只能是指向数组或者函数的指针。
    还有一个例子是lint程序,处于效率的愿意,被C语言编译器排除在外。

数组参数传递

int func(int *p);int func(int p[]);int func(int p[200]);//可以用下列任何实参传入上面的三个函数int n;int *n;int n[10];
调用时的实参 类型 通常目的 func(&my_int); 一个整型数的地址 一个int参数的传地址调用,为了改变int值 func(my_init_ptr); 指向整型数的指针 传递一个指针 func(my_int_array); 整型数组 传递一个数组 func(&my_init_array[i]); 一个整形数组某个元素的地址 传递数组的一部分

1、需要注意的是,在函数内部,根本无法判断传入的是一个数组,还是一个指针,是一个指向一个int的指针,还是一个指向很多int的指针,无法区分;因此在传递指针或者数组的时候,会同时传递一个长度信息(对于字符串则不需要,因为C语言中字符串结尾有’\0’作为标识,指针也不需要,可以用NULL做结尾)
2、如果非要把一个数组整体传递给函数,则需要把数组放在一个结构体中
3、函数内部无法用sizeof得到数组长度
4、因此,在声明一个函数的时候,你可以选择声明成什么形式,数组还是指针,不论选什么,对编译器都没有影响,有一些建议如下:
5、建议把所有声明都写成指针形式,和编译器保持一致;
6、但也有人觉得int a[]和int *a比较,前者更能突出表明了a内部有好几个元素的特征,提示函数会对其内部进行处理,见仁见智。

1个例外

有一样操作只能在指针里进行,不能对数组进行,就是修改它的值,数组名是不可修改的左值,代码如下:

func1(int *ptr){    ptr[1] = 3;    *ptr = 3;    ptr = array2;//正确}func2(int arr[]){    arr[1] = 3;    *arr = 3;    arr = array2;//正确,因为已经转化为指针}int array[100];int array2[100];int main(){    array[1] = 3;    *array = 3;    func1(array);//正确    func2(array2);//正确    array = array2;//错误}

多维数组\数组的数组

Ada语言标准明确说明数组的数组和多维数组是不一样的;
Pascal语言标准明确说明数组的数组和多维数组是一样的;
C语言里面只有一个别的语言成为数组的数组的形式,但C语言称它为多维数组

  • 可以把[i][j][k]计算出相应的偏移量,使用[z]来引用数组,当然这不是一种值得推荐的方法
  • C语言的数组就是一种向量,也就是某种对象的一维数组,但是这种对象可以是另一个数组
char carrot[10][20];typedef char vegetable[20];vegetable carrot[10];//注意此时是char carrot[10][20]不是[20][10]carrot[i][j];//无论上述哪种形式,编译器都会解析成*(*(carrot+i)+j)

多维数组的分解

这里写图片描述

正常情况下,赋值发生在两个相同的类型之间,如int和int,double和double等。但是上图中可以看出,“数组的数组的数组”中的每一个单独的数组都可以看作是一个指针,因为在表达式中的数组名会被编译器当作指向第一个元素的指针,所以不能把数组赋值给另一个数组,因为数组作为一个整体不能作为赋值的对象。

  • 指针运算也大不相同
  • r++;和t++;两个语句都会使得r和t指向它们各自的下一个元素(两者所指向的元素本身就是数组),所以它们的步长是不相同的,p走15,r走5,t走1.
int apricot[2][3][5];int (*p)[3][5] = apricot;int (*r)[5] = apricot[0];int (*t) = apricot[0][0];printf("%x,%x,%x",p,r,t);p++;r++;t++;printf("%x,%x,%x",p,r,t);

多维数组的内存布局

  • C语言的多维数组中,最右侧的下标是最先变化的,这个约定被称为“行主序”,由于“行/列主序”这个术语只适用于二维数组,所以更确切的说法是“最右侧的下标先变化”,绝大部分语言都是行主序,Fortran例外
    这里写图片描述

数组初始化

float banana[5] = {0.0,1.0,2.0,3.0,4.0};float honeydew[] = {0.0,1.0,2.0,3.0,4.0};short cantaloupe[2][5] = {  {10,12,3,4,-5},  {31,32,6,0,-5},//最后的,可以省略};int rhubarb[][3] = {{0,0,0},{1,1,1,},}//最后的,可以省略
  • 一维数组约定如果没有确切值,则按照花括号中的个数来确定长度
  • 只能够在对数组声明的时候对它初始化,只所以存在这个限制,并没有什么特别的理由,属于我就这样,爱咋咋地
  • 多维数组通过嵌套的花括号初始化,最后的‘,’可以省略,同时也可以省略第一维的长度
  • 如果数组的长度比所提供的初始化的值要大,剩余的初始化为0,如果是指针,初始化为NULL,如果是float,初始化为0.0

下面是一种初始化二维字符串的方法,注意字符串常量可以用做数组初始值,编译器会正确的把各个字符存储在数组中的地址,第二句初始化,要记住只要字符串常量才可以初始化指针数组,否则就要malloc。所以第三句初始化无法编译通过。

char vegetables[][9] = { "beet","bearley","basil","broccoli","beans"};char *vegetables[] = { "beet","bearley","basil","broccoli","beans"};int *weights[] = { {1,2,3,4,5},{6,7},{8,9,10}};//ERROR

非要初始化上述第三句,可以按照如下方式:

int row_1[] = {1,2,3,4,5};int row_2[] = {6,7};int row_3[] = {8,9,10};int *weight[]={row_1,row_2,row_3};

char ga[] = "abcdef";void print_array(char ca[]){    printf("address of array param = %#x \n",&ca);    printf("address (ca[0]) = %#x \n",&(ca[0]));    printf("address (ca[1]) = %#x \n",&(ca[1]));    printf("++ca = %#x \n",++ca);}void print_pointer(char *pa){    printf("address of ptr param = %#x \n",&pa);    printf("address (pa[0]) = %#x \n",&(pa[0]));    printf("address (pa[1]) = %#x \n",&(pa[1]));    printf("++pa = %#x \n",++pa);}int main(){    printf("address of gloable array = %#x \n",&ga);    printf("address (ga[0]) = %#x \n",&(ga[0]));    printf("address (ga[1]) = %#x \n",&(ga[1]));    print_array(ga);    print_pointer(ga);    return 0;}

Reference

C专家编程

0 0
原创粉丝点击