数组和指针的理解
1.1数组
1.1.1、数组定义
一组具有同名的同属性的数据就组成了一个数组(array)
由此可以知道:
1、数组是一组有序数据集合。数组中个数据的排列具有一定的规律,下标代表数据在数组中的序号。
int a[10];
方括号[ ]表明a是数组,方括号中的数字表明数组中的元素个数。
2、数组中每一个元素都属于同一个数据类型,数组a中的元素都是int类型。
3、要访问数组中的元素,通过使用数组下标数(也称为索引)表示数组中的各元素。数组元素的编号从0开始,所以a[0]表示a数组的第1个元素,a[9]表示第10个元素,也就是最后一个元素。
1.1.2、初始化数组
对于单个变量的初始化已经很熟悉
int a = 5;
这就是对变量a的初始化。
那么我们就会发现初始化一个数组可以用
int a[10] = {0,1,2,3,4,5,6,7,8,9};
如上所示,用以逗号分隔的值列表(用花括号括起来)来初始化数组,各值之间用逗号分隔.
那么a[0] = 0;a [1] = 1;…;
如果要想改变某一个值的话比如说要使第一个元素变为10;可以使用a [ 0 ] = 10;
如果不想改变数组的值就要用const 例如 :
const int a[10] = {0,1,2,3,4,5,6,7,8,9};
这样修改后,程序在运行过程中就不能修改该数组中的内容。
int a[6] = {0,0,0,0,0,1}; // 传统的语法
//而C99规定,可以在初始化列表中使用带方括号的下标指明待初始化的元素:
int a[6] = {[5] = 1}; // 把a[5]初始化为1;
1.1.3、数组的边界
在使用数组时,要防止数组下标超出边界。也就是说,必须确保下标是有效的值。例如,假设有下面的声明:
int a [10];
那么在使用该数组时,要确保程序中使用的数组下标在0~9的范围内,因为编译器不会检查出这种错误。
记住一点:数组元素的编号从0开始。
1.1.4、指定数组的大小
1.1.4.1使用常量
#define SIZE 4
int main(void)
{
int a[SIZE]; // 整数符号常量
double b[10]; // 整数字面常量
}
1.1.4.2 C99引入变长数组
int n = 5;
int m = 8;
float a1[5]; // 可以
float a2[5*2 + 1]; //可以
float a3[sizeof(int) + 1]; //可以
float a4[-4]; // 不可以,数组大小必须大于0
float a5[0]; // 不可以,数组大小必须大于0
float a6[2.5]; // 不可以,数组大小必须是整数
float a7[(int)2.5]; // 可以,已被强制转换为整型常量
float a8[n]; // C99之前不允许
float a9[m]; // C99之前不允许
1.2、一维数组和指针
1.2.1一维数组和指针的关系
首先思考一下什么指针?指针(pointer)是一个值为内存地址
的变量(或数据对象)。
数组和指针又有什么关系呢?首先数组的名字就是数组的基地址;
还是上面的例子:
int a [5] = {1,2,3,4,5};
a == &a[0]; // 数组名是该数组首元素的地址数组名是该数组首元素的地址a 和&a[0]都表示数组首元素的内存地址(&是地址运算符)。两者都是常量,在程序的运行过程中,不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值.
//定义一个int类型的指针
int *p;
//初始化指针
p = &a[0];
#include<stdio.h>
int main()
{
int a[5];
int *ptr;
int index;
double b[5];
double *ptf;
ptr = a;//数组地址付给指针变量
ptf = b;
printf("%23s %15s\n","int","double") ;
for(index = 0;index<5;index++)
{
printf("pointers + %d: % 10p %10p\n",index,ptr+index,ptf+index);
}
return 0;
}
第2行打印的是两个数组开始的地址,下一行打印的是指针加1后的地址,以此类推。注意,地址是十六进制的,显示的地址是怎么回事?
000000000022FE20+1 是否是 000000000022FE24
000000000022FDF0+1是否是 000000000022FDF8
系统中,地址按字节编址,在C中,指针加1指的是增加一个存储单元。对数组而言,这意味着把加1后的地址是下一个元素的地址,而不是下一个字节的地址。这是为什么必须声明指针所指向对象类型的原因之一。只知道地址不够,因为计算机要知道储存对象需要多少字节(即使指针指向的是标量变量,也要知道变量的类型,否则*pt 就无法正确地取回地址上的值.)
C Primer Plus
a + 2 == &a[2] // 相同的地址
*(a + 2) == a[2] // 相同的值
*a+2 //思考一下这个表示什么
*(a+2)//这两个一样吗?
以上关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素的值。从本质上看,同一个对象有两种表示法。实际上,C语言标准在描述数组表示法时确实借助了指针。也就是说,定义a[n]的意思是*(a+ n)。可以认为*(a+ n)的意思是“到内存的a位置,然后移动n个单元,检索储存在那里的值”。
C Primer Plus
2.1二维数组
2.1.1定义
int a[10][5];//内含10个数组元素的数组,每个数组元素内含5个int类型的元素
理解该声明的一种方法是,先看10:
int a[10][5];//a是内含10个元素的数组.
这说明数组a有10个元素,至于每个元素的情况,要查看声明的其余部分(5):
int a[10][5];//内含5个int元素的数组.
这说明每个元素的类型是a[5],也就是说,a的每个元素本身都是一个内含5个int类型值的数组。
根据以上分析可知,a的首元素a[0]是一个内含5个int类型值的数组。所以,a[1]、a[2]等也是如此。如果 a[0]是一个数组,那么它的首元素就是 a[0][0],第 2 个元素是a[0][1],以此类推。简而言之,数组a有10个元素,每个元素都是内含5个int类型元素的组,a[0]是内含5个int值的数组,a[0][0]是一个int类型的值。假设要访问位于2行3列的值,则使用a[1][2](记住,数组元素的编号从0开始,所以2行指的是第3行)。
2.1.2 初始化
初始化二维数组是建立在初始化一维数组的基础上。首先,初始化一维数组如下:
sometype ar1[5] = {val1, val2, val3, val4, val5};
这里,val1、val2等表示sometype类型的值。例如,如果sometype是int,那么val1可能是7;如果sometype是double,那么val1可能是11.34,诸如此类.
但是a是一个内含10个元素的数组,每个元素又是内含5个int类型元素的数组。所以,对a而言,val1应该包含5个值,用于初始化内含5个int类型元素的一维数组,如下所示:
{1,2,3,4,5}
也就是说,如果sometype是一个内含5个int类型元素的数组,那么val1就是一个由5个int类型值构成的数值列表。因此,为了初始化二维数组a,要用逗号分隔10个这样的数值列表:
int a [10][5] = {
{1,2,3,4,5},
{6,7,8,9,10},
{1,2,3,4,5},
{2,3,4,5,6},
{3,4,5,6,7},
{4,5,6,7,8},
{5,6,7,8,9},
{6,7,8,9,10},
{7,8,9,10,11},
{8,9,10,11,12};
}
这个初始化使用了10个数值列表,每个数值列表都用花括号括起来。第1个列表的数据用于初始化数组的第1行,第2个列表的数据用于初始化数组的第2行,以此类推。前面讨论的数据个数和数组大小不匹配的问题同样适用于这里的每一行。也就是说,如果第1个列表中只有5个数,则只会初始化数组第1行的前5个元素,而最后两个元素将被默认初始化为0。如果某列表中的数值个数超出了数组每行的元素个数,则会出错,但是这并不会影响其他行的初始化。
2.2 二维数组和指针
看一下C Primer Plus 里的例子
int zippo[4][2]; /* 内含int数组的数组 */
然后数组名zippo是该数组首元素的地址。在本例中,zippo的首元素是一个内含两个int值的数组,所以zippo是这个内含两个int值的数组的地址。下面,我们从指针的属性进一步分析。
因为zippo是数组首元素的地址,所以zippo的值和&zippo【0】的值相同。而**zippo[0]**本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素(一个整数)的地址(即&zippo[0][0]的值)相同。简而言之,zippo[0]是一个占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以zippo和zippo[0]的值相同。
给指针或地址加1,其值会增加对应类型大小的数值。在这方面,zippo和zippo[0]不同,因为zippo指向的对象占用了两个int大小,而zippo[0]指向的对象只占用一个int大小。因此, zippo + 1和zippo[0] + 1的值不同。
有上面的图估计很好理解下面的例子
2.3解引用
2.3.1使用行指针
来看一下这个例子
将这个数组看成2个“int【3】” 类型的数据;
1、a包含两个元素,a[0],a[1];
2、a[0],a[1]又分别是一个一维数组,包含三个元素。
所以定义一个int【3】类型的指针;
int (*p)[3];
以上代码把p声明为指向一个数组的指针,该数组内含三个int类型值。为什么要在声明中使用圆括号?因为[]的优先级高于*。
接着想一下二维数组的数组名字是什么? 就是数组的地址;
那么a就是一个包含两个元素a[0],a[1];的首地址.
接着就需要初始化指针P;
p = a;
看一下代码
#include<stdio.h>
int main(void)
{
int a[2][3]={{1,3,5},{2,4,6}};
int(*p)[3];
p = a;
printf(" p = %p, p+1 = %p\n",p,p + 1);
printf(" p[0] = %p, p[0]+1 = %p\n",p[0],p[0]+1);
printf(" *p = %p, *p + 1 = %p\n",*p,*p + 1);
printf(" p[0][0] = %d\n",p[0][0]);
printf(" *p[0] = %d\n", *p[0]);
printf(" **p = %d\n", **p);
printf(" p[1][1] = %d\n",p[1][1]);
printf(" *(*(p+1) + 1) = %d\n", *(*(p + 1) + 1));
return 0;
}
看一下程序的输出:
我们发现竟然可以用p[0][0]来索引一个值,这就和之前我们所的数组名就是地址对应起来了。
虽然p是一个指针,不是数组名,但是也可以使用 p[1][1]这样的写法。可以用数组表示法或指针表示法来表示一个数组元素,既可以使用数组名,也可以使用指针名:
a[m][n] ==* ( *(a + m) + n);//记住不能给a 赋值;
p[m][n] == * ( *(pz + m) + n);
2.3.2使用列指针
同时还可以把二维数组a看成一维数组,他有6个int类型的值;
看一下图可以更好地理解一下;
若让一个指针指向他,则应该定义一个int类型的指针,因为我们把数组看成6个int类型的值
再来回想一下:a是二维数组的名字它包含两个元素每个元素都有3个int类型的值,所以a是一个地址,那么a的值是他首元素的地址,a的首元素是a[0];所以a = &a[0];
a[0]又是一个数组包含三个int类型的值,那么a[0]的值又是一个地址,a[0]=&a[0][0];
int *p;
p = a[0]//*(*a+0)+0或者&a[0][0]指向第0行0列的元素
那么访问a[i][j]该怎样访问呢?
*(p+i*n+j)//(i*n)表明经过了几行,在哪一列
//想一下指针和数组名一样是不是还可以这样访问
p[n*i+j];