@xudongh
2016-10-27T15:03:52.000000Z
字数 4180
阅读 2055
计算机数组与指针
#include <stdio.h>int main(){int arr[3]={1,2,3};int * toarr1,* toarr2;toarr1 = &arr[0];toarr2 = toarr1+1;printf("%p\n",&arr[0]);printf("%p\n",toarr1);printf("%p\n",toarr2);printf("%d,%d\n",*toarr1,*toarr2);printf("%d",*arr);return 0;}
输出为
0x7fff5fbff73c0x7fff5fbff73c0x7fff5fbff7401,21
1.数组名是数组首元素的首地址。也就是说,如果flinzny是一个数组,下面的语句成立:
flizny == &flizny[0];
flizny和&flizny[0]都表示数组首元素的内存地址,两者都是常量,在程序的运行过程中,不会改变。
2.在C中,指针加1指的是增加一个存储单元。对于数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。指针加1,指针的值递增它所指向类型的大小(以字节为单位)。
3.
dates + 2 == &date[2]; //相同的地址*(dates + 2) == date[2]; //相同的值
4.实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是*(ar+n)。可以认为*(ar+n)的意思是“到内存的ar未知,然后移动n个单元,检索储存在那里的值”。
5.不要混淆*(dates + 2)和*date + 2。间接运算符(*)的优先级高于+,所以*date + 2相当于(*dates) + 2:
*(dates + 2) //dates第3个元素的值*dates + 2 //dates第1个元素的值加2
6.因为数组名是该数组元素的地址,作为实际参数的数组名要求形式参数时一个与之匹配的指针。只有在这种情况下,C才会把int ar[]和int * ar解释成一样。也就是说,ar是指向int的指针。由于函数原型可以省略参数名,所以下面4种原型都是等价的:
int sum(int *ar, int n);int sum(int *, int);int sum(int ar[], int n);int sum(int [], int);
但是,在函数定义中不能省略参数名。下面两种形式的函数定义等价:
int sum(int *ar, int n);{//其他代码省略}int sum(int ar[], int n);{//其他代码省略}
7.
int main(void){int arr[5] = {1, 2, 3, 4, 5 };total = sump(arr,arr + 5);return 0;}int sump(int * start, int * end){//其他代码省略}
循环最后处理的一个元素是end所指向位置的前一个元素,这意味着end指向的位置实际上在数组最后一个元素的后面。
C保证在给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。这使得如end的指针是有效的。使用这种“越界”指针的函数调用更为简洁。
注意:虽然C保证了arr + 5有效,但是对arr[5]未作任何保证,所以程序不能访问该位置。
8.千万不要解引用未初始化的指针。
int * pt; //未初始化的指针*pt = 5; //严重的错误
第2行的意思是把5储存在pt指向的位置。但是pt未被初始化,其值是一个随机值,所以不知道5将储存在何处。这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃。
切记:创建一个指针是,系统只分配了储存指针本身的内存,并未分配储存数据的内存。因此,在使用指针之前,必须先用已分配的地址初始化它。
9.当把数组传递给函数时,传递的是该数组的指针,因此,函数可以通过指针来改变原数组的值。为了避免这种情况出现,可在函数原型和函数定义中声明形式参数时使用关键字const。
int sum(const int ar[], int n); //函数原型int sum(const int ar[], int n); //函数定义{其他代码省略}
这样使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可更改。
10.指向const的指针不能用于改变值。
int rates[5] = {5, 9, 3, 11, 7};const int * pd = rates; //pd指向数组的首元素*pd = 15; //不允许pd[2] = 6; //不允许rates[0] = 15; //允许,因为rates未被const限定
无论是使用指针表示法还是数组表示法,都不允许使用pd修改它所指向数据的值。但是要注意,因为数组rates并未被声明为const,所以仍然可以通过rates修改元素的值。
11.可以声明并初始化一个不能指向别处的指针。
int rates[5] = {5, 9, 3, 11, 7};int * const pc = rates; //pd指向数组的首元素pc = &rate[2]; //不允许,因为该指针不能指向别处*pc = 15; //没问题
12.在创建指针是还可以使用const两次,该指针既不能改变它所指向的地址,也不能修改指向地址上的值:
int rates[5] = {5, 9, 3, 11, 7};int * const pc = rates;pc = &rate[2]; //不允许*pc = 15; //不允许
13.
把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的;
但是,只能把非const数据的指针赋给普通指针。
这个规则很合理,否则,通过指针就能改变const数组中的数据。
14.对于多维数组:
int arr[4]{2] ={{2, 4},{6, 8},{1, 3},{5, 7}};//以下关系成立:arr == &arr[0];arr[0] == &arr[0][0];*arr == arr[0];*arr == &arr[0][0];
arr[0]是一个占用一个int大小对象的地址,而arr是一个占用两个int大小对象的地址。arr会增加两个int字节的大小,而arr[0]会增加一个int字节的大小。arr + 1和arr[0] + 1的值是不用的。arr是地址的地址,必须解引用两次才能获得原始值。地址的地址或指针的指针就是双重间接的例子。15.与arr[2][1]等价的指针表示法是*(*(arr + 2) + 1)。
arr //二维数组首元素的地址arr + 2 //二维数组的第3个元素的地址*(arr + 2) //二维数组的第3个元素的首元素地址*(arr + 2) + 1 //二维数组的第3个元素的第2个元地址*(*(arr + 2) + 1) //二维数组的第3个元素的第2个元地址的值
16.声明指向多维数组的指针:
int (* pz)[2];
虽然pz是一个指针,不是数组名,但是也可以使用pz[2][1]这样的写法。可以用数组表示法或指针表示法来表示一个数组元素,既可以使用数组名, 也可以使用指针名:
arr[m][n] == *(*(arr + m) + n)pz[m][n] == *(*(pz + m) + n)
17.编写二维数组的函数,写出声明函数形参的方法:
①利用for循环把处理一维数组的函数应用到二维数组的每一行。
int junk[3][4] = { {2,4,5,8}, {3,5,6,9}, {12,10,8,6} };int i, j;int total = 0;for (i = 0; i<3 ; i++)total += sum(junk[i], 4);
如果junk是二维数组,junk[i]就是一维数组,可将其视为二维数组的一行。
②junk是一个指向数组的指针,可以这样声明函数的形参:
void somefunction( int (* pt)[4]);
另外,如果当且仅当pt是一个函数的形式参数时,可以这样声明:
void somefunction( int pt[][4]);
例如
int sum(int ar[][4], int rows){int r;int c;int tot = 0;for (r = 0; r < rows; r++)for (c = 0; c < 4; c++)tot += ar[r][c];return tot;}
列数内置在函数体中,但是行数靠函数传递得到,如果传入函数的行数是12,那么函数要处理的是12*4的数组。rows是元素的个数,每个元素都是数组,或者视为一行。
注意:下面的声明不正确:
int sum(int ar[][], int rows); //错误的声明
编译器会把数组表示法转换成指针表示法。例如,编译器会把ar[1]转换成ar+1。编译器对ar+1求值,要知道ar所指向的对象大小。下面的声明:
int sum(int ar[][4], int rows); //有效的声明
表示ar指向一个内含4个int类型值的数组(假设ar指向的对象占16字节),所以ar+1的意思是“该地址加上16字节”,如果第2对括号是空的,编译器就不知道该怎样处理。
也可以在第1对方括号中写上大小,但是编译器会忽略该值:
int sum(int ar[3][4], int rows); //有效的声明,但是3将被忽略
一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:
int sum(int ar[][12[20][30], int rows]);
因为第1对方括号只用于表明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。
下面的声明与该声明等价:
int sum(int (*ar)[12][20][30], int rows); //ar是一个指针
这里,ar指向一个12*20*30的int数组。
18.变长数组(VLA)中的“变”不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。这里的“变”指的是:在创建数组是,可以使用变量指定数组的维度。
声明一个带二维变长数组参数的函数:
int sum(int rows, int cols, int ar[rows][cols]); //ar是一个变长数组(VLA)
因为ar的声明要使用rows和cols,所以在形参列表中必须在声明ar之前先声明者两个参数。
注意,在函数定义的形参列表中声明的变长数组并未实际创建数组。和传统的语法类似,变长数组名实际上是一个指针。这说明变长数组形参的函数实际上是原始数组中处理数组,因此会修改传入的数组。