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

本文探讨了C语言中数组作为参数传递的原因,包括历史背景和效率考量,并详细解释了数组与指针之间的转换机制。此外,还介绍了多维数组的处理方法及数组初始化的相关规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本节主要说明为什么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专家编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值