我们在用指针访问数组的首元素时,可以这么写:
int main()
{
int arr[10] = {0};
int* p = &arr[0];
return 0;
}
但其实数组名有着自己的独特的意义,我们来看看下面这段代码:
我们知道,如果是指针的话,其大小为4个或8个字节,那这里为什么会输出40呢?
·sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节;
可以看到,相比于"&arr[0]+1"和"arr+1"只增加了4个字节,"&arr+1"增加了40个字节,刚好是数组中所有元素的大小,这充分说明了&arr取出的是整个数组的大小。
这是我们通常遍历和打印数组元素的方式:
其实我们还可以用指针进行访问:
int main()
{
int arr[10] = { 0 };
int* p = arr;
for (int i = 0; i < 10; i++)
{
scanf("%d", p + i);
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(arr + i));
}
也能这么表示:
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
所以我们可以得出结论:arr[i]=*(p+i)=*(arr+i)=p[i]。其实arr[i]这种写法,编译器在执行时,也会转换成*(arr+i)的方式执行,可以理解为数组的底层逻辑时指针。
在这里,我们要理解一个重要的关系,就是:
这有助于我们后续加深对指针的理解.
其实对数组的操作还有一个方式:
没错,我们可以将平常的arr[i]改写为i[arr],因为[ ]只不过是一种操作符,编译器运行的底层逻辑还是将arr[i]或者i[arr]转换成*(arr+i)或*(i+arr)进行操作。这种写法不被推荐,但有助于我们理解底层逻辑。
我们先来看看下面这段代码并猜测其运行结果:
void test(int arr[10])
{
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("sz2=%d\n", sz2);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8.9,10 };
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1=%d\n", sz1);
test(arr);
return 0;
}
按照常理,这不就是打印数组的长度吗?只不过一个在函数内完成,一个在主函数内完成,我们来看看结果:
我们由此可以得出以下结论:
1.函数的形参部分是不会真实创建一个和实参等大的数组的,那么我们在形参部分可以不写数组的大小;
2.函数形参部分应当使用指针变量来接收数组
传参(也能写成数组的形式,可以方便我们理解);
3.当我们想通过函数遍历数组的时候,可以把数组的长度作为一个整形变量单独传参。
int main()
{
int a = 10;
int* p = &a;
return 0;
}
int main()
{
int a = 10;
int* p = &a;//一级指针变量p,存放的是a的地址
int** p2 = &p;//二级指针变量p2,存放的是指针p的地址
return 0;
}
可以看到,二级指针里面是有两个“*”号的关于这两个“*”号,我们该怎么去理解呢?我们可以将二级指针这么写:
int* *p2;
"int*"是该指针要指向的数据的类型,即指向一个整形指针变量;而p2前面的那个“*”起到表示这是一个指针的作用。这样二级指针就和一级指针一样好理解了,三级或者更高级指针的理解方式与这个相同。
那么我们对二级指针的解引用就不难理解了:
int main()
{
int a = 0;
int b = 5;
int c = 6;
int* arr[] = { &a,&b,&c };//指针数组
return 0;
}
我们也可以想使用正常的数组一样去使用指针数组:
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
// int* int* int*
int* arr[] = { arr1,arr2,arr3 };
return 0;
}
我们也可以利用这个数组遍历打印二维数组的元素:
根据前文“数组名的理解”中的知识,我们知道此处的arr[i][j]可以视作*(*(arr+i)+j) ,其实这也是二位数组的本质。
下面我们来看看数组指针的书写方法:
int main()
{
char c = 'a';
char* pc = &c;//pc是一个字符指针
int num = 10;
int* pi = #//pi是一个整形指针
int arr[10] = { 0 };
int (*parr)[10] = &arr;//parr是一个数组指针变量,其指向长度为10整型数组
return 0;
}
这里的parr是指针的名字,和其他类型指针相同,创建指针时,*号都是加在指针名的前面;另外,这里的*和parr必须用()括起来,如果不括起来,会得到:
int* parr[10] = &arr;
由于[ ]的优先级是高于*的,*会和前面的int结合,parr会和[10]结合形成数组,整体会变成一个指针数组。
在创建各种类型的变量时,把其名字去掉,留下来的便是其类型:
int main()
{
char c = 'a';//c的类型是char
char* pc = &c;//pc的类型是char*
int num = 10;//num的类型是int
int* pi = #//pi的类型是int*
int arr[10] = { 0 };//arr的类型是 int [10]
int (*parr)[10] = &arr;//parr的类型是 int(*)[10],这也是&arr的类型
return 0;
}
既然是指针,我们也可以对数组指针进行解引用操作:
在上面那段代码中,parr储存的是&arr,那么*parr就是*(parr)----->*(&arr)----->arr,可以得到arr,即*(parr)就是数组名。
既然是数组名,也可以这样写,效果一样:
int main()
{
int arr[10] = { 0 };
int (*parr)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*((*parr)+i) = i + 1;//给数组元素赋值
}
for (i = 0; i < 10; i++)
{
printf("%d ", *((*parr)+i));//遍历打印数组元素
}
return 0;
}
我们先来看一个基本的二维数组传参:
数组的元素都是内置类型的,如果我们把一维数组作为数组的元素,这时候就是二维数组,二维数组作为数组元素的数组就是三维数组,二维数组以上的数组被称为多维数组。所以二维数组的本质其实是:由一维数组组成的数组,它的每个元素都是一个一维数组。
那么把二维数组的每一行都看作一个元素,它的首元素就是其第一行,或者说是第一行的一维数组。
所以上面的代码也可以这么写:
void PrintArr(int (*p)[5], int x, int y)
{
int i = 0, j = 0;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
{
printf("%d ", p[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
PrintArr(arr, 3, 5);
return 0;
}
形参中的指针p就是一个数组指针,其指向一个长度为5的整形一维数组:
我们也可以对形参的指针进行解引用来操作数组元素: