【C语言进阶】二.指针(中)

(五)函数指针

1.定义:

指向函数的指针:

#include<stdio.h>
int Add(int a, int b)
{
int z = 0;
z = a + b;
return z;
}
int main()
{
int a = 10;
int b = 20;
printf("%dn",Add(a, b));
printf("%pn", &Add);
printf("%pn", Add);
}

【C语言进阶】二.指针(中)

可以看到Add也有地址,且Add和&Add都是函数的地址

那怎么储存呢?

2.储存方式:

#include<stdio.h>
int Add(int a, int b)
{
int z = 0;
z = a + b;
return z;
}
int main()
{
int a = 10;
int b = 20;
printf("%dn",Add(a, b));
printf("%pn", &Add);
printf("%pn", Add);
//int *pa(int,int)=Add;这样写不对,pa先和()结合,是函数,函数的参数是int,返回值是int*;
int(*pa)(int,int)=Add;
printf("%dn",(*pa)(2,3));
return 0;
}

如果括号里的*,对结果有影响吗?

#include<stdio.h>
int Add(int a, int b)
{
int z = 0;
z = a + b;
return z;
}
int main()
{
int a = 10;
int b = 20;
int(*pa)(int, int) = Add;
printf("%dn",(pa)(2,3));
printf("%dn",(*pa)(2,3));
printf("%dn", (**pa)(2, 3));
printf("%dn", (***pa)(2, 3));
printf("%dn", (****pa)(2, 3));
printf("%dn", (*****pa)(2, 3));
return 0;
}

【C语言进阶】二.指针(中)

我们发现增加*不影响,但如果要用*,一定要扩起来。

3.小试牛刀:

#include<stdio.h>
void Print(char* str)
{
printf("%sn", str);
}
int main()
{
void(*p)(char*) = Print;//将函数地址存在*p中
(*p)("hello world");//*p找到函数地址,调用函数
return 0;
}

有趣的代码

(*(void (*)())0)();

void (*)():函数指针类型

(void (*)())0:将0强制转化为函数指针类型

*(void ()())0:解引用,就是一个函数 

(*(void (*)())0)():调用函数,参数是无参,返回类型为void;

void (*signal(int , void(*)(int)))(int);

signal(int , void(*)(int)):函数,两个参数int,void(*)(int)

void(*)(int)函数指针类型

//去掉函数名和参数,剩余的就是返回类型。

void (*)(int):*在圆括号里,是指针,指向函数,函数的参数是int,返回类型是void

所以signal是函数名,

相当于一次声明

void (*)(int)  signal(  int  ,  void(*)(int)  )//但是不能这样写

简化写法:

//理解意义的写法typedef void(*)(int)  pfun_t;给void(*)(int)重命名为pfun_t
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

(六)函数指针数组

我们从例子中了解一下

#include<stdio.h>

int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}

int main()
{
int a = 10;
int b = 20;
int(*pa)(int, int) = add;//因为四个函数参数和返回值相同,所以可以写在一起
int(*parr[4])(int, int) = { add,sub,mul,div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%dn", parr[i](2, 3));
}
}

​int(*parr[4])(int, int) = { add,sub,mul,div };​​中parr先和[4]结合,是数组,有4个元素,每个元素的类型是int(*)(int, int)函数指针

练习:

写一个函数指针pf,能够指向my_strcpy——char* my_strcpy(char* desk, const char* src);

​char*(*pf)(char*, const char*)​

写一个函数指针数组pfarr,能够存放4个my_strcpy函数的地址

​char*(*pfarr[4])(char*, const char*)​​相当于函数指针加了个[]

使用案例:计算器的实现

1.

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************n");
printf(" 1:add 2:sub n");
printf(" 3:mul 4:div n");
printf(" 0.退出 n" );
printf("*************************n");
printf("请选择:");
scanf("%d", &input);
printf("输入操作数:");
scanf("%d %d", &x, &y);
switch (input)
{
case 1:
ret = add(x, y);
printf("ret = %dn", ret);
break;
case 2:
ret = sub(x, y);
printf("ret = %dn", ret);
break;
case 3:
ret = mul(x, y);
printf("ret = %dn", ret);
break;
case 4:
ret = div(x, y);
printf("ret = %dn", ret);
break;
case 0:
printf("退出程序n");
break;
default:
printf("选择错误n");
break;
}
} while (input);
return 0;
}

我们发现这样写虽然可以计算,但是当输入0时仍会出现:

【C语言进阶】二.指针(中)

说明逻辑错误,应该改为

2.

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************n");
printf(" 1:add 2:sub n");
printf(" 3:mul 4:div n");
printf(" 0.退出 n" );
printf("*************************n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %dn", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %dn", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %dn", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %dn", ret);
break;
case 0:
printf("退出程序n");
break;
default:
printf("选择错误n");
break;
}
} while (input);
return 0;
}

但是,我们发现要添加计算总类时,就需要增加一个case,当总类过多时,较为冗杂。

3.改为函数指针数组

#include <stdio.h>
menu()
{
printf("*************************n");
printf(" 1:add 2:sub n");
printf(" 3:mul 4:div n");
printf(" 0.退出 n");
printf("*************************n");
}
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int (*pfarr[5])(int, int) = {0,add,sub,mul,div};
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
int ret = pfarr[input](x, y);
printf("%dn", ret);
}
else if (input == 0)
{
printf("退出n");
}
else
{
printf("选择错误n");
}
} while (input);
return 0;
}

这样添加,只需要在数组里直接增加。

函数指针数组的用途:转移表

(七)指向函数指针数组的指针

指向函数指针数组的指针是一个:指针

指针指向一个数组 ,数组的元素都是函数指针

#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
//指针数组
int* arr[10];
//数组指针
int(*pa)[10]=&arr;
//函数指针
int(*pa)(int, int) = add;
int sum = (* pa)(1, 2);
printf("%dn", sum);
//函数指针的数组
int(*parr[6])(int, int) = add;
//指向函数指针数组的指针
int(*(*pparr)[6])(int, int) = &parr;
return 0;
}

​ int(*(*pparr)[6])(int, int) = &parr;​

​(*pparr)​​指针

​(*pparr)[6]​​指向一个数组,数组有六个元素

​ int(*)(int, int)​​每个原宿类型是指针函数

(八)回调函数

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************n");
printf(" 1:add 2:sub n");
printf(" 3:mul 4:div n");
printf(" 0.退出 n" );
printf("*************************n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %dn", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %dn", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %dn", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %dn", ret);
break;
case 0:
printf("退出程序n");
break;
default:
printf("选择错误n");
break;
}
} while (input);
return 0;
}

我们发现这个含数中

​printf("输入操作数:");​

​scanf("%d %d", &x, &y);​

​ret = div(x, y);​

两句多次出现,就只有调用的函数不同

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}

void calc(int(*pf)(int,int))//相当于是int(*pf)(int,int)=形参
{
int x, y;
printf("输入操作数:");
scanf("%d %d", &x, &y);
printf("ret = %dn", pf(x,y));
}

int main()
{
int input = 1;
do
{
printf("*************************n");
printf(" 1:add 2:sub n");
printf(" 3:mul 4:div n");
printf(" 0.退出 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");
break;
}
} while (input);
return 0;
}

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个

函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数

的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进

行响应。

以冒泡函数为例:

首先演示一下qsort函数的使用

知识预备

​void *p​​这个类型的指针可以接收任意类型的地址;但是void*类型指针不能进行解引用操作(没有具体类型,不知道解引用时的空间);也不能进行加减整数的操作。

quick sort

int

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
return (*( int *)p1 - *(int *) p2);//整型指针p1指向的值减去整型指针p2指向的值
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("n");
return 0;
}

float

int float_cmp(const void* p1, const void* p2)
{
return ((int)(*(float*)p1 - *(float*)p2));
}
int main()
{
float arr[] = { 1.0, 3.0, 5.0, 7.0, 9.0, 2.0, 4.0, 6.0, 8.0, 0.0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), float_cmp);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%f ", arr[i]);
}
printf("n");
return 0;
}

struct

age

#include<stdio.h>
struct stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1, const void* p2)
{
return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
int main()
{
struct stu s[3] = { {"zhangsan,20"} ,{"lisi,30"}, {"wangwu,65"} };
int i = 0;
qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_stu_by_age);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
for (i = 0; i < sizeof(s) / sizeof(s[0]); i++)
{
printf("%d", s[i]);
}
printf("n");
return 0;
}

name

#include<stdio.h>
#include<string.h>
struct stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct stu*)p1)->name , ((struct stu*)p2)->name);
}
int main()
{
struct stu s[3] = { {"zhangsan,20"} ,{"lisi,30"}, {"wangwu,65"} };
int i = 0;
qsort(s, sizeof(s) / sizeof(s[0]), sizeof(s[0]), cmp_stu_by_name);
//数组首地址,数组元素个数,元素字节,不同类型元素比较的函数
return 0;
}

我们的冒泡函数只有:

#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
struct stu
{
char name[20];
int age;
};

int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);

//struct stu s[3] = { {"zhangsan,20"} ,{"lisi,20"}, {"wangwu,20"} };
//float f[10] = { 1.0,2.0,3.0,4.0 };
//不能使用

bubble_sort(arr, sz);

int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d", arr[i]);
}
return 0;
}

改造

#include <stdio.h>
//这段由使用bubble的程序员写
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}



void _swap(void* p1, void* p2, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
//char tmp = *p1;
//*p1 = *p2;
//*p2 = tmp;
//p1++;
//p2++;

}
}

void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
//比较方法不同,那你就把你的比较方法传进来
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}

int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
//char *arr[] = {"aaaa","dddd","cccc","bbbb"};
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("n");
return 0;
}

(九)指针和数组笔试题

1.一维数组

int a[] = {1,2,3,4};
printf("%dn",sizeof(a));//16——计算的是数组总大小,单位是字节
printf("%dn",sizeof(a+0));//4/8——首元素地址,大小为4或8
printf("%dn",sizeof(*a));//4——首元素地址解引用,为此处元素
printf("%dn",sizeof(a+1));//4/8——第二个元素的地址
printf("%dn",sizeof(a[1]));//4——第二个元素的大小
printf("%dn",sizeof(&a));//4/8——取出的是整个数组的地址
printf("%dn",sizeof(*&a));//16——整个元素地址解引用,为所有元素
printf("%dn",sizeof(&a+1));//4/8——&a是数组地址,+1跳过整个数组,还是地址
printf("%dn",sizeof(&a[0]));//4/8第一个元素的地址
printf("%dn",sizeof(&a[0]+1));//4/8第二个元素的地址

【C语言进阶】二.指针(中)

注:

数组名是首元素地址,除了&数组名和sizeof(数组名)

地址大小为4/8由系统平台决定

2.字符数组

2.1char arr[] = {'a','b','c','d','e','f'};

char arr[] = {'a','b','c','d','e','f'};
printf("%dn", sizeof(arr));//6——计算的是数组大小,每个元素char大小为1
printf("%dn", sizeof(arr+0));//4/8——首元素地址
printf("%dn", sizeof(*arr));//1——arr首元素地址,*解引用,变为首元素
printf("%dn", sizeof(arr[1]));//1
printf("%dn", sizeof(&arr));//4/8——整个数组的地址,还是4/8
printf("%dn", sizeof(&arr+1));//4/8——跳过整个数组后的地址,还是地址
printf("%dn", sizeof(&arr[0]+1));//4/8——第二个元素的地址
printf("%dn", strlen(arr));//随机值——没有,strlen会往后走
printf("%dn", strlen(arr+0));//随机——和上一个一样
//printf("%dn", strlen(*arr));//崩溃,*arr解引用为'a',是97,将97当作地址访问
//printf("%dn", strlen(arr[1]));//崩溃
printf("%dn", strlen(&arr));//随机——取整个数组的地址,也是从第一个开始,应该和第9,10相同
printf("%dn", strlen(&arr+1));//随机值,比第13少6
printf("%dn", strlen(&arr[0]+1));//随机值,比第13少1

【C语言进阶】二.指针(中)

2.2char arr[] = "abcdef";

char arr[] = "abcdef";
printf("%dn", sizeof(arr));//7——后面有
printf("%dn", sizeof(arr+0));//4/8——首元素地址
printf("%dn", sizeof(*arr));//1——首元素地址解引用,是首元素
printf("%dn", sizeof(arr[1]));//1——第二个元素
printf("%dn", sizeof(&arr));//4/8——地址
printf("%dn", sizeof(&arr+1));//4/8_跳过该地址后的地址,还是地址
printf("%dn", sizeof(&arr[0]+1));//4/8——第二个地址
printf("%dn", strlen(arr));//6——strlen求到
printf("%dn", strlen(arr+0));//6——首元素地址开始计数
//printf("%dn", strlen(*arr));//崩溃,将'a'传入strlen,strlen要地址
//printf("%dn", strlen(arr[1]));//崩溃,同理
printf("%dn", strlen(&arr));//6——strlen的const char*与&arr的char(*)[7]=&arr冲突,但也能算
printf("%dn", strlen(&arr+1));//随机,后面有什么不知道
printf("%dn", strlen(&arr[0]+1));//5——第二个元素开始计数

【C语言进阶】二.指针(中)

2.3char *p = "abcdef";

char *p = "abcdef";//首元素地址放在p中
printf("%dn", sizeof(p));//4/8——计算指针变量p的大小
printf("%dn", sizeof(p+1));//4/8——p+1是字符b的地址
printf("%dn", sizeof(*p));//1——*p就是'a'
printf("%dn", sizeof(p[0]));//1——p[0]=*(p+0),就是'a'
printf("%dn", sizeof(&p));//4/8——地址
printf("%dn", sizeof(&p+1));//4/8——跳过这个地址后的地址
printf("%dn", sizeof(&p[0]+1));//4/8——'b'的地址
printf("%dn", strlen(p));//6
printf("%dn", strlen(p+1));//5
//printf("%dn", strlen(*p));//报错
//printf("%dn", strlen(p[0]));//报错
printf("%dn", strlen(&p));//随机,p里存的是a的地址,不确定的位置
printf("%dn", strlen(&p+1));//随机,p的地址后面的空间有什么不知道
printf("%dn", strlen(&p[0]+1));//5——[0]限定是元素a的地址,+1为b,从b开始计数

【C语言进阶】二.指针(中)

3.二维数组

int a[3][4] = {0};
printf("%dn",sizeof(a));//48——3*4*4
printf("%dn",sizeof(a[0][0]));//4——一个元素的大小
printf("%dn",sizeof(a[0]));//16——a[0]相当于第一行的数组名,数组名放在sizeof里面,直接计算第一行的大小
printf("%dn",sizeof(a[0]+1));//4/8——第一行第二个元素的地址
printf("%dn",sizeof(*(a[0]+1)));//4——第一行第二个元素解引用,是个int
printf("%dn",sizeof(a+1));//4/8——a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素的地址
//二维数组看作一维数组时,二维数组的首元素是它的第一行,a就是第一行的地址,a+1就是第二行的地址
//a+1就是第二行的地址
printf("%dn",sizeof(*(a+1)));//16——第二行的地址解引用,是4*4
printf("%dn",sizeof(&a[0]+1));//4/8——a[0]是第一行的数组名,数组名取地址+1就是第二行的地址
printf("%dn",sizeof(*(&a[0]+1)));//16——第二行地址元素解引用,为4*4
printf("%dn",sizeof(*a));//16——没有sizeof(数组名),也没有&(数组名),a是第一行地址,*a就是第一行元素,4个int
printf("%dn",sizeof(a[3]));//16——sizeof不会计算括号里的表达式,我不会真的取访问数组,a[3]的类型和a[0]的一样

【C语言进阶】二.指针(中)

ps:

是不是很神奇,嘿嘿嘿

多看几遍就好了

指针(下)还有经典例题

头发留不住了

发表评论

相关文章