C语言中数组与指针
初识数组
1.一维数组
a.声明一个一维数组
int a[] = {12,12,3,12,3};
// 等价于
int a[5] = {12,12,3,12,3};
特别注意
使用{}
给数组赋值时,只能在数组被声明的时候使用,因为c语言在创建数组的时候数组的地址是不能被改变的,下面的例子都是错误。
// 错误
int a[10];
a = {12,23,2,3};
// 错误
int a[] = {123,23,,23};
a = {123};
// 错误
int a[10] = {12};
a=NULL;
如果想让数组里的值全为0,可以使用int a[10] = {0}
。
在声明数组时,特别注意,如果赋的值数量大于了数组的长度,那么编译器会编译不通过,如果少于数组长度,那么其余的值会被赋值为0。
当然,除了上面这样的初始化数组,我们还可以使用指定位置初始化数组。
int a[] = {12,123,[4]= 2}; // 12,123,0,0,2
int b[] = {[2] = 23,[4] =22}; // 0,0,23,0,22
printf("%d",sizeof(a)/sizeof(a[0])); // 5
printf("%d",sizeof(b)/sizeof(b[0])); // 5
通过这种方式,我们可以给数组的指定位置赋值,其余位置会自定赋值为0
.
2.二维数组
和一维数组差不多,但是这里有一个很有意思的操作,当然一位数组也可以,但是二维数组迷惑性更强。
int a[4][2] = {(1,2),(2,1),(2,3),(12,23)};
其实这里只需要注意一下这个逗号运算符,实际上就是int a[4][2] = {2,1,3,23}
.
一.初识指针
在 c语言中指针可以通俗的理解为保存地址的变量,如下就是一个简单的例子。
#include<stdio.h>
int main(){
int a = 10;
// 这就是一个简单的指针
int *p = &a;
return 0;
}
通过上述的例子我们定义了一个简单的指针,通过定于的指针,我们通过 &
将变量 a
的地址保存到指针 p
中,也可以说是指针 p
指向了 a
,当然,我们也可以输出 a
的值.
#include<stdio.h>
int main(){
int a = 10;
// 这就是一个简单的指针
int *p = &a;
printf("a = %d",*p);
return 0;
}
通过解引号 *
获取指针 p
所指向的 a
的地址.
二.指向数组的指针
因为指针可以保存地址,自然而然,我们当然也可以将指针指向数组,当然根据不同的情况可以分为如下的两种情况。
1.指向数组第一个元素的指针
#include<stdio.h>
int main(){
int a[] = {1,2,3};
int *p = a;
}
可以看到上述的例子,因为数组名本身就代表着数组第一个元素的地址。
int *p = &a[0];
这也是完全可以的。
但是要注意,虽然你使用如下的方式也是可以的,但是它们所代表的意义是不同的 &a
返回的是整个数组的地址,它的类型是 int (*)[]
。
int *p = &a;
那么要如何使用指针变量呢,其实和数组类似。
#include<stdio.h>
int main(){
int a[] = {1,2,3};
int *p = a;
printf("*p = %d",*p);
printf("*p = %d",*(p+1));
printf("*p = %d\n",*(p+2));
printf("p[0] = %d",p[0]);
printf("p[1] = %d",p[1]);
printf("p[2] = %d",p[2]);
return 0;
}
可能你发现了,为什么可以 p[下标]
的方式来取值,但是实际上 p[i]
是数组下标运算的语法糖,编译器会将其转化为指针运算 *(p + i)
。
例如,p[1]被编译器转化为*(p + 1),两者在执行时的行为完全相同。
2.数组类型的指针
接下来我们看一下如何定义一个数组类型的指针。
int a = {1,2,3};
int (*pa)[] = &a;
这里需要注意几点:
- 因为
[]
的优先级高于*
,如果不使用括号的话,那么意义将完全不一样,int *pa[]
它代表的是一个元素为指针的数组。 - 这里因为我们定义的是一个数组类型的指针,所以我们这里必须使用
&a
,而不能是a
。
那么该如何使用呢?
printf("a[1] = %d",(*pa)[1]);
printf("a[1] = %d",*((*pa)+1));
如何理解呢,因为我们知道 p[i]
是数组下标运算的语法糖,编译器会将其转化为指针运算 *(p + i)
。所以在这里 (*pa)[1]
和 *((*pa)+1)
实际上是一样的效果。
三.元素是指针的数组
既然数组可以存放数组,地址也是一个特所的数字,自然,我们也可以通过数组来存放地址,当然这可能会和普通的 数组不一样。
int a=1,b =2;
int *arr[] = {&a,&b};
如上就是一个简单的元素是指针的数组,因为元素是地址,我们可以这样来输出 a
的值。
printf("a = %d",*(*arr));
四.指向元素是指针的数组类型指针
代码如下如下。
#include<stdio.h>
int main(){
int a=10,b=20,c=30;
int arr = {&a,&b,&c};
// 指向一个元素是地址的数组类型的指针
int * (*pa)[] = &arr;
return 0;
}
我们可以通过
printf("arr[0] = %d",*(*(*pa)));
printf("arr[0]= %d",*((*pa)[0]));
但是我们也发现但我们运行如下的代码时发现其实输出的地址都是一样的,都是 'arr' 这个数组的地址,但是为什么在取值时是 *(*(*pa))
而不是 *(*p)
.虽然这里的两种方式输出的地址是一样的,但是还是有区别的.
pa
指向的是一个数组的指针。*pa
代表的是这个指针数组本身,因此可以使用(*pa)[0]
来取得数组中的值。
printf("pa = %d\n",pa);
printf("*pa = %d\n",*pa);
五.二维数组
1.什么是二维数组
二维数组实际上是一维数组,在内存中和一维数组一样,都是按照顺序排列的,说二维只是我们人为的规定。
定义一个二维数组
int arr[2][2] = {1,2,1,3};
int arr[][2] = {{1,2},{1,3}};
int arr[2][] = {{1,2},{1,3}};
2.二维数组指针
通过前面,我们知道,其实二维数组其实就是一维数组构成的,所以我们在创建指针的实际上创建的其实是一维数组的指针。
a.行指针
int (*p)[2] = arr;
int (*p)[2] = &arr;
int (*p)[2] = &arr[0];
以上这几种声明方式都是可以的,虽然都可以实现功能,但是他们代表的含义还是不同的
arr
and&arr[0]
代表的是arr这个数组的首行。&arr
代表的是整个arr
数组,其类型为int (*)[2][2]
,在我们将二维指针p
指向它时,其实这里发生了隐式类型转换。也就是int (*p)[2] = (int (*)[2])&arr[0];
。
第一种输出方式 (下标)
for (int i = 0;i<2;i++){
for(int j = 0;j<2;j++){
printf("arr[%d][%d]",i,j,p[i][j]);
}
printf("\n");
}
第二种方式(指针)
for (int i = 0;i<2;i++){
for(int j = 0;j<2;j++){
printf("arr[%d][%d]",i,j,(*p+1)[j]);
}
printf("\n");
}
通过前面的学习,其实我们知道下标取值语法其实就是语法糖,如上述的操作 (*p+1)[j]
其实就是 *(*(p+1)+j)
。
b.列指针
由于列指针是指向二维数组的元素的,如下图:
因此我们可以使用普通的指针类型定义它。
int *p = arr[0]; // 第一行的地址
int *p = *arr; // 解引用出第一行的地址
int *p = &arr[0][0]; // a[0][0] 的地址
由于现在我们是把它当作一维数组看待,所以不能再使用[][],而是一维数组的方式。
for (int i=0;i<2;i++){
for (int j = 0;j<2;j++){
printf("%d ",p[i*m+j]);
}
}
同上,上面的代码也可以写为如下的形式。
for(int i=0;i<2;i++){
for(int j = 0;j<2;j++){
printf("%d",*(p+i*m+j));
}
}
六.总结
总的来说,其实没什么好说的。