之前已经讲解过了整型指针数组,用于存放整型指针。那么这里的函数指针数组就是用于存放函数指针的。写法是怎样的呢?
void test()
{
printf("hehe\n");
}
int main()
{
void(*pf[10])() = { test };
return 0;
}
代码的第7行,pf先与[]结合,说明pf是个数组名,将 pf[10]
去掉,剩下的部分就是数组的元素类型,即void(*)();
。所以pf是个函数指针数组。
当我们想要实现一个对于整数的四则运算的计算器时,我们或许可以这样写:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int x = 0;
int y = 0;
int input = 0;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请输入你想做的运算:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入操作数:\n");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入操作数:\n");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入操作数:\n");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入操作数:\n");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出程序!\n");
break;
default:
printf("您的输入有误,请重新输入!\n");
}
} while (input);
return 0;
}
但是这种写法会导致代码中重复的内容过多。那么能否改进改进呢?
我们观察这四个函数,可以发现他们的返回值和函数参数都是相同的。所以可以将这四个函数存放进一个函数指针数组中,这样在switch语句中调用对应的函数时,就可以使用下标进行快速访问。
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int x = 0;
int y = 0;
int input = 0;
int ret = 0;
int(*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请输入你想做的运算:");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入操作数:\n");
scanf("%d %d", &x, &y);
ret = pf[input](x, y);
printf("运算结果为:%d\n", ret);
}
else if(input==0)
{
printf("退出程序!\n");
}
else
{
printf("你的输入有误,请重新输入!\n");
}
} while (input);
return 0;
}
代码的第23行,这里使用0进行占位,将1,2,3,4这四个下标所对应的位置各自对应相应的函数。
指向函数指针数组的指针是一个指针,其指向一个函数指针的数组。
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
这里注意:函数名代表的是函数的地址,&函数名代表的也是函数的地址。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我们可以通过回调函数的机制,可以将上面的计算器的代码再进行改进。
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void Calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
printf("请输入两个操作数:\n");
scanf("%d %d", &x, &y);
int ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int x = 0;
int y = 0;
int input = 0;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请输入你想做的运算:");
scanf("%d", &input);
switch (input)
{
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:
printf("退出程序!\n");
break;
default:
printf("您的输入有误,请重新输入!\n");
}
} while (input);
return 0;
}
查看官方文档:
qsort函数可以对任意数组进行排序,包括结构体数组也可以。qsort函数有四个参数,第一个参数是目标数组的起始位置。第二个参数是数组中元素的个数。第三个参数是数组中每个元素的大小。第四个参数是一个函数,这个函数能够用于比较数组中的两个元素。
注意:这里qsort函数中的最后一个参数是一个函数名,这个函数的两个类型必须是void*类型的。为什么必须是void*类型的呢?
这是因为void*类型指针变量可以接收任意类型的指针变量。因为对于这个函数的设计者而言,他也不知道用户要比较的什么类型的元素,所以就设计了void*这个可以接收任意类型指针的“万能指针变量”,但是void*类型的指针变量不能进行解引用操作,也不能进行加减操作。例如:
//这段代码不会有任何的警告和报错。
int main()
{
int a = 0;
char c = 0;
float f= 1.0;
void* pv;
pv = &a;
pv = &c;
pv = &f;
return 0;
}
qsort函数的底层是快速排序算法,假定我们使用qsort函数对整数组中的元素进行排序:(qsort函数默认是升序排列的)
int compare_int(void* e1, void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
void print(int* p,int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", p[i]);
}
}
int main()
{
int arr[10] = { 1,2,4,1,6,2,21,4745,64 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
printf("\n");
qsort(arr,sz,sizeof(arr[0]),compare_int);
print(arr, sz);
return 0;
}
这里使用冒泡排序模拟实现qsort函数,对乱序的整型数组进行排序。
void swap(char* e1,char*e2,int sz)
{
char tmp = 0;
while (sz--)
{
tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
int compare_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
void myBubble(void* base, int num, int sz, int (*pf)(void*, void*))
{
int i = 0;
for (i = 0; i < num - 1; i++)
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)
{
if (pf((char*)base +j*sz, (char*)base + sz*(j + 1)) > 0)
{
swap((char*)base + j * sz,(char*)base+(j+1)*sz,sz);
}
}
}
}
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", p[i]);
}
printf("\n");
}
int main()
{
int arr[10] = { 1,0,4,23,55,34,756,754,7,999 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
myBubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare_int);
print(arr, sz);
return 0;
}
这里bubble函数和库函数qsort的参数类型设计的是一样的,这里要特别注意,qsort函数的最后一个参数是一个函数名,这个函数的参数的是void*类型的,也就是说这个函数是用于接收要比较的两个元素的地址的,并不是接收两个函数本身,所以要如何找到这两个要比较的元素的地址呢?可以将base这个参数强制转换为char*类型的,这样子base每执行一次+1操作都会向后移动一个字节,要比较的元素的大小是已知的,为sz。假如要找到bubble函数中找到arr[4]这个元素的地址,就可
(char*)base+4*sizeof(int);
,base指针向后移动16个字节,就成功找到了arr[4]的地址。
在swap函数中,参数是两个元素的地址,第三个参数是每个元素的大小,要交换这两个元素,只需要交换这两个元素的每一个字节就可以了。
假如要比较结构体数组:(假设以姓名为标准进行比较:)
struct Student
{
char name[20];
int age;
};
void swap(char* e1, char* e2, int sz)
{
char tmp = 0;
while (sz--)
{
tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
int compare_name(const void* e1, const void* e2)
{
return strcmp(((struct Student*)e1)->name, ((struct Student*)e2)->name);
}
void myBubble(void* base, int num, int sz, int (*pf)(const void*,const void*))
{
int i = 0;
for (i = 0; i < num - 1; i++)
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)
{
if (pf((char*)base + j * sz, (char*)base + sz * (j + 1)) > 0)
{
swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
}
}
}
}
int main()
{
struct Student arr[3] = { {"zhangshan",12},{"lisi",20},{"wangwu",16} };
int sz = sizeof(arr) / sizeof(arr[0]);
myBubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare_name);
return 0;
}
注意:这里字符串的比较是使用的strcmp函数进行比较的。不能直接进行字符串之间的加减操作。
假定以年龄为标准进行比较:
struct Student
{
char name[20];
int age;
};
void swap(char* e1, char* e2, int sz)
{
char tmp = 0;
while (sz--)
{
tmp = *e1;
*e1 = *e2;
*e2 = tmp;
e1++;
e2++;
}
}
int compare_int(const void* e1, const void* e2)
{
return ((struct Student*)e1)->age - ((struct Student*)e2)->age;
}
void myBubble(void* base, int num, int sz, int (*pf)(const void*,const void*))
{
int i = 0;
for (i = 0; i < num - 1; i++)
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)
{
if (pf((char*)base + j * sz, (char*)base + sz * (j + 1)) > 0)
{
swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
}
}
}
}
int main()
{
struct Student arr[3] = { {"zhangshan",12},{"lisi",20},{"wangwu",16} };
int sz = sizeof(arr) / sizeof(arr[0]);
myBubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare_int);
return 0;
}
这里就可以直接对年龄进行相减的操作了。
请预测下面代码的运行结果:
前言:数组名只在两种情况下代表数组名整个数组:其他情况下,数组名代表的是数组首元素的地址。
//一维数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//16
//数组名被单独放在sizeof内部,代表的是整个数组,计算结果是整个数组的大小。
printf("%d\n",sizeof(a+0));//4/8
//数组名不符合前言中的任何一种情况,此时数组名代表数组首元素的地址,地址的大小为4/8个字节
printf("%d\n",sizeof(*a));//4
//数组名不符合前言中的任何一种情况,此时数组名代表数组首元素的地址,解引用操作,拿到了int类型的首元素,大小4字节。
printf("%d\n",sizeof(a+1));//4/8
//数组名不符合前言中的任何一种情况,此时数组名代表数组首元素的地址,+1是第二个元素的地址。为4/8个字节。
printf("%d\n",sizeof(a[1]));//4
//a[1]是数组的第二个元素,大小是4字节。
printf("%d\n",sizeof(&a));//4/8
//&a取出的是整个数组的地址,但也是地址,大小为4/8个字节
printf("%d\n",sizeof(*&a));//16
//对数组名进行&和*操作,得到的最终还是数组名,相当于是将数组名单独放在sizeof内部,计算的是整个数组的大小。
printf("%d\n",sizeof(&a+1));//4/8
//&a取出整个数组的地址,+1操作跳过整个数组,得到一个新地址,但只要是地址,就是4/8个字节。
printf("%d\n",sizeof(&a[0]));//4/8
//a[0]是数组首元素,&a[0]是数组首元素的地址,地址的大小是4/8个字节。
printf("%d\n",sizeof(&a[0]+1));//4/8
//&a[0]是数组首元素的地址,+1操作,得到了数组中第二个元素的地址,大小为4/8个字节。
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6
//sizeof(数组名)计算的是整个数组的大小。数组中有6个元素,大小就是6.
printf("%d\n", sizeof(arr+0));//4/8
//数组名没有单独放在sizeof内部,所以代表的是首元素的地址,+0操作,还是首元素的地址,地址就是4/8字节
printf("%d\n", sizeof(*arr));//1
//*arr是数组首元素,数组中的元素都是char类型,所以计算结果为1字节
printf("%d\n", sizeof(arr[1]));//1
//arr[1]是数组中的第二个元素,大小是1个字节。
printf("%d\n", sizeof(&arr));//4/8
//&arr取出的是整个数组的地址,地址的大小是4/8.
printf("%d\n", sizeof(&arr+1));//4/8
//&arr代表的是整个数组的地址,+1操作跳过整个数组,得到一个新的地址,还是4/8个字节。
printf("%d\n", sizeof(&arr[0]+1));//4/8
//&arr[0]是数组首元素的地址,+1操作,得到数组第二个元素的地址,为4/8字节。
//要注意strlen函数的机制,strlen函数会计算从当前地址开始,一直到'\0'之前的所有元素个数
printf("%d\n", strlen(arr));//随机值
//arr是数组的起始地址,但是arr数组中并没有'\0',所以strlen会一直向后计算,知道遇到了'\0'为止。所以是个随机值。
printf("%d\n", strlen(arr+0));//随机值
//也是随机值,和上面的原因一样的
printf("%d\n", strlen(*arr));//报错
//这里将数组的第一个元素的ascll码,即97传过去了,意味着strlen函数要从97地址处开始向后计算,但是该内存空间是不允许被访问的,所以会报错。
printf("%d\n", strlen(arr[1]));//报错
//这里的原因与上一个类似的
printf("%d\n", strlen(&arr));//随机值
//&arr取出整个数组的地址,传递给strlen,由于不知道'\0'的位置,所以会计算出一个随机值。
printf("%d\n", strlen(&arr+1));//随机值
//&arr+1得到的是跳过一个数组之后的地址,由于不知道'\0'的位置,所以计算结果是随机值。
printf("%d\n", strlen(&arr[0]+1));//随机值
//同样的道理,也会计算出一个随机值
char arr[] = "abcdef";//要注意:字符串的末尾会自动添加'\0'。所以这个数组中含有7个元素
printf("%d\n", sizeof(arr));//7
//sizeof(数组名)计算的是整个数组的大小,要注意:字符串的末尾会自动添加'\0',会被sizeof加入计算结果的。
printf("%d\n", sizeof(arr+0));//4
//arr没有单独放在sizeof内部,+0仍然代表的是首元素的地址,大小4/8字节
printf("%d\n", sizeof(*arr));//1
//*arr是数组的首元素,数组是char类型数组,元素的大小都为1字节
printf("%d\n", sizeof(arr[1]));//1
//arr[1]是数组的第二个元素,数组是char类型的数组,大小都是1字节
printf("%d\n", sizeof(&arr));//4/8
//&arr取出的是整个数组的地址,只要是地址,大小就是4/8字节
printf("%d\n", sizeof(&arr+1));//4/8
//&arr取出整个数组的地址,+1操作跳过一个数组得到一个新的地址,大小仍然为4/8
printf("%d\n", sizeof(&arr[0]+1));//4/8
//&arr[0]取出数组首元素的地址,+1操作得到数组第二个元素的地址,只要是地址,大小就是4/8字节
printf("%d\n", strlen(arr));//6
//数组中含有'\0',arr是数组首元素的地址,到'\0'这个字符之前共有6个元素
printf("%d\n", strlen(arr+0));//6
//相同的道理
printf("%d\n", strlen(*arr));//报错
//*arr是将a字符的ascll码值传递给了strlen函数,这块内存空间访问是非法的,报错。
printf("%d\n", strlen(arr[1]));//报错
//arr[1]是将b字符的ascll码值传入了,该空间访问也是非法的,报错
printf("%d\n", strlen(&arr));//6
//&arr是数组的首地址,传入strlen函数时,会被转化为char*类型。得出计算结果为6
printf("%d\n", strlen(&arr+1));//随机值
printf("%d\n", strlen(&arr[0]+1));//5
//&arr[0]+1是第二个元素的地址,向后计算时不会将第一个元素算入,结果为5
char *p = "abcdef";
printf("%d\n", sizeof(p));//4/8
//p是一个指针变量,指向字符串的第一个元素的地址,指针变量的大小是4/8字节
printf("%d\n", sizeof(p+1));//4/8
//p+1是第二个字符的地址,地址的大小是4/8个字节
printf("%d\n", sizeof(*p));//1
//*p是字符串的第一个元素,大小是1字节
printf("%d\n", sizeof(p[0]));//1
//p[0]是字符串的第一个元素,大小也是1字节
printf("%d\n", sizeof(&p));//4/8
//&p是指针变量的地址,&p是个二级指针,大小也是4/8字节
printf("%d\n", sizeof(&p+1));//4/8
//&p+1也是一个二级指针,大小是4/8字节
printf("%d\n", sizeof(&p[0]+1));//4/8
//&p[0]+1是数组第二个元素的地址,大小是4/8字节
printf("%d\n", strlen(p));//6
//p指向的是字符串的第一个元素的地址,strlen能计算出这个字符串中的元素个数,为6
printf("%d\n", strlen(p+1));//5
//p+1地址处开始算,元素个数就是5个
printf("%d\n", strlen(*p));//报错
//*p的值是97,该空间访问是非法的,会报错
printf("%d\n", strlen(p[0]));//报错
//p[0]的值是97,同理,该空间访问非法,会报错
printf("%d\n", strlen(&p));//随机值
//&p是p这个指针变量的地址,由于不知道'\0'的位置,所以计算出一个随机值
printf("%d\n", strlen(&p+1));//随机值
//&p是二级指针,+1操作之后,仍因为不知'\0'的位置,最终计算出一个随机值。
printf("%d\n", strlen(&p[0]+1));//5
//&p[0]+1是数组第二个元素的地址,第一个元素不会被算入,计算结果就是5了
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48
//sizeof(数组名)计算的是整个数组的大小,为48字节
printf("%d\n",sizeof(a[0][0]));//4
//a[0][0]是数组中第一行第一列的元素0,大小是4字节
printf("%d\n",sizeof(a[0]));//16
//a[0]是二维数组中第一个一维数组的数组名,计算的是整个第一行的大小,为16个字节
printf("%d\n",sizeof(a[0]+1));//4/8
//a[0]并没有单独放在sizeof内部,所以代表的是二维数组第一个一维数组的第一个元素的地址,也就是int*类型的,再+1操作,代表的是二维数组的第一个一维数组的第二个元素地址,大小是4/8字节
printf("%d\n",sizeof(*(a[0]+1)));//4
//sizeof括号里的内容可以改写为:a[0][1],所以计算结果为4
printf("%d\n",sizeof(a+1));//4/8
//a这个数组名没有单独放在sizeof内部,所以a代表第一个一维数组的地址,+1代表第二个一维数组的地址,地址的大小是4/8字节
printf("%d\n",sizeof(*(a+1)));//16
//sizeof括号里的可改写做:a[1],a[1]是数组第二行元素数组名,被单独放在sizeof内部,计算的是第二行元素的大小。16
printf("%d\n",sizeof(&a[0]+1));//4/8
//&a[0]代表的是第一个一维数组的地址,+1代表的第二个一维数组的地址,地址的大小是4/8字节
printf("%d\n",sizeof(*(&a[0]+1)));//16
//sizeof括号里可以改写:a[1],是第二个一维数组的数组名,被单独放在sizeof的括号里,计算的是第二个一维数组的大小。16
printf("%d\n",sizeof(*a));//16
//*a与a[0]等价,被单独放在sizeof内部,计算的是第一行元素的大小。16
printf("%d\n",sizeof(a[3]));//16
虽然a[3]不存在,但是与sizeof的计算结果无关,sizeof只关心括号里的数据类型。最终将这个数据类型的大小算出来即可。
//a[3]也是一个一维数组的数组名,被单独放在sizeof内部,计算的是一行的元素的大小,为16.
结尾:指针进阶的内容先讲解到这里,后续会继续更新,如有不足,敬请指正!!