指针(指针高阶)
数组和指针
理解数组名
数组名表示数组首元素的地址
c
#include <stdio.h>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//arr和arr[0]的内存地址是一样的, 但是意义一样吗? 一样
printf("&arr[0] = %p\n", &arr[0]);
printf(&arr = %p\n", arr);
return 0;
}
sizeof和数组名
c
#include <stdio.h>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("%d\n", sizeof(arr)); //表示求整个数组的字节大小
printf("%d\n", sizeof(arr[0])); //表示求这个数组中一个数字所占字节大小
return 0;
}
输出的结果为: 40, 如果arr是数组首元素地址, 那输出应该是4或者8才对 总结
sizeof(数组名), sizeof中给单独存放数组名, 这里的数组名表示整个数组, 计算的是整个数组的大小, 单位是字节
&数组名
叫做取地址数组名 此时也表示整个数组, 取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的) 下面代码从结果看是一样的, 但是意义一样吗?
c
#include <stdio.h>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//值一样不代表意义一样
//意义一样 表示数组首元素的地址
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr[0]));
//意义不一样 表示整个数组的地址
printf("&arr = %p\n", &arr);
//表示数组首元素的地址
printf("&arr[0] == %d\n", &arr[0]);
printf("&arr[0] + 1 == %d\n", &arr[0] + 1); //加了4个字节
printf("arr == %d\n", arr);
printf("arr + 1 == %d\n", arr + 1); //加了4个字节
//整个数组的地址
printf("&arr == %d\n", &arr);
printf("&arr + 1 == %d\n", &arr + 1); //加了40个字节(数组内有10个值, 每个值占4个字节)
return 0;
}
此时代表的意义分别是:
- &arr0: 数组的第一个元素地址
- arr: 数组的第一个元素地址
- &arr: 整个数组的地址
总结
sieof(arr)表示的是整个数组的大小 &arr表示整个数组的地址
一维数组传参, 形参的部分可以写成数组的形式, 也可以写成指针的形式
二级指针
语法
c
数据类型** 指针变量名
主要作用存储一级指针的地址
示例
c
int a = 10;
int* p1 = &a; //一级指针 存放a的地址
int** p2 = &p1; //二级指针 存放p1的地址 间接访问两回
//可以一直写
int*** p3 = &p2; //三级指针
int**** p4 = &p3; //四级指针
结论
- 对二级指针
p2进行2次解引用, 实际上获取到的是一级指针p1指向内存中所存储的内容, 也就是变量a的值- 二级指针指向一级指针的地址(可以修改被指向的一级指针内存储的地址), 一级指针指向某个元素的地址
- 注: 指针可以一直套, 可以有三级 四级 五级等级别的指针, 但是不建议
指针数组
指针数组是指针还是数组? 指针数组: 存放指针的数组, 所以是数组
c
int arr[] = {1, 2, 3, 4, 5}; //整型数组, 存储的是整形
定义
c
//int类型的指针数组
int* arr[];
int* arr[] = {&a, &b, &c};
练习
c
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int* arr[] = {&a, &b, &c, &d};
printf("%p\n", *(arr + 1)); //表示什么 输出下标1的地址
printf("%d\n", **(arr + 1)); //表示什么 输出下标1的内容
char* str[] = {"abcd", "hello", "world"}; //char*类型的数组, 通俗来说当前整个数组就是一个字符串数组
数组指针
数组指针: 是一个指针 指向数组的指针 整形指针: int* p; 指向整型的指针 字符型指针: char* p; 指向字符的指针
定义
c
int(*p)[4]; 指针p指向一个长度为4的数组, 数组的每个元素是int类型的
int arr[] = {1, 2, 3, 4};
int(*p)[4] = &arr; //指针指向数组地址
练习
c
int main() {
int arr[4] = {1, 2, 3, 4};
int(*p)[4] = &arr;
//表示什么?
printf("%p\n", *p); //首元素的地址
//表示什么?
printf("%d\n", (*p)[1]); //2
return 0;
}
二维数组传参
目前学会的:
c
void print_arr(int arr[2][3], int row, int col) {
for(int i = 0; i < row; i++) {
for(int j = 0; j < col; j++) {
printf("%d ", arr[i][j]);
}
printf("\n);
}
}
int mian() {
int arr[2][3] = {1., 2, 3, 4, 5, 6};
print_arr(arr, 2, 3);
return 0;
}
那么能否用指针实现? 二位数组的数组名: 数组首元素的地址(和数组一样, 二维数组是特殊的一维数组)
c
void print_arr(int(*p)[3], int row, int col) {
for(int i = 0; i < row; i++) {
for(int j = 0; j < col; j++) {
printf("%d ", arr[i][j]);
}
printf("\n);
}
}
int mian() {
int arr[2][3] = {1., 2, 3, 4, 5, 6};
print_arr(arr, 2, 3);
return 0;
}
printf("%d", p[i][j]); == printf("%d", *(*(p+i));
函数指针
实际上还有更加复杂的指针, 叫做函数指针数组, 这个数组里放的是函数指针
什么是函数指针变量呢?
根据前面学习整形指针, 数组指针的时候, 不难得出结论: 函数指针变量应该是用来存放函数地址的, 通过地址能够调用函数 下面验证一下函数是否有地址:
c
void test() {
printf("hehe\n");
}
int main() {
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
确实打印出来了地址, 所以函数是有地址的, 函数名就是函数的地址, 也可以使用&函数名的方式获得函数的地址 地址拿到了怎么去存储呢? 这就要创建函数指针变量了, 函数指针变量的写法和数组指针的写法非常相似: void test2() == void (*p) () = test2
c
int add(int x, int y) {
return x + y;
}
int main() {
int (*p1) (int, int) = add //定义的函数指针指向add函数
int (*p1) (int, int) = &add //定义的函数指针指向add函数
return 0;
}
函数指针的调用
那么如何进行调用呢?
c
int add(int x, int y) {
return x + y;
}
int main() {
int (*p1)(int, int) = add
int ret = (*p1)(1, 2);
printf("%d\n", ret); //输出3
//可以写成这样:
printf("%d\n", p1(10, 20)); //输出30
return 0;
}
函数指针的用途
一般用在一些回调函数上, 什么是回调函数: 玩游戏的时候一些功能按钮(如登录), 当点击该按钮会跳转到对应的页面(登录页面), 在登录过程中有可能调用其他功能, 那么这个时候就可以把这些功能当作参数传进来, 这就叫回调函数
函数指针数组
数组当中给存放的是函数指针
练习
二维数组是一个特殊的一维数组 对于任何一种指针, 在解题的时候, 一定要判断好它的类型
c
int main() {
int aa[2][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int* ptr1 = (int*)(&arr + 1);
int* ptr2 = (int*)(*aa + 1);
printf("%d %d\n", *(ptr1 - 1), *(ptr2 - 1)); //10 5
return 0;
}
c
//注: x86(32位)模式下运行
int main() {
int a[] = {1, 2, 3, 4};
printf("%zu\n", sizeof(a)); //16 数组的字节大小
printf("%zu\n", sizeof(a + 1)); //4 &a[1]
printf("%zu\n", sizeof(a[1])); //4 a[1]当中的数据字节大小
printf("%zu\n", sizeof(&a)); //4 整个数组的地址, 不管是什么地址, 终究还是地址
printf("%zu\n", sizeof(&a + 1)); //4 整个数组的地址+1, 加了整个数组, 终究还是地址
printf("%zu\n", sizeof(&a[0])); //4 首元素地址
printf("%zu\n", sizeof(&a[0] + 1)); //4 首元素地址
return 0;
}
c
int main() {
char *a[] = {"work", "at", "alibaba"}; //指针数组
//a是数组名, 数组名表示数组首元素的地址, 数组的首元素此时是什么? 数组里边的元素是char*
char** pa = a;
pa++; //对二级指针+1, 加4个字节
printf("%s\n", *pa);
return 0;
}
/*
指针+1加几个字节? 公式: 去掉*(一个*)求sizeof()
*/
char* pc = 'a';
char** pa = &pc;
printf("%d\n", pa); //04
printf("%d\n", pa + 1); //08
printf("%d\n", pa + 2); //12
int arr[] = {1, 2, 3, 4};
arr[1] == *(arr + 1)
int arr[2][2] = {1, 2, 3, 4};
arr[1][1] == *(arr + 1)[1] == *(*(arr+1)+1)