[关闭]
@xudongh 2016-10-27T23:03:52.000000Z 字数 4180 阅读 1847

数组与指针

计算机

数组与指针

  1. #include <stdio.h>
  2. int main(){
  3. int arr[3]={1,2,3};
  4. int * toarr1,* toarr2;
  5. toarr1 = &arr[0];
  6. toarr2 = toarr1+1;
  7. printf("%p\n",&arr[0]);
  8. printf("%p\n",toarr1);
  9. printf("%p\n",toarr2);
  10. printf("%d,%d\n",*toarr1,*toarr2);
  11. printf("%d",*arr);
  12. return 0;
  13. }

输出为

  1. 0x7fff5fbff73c
  2. 0x7fff5fbff73c
  3. 0x7fff5fbff740
  4. 1,2
  5. 1

1.数组名是数组首元素的首地址。也就是说,如果flinzny是一个数组,下面的语句成立:

  1. flizny == &flizny[0];

flizny和&flizny[0]都表示数组首元素的内存地址,两者都是常量,在程序的运行过程中,不会改变。


2.在C中,指针加1指的是增加一个存储单元。对于数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。指针加1,指针的值递增它所指向类型的大小(以字节为单位)。


3.

  1. dates + 2 == &date[2]; //相同的地址
  2. *(dates + 2) == date[2]; //相同的值

4.实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是*(ar+n)。可以认为*(ar+n)的意思是“到内存的ar未知,然后移动n个单元,检索储存在那里的值”。


5.不要混淆*(dates + 2)*date + 2。间接运算符(*)的优先级高于+,所以*date + 2相当于(*dates) + 2:

  1. *(dates + 2) //dates第3个元素的值
  2. *dates + 2 //dates第1个元素的值加2

6.因为数组名是该数组元素的地址,作为实际参数的数组名要求形式参数时一个与之匹配的指针。只有在这种情况下,C才会把int ar[]int * ar解释成一样。也就是说,ar是指向int的指针。由于函数原型可以省略参数名,所以下面4种原型都是等价的:

  1. int sum(int *ar, int n);
  2. int sum(int *, int);
  3. int sum(int ar[], int n);
  4. int sum(int [], int);

但是,在函数定义中不能省略参数名。下面两种形式的函数定义等价:

  1. int sum(int *ar, int n);
  2. {
  3. //其他代码省略
  4. }
  5. int sum(int ar[], int n);
  6. {
  7. //其他代码省略
  8. }

7.

  1. int main(void)
  2. {
  3. int arr[5] = {1, 2, 3, 4, 5 };
  4. total = sump(arr,arr + 5);
  5. return 0;
  6. }
  7. int sump(int * start, int * end)
  8. {
  9. //其他代码省略
  10. }

循环最后处理的一个元素是end所指向位置的前一个元素,这意味着end指向的位置实际上在数组最后一个元素的后面。
C保证在给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。这使得如end的指针是有效的。使用这种“越界”指针的函数调用更为简洁。
注意:虽然C保证了arr + 5有效,但是对arr[5]未作任何保证,所以程序不能访问该位置。


8.千万不要解引用未初始化的指针。

  1. int * pt; //未初始化的指针
  2. *pt = 5; //严重的错误

第2行的意思是把5储存在pt指向的位置。但是pt未被初始化,其值是一个随机值,所以不知道5将储存在何处。这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃。
切记:创建一个指针是,系统只分配了储存指针本身的内存,并未分配储存数据的内存。因此,在使用指针之前,必须先用已分配的地址初始化它。


9.当把数组传递给函数时,传递的是该数组的指针,因此,函数可以通过指针来改变原数组的值。为了避免这种情况出现,可在函数原型和函数定义中声明形式参数时使用关键字const

  1. int sum(const int ar[], int n); //函数原型
  2. int sum(const int ar[], int n); //函数定义
  3. {
  4. 其他代码省略
  5. }

这样使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可更改。


10.指向const的指针不能用于改变值。

  1. int rates[5] = {5, 9, 3, 11, 7};
  2. const int * pd = rates; //pd指向数组的首元素
  3. *pd = 15; //不允许
  4. pd[2] = 6; //不允许
  5. rates[0] = 15; //允许,因为rates未被const限定

无论是使用指针表示法还是数组表示法,都不允许使用pd修改它所指向数据的值。但是要注意,因为数组rates并未被声明为const,所以仍然可以通过rates修改元素的值。


11.可以声明并初始化一个不能指向别处的指针。

  1. int rates[5] = {5, 9, 3, 11, 7};
  2. int * const pc = rates; //pd指向数组的首元素
  3. pc = &rate[2]; //不允许,因为该指针不能指向别处
  4. *pc = 15; //没问题

12.在创建指针是还可以使用const两次,该指针既不能改变它所指向的地址,也不能修改指向地址上的值:

  1. int rates[5] = {5, 9, 3, 11, 7};
  2. int * const pc = rates;
  3. pc = &rate[2]; //不允许
  4. *pc = 15; //不允许

13.
const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的;
但是,只能把非const数据的指针赋给普通指针。
未标题-1.png-18kB
这个规则很合理,否则,通过指针就能改变const数组中的数据。


14.对于多维数组:

  1. int arr[4]{2] =
  2. {
  3. {2, 4},
  4. {6, 8},
  5. {1, 3},
  6. {5, 7}
  7. };
  8. //以下关系成立:
  9. arr == &arr[0];
  10. arr[0] == &arr[0][0];
  11. *arr == arr[0];
  12. *arr == &arr[0][0];

15.与arr[2][1]等价的指针表示法是*(*(arr + 2) + 1)

  1. arr //二维数组首元素的地址
  2. arr + 2 //二维数组的第3个元素的地址
  3. *(arr + 2) //二维数组的第3个元素的首元素地址
  4. *(arr + 2) + 1 //二维数组的第3个元素的第2个元地址
  5. *(*(arr + 2) + 1) //二维数组的第3个元素的第2个元地址的值

16.声明指向多维数组的指针:

  1. int (* pz)[2];

虽然pz是一个指针,不是数组名,但是也可以使用pz[2][1]这样的写法。可以用数组表示法或指针表示法来表示一个数组元素,既可以使用数组名, 也可以使用指针名:

  1. arr[m][n] == *(*(arr + m) + n)
  2. pz[m][n] == *(*(pz + m) + n)

17.编写二维数组的函数,写出声明函数形参的方法:
①利用for循环把处理一维数组的函数应用到二维数组的每一行。

  1. int junk[3][4] = { {2,4,5,8}, {3,5,6,9}, {12,10,8,6} };
  2. int i, j;
  3. int total = 0;
  4. for (i = 0; i<3 ; i++)
  5. total += sum(junk[i], 4);

如果junk是二维数组,junk[i]就是一维数组,可将其视为二维数组的一行。

junk是一个指向数组的指针,可以这样声明函数的形参:

  1. void somefunction( int (* pt)[4]);

另外,如果当且仅当pt是一个函数的形式参数时,可以这样声明:

  1. void somefunction( int pt[][4]);

例如

  1. int sum(int ar[][4], int rows)
  2. {
  3. int r;
  4. int c;
  5. int tot = 0;
  6. for (r = 0; r < rows; r++)
  7. for (c = 0; c < 4; c++)
  8. tot += ar[r][c];
  9. return tot;
  10. }

列数内置在函数体中,但是行数靠函数传递得到,如果传入函数的行数是12,那么函数要处理的是12*4的数组。rows是元素的个数,每个元素都是数组,或者视为一行。

注意:下面的声明不正确:

  1. int sum(int ar[][], int rows); //错误的声明

编译器会把数组表示法转换成指针表示法。例如,编译器会把ar[1]转换成ar+1。编译器对ar+1求值,要知道ar所指向的对象大小。下面的声明:

  1. int sum(int ar[][4], int rows); //有效的声明

表示ar指向一个内含4个int类型值的数组(假设ar指向的对象占16字节),所以ar+1的意思是“该地址加上16字节”,如果第2对括号是空的,编译器就不知道该怎样处理。
也可以在第1对方括号中写上大小,但是编译器会忽略该值:

  1. int sum(int ar[3][4], int rows); //有效的声明,但是3将被忽略

一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:

  1. int sum(int ar[][12[20][30], int rows]);

因为第1对方括号只用于表明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。
下面的声明与该声明等价:

  1. int sum(int (*ar)[12][20][30], int rows); //ar是一个指针

这里,ar指向一个12*20*30的int数组。


18.变长数组(VLA)中的“变”不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。这里的“变”指的是:在创建数组是,可以使用变量指定数组的维度。
声明一个带二维变长数组参数的函数:

  1. int sum(int rows, int cols, int ar[rows][cols]); //ar是一个变长数组(VLA)

因为ar的声明要使用rowscols,所以在形参列表中必须在声明ar之前先声明者两个参数。

注意,在函数定义的形参列表中声明的变长数组并未实际创建数组。和传统的语法类似,变长数组名实际上是一个指针。这说明变长数组形参的函数实际上是原始数组中处理数组,因此会修改传入的数组。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注