《C专家编程》:深度剖析数组与指针(七)

         数组与指针的关系颇有点像诗和词的关系:他们都是文学形式之一,有不少共同之处,但在实际的表现手法上又有各自的特色。---Peter Van Der Linden
         在笔记(4)中我们讨论了数组和指针并不一致的情况(在一个文件中定义为数组,在另一个文件中声明为指针)。所以在代码中的定义和引用时一定要“配套使用”。不然可能会出现一想不到的问题。这一节,我们将继续讨论可以把指针和数组看作相同的情况,因为数组和指针可以互换的情形要比两者不可互换的情形更为常见。
一、声明和使用
声明可以进一步分成3中情况:
(1)外部数组(external array)的声明;
(2)数组的定义(定义时声明的一种特殊情况,他分配内存空间,并可能提供一个初始值);
(3)函数参数的声明;
所有作为函数参数的数组名总是可以通过编译器转换为指针;在笔记(4)中详细的讨论了:数组的声明就是数组,指针的声明就是指针,两者不能混淆。

但在使用数组(在语句或表达式中引用)时,数组总是可以写成指针的形式,两者可以互换。对情况进行了总结如下图一。


图一

     切记:当作为函数定义的形式参数时,数组下标表达式总是可以改写为带偏移量的指针表达式。
   因为数组和指针在编译器处理时是不同的,运行时的表现形式也是不一样的。 对编译器而言,一个数组就是一个地址,一个指针就是一个地址。我们应该根据情况作出选择。
   例如下面的代码都是可以的:
char my_array[10];
char *my_ptr;
i=strlen(my_arry);
j=strlen(my_ptr);
我们还可以看到很多类似的程序:
printf("%s %s",my_array,my_ptr);
它清楚的展示了数组和指针的互换性。但是它的前提条件是它们作为函数的参数时。
printf(" %x %s",my_array,my_ptr);
在同一条语句中,即把数组名作为地址,又可以将其作为一个指针。它之所以可以这样互换,因为printf是一个函数。
还有main函数里的两个参数:
int main(int argc,char **argv){}  
//或者:
int main(int argc,char *argv[]){}

啰嗦这么多就是想说明一个问题:那就是只有在作为函数参数时,指针和数组才能互换。而不是我们心里想的,指针和数组就是一样的!

二、C语言的多维数组
    多维数组本来在我的C基础里有详细的讲解,但是为了熟悉巩固,我又复习一遍,并且画出了一张图,来说明多维数组与指针的用法。 

    C语言中的数组就是一维数组,当提到C语言的数组时,就把它看作是一种向量(vector),也就是某种对象的一维数组,数组的元素可以是另一个数组。
     如何分解多维数组,以及多维数组和指针的关系,如下代码,详解如下图二所示:

    代码示例:

#include <iostream>
using namespace std;
int main()
{
	int array[2][2][2]={1,2,3,4,5,6,7,8};
	int (*p)[2][2][2]=&array;
	int (*p1)[2][2]=array;
	cout<<sizeof(array)<<endl;
	cout<<sizeof(&array)<<endl;
	cout<<array<<endl;
	cout<<&array<<endl;
	cout<<array+1<<endl;
	cout<<&array+1<<endl;

	cout<<array[0][0][0]<<" "<<&array[0][0][0]<<endl;
	cout<<array[0][0][1]<<" "<<&array[0][0][1]<<endl;
	cout<<array[0][1][0]<<" "<<&array[0][1][0]<<endl;
	cout<<array[0][1][1]<<" "<<&array[0][1][1]<<endl;

	cout<<array[1][0][0]<<" "<<&array[1][0][0]<<endl;
	cout<<array[1][0][1]<<" "<<&array[1][0][1]<<endl;
	cout<<array[1][1][0]<<" "<<&array[1][1][0]<<endl;
	cout<<array[1][1][1]<<" "<<&array[1][1][1]<<endl;

	system("pause");
	return 0;
}
运行结果:

具体分析如下图所示:


图二
注意: 使用指向数组的指针的时候,一定要注意每一个++和--中指针的类型,也就是步长的大小。否则很容易出错。
在C语言的多维数组中,最右边的下标示最先变化的,这个约定被称为“行/列主序”。

但是Fortran里则是“列行主序”,如果将C语言的矩阵传递给
所以一个Fortran程序,矩阵就会被自动转置,这是一个非常厉害的邪门秘技。

三、什么时候指针和数组是一样的?
规则一:表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针。

如下实例:


事实上,可以采用的方法有很多,对数组的引用如a[i]在编译时总是被编译器改写为*(a+i)的形式。
在表达式中:指针和数组是可以互换的,因为他们在编译器里的最终形式都是指针。

规则二:下表总是与指针的偏移量相同;
c语言中总是把数组下标作为指针的偏移量。把数组下标作为指针加偏移量是c语言从BCPL[Basic Combined Programming Language]继承而来。 c语言把数组下标改写成指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本类型。
规则三:在函数参数声明中,数组名被编译器当作指向该数组第一个元素的指针。
这个规则中设计到两个概念:
形参(parameter):它是一个变量,在函数定义或函数声明的原型中定义。又称“形式参数(formal parameter)”。
int function(int a,float b){} //其中a,b都是形参;
实参:在实际调用中传给函数的具体的值;
function(12,3.14);//12和3.14就是实参
在函数形参定义这个特殊的情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式,编译器执行函数传递数组的地址,而不是整个数组的值。
例如下面的函数声明都是合法的:
function(int *array){}
function(int array[100]){}
function(int array[]){}
四、为什么C语言把数组形参当作指针?
    把作为形参的数组和指针等同起来主要是出于 对效率的考虑。在C语言中,所有非数组形式的数据实参均以传值形式(对实参做一份拷贝并传递给调用的函数,函数不能修改作为实参的实际变量的值,而是只能修改传递给它的那份拷贝)调用(也不绝对,如+&)。然而, 如果拷贝整个数组,无论在时间还是在空间上的开销都是很大的。而且在绝大多数情况下,我们并不需要整个数组的值,函数只是想知道某一时刻对数组的那个特定的值感兴趣。要达到这个目的,我们就可以采取传地址调用。所以, 所有的数组在作为参数传递时都转换为指向数组起始地址的指针,而其他的参数均采用传值调用的约定,就可以简化编译器。类似的,函数的返回值绝不可能是一个数组,而只能是指向数组或函数的指针。当然不仅是数组是传地址的调用方式,如果是其他的数据,我们也可以加“&”来进行传地址的调用。
     数组形参具体引用的过程如下图三所示。它和前面的学习笔记(4)中的图三--对指针的下表引用--是比较类似的。

图三


       只有遵守这样的规则,编译器就可以产生正确的代码,并不需要对数组和指针进行仔细的区分。虽然没有办法把数组本身传递给函数, 但是在函数内部使用指针,所能进行的对数组的操作几乎跟传递原原本的数组没有差别。只不过如果想用sizeof(实参)来获得数组的长度,所得到的结果不正确而已,所以一般我们传递数组的时候,还需要传递一个数组的长度。
    我们始终倾向于把数组定义为指针,是因为这是编译器内部所使用的形式。

   注意有一样操作只能在指针里完成而无法再数组中进行,那就是修改它的值。数组名是不可修改的左值,它的值是不能改变的。如下实例所示:


    语句arry=arry2;将引起一个编译的错误,错误信息是“无法修改数组名”。但是,ptr=arry2和arr=array2是合法的,因为arr虽然声明是一个数组,但是实际上却是一个指针。
五、数组片段的下表
    可以通过向函数传递一个指向数组第一个元素的指针来访问整个数组,但也可以让指针指向任何一个元素,这样传递给函数的就是从该元素之后的数组片段。
    例如人们常常熟悉的下表为1,2...N,但是很不幸在数组里面由于编译的原因数组的下表总是从0开始。
    所以为了使数组的下表从1开始,Fortran程序员用了一种方法来扩展这种技巧。 他们向函数传递一个参数(a[-1]),这样就可以是数组的小标为1..N,而不是0...N-1。
    但是很不幸这个规则完全为C标准所不容。如果在C语言中取得这样的效果 ,我们可以申请空间的长度总是大于最大长度+1,下表的范围为0-N,但是我们使用1-N就可以了!
六、如何检查位模式。
编写一个程序,检查你的系统中个,浮点数的0.0是否与整型数0的位模式相同。
#include<stdio.h>
int main(void)
{
	double d0=0.0;
	int    i0[2]={0,0};
	printf("sizeof double is:%d\n",sizeof(double));
	printf("sizeof int    is:%d\n",sizeof( int ));


	if(*(double*)i0==d0){
		printf("%lf\n",*(double *)i0);
		printf("%lf\n",d0);
		puts("is the same!");
	}
	else {
		puts("not the same!");
	}
	system("pause");
	return 0;
}
代码设计参考网上的资料与知识,若有不对,欢迎指正。

运行的结果:




  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值