一、小补充
const char* ptr1 = "abcdsf"; // 此语句不是把“abcdsf”存入ptr1中,而是把字符串常量“abcdsf”的首地址放进了ptr1中
const char* ptr2 = "abcdsf"; // 此时ptr1 == ptr2,常量数组不能修改,不会另外开辟一个一样的。因此,ptr1和ptr2指向同一块空间
int * p1 [10]; // 此条语句中p1是个数组,该数组里面存放的是int*指针变量
int (*p2) [10]; // 词条语句中p2是个指针,指向的类型为int [10],p的类型是int (*)[10]
数组名是数组首元素的地址,二维数组的每一行可以理解为一个二维数组的元素,该元素是一个一维数组。因此,二维数组其实是一维数组的数组,首元素地址就是第一行的地址。
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int (*ptr) [10] = &arr; // 此时ptr是整个数组的指针,+1跳过整个数组范围
// *ptr == arr,但解引用以后ptr只是首元素的地址,而不是整个数组的元素
二、函数指针
函数指针,就是指向函数的指针。
void func()
{}
printf("%p\n", &func); // 取出了函数的地址
printf("%p\n", func); // 函数名就是函数的地址
返回类型 (*ptr)(函数参数类型) = &func; // ptr为函数指针变量;
int ret = (*ptr)(3, 5); // 此为函数指针的用法
int ret = ptr(3, 5); // 不解引用也可以
(*(void(*)())0)(); // 该语句调用0地址处的函数,该函数类型是void ()
void(* signal (int, void (*)(int)))(int); // 该语句表示一个signal函数,函数的参数是int和void (*)(int),返回值为void (*)(int)
// 这样的语句太复杂,可读性也很低,因此我们常常使用typedef来优化
typedef unsigned int u_int; // 对整型重定义
typedef int* ptr_t; // 对整型指针重定义
typedef int (*)[10] ptr_arr; // error,此种重定义数组指针的方法是错的
typedef int (*ptr_arr)[10]; //对数组指针重定义为ptr_arr
typedef int (*)(int, int) pf_t; // error,此种重定义函数指针的方法是错的
typedef int (*pf_t)(int, int); //对函数指针重定义为pf_t
//开始简化最开始两行代码
typedef void(aaa*)();
typedef void(bbb*)(int);
(*(aaa)0)();
bbb signal(int, bbb);
三、函数指针数组
数组的每个元素都是函数指针;
int (* arr[3]) (int, int)
好处是可以通过数组下标,在不同的情况调用不同的函数,且不用增加判断分支语句,代码量和需要修改的代码少。—— 这种使用方式被称为“转移表”
四、指向函数指针数组的指针
int (*p) (int, int); // 函数指针
int (*p[3]) (int, int); // 函数指针数组
int (*(*p)[3]) (int, int); // 指向函数指针数组的指针
五、回调函数
函数指针有一个非常大的用途就是实现回调函数;回调函数,就是将A的函数指针作为参数传给函数B,由函数B在某些特定的情况下通过该指针调用函数A,这样就实现了回调;
// 标准库里面的 —— “qsort”
// 1、采用快速排序
// 2、适合于任何类型
// void qsort(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*))
// base指向被排序数组的首地址,单个元素是size长,总共有num个数据,排序规则为cmp
// 这里面cmp就是回调函数,实现不同任意类型排序
//扩展
//void*—— 无具体类型的指针,什么类型的指针都可以存放在void*中
// 这种类型的指针是不能进行解引用操作的,也不能进行指针运算;