@xudongh
2016-10-27T23:03:52.000000Z
字数 4180
阅读 1847
计算机
数组与指针
#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;
}
输出为
0x7fff5fbff73c
0x7fff5fbff73c
0x7fff5fbff740
1,2
1
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
之前先声明者两个参数。
注意,在函数定义的形参列表中声明的变长数组并未实际创建数组。和传统的语法类似,变长数组名实际上是一个指针。这说明变长数组形参的函数实际上是原始数组中处理数组,因此会修改传入的数组。