C语言

<1>.数组名 
声明中:当我们声明一个数组时,编译器将根据声明所指定的元素数量及类型为数组保留内存空间,然后再创建数组名,编译器会产生一个符号表,用来记录数组名和它的相关信息,这些信息中包含一个与数组名相关联的值,这个值是刚刚分配的数组的第一个元素的首地址(一个元素可能会占据几个地址,如整型占4个,此处是取起始地址)。现在声明一个数组:int ia[100]; 编译器此时为它分配空间,假设第一个数组元素的地址为0x22ff00;那么编译器会进行类似#define ia 0x22ff00的操作,这里只是模拟,真实情况并非完全一样,我们在编程时无需关注编译器所做的事情,但要知道此时(声明时)数组名只是一个符号,它与数组第一个元素的首地址相关联。注意:数组的属性和指针的属性不相同,在声明数组时,同时分配了用于容纳数组元素的空间;而声明一个指针时,只分配了用于容纳指针本身的空间。 
表达式中:当我们在表达式中使用数组名,如:ia[10] = 25;时,这个名字会被编译器转换为指向数组第一个元素的常量指针(指针本身的值不可变),它的值还是数组的第一个元素的首地址(一个指针常量),编译器的动作类似于int const ia = (void )0x22ff00; 这里我们应重点关注的是:数组名是一个常量指针(常指针),即指针自身的值不能被改变。如果有类似ia++或ia+=3这类的语句是绝对不对的,会产生编译错误。注意:当数组名作为sizeof操作符的操作数时,返回的是整个数组的长度,也就是数组元素的个数乘以数组元素类型的大小;另外,在对数组名实施 & 操作时,返回的是一个指向数组的指针,而非具有某个指针常量值的指针(这个问题在后面会详细论述)。 
通过数组名引用数组元素时:在前面讲过的指针算术运算中指针加上一个整型数,结果仍然是指针,并且可以对这个指针直接解引用,不用先把它赋给一个新指针。如int last = (ia + 99); 此时的ia已经是一个常指针了,这个表达式计算出ia所指向元素后面的第99个元素的地址,然后对它解引用得到相应的值。这个表达式等价于int last = ia[99];事实上每当我们采用[ ]的方式引用数组元素时,如:ia[99],在编译器中都会转换成指针形式,也就是(ia + 99) 
(这里ia + 99 和 &ia[99] 的值都为数组最后一个元素的首地址,所以(ia + 99)和&ia[99]得到的结果是一样的,较难理解的是*&ia[99],按照优先级和结合性规则,先对ia[99] 取地址再解引用,有些编译器见到这种表达式会直接优化成ia[99]。)现在可以看出来在表达式中,指针和数组名的使用可以互换,但唯一要注意的就是:数组名是常指针,不能对它的值进行修改。ia 
+ 99是可以的,但ia++是不行的,它的意思是ia = ia +1; 修改了ia的值。 
作为函数参数:先来了解一下函数的实参与形参。实参(argument)是在实际调用时传递给函数的值;形参(parameter)是一个变量,在函数定义或者原型中声明。C语言标准规定作为形参的数组声明转换为指针。在声明函数形参的特定情况下,编译器会把数组形式改写成指向数组第一个元素的指针。所以不管下面哪种声明方式,都会被转换成指针: 
void array_to_pointer(int *ia){……} //无需转换 
void array_to_pointer(int ia[ ]){……} //被转换成*ia 
void array_to_pointer(int ia[100 ]){……} //被转换成*ia 
那么如果有下面的操作 
void array_test(int ia[100]) 

double da[10]; 
printf(“%d”, sizeof( ia )); 
ia++; 
//da++; //编译错误,数组名是常指针 

输出的结果为4,此时的ia是作为函数形参而声明的数组,已经被转换为了一个不折不扣的指针(不再是常指针了),因此ia++;是合法的,不会引发编译错误。为什么C语言要把数组形参当作指针呢?因为C语言中所有非数组形式的数据实参(包括指针)均以值传递形式调用(所谓值传递就是拷贝出一个实参的副本并把这个副本赋值给形参,从此实参与形参是各不相干的,形参值的变化不会影响实参)。如果要拷贝整个数组,在时间和空间上的开销都很大,所以把作为形参的数组和指针等同起来是出于效率原因的考虑。我们可以把形参声明为数组(我们打算传递给函数的东西)或者指针(函数实际接收到的东西),但在函数内部,编译器始终把它当作一个指向数组第一个元素(数组长度未知)的指针。在函数内部,对数组参数的任何引用都将产生一个对指针的引用。我们没有办法传递一个数组本身,因为它总是被自动转换为指向数组首元素的指针,而在函数内部使用指针时,能对数组进行的操作几乎和传递数组没有区别,唯一不同的是:使用sizeof(形参数组名)来获得数组的长度时,得到的只是一个指针的大小,正如上面所述的ia。但要注意:以上讨论的都是数组名作为函数形参的特殊情况,当我们在函数体内声明一个数组时,它就是一个普通的数组,它的数组名仍是一个常指针,所以上面的da++;仍会引起编译错误,请大家不要混淆。 
还有一点,既然是值传递,那么理所当然地,在用数组名作为实参调用函数时,实参数组名同样会被转换为指向数组第一个元素的指针。 
<2>.指向数组的指针 
好了,关于数组名的讨论可以告一段落了,现在来看指针与数组的另一种联系。在前面说过,当对一个一维数组的数组名进行 & 操作时,返回的是一个指向数组的指针。现在我们就来看看什么是指向数组的指针。在C语言中,所谓的多维数组实际上只是数组的数组,也就是说一个数组中的每个元素还是数组,由于二维数组较为常用,所以本文着重讨论二维数组,更多维数组的原理与二维数组相同。所谓二维数组(数组的数组),就是每个元素都是一个一维数组的一维数组。另外,请大家先有一个感性的认识:指向数组的指针主要用来对二维数组进行操作,大家不理解没有关系,我会在后面详细说明。 
通常我们声明一个指向一维数组中的元素的指针是这样做的:int ia[100], ip = ia; ip指向这个数组的第一个元素,通过指针的算术运算,可以让ip指向数组中的任一元素。对于二维数组,我们的目的同样是让一个指针指向它的每一个元素,只不过这次的元素类型是一个数组,所以在声明这个指针时稍有不同,假设有二维数组int matrix[50][100], C语言采用如下的方式来声明一个指向数组的指针。int (*p) [100]; 比普通声明稍复杂一些,但并不难理解。由于括号的优先级是最高的,所以首先执行解引用,表明了p是一个指针,接下来是数组下标的引用,说明p指向的是某种类型的数组,前面的int表明p指向的这个数组的每个元素都是整数。对于这个声明还可以换一个角度来理解:现在要声明的是一个指针,因此在标识符p前面加上。如果从内向外读p的声明,可以理解为*p是int[100] 类型,即p是一个指向含有100个元素的数组的指针。 
有些朋友可能对于一个用来操纵二维数组的指针只使用一个下标表示困惑,为什么声明不是int (*p) [50][100]呢?现在来回顾一下操纵一维数组的指针声明int *ip = ia;它表示ip指向了一个数组的第一个元素,通过对指针的算术运算可以使它指向数组中的任何一个元素,编译器不需要知道指针ip指向的是一个多长的数组。对于二维数组道理相同,int (*p) [100] = matrix; matrix可以看成是一个长度为50的一维数组,每个元素都是一个int[100]型的数组,p同样指向了matrix数组的第一个元素(第一个int[100]型的数组),通过对p的算术运算也可以使它指向matrix数组中的任意一个元素而不需要知道matrix是一个多长的数组,但一定需要知道matrix中每个数组元素的长度,所以就有了int 
(p) [100]这种形式的声明。由此可知,如果进行p + n (n为整数)这样的运算,每次的步长就是n 100 * sizof (int),相当于跳过了矩阵中的n行,因为每行都有100个元素并且元素为整型,所以跳过了n * 100 * sizof (int)个字节,指向这些字节之后的位置。现在,对指向数组指针的声明方式的疑惑我认为已经讲清楚了。下面来看一个关于数组长度的问题。 
在C语言中没有一种内建的机制去检查一个数组的边界范围,完全是由程序员自己去控制,这是C语言设计的一种哲学或者说一种理念:给程序员最大的自由度,程序员应该知道自己在做什么。凡事有利有弊,自由度大了,出错的几率就高了。很有朋友(包括我自己)在初用数组时应该会或多或少地遇到过数组越界的问题。在前面的论述中提到了通过对指针的算术运算可以使它指向数组中的任何一个元素包括超出数组范围的第一个元素,这个超出范围的第一个元素实际上是不存在的,这个“元素”的地址在数组所占的内存之后,它是数组的第一个出界点,这个地址可以赋给指向数组元素的指针,但ANSI 
C仅允许它进行赋值或比较运算,不能对保存这个地址的指针进行解引用或下标运算。

备注: 
int a[10][20]; 
int **p; 
p = a; //这种方式是错误的!! 
a是一个数组类型,而p是一个指向指针的指针类型,此时p里面是一个地址,通过该地址所得到的值还是一个整型指针的类型。即*p也是一个整型指针。而此处a为数组,其里面的内容为整型,让p指向这个数组的首地址,而该首地址里面的内容又为一个整型,不是一个指针类型。所以会出现错误。 
因此, 只能采取指向数组的指针类型才能够完成这个功能,即 
int (*q)[20]; 
q = a; 
此时q是一个指针,其指向的是一个具有20个整型变量的一维数组。这样, 就可以使用q[i][j]或者((q+i)+j)的方式来访问这个二维数组了。 
另外, 对于一维数组。 
int a[10]; 
int (*q)[10] = &a; 
也可以进行访问, 但是此时q[i][j]中的i值就只能是为0了。 
在这个地方,也可以说明为什么a+1与&a+1不相同了。 
a表示的是一个元素, 所以a+1也只是地址增加一个整型的长度,而&a+1则是以整个数组为角度来看的,相当于q+1,所以其地址增加的是10*sizeof(int)这么长。 
同理,对于二维数组, int a[10][20]; a+1地址增加的为20个整型的长度。 因为其是以一个一维数组的角度来看的,此时相当于是int (*q)[20] = a;这种形式。 
对于二维数组,a+1与(a+1)都是同一个地址,但是角度不相同,前一个是以一个20长度的一维数组来看的,即a[1]的地址,而后面一个则是 a[1]的值, 因为a[1]为a[1][0]的首地址,而该首地址又与a[1]的首地址相同, 所以此时a+1与(a+1)是相同的。 *(a+1)+2就是a[1][2]的首地址了,即相当于是a[1]+2了,此时是以单个元素来看的。 
int a[10]; int (q)[10]; q = &a; &a的类型变为int [10] ,即指向一个10长度的一维数组 
//int a[10]; int (*q)[10]; q = a; &a 是整个数组的首地址,a是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“=”号两边的数据类型必须是相同的,如果不同需要显示或隐式的类型转换。q 这个定义的“=”号两边的数据类型就不一致了。左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。所以错误表达 
int b; int p; p = &b; &b的类型变为int 
int c[2]; int pp;pp = c; c为int [2] 型,&c为int [2] 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值