函数、数组、指针和指针操作

一、函数、数组和指针
1.使用指针形参
(1)对于一维数组来说,数组作为函数参数传递,实际上传递了一个指向数组的指针,在c编译器中,当数组名作为函数参数时,在函数体内数组名自动退化为指针。此时调用函数时,相当于传址,而不是传值,会改变数组元素的值。
例如:void fun(int a[]); 若在fun函数中有a[i]++;等语句,那么对应的数组元素会被修改,调用时直接用fun(a);即可。
(2)对于高维数组来说,可以用二维数组名作为实参或者形参,在被调用函数中对形参数组定义时可以指定所有维数的大小,也可以省略第一维的大小说明,如:
void fun(int array3);
void fun(int array);
二者都是合法而且等价,但是不能把第二维或者更高维的大小省略,如下面的定义是不合法的:
void fun(int array);
因为从实参传递来的是数组的起始地址,在内存中按数组排列规则存放(按行存放),而并不区分行和列,如果在形参中不说明列数,则系统无法决定应为多少行多少列,不能只指定一维而不指定第二维,下面写法是错误的:
void fun(int array3);
实参数组维数可以大于形参数组,例如形参数组定义为:
void fun(int array3);
而实参数组定义为:
int array5;
这时形参数组只取实参数组的一部分,其余部分不起作用。
可以看到,将二维数组当作参数的时候,必须指明所有维数大小或者省略第一维的,但是不能省略第二维或者更高维的大小,这是由编译器原理限制的。学编译原理的时候应该 知道编译器是这样处理数组的:
对于数组 int pm;
如果要取pi的值(i>=0 && i<m && 0<=j && j < n),编译器是这样寻址的,它的地址为:
p + in + j;
从以上可以看出,如果我们省略了第二维或者更高维的大小,编译器将不知道如何正确的寻址,即这里的n值是在形参定义的时候就要明确知道的。但是我们在编写程序的时候却需要用到各个维数都不固定的二维数组 作为参数,这就难办了,编译器不能识别阿,怎么办呢?不要着急,编译器虽然不能识别,但是我们完全可以不把它当作一个二维数组,而是把它当作一个普通的指针,再另外加上两个参数指明各个维数,然后我们为二维数组手工寻址,这样就达到了将二维数组作为函数的参数传递的目的,根据这个思想,我们可以把维数固定 的参数变为维数随即的参数,例如:
void fun(int array3);
void fun(int array);
变为:
void fun(int array, int m, int n);
在转变后的函数中,arrayi这样的式子是不对的(不信,大家可以试一下),因为编译器不能正确的为它寻址,所以我们需要模仿编译器的行为把arrayi这样的式子手工转变为:
(array + ni + j);
在调用这样的函数的时候,需要注意一下,如下面的例子:
int a3 = { {1, 1, 1}, {2, 2, 2}, {3, 3, 3}};
fun(a, 3, 3);
根据不同编译器不同的设置,可能出现warning 或者error,可以进行强制转换如下调用:
fun((int)a, 3, 3);
2.指针表示法和数组表示法
①首先,明确指针和数组的关系十分密切,可以使用指针标识数组的元素和获得元素的值
②从本质上看,同一个对象有俩种表示法(以数组 int arr[6]为例)地址的表示有俩种方法:
arr+2&arr[2]
数组的值的表示有俩种方法:
(arr+2)
arr[2]
③其实质为
定义arr[n]的意思是(arr+n),可以认为(arr+n)的意思是 “到内存的arr位置(首元素的地址),然后移动n个单元,检索存储在那里的值”*
④运算符的优先级高于+所以不要混淆 (arr+2) 和 arr+2,
(arr+2)表示数组arr第三个元素的值arr+2表示数组arr中的第一个元素的值(不是地址)加上2。
二、指针操作
程序清单ptr_ops.c程序
//ptr_ops.c--指针操作

include <stdio.h>

int main(void)
{
int urn[5]={100,200,300,400,500};
int ptr1,ptr2,ptr3;
ptr1=urn;//把一个地址赋给指针
ptr2=&urn[2];//把一个地址赋给指针
//解引用指针,以及获得指针的地址
printf("pointer value, dereferenced pointer, pointer address:\n");
printf("ptr1=%p,ptr1=%d,&ptr1=%p\n",ptr1,ptr1,&ptr1);
//指针加法
ptr3=ptr1+4;
printf("\n adding an int to a pointer:\n");
printf("ptr1+4=%p,(ptr1+4)=%d\n",ptr1+4,(ptr1+4));
ptr1++;//递增指针
printf("\n values after ptr1++:\n");
printf("ptr1=%p,ptr1=%d,&ptr1=%p\n",ptr1,ptr1,&ptr1);
ptr2--;//递减指针
printf("\n values after ptr2++:\n");
printf("ptr2=%p,ptr2=%d,&ptr2=%p\n",ptr2,ptr2,&ptr2);
--ptr1;//恢复为初始值
++ptr2;//恢复为初始值
printf("\n Pointers reset to original values:\n");
printf("ptr1=%p,ptr2=%p\n",ptr1,ptr2);
//一个指针减去另一个指针
printf("\n subtracting one pointer from another:\n");
printf("ptr2=%p,ptr1=%p,ptr2-ptr1=%td\n",
ptr2,ptr1,ptr2-ptr1);
//一个指针减去一个整数
printf("\n subtracting an int from a pointer :\n");
printf("ptr3=%p,ptr3-2=%p\n",ptr3,ptr3-2);
return0;
}
下面是我们的系统运行该程序后的输出:
pointer value, dereferenced pointer,pointer address:
ptr1=0x7fff5fbff8d0,ptr1=100,&ptr1=0x7fff5fbff8c8
adding an int to a pointer:
ptr1+4=0x7fff5fbff8e0,(ptr1+4)=500
values after ptr1++:
ptr1=0x7fff5fbff8d4,ptr1=200,&ptr1=0x7fff5fbff8c8
values after--ptr2:
ptr2=0x7fff5fbff8d4,ptr2=200,&ptr2=0x7fff5fbff8c0
Pointersresettooriginalvalues:
ptr1=0x7fff5fbff8d0,ptr2=0x7fff5fbff8d8
subtractingonepointerfromanother:
ptr2=0x7fff5fbff8d8,ptr1=0x7fff5fbff8d0,ptr2-ptr1=2
subtracting an int from a pointer:
ptr3=0x7fff5fbff8e0,ptr3-2=0x7fff5fbff8d8
指针变量的基本操作
①赋值:可以把地址赋给指针。例如,用数组名、带地址运算符(&)的变量名、另一个指针进行赋值。在该例中,把urn数组的首地址赋给了ptr1,该地址的编号恰好是0x7fff5fbff8d0。变量ptr2获得数组urn的第3个元素(urn[2])的地址。注意,地址应该和指针类型兼容。也就是说,不能把
double类型的地址赋给指向int的指针,至少要避免不明智的类型转换。C99/C11已经强制不允许这样做。
②解引用:运算符给出指针指向地址上储存的值。因此,ptr1的初值是
100,该值储存在编号为0x7fff5fbff8d0的地址上。
③取址:和所有变量一样,指针变量也有自己的地址和值。对指针而言,&运算符给出指针本身的地址。本例中,ptr1储存在内存编号为0x7fff5fbff8c8的地址上,该存储单元储存的内容是0x7fff5fbff8d0,即urn的地
址。因此&ptr1是指向ptr1的指针,而ptr1是指向utn[0]的指针。
④指针与整数相加:可以使用+运算符把指针与整数相加,或整数与指针相加。无论哪种情况,整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。因此ptr1+4与&urn[4]等价。如果相加的结果超出了初始指针指向的数组范围,计算结果则是未定义的。除非正好
超过数组末尾第一个位置,C保证该指针有效。
⑤递增指针:递增指向数组元素的指针可以让该指针移动至数组的下一个元素。因此,ptr1++相当于把ptr1的值加上4(我们的系统中int为4字节),ptr1指向urn[1](见图10.4,该图中使用了简化的地址)。现在ptr1的值是0x7fff5fbff8d4(数组的下一个元素的地址),*ptr的值为200(即urn[1]的
值)。注意,ptr1本身的地址仍是0x7fff5fbff8c8。毕竟,变量不会因为值发
生变化就移动位置。
⑥指针减去一个整数:可以使用-运算符从一个指针中减去一个整数。指针必须是第1个运算对象,整数是第2个运算对象。该整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。所以ptr3-2与&urn[2]等价,因为ptr3指向的是&arn[4]。如果相减的结果超出了初始指针所
指向数组的范围,计算结果则是未定义的。除非正好超过数组末尾第一个位置,C保证该指针有效。
⑦递减指针:当然,除了递增指针还可以递减指针。在本例中,递减ptr3使其指向数组的第2个元素而不是第3个元素。前缀或后缀的递增和递减运算符都可以使用。注意,在重置ptr1和ptr2前,它们都指向相同的元素urn[1]。
⑧指针求差:可以计算两个指针的差值。通常,求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。例如,ptr2-ptr1得2,意思是这两个指针所指向的两个元素相隔两个int,而不是2字节。只要两个指针都指向相同的数组(或者其中一个指针指向数组后面的第一个地址),C都能保证相减运算有效,如果指向两个不同数组的指针进行求差运算,可能会得出一个值或者导致运行时错误。
⑨比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。
注意,这里的减法有两种。可以用一个指针减去另一个指针得到一个整数,或者用一个指针减去一个整数得到另一个指针。在递增或递减指针时还要注意一些问题。编译器不会检查指针是否仍指向数组元素。C只能保证指向数组任意元素的指针和指向数组后面第1个位置的指针有效。但是,如果递增或递减一个指针后超出了这个范围,则是未
定义的。另外,可以解引用指向数组任意元素的指针。但是,即使指针指向数组后面一个位置是有效的,也能解引用这样的越界指针。
参考 《C Primer Plus》

上一篇:ORB-SLAM2之LocalMapping线程——代码篇


下一篇:重学C++程序设计(四):今天还是巩固指针与面向对象程序设计!