目录
八,回调函数(qsort)
一,字符指针
int main()
{
char n = 'i';
char* pn = &n;
*pn = 'I';
printf("%c", n);
return 0;
}
这是我们常用的字符指针用法,但是还可以这样用。
char* pn = "hello";
这里有一个大家都很容易误解的误区,就是以为"hello"存放在pn里面,这很显然是不对的。
我们用一道题来解释:
int main()
{
char str1[] = "hello";
char str2[] = "hello";
char* p1 = "hello";
char* p2 = "hello";
printf("地址相同的是:\n");
if (str1 == str2)
{
printf("str1 str2\n");
}
if (p1 == p2)
{
printf("p1 p2\n");
}
return 0;
}
这里的解释是:系统为str1和str2是开辟了不同的空间,开辟了一份空间存放字符串常量"hello",指针变量p1和p2指向相同的"hello"。
我们可以得出:字符串常量需要空间存放,字符串数组刚好有空间,所以可以存放,而指针是存放地址的,所以字符指针是存放字符串常量的首元素地址,而非这个字符串。
显然,当p1指向字符串常量时,*p是不能修改的,而p1指向str1字符串数组时,*p是可以修改的(但是修改str1的值不建议这样做,很可能会报错,因为字符串赋值和修改的原理都是一个一个字符进行修改的)
二,指针数组
顾名思义,存放指针的数组。数组中每个元素都存放一个地址。
char* arr[5];
数组指针的用法实例:
模拟可变长的二维数组,但是不好打印,所以我特地这样设置。
int main()
{
int a1[] = { 1,2,3,4,5 };
int a2[] = { 2,3,4,5 };
int a3[] = { 3,4,5 };
int a4[] = { 4,5 };
int a5[] = { 5 };
int* a[5] = { a1,a2,a3,a4,a5 };
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5-i; j++)
{
printf("%d ", a[i][j]);
//等效的写法
//printf("%d ", *(a[i] + j));
//printf("%d ", *(*(a + i) + j));
}
printf("\n");
}
return 0;
}
三,数组指针
我们知道了指针数组本质是数组,那么数组指针又是什么呢?显然,数组指针是指针。
我们先来看看这些指针:
类型 | 例子 |
char | char* p; |
int | int* p; |
那数组指针呢?它是这样的
★ | 【类型名】 (*变量名)[指向数组的个数] | int (*p)[5],char (*p)[10] |
int main()
{
char b[3];
char(*p)[3] = &b; //这里一点是&b,而不是b,因为要取整个数组的指针大小
//格式严格对应
int a[5] = { 3,1,4,5,3 };
int(*p)[5] = &a;
return 0;
}
简单点理解,可以将int(*p)[5]和int a[5]类比,你会发现,*p就是a,又因为数组名就是地址,所以我们可以将其相似等换。不过事实确实十分相似,这里拿二维数组来举例,pa就相当于a的一行。pa+1就是a的下一行。
四,一维指针和二维指针传参
假设有这样一个函数:
void func(int *p) {}
那么我们在主函数里该传相应的什么值呢?一维指针有三种传参方式:
一维指针 | 变量地址 | 数组名 |
int* p; func(p); | int a; func(&a); | int b[10]; func(b); |
本质:传地址过去
那二维指针呢?
void func(int **p) {}
同样有三种:
二维指针 | 指针的地址 | 指针数组 |
int** p; func(p); | int* p; func(&p); | int *a[10]; func(a); |
本质:地址的地址
五,函数指针
我们经常使用函数,但你有没有思考过,函数是如何被调用的,是否也是通过地址调用的。没错,函数也是有地址的,并且我们还可以定义指针来存放函数的地址,这类指针就是函数指针。
- 形式
假设我们有这么一个函数
void func(int a,char b){}
那么它的指针就是这样的
void (* pf)(int a,char b) = &func;
这里说明一下:函数名就相当于函数地址,&func和fun是等效的
我们让 函数指针 和 数组指针 比较一下,是不是很像(这里以整型为例)
★函数指针 | 数组指针 | |
格式 | int (* )(int ,char ) | int (* )[10] |
例子 | int (*pa) (int ,char); | int (*a)[10] |
- 函数指针的调用
int sub(int a, int b)
{
return a - b;
}
int main()
{
int (*ps)(int, int) = sub;
printf("%d ", sub(5, 1));
printf("%d ", (&sub)(5, 1));
printf("%d ", ps(5, 1));
printf("%d ", (*ps)(5, 1));
printf("%d ", (****ps)(5, 1));
return 0;
}
这里解释一下:ps的调用有很多效果一样的形式,ps、*ps、****ps、和sub、&sub的效果都是一样的。所以大家以后见到了不用惊讶,都是基操,效果一样。
那我们来看个奇怪的代码巩固一下函数指针的内容:
(* ( void(*)() ) 0) ();
把0这个地址强制转换成void(*)()类型的函数指针,然后调用,这个0地址的函数 。
void( *signed( int , void(*)(int ) ) ) (int);
这是一个函数声明:
突破口在*signed(int,void(*)(int))这里
声明了一个函数名为signed,参数为int和void(*)(int),返回值是void(*)(int)类型的函数。
void(*)(int)参数是int,返回值是void
由于这个代码很复杂,不好读,我们可以typedef一下 ,使其简化
typedef void(* pf )(int);
pf signed(int, pf);
六,函数指针数组
前面我们介绍了函数指针,那当我们遇到多个相同类型参数的函数想将他们放在一起时,我们这个时候就可以使用函数指针数组,同指针数组一样,本质还是存放函数指针的数组。
指针数组 | int* a[10] | int* a[10] = {地址1,地址2,地址3,...} |
函数指针 | int (*p)(int ,int) | int (*p)(int , int ) = 函数名 |
★函数指针数组 | int (*pa[10])(int ,int) | int (*pa[10])(int ,int) = {函数1,函数2,函数3,...} |
这里都用了int来举例便于理解,其他类型如法炮制。
- 用法
当我们要写很多相同类型的函数时,我们就可以用函数指针数组来管理。
下面就一个简易的,具有加减乘除功能的计算器代码,用函数指针数组来管理四个函数。
float add(int a, int b)
{
return a + b;
}
float subtract(int a, int b)
{
return a - b;
}
float multiply(int a, int b)
{
return a * b;
}
float divide(int a, int b)
{
assert(b!=0);
return (float)a / b;
}
int main()
{
menu();
float(*pf[5])(int, int) = { 0,add,subtract,multiply,divide };
int input;
do
{
printf("选择功能:");
scanf("%d", &input);
if (input)
{
int n, m;
printf("计算的两个值:");
scanf("%d%d", &n, &m);
float value = pf[input](n, m);
printf("%f\n", value);
}
else
printf("感谢使用!\n");
} while (input);
return 0;
}
这里要注意的是:函数指针数组里的函数都是同一类的函数(参数,返回值都得一致)
七、指向函数指针数组的指针
这个嘛就是,套娃了。我们对比的来了解这个指针
假设有这些(同类)函数 | int add (int a,int b){ },int sub (int a,int b){ } int mul (int a,int b){ },int div (int a,int b){ } |
函数指针 | int ( *p )( int , int ) =add |
函数指针数组 | int ( *pArr[ 5 ] )( int , int ) = {add,sub,mul,div} |
★函数指针数组的指针 | int ( *(*pa) [ 5 ] )( int , int ) = & pArr |
总之,这个指针我们只需要了解他的定义方法,后面的什么指针的指针都是一个模样的。套娃嘛,往里套就行了
八,回调函数
定义:回调函数就是一个通过函数指针调用的函数。
通俗点讲,就是通过一个函数为踏板去调用目的函数。类似函数的嵌套调用,函数里调用别的函数,但是形参是函数指针,实参是函数的地址(即函数名)。
举个栗子:(这里的add和divide函数是上面计算器的函数,我就没有放出来了)
void calculation(float (*p)(int, int))
{
printf("输入两个值:");
int n, m;
scanf("%d%d", &n, &m);
float value = p(n, m);
printf("%d ? %d = %f\n", n, m, value);
}
int main()
{
calculation(add);
calculation(divide);
return 0;
}
我们也可以将这个回调函数理解成中转站,里面有个形参存放这目的函数的指针。
再来看一个例子:C语言里自带的排序函数qsort()
这是qsort()函数参数定义
void qsort(void* base, size_t num, size_t size,int (*compar)(const void*, const void*));
可以看到,qsort()的排序原理是快排,快排是需要 前后下标 的来函数递归来实现的,因为qsort()函数的形参缺少首元素和最后一个元素的下标,只有长度,所以我就在my_qsort函数调用quick()。
void my_qsort(void* a, int num, int type_size, int (*compare)(const void*, const void*))
{
int r = num - 1;
int l = 0;
quick(a, l, r, type_size,compare);
}
void quick(void* arr, int left, int right, int type_size, int (*compare)(const void*, const void*))
{
char* a = (char*)arr; //初始为最小字节单位,便于乘上type_size来转换成不同的类型
if (left > right || right <= 0)
return;
int l = left;
int r = right;
char* key = a + l * type_size; // 设定基值是左边第一个,这里取地址得值。
while (l < r)
{
// 比较的地方就用 compare
while (compare(a + r * type_size, key) >= 0 && l < r)
r--;
while (compare(a + l * type_size, key) <= 0 && l < r)
l++;
// 交换用swap函数,交换的是数据的二进制值
swap(a + l * type_size, a + r * type_size, type_size);
}
//交换 key 和 a[r](此时r == l)
swap(key, a + r * type_size, type_size);
quick(a, left, l - 1, type_size, compare);
quick(a, r + 1, right, type_size, compare);
}
void swap(char* n, char* m, int width)
{
for (int i = 0; i < width; i++)
{
char temp = *n;
*n = *m;
*m = temp;
n++;
m++;
}
}
排序 int 类型
int compare_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void test_int()
{
int a[10] = { 0 };
printf("原 :");
int len = sizeof(a) / sizeof(a[0]);
for (int i = 0; i < len; i++)
{
a[i] = rand() % 10;
printf("%d ", a[i]);
}
printf("\n快排后:");
my_qsort(a, len, sizeof(a[0]), compare_int);
for (int i = 0; i < len; i++)
{
printf("%d ", a[i]);
}
printf("\n");
}
排序char类型
int compare_char(const void* e1, const void* e2)
{
return *(char*)e1 - *(char*)e2;
}
void test_char()
{
char a[10] = "gfedcba";
printf("前:");
puts(a);
my_qsort(a, strlen(a), sizeof(a[0]), compare_char);
printf("后:");
puts(a);
}
排序字符串类型
int compare_str(const void* e1, const void* e2)
{
return strcmp(*(char**)e1, *(char**)e2);
// 字符串比较时是优先比较前面的字符大小,在相同字符的情况下比较再比较长度
// 所以**是取第一个字符的地址比较
}
void test_str()
{
char *a[5] = { "zhangsan","lisi","wangwu","ergou","niuniu" };
printf("前:");
for (int i = 0; i < sizeof(a) / sizeof(a[0] ); i++)
{
printf("%s ", a[i]);
}
printf("\n");
my_qsort(a, sizeof(a)/sizeof(a[0]), sizeof(a[0]), compare_str);
printf("后:");
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%s ", a[i]);
}
printf("\n");
}
值得注意的是:每种类型的compare函数都不一样,需要按照所需的类型来实现。
调用主函数后:
int main()
{
srand((unsigned int)time(NULL));
test_int();
test_char();
test_str();
return 0;
}
运行结果
总结
指针这方面的知识还需要多下手,多写代码实现功能,理解起来就会更快更轻松,映象也会更深刻。纸上得来终觉浅,绝知此事要躬行。多敲代码多复习。