C语言指针高阶

22 阅读7分钟

指针(指针高阶)

数组和指针

理解数组名

数组名表示数组首元素的地址

#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和数组名

#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中给单独存放数组名, 这里的数组名表示整个数组, 计算的是整个数组的大小, 单位是字节

&数组名

叫做取地址数组名 此时也表示整个数组, 取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的) 下面代码从结果看是一样的, 但是意义一样吗?

#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;
}

此时代表的意义分别是:

  • &arr[0]: 数组的第一个元素地址
  • arr: 数组的第一个元素地址
  • &arr: 整个数组的地址

总结

sieof(arr)表示的是整个数组的大小 &arr表示整个数组的地址

一维数组传参, 形参的部分可以写成数组的形式, 也可以写成指针的形式

二级指针

语法

数据类型** 指针变量名

主要作用存储一级指针的地址

示例

int a = 10;
int* p1 = &a;    //一级指针 存放a的地址
int** p2 = &p1;    //二级指针 存放p1的地址 间接访问两回
//可以一直写
int*** p3 = &p2;    //三级指针
int**** p4 = &p3;    //四级指针

结论

  • 对二级指针p2进行2次解引用, 实际上获取到的是一级指针p1指向内存中所存储的内容, 也就是变量a的值
  • 二级指针指向一级指针的地址(可以修改被指向的一级指针内存储的地址), 一级指针指向某个元素的地址
  • 注: 指针可以一直套, 可以有三级 四级 五级等级别的指针, 但是不建议

指针数组

指针数组是指针还是数组? 指针数组: 存放指针的数组, 所以是数组

int arr[] = {1, 2, 3, 4, 5};    //整型数组, 存储的是整形

定义

//int类型的指针数组
int* arr[];
int* arr[] = {&a, &b, &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; 指向字符的指针

定义

int(*p)[4]; 指针p指向一个长度为4的数组, 数组的每个元素是int类型的

int arr[] = {1, 2, 3, 4};
int(*p)[4] = &arr;    //指针指向数组地址

练习

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;
}

二维数组传参

目前学会的:

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;
}

那么能否用指针实现? 二位数组的数组名: 数组首元素的地址(和数组一样, 二维数组是特殊的一维数组)

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));

函数指针

实际上还有更加复杂的指针, 叫做函数指针数组, 这个数组里放的是函数指针

什么是函数指针变量呢?

根据前面学习整形指针, 数组指针的时候, 不难得出结论: 函数指针变量应该是用来存放函数地址的, 通过地址能够调用函数 下面验证一下函数是否有地址:

void test() {
    printf("hehe\n");
}
int main() {
    printf("test: %p\n", test);
    printf("&test: %p\n", &test);
    return 0;
}

确实打印出来了地址, 所以函数是有地址的, 函数名就是函数的地址, 也可以使用&函数名的方式获得函数的地址 地址拿到了怎么去存储呢? 这就要创建函数指针变量了, 函数指针变量的写法和数组指针的写法非常相似: void test2() == void (*p) () = test2

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;
}

函数指针的调用

那么如何进行调用呢?

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;
}

函数指针的用途

一般用在一些回调函数上, 什么是回调函数: 玩游戏的时候一些功能按钮(如登录), 当点击该按钮会跳转到对应的页面(登录页面), 在登录过程中有可能调用其他功能, 那么这个时候就可以把这些功能当作参数传进来, 这就叫回调函数

函数指针数组

数组当中给存放的是函数指针

练习

二维数组是一个特殊的一维数组 对于任何一种指针, 在解题的时候, 一定要判断好它的类型

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;
}
//注: 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;
}
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)