今天萌新博主来给大家介绍C语言指针中指针与数组部分,在这一块也是非常难以理解的,闲言少叙,咱们开始吧!
大家都知道数组与指针有着紧密的联系,数组是由一组若干个元素构成,我们在访问数组的时候,采用的是循环体的方式,就是把数组的下标逐步的循环一次,这样我们就可以读出所有数组元素的值。那么重点来了,数组下标与地址的之间的关系 ,如果我们掌握好,大家学起来指针与数组就会非常的吃力,萌新博主与大家也是感同身受啊!
【数组的存储方式】 数组的三个特点: 1.数组有若干个元素 2.元素的数据类型必须相同 3.数组中的数据元素是有序排列的
1 2 3 arr1[4 ]={1 ,2 ,3 ,4 } arr2[4 ]={4 ,3 ,2 ,1 }
数组元素占用的字节长度都是相等的; 指向数组的指针的初始值就是数组的首地址;
每个元素占用的字节长度为:sizeof(int);
若sizeof(int)==2,假设数组的第一个元素a[0]的地址为0x00000001,则数组的第二个元素a[1]的地址为0x00000003,则数组的第三个元素a[2]的地址为0x00000005
所以说数组中的数据元素是有序排列的,那我们在查找数组元素的时候,只需要查找到第一个元素的地址,就能按照顺序找到相应的元素了。
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main (void ) { int arr[4 ]={1 ,2 ,3 ,4 }; printf ("arr[0]=%p\n" ,&arr[0 ]); printf ("arr[1]=%p\n" ,&arr[1 ]); printf ("arr[2]=%p\n" ,&arr[2 ]); printf ("arr[3]=%p\n" ,&arr[3 ]); return 0 ; }
运行结果: 那我们再来看指针的性质:【指针与整数的运算】
1 指针加n=指针原来的值+n*sizeof (指针所指变量类型)
1 指针减n=指针原来的值-n*sizeof (指针所指变量类型)
int *p=NULL p=&arr[0] p=0x000001 p+1=0x000001+1×sizeof(int) p+2=0x000001+2×sizeof(int) p+3=0x000001+3×sizeof(int)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> int main (void ) { int arr[4 ]={1 ,2 ,3 ,4 }; int *p=NULL ; int i=0 ; p=&arr[0 ]; printf ("sizeof(int)=%d\n" ,sizeof (int )); printf ("arr[0]=%p\n" ,&arr[0 ]); printf ("arr[1]=%p\n" ,&arr[1 ]); printf ("arr[2]=%p\n" ,&arr[2 ]); printf ("arr[3]=%p\n" ,&arr[3 ]); for (i=0 ;i<=3 ;i++) { printf ("p+%d=%p\n" ,i,p+i); } }
1 2 3 4 5 6 7 8 9 10 结果: sizeof (int )=4 arr[0 ]=0019F F20 arr[1 ]=0019F F24 arr[2 ]=0019F F28 arr[3 ]=0019F F2C p+0 =0019F F20 p+1 =0019F F24 p+2 =0019F F28 p+3 =0019F F2C
我们不难发现,我们可以通过指针来访问一维数组 注意:以上对应关系的成立,指针p必须指向数组元素的首地址。
【那指针是如何访问数组的呢?】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> int main (void ) { int arr[8 ]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 }; int i=0 ; int *p=NULL ; p=arr; printf ("arr[i]\n" ); for (i=0 ;i<8 ;i++) { printf ("arr[%d]=%d\n" ,i,arr[i]); } printf ("p+i\n" ); for (i=0 ;i<8 ;i++) { printf ("p+%d=%d\n" ,i,*(p+i)); } return 0 ; }
运行结果:【访问指针的四种方法】 1.直接下标法 有一个数组a[i]
int *p=a
等价于int *p=&a[0]
2.首地址自加法*(a+i) 数组名就是首地址:a等价于p
3.指针自加法*(p+i)
4.指针下标法p[i]通过指针可以修改数组元素
1 2 3 4 5 6 7 int arr1[8 ]={省略};int arr2[8 ]={省略};int i=1 ;int *p1=arr1;int *p2=arr2;p1=p2; p++;
注意:指针可以自加,但是数组名不可以自加,如arr1++
,同时数组名也不能相互赋值,如arr1=arr2
;
【用数组访问与用指针访问的区别】 1.访问方式不同
使用数组名访问时直接访问,使用指针是间接访问; 下标从0开始的原因:因为下标的实质是地址偏移量
2.占用空间不同
系统为arr分配了“sizeof(类型)*元素个数”个字节,给指针分配了“sizeof(类型)”个字节
换句话说使用指针可以降低空间复杂度 ,我们来看个程序:
1 2 3 4 5 6 7 8 9 10 #include <stdio.h> int main (void ) { int arr[8 ]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 }; int *p=NULL ; p=arr; printf ("sizeof(arr)=%d\n" ,sizeof (arr)); printf ("sizeof(*p)=%d\n" ,sizeof (*p)); return 0 ; }
运行结果: 很明显,系统为arr分配了32个字节,给指针分配了4个字节。
【让我们来回顾二维数组】 二维数组a[i][j],具有两个下标:行下标与列下标 。 存储方式:先存储行下标,再存储列下标 即先在一个行下标内把a[0]列存满,再存a[1]列的元素。
1 2 a[0 ][0 ],a[0 ][1 ],a[0 ][2 ],a[0 ][3 ] a[1 ][0 ],a[1 ][1 ],a[1 ][2 ],a[1 ][3 ]
【二维数组arr[ ][ ]的首地址有多种表达形式】 1.将二维数组看成元素是一维数组的元素 2.数组中第0行第0列的地址,&arr[0][0] 3.由于二维数组先存储行,所以第一行的第一个元素的地址,即&a[0]也是首地址
二维数组的元素个数:i×j
1 2 3 4 arr[2 ][3 ]={(1 ,2 ,3 ),(4 ,5 ,6 )} arr[2 ][3 ]={arr1[3 ],arr2[3 ]} 二维数组的首地址:&arr[0 ][0 ],&arr[0 ] 二维数组的数组名,不是首地址
1 2 int arr[i][j];int *p=&arr[0 ][0 ];
此时p指向第一行第一个元素,即arr[0][0],而列指针是arr[i],指向第(i+1)列的元素。
不允许操作:p=arr或声明的时候使用int *p=arr(arr会指向一整行的地址)
那我们从a[0][0]跳到a[2][2],如何操作?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> int main (void ) { int a[3 ][3 ]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 }; int i=0 ; int j=0 ; for (i=0 ;i<3 ;i++) { for (j=0 ;j<3 ;j++) { printf ("a[%d][%d]=%d\t" ,i,j,a[i][j]); } printf ("\n" ); } }
运行结果: a[0][0]是第一行第一列的元素 ,p=&[0][0] a[2][2]是第三行第三列的元素
有一个指针p指向二维数组的首地址:&a[0][0] 指针的偏移量为:p+行数×行下标+列下标;
我们来看个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main (void ) { int a[3 ][3 ]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 }; int i=0 ; int j=0 ; int *p=NULL ; p=&a[0 ][0 ]; for (i=0 ;i<3 ;i++) { for (j=0 ;j<3 ;j++) { printf ("a[%d][%d]=%d\t" ,i,j,a[i][j]); } printf ("\n" ); } for (i=0 ;i<3 ;i++) { for (j=0 ;j<3 ;j++) { printf ("p+N*%d+%d\t" ,i,j,*(p+3 *i+j)); } printf ("\n" ); } printf ("p+N*i+j\n" ); }
运行结果: 于是我们得到二维数组的四种表现形式,如下图【二维数组的矩阵转置】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main (void ) { int a[3 ][3 ]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 }; int i=0 ; int j=0 ; int *p=NULL ; p=&a[0 ][0 ]; printf ("转置前:\n" ); for (i=0 ;i<3 ;i++) { for (j=0 ;j<3 ;j++) { printf ("a[%d][%d]==%d\t" ,i,j,*(p+3 *i+j)); } printf ("\n" ); } printf ("转置后:\n" ); for (j=0 ;j<3 ;j++) { for (i=0 ;i<3 ;i++) { printf ("a[%d][%d]==%d\t" ,i,j,*(p+3 *i+j)); } printf ("\n" ); } }
运行结果: 在for循环中,我们只需要把i换成j,j换成i就可以实现矩阵的转置,而不需要中间变量。
【二维数组形参的形式】 另外当我们用数组作为函数的形式参数,我们需要给函数传递一个数组首地址和一个数组长度
【将数组和数组长度分别作为形参】
1 2 void function (int array [],int size) void function (int *arry,int size)
我们再来一个例子【在一个升序数组中查找大于3的元素】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> void printarray (int *arr,int n) { int i=0 ; for (i=0 ;i<n;i++) { printf ("%4d" ,arr[i]); } printf ("\n" ); } int main (void ) { int arr[9 ]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 }; int *p=NULL ; p=arr; while (*p<3 ) { p++; } printarray(p,9 -(p-arr)); return 0 ; }
运行结果:【用二维数组形参函数来完成矩阵倒置】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> void function1 (int *p,int M,int N) { int i,j; for (i=0 ;i<M;i++) { for (j=0 ;j<M;j++) { printf ("%d" ,*(p+i*N+j)); } printf ("\n" ); } } void function2 (int *p,int M,int N) { int i,j; for (j=0 ;j<M;j++) { for (i=0 ;i<M;i++) { printf ("%d" ,*(p+i*N+j)); } printf ("\n" ); } } int main (void ) { int arr[3 ][3 ]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 }; int *p=NULL ; p=&arr[0 ][0 ]; function1(p,3 ,3 ); printf ("\n" ); function2(p,3 ,3 ); return 0 ; }
运行结果:二维数组形参的其他形式 数组首地址表示法