字符指针,数组指针,指针数组,数组传参和指针传参,函数指针,函数指针数组,指向函数指针数组的指针,回调指针,指针和数组面试题的解析
一、指针的基本概念
1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间
2.指针的大小是固定的4/8个字节(32位平台/64位平台)
3.指针是有类型的,指针的类型决定了指针的+/-这还能数的步长,指针解引用操作的时候的权限。
字符指针:指向字符变量的指针 -- char* a
数组指针:指向数组首元素地址的指针 -- int(*p)[ ]
指针数组:数组的元素是指针类型 -- int *p[ ]
函数指针:指向函数地址的指针 -- int (int* )(int int)
未申明的类型以整型为例,关于如何判断一个表达式是数组还是指针,我们知道[ ]的优先级比*高,如果我们p与*在括号类就是指针,指针指向的就是去掉(*p)后的类型,否则p先与[ ]结合,那么此时是一个数组,每个元素的类型就是去掉p[ ]的部分。
其次解引用,我们判断一个指针类型是int*或者char*,也就决定课了我们解引用的步长。
二、 这里是关于常量字符串的一个知识点,在我们的内存管理中,分四个部分:堆区、栈区、静态区、代码段。而我们见到的这种“abcdef”常量字符串就放在代码段,是不可被修改的。
在下面的这串代码,判断arr1和arr2是否相同,答案是arr1、arr2相同,p1和p2不相同,简单解释是,我们开辟的数组是两块不同的空间,而p1、p2都是从常量区那里去调用这个字符串的地址(只开辟了一次),详细解释我也在下面加了注释。
//int main()
//{
// char arr1[] = "abcdef";
// char arr2[] = "abcdef";
// const char *p1 = "abcdef";
// const char *p2 = "abcdef";
// //前两个不相同,后两个相同
// if (p1 == p2)//这两个字符串相同,常量字符串不可被修改,只能拿去拷贝之类,p1,p2的空间都用来存放该字符串的地址,因此他们的地址相同。p1和p2
// {//存的地址相同,p1、p2指向的字符串首元素地址是在常量字符区,只开辟一次,p1、p2都存放它,来使用它,那么
// 他们指向的地址相同
// printf("hehe\n");
// }
// else
// printf("haha\n");
// if (arr1 == arr2)//arr1,arr2里面都是首元素地址,
// {
// printf("hehe\n");
// }
// else
// printf("haha\n");
// return 0;
char *p = "abcdef";"abcdef"是一个常量字符串,就是把a的地址赋给了p,而不是将字符串“abcdef”放入字符指针p中,通过这个首地址就能找到整个字符串
三、 *parr是指针数组,数组的每个元素都是一个int* 指针,分别指向arr1/2/3,我们打印时候,*(parr[ i]+j), parr[ i ] 就相当于*(parr+i)。
//int main()
//{
// int arr1[] = { 1, 2, 3, 4, 5 };
// int arr2[] = { 2, 3, 4, 5, 6 };
// int arr3[] = { 3, 4, 5, 6, 7 };
// int *parr[] = { arr1, arr2, arr3 };
// int i = 0;
// for (i = 0; i < 3; i++)
// {
// int j = 0;
// for (j = 0; j < 5; j++)
// {
// printf("%d ", *(parr[i] + j));
// }
// printf("\n");
// }
// return 0;
//}
char** arr[5];//二级字符指针的数组, 表达式是个数组,每个元素是个二级指针,这就很好理解了。
这块写的是关于数组指针,它应用在二维数组中较多,接收二维数组首元素地址(也就是第一行的地址)。
//int main()
//{
// char *arr[5];
// char* (*pa)[5] = &arr;//首先(*pa)是一个指针,(*pa)[5]是一个数组指针:指向一个具有五个元素的数组
// //char*(*pa)[5]是这个(*pa)指针指向一个数组,这个数组有五个元素,且每个元素的类型是char*
// int arr2[10] = { 0 };
// int (*pa2)[10] = &arr2;
// return 0;
//}
我们写的是char*(*pa)【5】=&arr,我们不要误以为指针指向的是数组的地址,这里*pa=arr,其实指向的是数组首元素的地址,否则下面的第一行打印就是*pa+i再引用就是跳过一个数组,实际不是的,指的是第一个元素,而加上&是因为语法要求。
#include<stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int(*pa)[10] = &arr;//
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%p ", *(*pa + i));//*pa=arr,就是相当于首元素地址
}
}
for (i = 0; i < 10; i++)
{
printf("%d ", (*pa)[i]);//(*pa)[i]=*((*pa)+1)
}
}
上述的代码用数组指针来较麻烦,而且数组指针一般用于二级指针,所以不建议使用
四、 关于二维数组传参,形参可以是二维数组(不是二级指针,二级指针接收一级指针的地址,而二维数组的首元素地址是个数组地址
//int add(int x, int y)
//{
// int z = 0;
// z = x + y;
// return z;
//}
//int main()
//{
// int a = 10;
// int b = 20;
// //&函数名和函数名都是函数的地址
// //printf("%d\n", add(a, b));
// //printf("%p\n", &add);//打印函数的地址
// int (*pa)(int ,int) = add;//函数指针是一个指针,用来存放函数地址的一个指针,指针指向这个函数的地址
// printf("%d\n", (*pa)(2, 3));//注意的是int* pa(int,int)这是一个函数,该函数的参数是指针,需要加(*pa)才是函数指针
// printf("%d\n",pa(2,3));//对于函数指针来说,其实*没有作用,不解引用的话,我们直接通过pa找到这个函数的地址去调用这个函数,跟上述情况都可
// return 0;//但如果加*则必须将*与pa用括号括起来,否则就不要加*
//}
),也可以是数组指针,这里注意!!!
不管是一维数组还是二维数组,我们实参用数组名,形参用数组名或者指针都属于传址调用,都是可以改变原数组的值,一维数组里同样满足。
关于函数指针的概念,函数名加不加&都可以代表函数地址,
这里举了一些例子
//int arr[5];--arr是一个有五个元素的数组
//int * parr1[10];--parr1是一个数组,该数组有十个元素,每个元素的类型是int*,parr1是指针数组
//int(*parr2)[10];--parr2是一个指针,该指针指向一个数组,数组有10个元素,每个元素的类型是int-parr2是数组指针
//int(*parr3[10])[5]--parr3是一个数组,该数组有十个元素,每个元素是一个指针,该指针指向的数组的元素有五个元素,每个元素是int
最后两行重点理解:关于二维数组的解引用:首先p代表指针指向arr这个二维数组的首元素地址(第一行的地址);此时p+1代表的是指向第二行的一维数组的地址,当我们解引用后,得到的是第二行的数组名,也就是没有了指针指向,p代表得是指针指向该地址,我们解引用完之后,第二行的数组名和第二行的地址在数值上是相等的,所以你打印p+1跟*(p+1)结果一样,但意义不一样,此时*(p+1)得到第二行的数组名,此时我们再+1再解引用*((*p+1)+1),就是第二行的数组名+1,因为第二行的数组名+1相当于第二个元素的地址(数组名就是第一个元素的地啊)此时我们再解引用就是第二行第二个元素的值,
补充:数组名+1,是+数组元素大小的字节个数; 数组名地址+1,是+整个数组大小的字节数,所以在上面我们第一次不解引用的话就是该数组名的地址,
解引用完就是该数组名,但是你把解引用和不解引用打印出来结果是相同的
因为,你不解引用,是该数组的地址,解完该数组名代表的是首元素地址在数值上仍然与该数组的地址是相同的
//#include<stdio.h>
//int main()
//{
// int arr[3][5] = { { 1, 2, 3 }, { 2, 3, 4, 5, 6 }, { 3, 4, 5, 6, 7, } };
// int(*p)[5] = &arr;
// printf("%p\n", p[1]);
// printf("%p\n", *(arr + 1));
// printf("%p\n", arr[1]);//arr[i]=*(arr+i)=*(p+i)=p[i]
// printf("%p\n", *(*(p + 1) + 1));
// printf("%p\n", *(p + 1));
一级指针传参,可以用一级指针来接收,这时是同级接收,意味着传值调用,但数组那块不能这么理解,,或者我们用二级指针来接收,就是传址调用
void test1(int *p)//一级指针传参的一些可能性
{}
void test(char* p2)
{}
int main()
{
int a = 10;
int *p1 = &a;
test1(&a);
test(p1);
char ch = 'w';
char *p2 = &ch;
test2(&ch);
test2(p2);
return 0;
}
关于函数指针的概念,加不加&,函数名都可表示函数地址,但是我们对指针加*了,就必须加上(),否则就不是函数指针了,就成了指针函数了。
(*(void(*)() 0))():首先void(*)():是函数指针,放进()里就是将该指针后面的0进行强制类型转换,转换完就是一个地址,0就是这个函数的地址,然后再加*进行解引用找到这个函数最后加上一个()就是调用这个函数
//将0强制类型转成一个函数指针类型,该指针指向的函数类型是无参,返回类型是void,但我们将0变成这个函数的地址后多它进行解引用操作,再去调用以0为地址的无参函数
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
//&函数名和函数名都是函数的地址
//printf("%d\n", add(a, b));
//printf("%p\n", &add);//打印函数的地址
int (*pa)(int ,int) = add;//函数指针是一个指针,用来存放函数地址的一个指针,指针指向这个函数的地址
printf("%d\n", (*pa)(2, 3));//注意的是int* pa(int,int)这是一个函数,该函数的参数是指针,需要加(*pa)才是函数指针
printf("%d\n",pa(2,3));//对于函数指针来说,其实*没有作用,不解引用的话,我们直接通过pa找到这个函数的地址去调用这个函数,跟上述情况都可
return 0;//但如果加*则必须将*与pa用括号括起来,否则就不要加*
}
首先我们分析:它有(* ),是个指针,指向的地方是去掉(*)的剩余部分,在这里面有个()是函数,指向的这个函数的地址,而(void ()0),我们知道将一个数前面加(类型)是强制意思,这个就是说把0强转成void,综合起来就是指针指向的函数的地址,将0强转成void型作为这个函数的地址。
(*(void(*)() 0))():首先void(*)():是函数指针,放进()里就是将该指针后面的0进行强制类型转换,转换完就是一个地址,0就是这个函数的地址,然后再加*进行解引用找到这个函数最后加上一个()就是调用这个函数
//将0强制类型转成一个函数指针类型,该指针指向的函数类型是无参,返回类型是void,但我们将0变成这个函数的地址后多它进行解引用操作,再去调用以0为地址的无参函数
这个解释也放在注释里,而且很清晰
//void(*signal (int, void(*)(int)))(int)
//1.signal是一个函数声明
//2.signal函数的参数有两个,第一个是int,第二个是函数指针,该函数指针指向的函数的参数类型是int,返回值是int
//3.signal函数的返回类型也是一个函数指针:该函数指针指向的函数参数是int,返回类型是void
五、 回调函数就是一个通过函数指针调用的函数。如果你把函数的地址作为参数传递给另一个函数指针时,当这个指针被用来调用其所指向的函数时,这就是回调函数
回调函数:这里print函数的地址被调用到函数指针里去,用函数指针去接收这个函数的地址,我们在将通过修改p可以修改print函数里的内容。这就是回调函数,通俗的讲就是函数指针作为中间媒介去联系print函数和主函数部分。
print(char* str)
{
printf("hehe:%s", str);
}
void test(void (*p)(char*))//这里写的是函数指针来接收下面test(print)中print函数的地址
{
printf("test\n");
p("bit");//p指向这个函数,然后再通过p来找到这个print函数,
此时p中的这个bit传入print函数里去,被字符指针char*接收,在打印
}
int main()
{
test(print);//我们刚开始通过test函数将print函数的地址传进p指针里去
return 0;
}//这种机制就叫做回调函数,回调函数往往不是直接调用,而是通过中间媒介去调用
这里又是一些案例表达式:
int*parr[10]()--啥都不是
int(*)()parr3[10]--啥都不是
//char* my_strcpy(char* dest, const char* src);
//写一个函数指针pf能够指向my_strcpy
//写一个函数指针数组pfArr,能够存放4个my_strcpy函数的地址
//char* (*pf)(char* dest, const char* src) = my_strcpy;
//char* (*parr[4])(char*, const char*);
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,这就是回调函数
void calc(int(*pf)(int,int))//这个calc函数里的参数是个函数指针接收下面calc的函数地址,接着调用
计算器
void menu()
{
printf("***************************\n");
printf("******** 1.add. 2.sub ***\n");
printf("******** 3.mul. 4.div ***\n");
printf("***** 5.xor 0.exit *****\n");
printf("***************************\n");
}
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 xor(int x, int y)
{
return x^y;
}
int main()
{
int input = 0;
do{
menu();
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 5:
calc(xor);
case 0:
printf("退出\n");
break;
default:
printf("选择错误\n");
}
} while (input);
return 0;
}
以上就是关于指针、数组、函数、回调函数的一些详细讲解。