实用经验 26 论数组和指针的等价性

数组和指针是C/C++语言的一对欢喜冤家。两者有时可等同看待,有时不能等同看待。这是让C++新手颇为头痛的一件事。

一般情况下,即使是一个C++开发新手也都明白“所有作为函数参数的数组名总是可以通过编译器转化为指针”。也正因为如此,才导致了数组和指针的混乱局部。《C专家编程》对指针和数组的等同情况进行了总结。如图4-2所示。

在这里插入图片描述

图4-2 什么时候数组和指针等价

在C/C++中,数组和指针的关系颇为特殊,有点儿像文学中诗和词的关系:诗和词都是文学形式之一,存有不少的相同之处,但是在表现手法上有各具春秋。

什么时候数组和指针等价?

C语言标准对此作出如下说明:

  • 规则1:表达式中的数组名(与声明不同)被编译器当做一个指向该数组首元素的指针。
  • 规则2:下标总是与指针的偏移量相同。
  • 规则3:在函数形式参数声明中,数组名被编译器当作指向数组首元素的指针。

“表达式中的数组名”就是指针,如果把规则1和规则2合在一起,可得出结论:对数组下标的引用总是可以写成“一个指向数组起始地址的指针加上偏移量”。按照这个结论,假设我们声明:

int  a[10] = {12345678910};
int  *p = NULL;
int  i = 2;

可通过下面任何一种方式访问a[i],且都是等价

第一种                 		第二种                     		第三种
p = a;                  	p = a;                       	p= a+I;
p[i];                   	*(p+i);                    		*p;

然而,事实上,对数组的应用,例如a[i]在编译时总是被编译器改写成*(a+i)这种指针的形式。编写过类[]操作符重载的都会了解这一情形,在[]操作符重载时,程序基本上都是返回*(指针+偏移)这种形式。

“作为函数参数的数组名”等同于指针,这是规则3。当数组名作为实参传递给函数时,编译器进行了两步处理:1、数组不会发生复制;2、使用数组名称时,数组名会自动转化为指向第一个元素的。针。例如,可通过下面三种方式指定数组形参:

void Print_Val(int *) {/**/}
void Print_Val(int []) {/**/}
void Print_Val(int [10]){/**/}

通常,在函数声明或定义时,将数组形参直接定义为指针要比使用数组语法定义更好,这种做法可以明确的表示,函数操作的是指向数组元素的指针,而不是什么数组。

除此之外,如果采用数组语法定义时,如果函数定义时包含了数组长度会特别容易引起误解。因为数组长度在函数内部是无意义的,编译器会忽略数组形参指定的长度。

下面是一个因数组长度引起误解的例子:

void PrintVal(const int ia[10])
{
	// 这段代码假设数组ia包含10个元素。但实际上并非如此。
	for (int i = 0; i < 10; i++)
	{
		cout << ia[i] << endl;
	}
}

int main()
{
	int i = 0; 
    int ia[2] = {1, 2};
	PrintVal(&i);  // 由于&i是int *,因此这句代码可以编译通过
   	PrintVal(ia);
}

可看出,虽然PrintVal假定所传递的数组长度至少应包含10个元素。但是C++没有任何机制限制这个假设。所以导致PrintVal(&i);和PrintVal(ia);这样的代码可以编译通过。

但问题也就出现了,PrintVal假定接收的数组长度至少为10个元素。但是main函数中传递的实参数组元素个数均为达到10个。所以这两个调用都是错误的。

然而,为什么C/C++语言在函数参数传递时把形参当作指针呢?C++采用这种处理方式主要出发点是出于效率的考虑。

在C++中,所有非数组形式的数据实参均以传值的形式(对实参作一份拷贝并传递给调用的函数,函数不能修改实参的实际变量的值,而只能修改传递给它的那份拷贝)调用,然而如果数组传值时拷贝整个数组,无论在时间上还是在空间上的代价都是巨大的。如果数组中存放的是类对象,在拷贝时会附带着类拷贝构造函数的调用,开销就更大了。

不仅如此,函数的形参采用传址调用方式还可以简化编译的实现。还有一举两得的效果。类似的,函数的返回值也不能是一个数组,而只能是指向数组的指针。

最后,数组和指针何时等价进行总结:

  1. 采用a[i]这种形式访问数组,编译总会把其“改写”成像*(a+i)这种指针访问。
  2. 指针始终是指针,任何时候也不会改写成数组,但可采用数组下标方式访问指针。
  3. 作为函数的形式参数,一个数组的声明就是一个指针,一个指向数组第一个元素的指针。

请谨记

  • 作为函数形参时,指针和数组等价,数组会退化为一个指向数组首元素的指针。
  • 数组名(与声明不同)被编译器当做一个指向该数组首元素的指针,而且是const指针。
  • 无论采用a[i]或*(a+i)形式访问,编译器在编译时均会改写成*(a+i)指针形式访问。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值