在指针类型中,我们知道字符类型的指针为char*
一般使用:
int main()
{
char ch = 'w';
char* p = &ch;
*p = 'a'; // ch == 'a'
return 0;
}
还有一种使用方法如下:
int main()
{
char *p = "Hello world";
printf("%s", p);
return 0;
}
像这种情况,很多人会认为是吧"Hello world"存放到了p当中,
其实并不是,这行代码的本质只是将"Hello world"的首字符地址存放到了p当中。
我们知道,数组是用来存放相应元素的,例如整形数组用来存放整形,字符型数组用来存放字符,那么指针数组就是用来存放指针的一个数组。
指针数组的创建和普通数组的创建是大同小异的,只不过他们的类型有所区别;
int arr[]是用来存放整形的数组,那么int* arr[]就是用来存放整形指针的数组
接下来我们创建一个简单的整形指针数组:
int main()
{
int a = 1;
int b = 2;
int c = 10;
int* pa = &a;
int* pb = &b;
int* pc = &c;
int* arr[] = { pa, pb, pc };
}
什么是数组指针,数组指针到底是指针还是数组?
答案是指针!!! (这里我们要区别于指针数组)
我们已经认识了
整形指针: int* pint; 指向整形的指针
字符指针: char* pchar; 指向字符的指针
那么顾名思义,数组指针就是指向数组的指针。
那么以下两个哪个是数组指针呢?
int* p1[10];
int (*p2)[10];
就多一个括号而已,又有什么不一样吗?
我们知道定义一个整形变量,我们需要他的类型和变量名来完成定义;
例如:
int a;
int arr[10];
那么,在这个地方,他的类型为int,也就是整形,a就是他的变量名。
那么一个数组的类型又是什么呢?
答案: int [10]就是他的类型!!!
再来看我们的问题,p1和p2的区别就在于,p2和*号被括号括起来了,就说明p2是一个指针,而他的类型又是int [10],所以说,p2就是数组指针!!!!
在学习数组的内容的时候,我们了解到了数组名就是数组首元素的地址,那么&数组名又和数组名有什么区别呢?
我们不妨尝试运行一下下面这行代码:
int main()
{
int arr[10];
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
其运行结果如下:
可见arr和&arr的地址是一样的,那么他们的区别又在哪里呢?
我们继续看下一组代码:
int main()
{
int arr[10];
printf("%p\n", arr);
printf("%p\n", arr + 1);
printf("\n");
printf("%p\n", &arr);
printf("%p\n", &arr + 1);
return 0;
}
其运行结果如下:
由运行结果可知,arr + 1与arr相比,其地址多了4,这是因为数组存放的是整形,而整形的大小为四个字节;
而&arr + 1与&arr的区别却是40,也就是10个整形的大小,
由此可见,&arr代表的是整个数组的地址,而arr仅仅代表首元素的地址,但在编译器上表现的都是首元素的地址!!!
接下来我们来进行练习,判断以下代码的意思是什么:
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
第一个: 创建一个大小为5的整形数组;
第二个: 创建一个数组名为parr1,大小为10的整形指针数组;
第三个: 创建一个指针名为parr2,所指向的数组大小为10的整形数组指针;
第四个: 创建一个指针名为parr3, 大小为10的存放大小为5的整形指针数组的数组;
思考:一下五种函数的形参分别代表的是什么?
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
第一种:用arr[ ]数组接受arr,该形参代表一个新的arr数组,指向的地址和实参一样;
第二种:和第一种并无区别;
第三种: 形参为整形指针,接收的是arr数组的首元素地址
第四种: 用大小为20的指针数组接收arr2;
第五种: 是二级整形指针,接收的是arr2首元素的地址;
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int* arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int(*arr)[5])//ok?
{}
void test(int** arr)//ok?
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
同一维数组传参类似,只有第二种是不符合语法规定的形参,因为二维数组可以省略行数,但是不能省略其列数为多少,故不能使用第二种;
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
以上代码就是将arr数组给遍历一次,将arr数组的首元素地址和数组长度传给自定义函数print,由于数组中的元素在内存中是连续存放的,故可以用for循环进行遍历;
void func(int** pp)
{
}
int main()
{
int a = 10;
int* p = &a;
int** pp = &p;
func(pp);
func(&p);
return 0;
}
以上代码就是将二级指针pp传给函数func,这就是二级指针的简单使用;
我们先看以下代码:
void func()
{}
int main()
{
printf("%p\n", func);
printf("%p\n", &func);
return 0;
}
其运行结果如下:
那我们又应该如何存储函数的地址呢?
类似与数组指针的创建:
数组指针的创建为:
int (*p)[10];
*p代表其为指针,他的类型为 int(*)[10];
同理如果一个函数的定义是这样的:
void func1()
{}
void func2(int a, int b)
{}
那么函数指针的创建就应该为:
void (*p1)()
void (*p2)(int int)
接下来我们看两段有趣的代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
第一种: void (*)()是函数指针类型,将0强制类型转换成函数指针,并解引用,调用该函数
第二种: void(*)(int)是函数指针类型,而signal是函数名,(int, void(*)(int))是函数signal的参数;故这行代码意思就是声明一个函数名为signal返回值为void(*)(int)的参数为int和,void(*)(int)的函数;
如果你能够看懂以上两段代码了,那就说明你对函数指针有了比较深的理解了;
我们已经了解了指针数组,那么函数指针数组又应该如何定义呢?
和指针数组类似:
void (*parr[10])();
那么函数指针数组又有说明用处呢?
答案:转移表
我们接下来举一个例子来说明一下转移表如何使用 (简单的计算机的两种实现方式):
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mlt(int a, int b)
{
return a * b;
}
int Div(int a, int b)
{
return a / b;
}
int main()
{
int a = 0, b = 0;
char ch;
printf("请输入两个整数:\n");
scanf("%d %d", &a, &b);
printf("请输入想要进行的操作:\n (+ - * /)\n");
scanf(" %c", &ch);
switch (ch)
{
case '+':
{
printf("%d", add(a, b));
break;
}
case '-':
{
printf("%d", sub(a, b));
break;
}
case '*':
{
printf("%d", mlt(a, b));
break;
}
case '/':
{
printf("%d", Div(a, b));
break;
}
default:
{
printf("输入错误!\n");
}
}
return 0;
}
运行结果:
这是用我们以往的知识来写一个简单的计算器,但是如果我们用转移表来实现的话,就会方便很多:
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mlt(int a, int b)
{
return a * b;
}
int Div(int a, int b)
{
return a / b;
}
int main()
{
int a = 0, b = 0, n = 0;
printf("请输入两个整数:\n");
scanf("%d %d", &a, &b);
int (*parr[5])(int, int) = { 0, add, sub, mlt, Div };
printf("请输入要进行的操作\n");
printf("1. 加法 2.减法\n3. 乘法 4.除法\n");
scanf("%d", &n);
printf("%d", (*parr[n])(a, b));
return 0;
}
运行结果:
使用了转移表之后,我们选择调用哪个函数只需要使用函数指针数组的对应的下标元素就好了,不需要像上述示例一样使用switch语句去判断,使得代码简单明了!!
同上述指针的定义相同,只需要在指针名之前添加*,加上函数指针数组的类型即可:
void func(int x)
{
}
int main()
{
void (*pfunc)(int) = func; //函数指针
void (*pfuncarr[5])(int); //函数指针数组
pfuncarr[0] = func;
void (*(*ppfuncarr)[5])(int);//指向函数指针数组的指针
ppfuncarr = &pfuncarr;
return 0;
}
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当 这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调 用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
qsort函数中的第四个参数就是一个很经典的回调函数,他的具体参数如下:
void qsort (void* base, size_t num, size_t size,
int (*compar)(const void*,const void*));
可知他有四个参数,最后一个参数是一个函数指针,调用了compar函数,故compar函数是一个回调函数。
以上就是该文章的全部内容了!!!
希望大家多多包涵,如果有错误希望大佬及时指出(毕竟我也只是初学者)O.o
感谢大家的收看!!!