指针详解(2)
对数组名的理解
在C语言里数组名还表示着数组首元素地址。
int arr[5] = {1, 2, 3, 4, 5};
int* p = &arr[0];
int* p = arr;
以上这两种,对指针p进行赋值的操作均是等价的,都将数组首元素的地址赋给指针p。
不妨,我们可以测试一下
int main()
{
int arr[5] = {1, 2, 3, 4, 5};
printf(" &arr[0] = %p\n", &arr[0]);
printf(" arr = %p\n", arr);
return 0;
}
、
更具运行结果能够证明,arr 与 &arr[0] 是等价的,都表示着数组首元素的地址
但也有两个例外,
- sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位字节
- &数组名,数组名表示整个数组,取出的是整个数组的地址。(整个数组的地址与首元素地址有区别的)
除此之外,数组名表示首元素的地址
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
printf("sizeof(arr) = %d\n", sizeof(arr));
return 0;
}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int* p = &arr;
printf("&arr = %p\n", &arr);
printf("arr = %p\n", arr);
return 0;
}
这里会不会看着有点懵,&arr,与arr地址是相同的。不妨看看这串代码。
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0] + 1= %p\n", &arr[0] + 1);
printf("arr = %p\n", arr);
printf("arr + 1 = %p\n", arr + 1);
printf("&arr = %p\n", &arr);
printf("&arr + 1 = %p\n", &arr + 1);
return 0;
}
根据运行结果不难看出,
-
&arr[0] 和 arr,表示着数组名首元素的地址
-
&arr[0] + 1 和 arr + 1,表示第二的数组元素的地址,地址大小加4
-
&arr,表示整个整个数组元素,打印的地址是首元素的地址
-
&arr + 1,由00AFF934 - 00AFF920得出地址向后偏移了20个字节,意味着&arr + 1跳出了数组元素的空间,指向了一块没有使用的空间
int arr[5] = { 1, 2, 3, 4, 5 };
int* p = arr + 1;
printf("p = %n\n", p);
可以运行这窜代码,将会报错,指针变量p 现在是一个野指针,使用printf函数解引用会报错。
一维数组传参本质
有了上述对数组名的理解,看看一下代码试着猜测运行结果。
void test(int arr[])
{
printf("sizeof(arr) = %d\n", sizeof(arr));
}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
printf("sizeof(arr) = %d\n", sizeof(arr));
test(arr);//等价test(&arr[0]);
return 0;
}
运行结果:
很明显在函数里计算数组元素的大小为4,主函数里为20。
根据代码运行结果发现,一维数组在进行传参时,因为数组名是首元素地址,在本质上传递的实际上是数组首元素的地址。在在理论上参数因该使用指针变量来接收数组元素的指针。
函数里使用sizeof(arr)计算数组大小时实际上计算的是指针变量的大小而不是数组的大小。
这里将编译器改变为64位,重新运行代码后的结果,在64位下计算指针的大小,为8个字节。
由于传递的是地址,在函数里可以使用指针变量来接收
void test(int* arr)
{
//printf("arr[2] = %d", arr[2]);
printf("*(arr + 1)= %d", *(arr + 1);//两者等价
}
这里,通过指针的写法来访问数组元素,*(数组名 + 下标),这种写法与 arr[1]是等价的,数组名表示收元素地址,对地址+ 1或+ 其它数字,地址就会跳转到该数字对应下标的数组元素。
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
printf("*(arr + 4) = %d\n", *(arr + 4));
return 0;
}
注意:数组下标最大是4,在使用指针的写法时,*(arr + 5)
这样是错误的写法,将会对野指针解引用,程序报错。
二级指针
指针变量用来存放变量地址,那指针变量的地址呢?变量的地址存放在二级指针里
int main()
{
int a = 0;
int* pa = &a;
int** ppa = &pa;
**ppa = 6;
printf("%d\n", a);
return 0;
}
变量 | 地址 | 值 |
---|---|---|
a=*pa=**ppa | 0X00FFA4 | 0 |
pa=*ppa | 0X00FFA8 | 0X00FFA4 |
ppa | 0X00FFAC | 0X00FFA8 |
对一级指针pa进行解引用 *pa,找到了变量a,a的值为 0,对二级指针ppa进行解引用找到了一级指针pa,pa的值为0X00FFA4,是变量a的地址,在进行解引用就找到了变量a,a的值为0。
案例:对二级指针 ppa解引用一次
int main()
{
int a = 0;
int b = 20;
int* pa = &a;
int** ppa = &pa;
*ppa = &b;
printf("%d\n", **ppa);
return 0;
}
这里对二级指针进行解引用(*ppa),实际上就是对一级指针进行操作,将b的地址赋给一级指针pa。它们之间的关系变为:
变量 | 地址 | 值 |
---|---|---|
b=*pa=**ppa | 0X00FFA0 | 20 |
pa =*ppa | 0X00FFA8 | 0X00FFA0 |
ppa | 0X00FFAC | 0X00FFA8 |
对ppa解引用两次就是b的值,打印的结果为20
指针数组
指针数组是什么?是指针,还是数组?更具之前学习过的,整形数组(int arr[]),字符数组(char arr[]),整形数组使用来存放整形的数组,字符数组是用来存放字符的数组,指针数组使用来存放指针的数组吗?是滴没错!
指针数组的写法,它可以是整形,也可以是字符类型,int* arr[5]; char* arr[5];
数字5代表着这个指针数组可以存放5个指针变量。指针数组与整形数组字符数组的理解相比并不难,它只是用来存放指针的数组。
初始化:
int a = 10;
int b = 6;
int* parr[2] = {&a, &b};
int* p[5] = { 0 };
使用指针数组,它与常见的整形数组用法区别不大
printf("%d\n", *parr[0]);
由于数组里存放的是指针,parr[0]是一个指针,对指针进行解引用来获取它指向的数据。
使用指针数组模拟二维数组
首先,二维数组在空间上是连续存放的,int arr[3][5];
,arr数组里,每行有5个元素,一共有三行,我们说可以近似的将这个三行的二维数组看作三个一维数组的拼接,诶~事实上是可以这样操作的。是以指针数组就可以完成这样的作法。
#include <stdio.h>
int main()
{
int i, j;
int arr1[5] = { 0 };
int arr2[5] = { 0 };
int arr3[5] = { 0 };
int* parr[5] = {arr1, arr2, arr3};
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
parr[i][j] = i + j;
}
}
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
这里呢,使用指针数组模拟了二维数组的赋值和打印,为什么可以这样 parr[i][j];
,数组下标引用操作符,本质上可以理解为 *(arr + 1)
,对数组首元素加有效的数字,就获得数组下标对应元素的地址,然后进行解引用,就获取了地址对应的值,parr[i][j];
同样是这样理解,第一个下标引用操作符找到这个指针数组的元素,
parr[0][j] 等价 arr1[j]
,在对arr1使用数组下标引用操作符就可以找到 i,j对应的位置,和元素。
同理,也可以使用二级指针模拟二维数组。首先对二级指针开辟空间一块整形指针类型的空间,然后在循环里
对每一个整形指针开辟相同大小的空间,就完成了对二维数组结构建立,之后就可以正常使用下标引用操作符,对二级指针赋值,打印之类的操作。
有效的数字,就获得数组下标对应元素的地址,然后进行解引用,就获取了地址对应的值,parr[i][j];
同样是这样理解,第一个下标引用操作符找到这个指针数组的元素,
parr[0][j] 等价 arr1[j]
,在对arr1使用数组下标引用操作符就可以找到 i,j对应的位置,和元素。
同理,也可以使用二级指针模拟二维数组。首先对二级指针开辟空间一块整形指针类型的空间,然后在循环里
对每一个整形指针开辟相同大小的空间,就完成了对二维数组结构建立,之后就可以正常使用下标引用操作符,对二级指针赋值,打印之类的操作。