目录
1、数组名理解
2、一维数组传参本质
3、二级指针
4、指针数组和数组指针
5、函数指针变量
1、数组名理解
首先来看一段代码:
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));return 0;
}
输出的结果是:40,如果arr是数组首元素的地址,那输出应该是4/8才对。
其实数组名就是数组首元素(第一个元素)的地址是对的,但是有两个例外:
- sizeof(数组名)。sizeof中单独放数组名,这里的数组表示整个数组,计算的是整个数组的大小,单位是字节
- &数组名。这里的数组名表示整个数组,取出的是整个数组的地址(不是首元素的地址)。
除此之外,任何地方使用数组名,数组名都表示首元素的地址。
再来看一段代码:
#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };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[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和 arr 都是首元素的地址,+1就是跳过1个元素。
但是&arr 和 &arr + 1 相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
2、一维数组传参本质
先看一段代码:
#include <stdio.h>
void test(int arr[])
{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;
}
输出结果
在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址。所以函数形参的部分理论上应该使用指针变量来接受首元素的地址。
3、二级指针
假设有下面的声明:
int zippo[4][2];//内含int数组的数组
- 因为zippo是数组首元素的地址,所以zippo的值和&zippo[0]相同。而zippo[0]本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素(一个整数)的地址(即&zippo[0][0]的值相同。简而言之,zippo[0]是一个占用一个int大小对象的地址。所以zippo和zippo[0]的值相同。
- 给指针或地址加1,其值会增加对应类型大小的值。在这方面,zippo和zippo[0]不同,因为zippo指向的对象占用了两个int大小,而zippo[0]指向的对象只占用一个int大小。因此,zippo+1和zippo[0]+1的值不同。
- zippo[0]是该数组首元素(zippo[0][0])的地址,所以 *(zippo[0])表示存储在zippo[0][0]上的值。与此类似,*zippo代表该数组首元素(zippo[0])的值,但是zippo[0]本身是一个int类型值的地址。该值的地址是&zippo[0][0]。所以*zippo就是&zippo[0][0]。**zippo与*&zippo[0][0]等价。
#include <stdio.h>
int main()
{int zippo[4][2] = { {2,4},{6,8},{1,3},{5,7} };printf(" zippo = %p, zippo + 1 = %p\n", zippo, zippo + 1);printf("zippo[0] = %p, zippo[0] + 1 = %p\n", zippo[0], zippo[0] + 1);printf(" *zippo = %p, *zippo + 1 = %p\n", *zippo, *zippo + 1);printf("zippo[0][0] = %d\n", zippo[0][0]);printf(" *zippo[0] = %d\n", *zippo[0]);printf(" **zippo = %d\n", **zippo);printf("zippo[2][1] = %d\n", zippo[2][1]);printf("*(*(zippo+2) + 1) = %d\n", *(*(zippo + 2) + 1));return 0;
}
运行结果:
zippo 二维数组首元素的地址(每个元素都是内含两个 int 类型元素的一维数组)
zippo+2 二维数组的第 3个元素 (即一维数组)的地址
*(zippo+2) 二维数组的第 3个元素(即一维数组)的首元素(一个 int 类型的值)地址
*(zippo+2) + 1 二维数组的第 3个元素(即一维数组)的第 2个元素(也是一个 int 类型的值)地址
*(*(zippo+2) + 1) 二维数组的第 3个一维数组元素的第 2个int 类型元素的值,即数组的第3行第2
列的值 (zippo[2][1])
4、指针数组和数组指针
指针数组的本质其实是数组,它里面存放的是指针,数组中每个元素的类型是指针类型(int*或char*)。
指针数组的创建方式:
int * arr[10] = {0};
数组指针的本质其实是指针变量,存放的是数组的地址,能够指向数组的指针变量。
数组指针的创建方式:
int (*pt)[10];
数组指针的初始化:
int arr[10] = {0};
int (*pt)[10] = &arr;
字符指针变量:
char* p = "hello";
这里本质上是把字符串“hello”,首字符h的地址存放在p中。
5、函数指针变量
我们先来看一段代码:
#include <stdio.h>
void test()
{printf("hehe\n");
}
int main()
{printf("test: %p\n", test);printf("&test: %p\n", &test);return 0;
}
运行结果:
这说明函数是有地址的,和数组一样,函数名就是函数的地址。当然也可以通过&函数名的方式后的函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量。
void test()
{printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;int Add(int x, int y)
{return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的
函数指针变量的使用:通过函数指针调用指针指向的函数。
#include <stdio.h>
int Add(int x, int y)
{return x + y;
}
int main()
{int(*pf3)(int, int) = Add;printf("%d\n", (*pf3)(2, 3));printf("%d\n", pf3(3, 5));return 0;
}
结果是 5 8