数组参数和指针参数
一维数组传参
一维数组传参时参数可以写成数组也可以写成指针。
void test(int arr[])
{}
void test(int* arr)
{}
void test1(int* arr1[])
{}
void test1(int** arr1)
{}
int main()
{
int arr[] = { 0 };
int* arr1[10] = { 0 };
test(arr);
test1(arr1);
return 0;
}
上述代码中数组传参的两种方法都是可以的。在第1行代码和第5行代码中[]内的数字可以写也可以不写甚至可以写错,在代码中不起作用。数组传参后的类型需要和数组中元素的类型一致。
二维数组传参
1. 二维数组传参时参数可以和主函数中的数组形式保持完全一致。
void test(int arr[3][5])
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
上述代码中数组传参时的行都可以省略,列都不能省略。 2. 二维数组传参时参数可以写成指向数组的指针。
void test(int(*arr)[5])
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
指针传参
一级指针传参时可以传递变量地址和保存地址的指针:
void test(int* a)
{}
int main()
{
int a = 0;
int* p = &a;
test(&a);
test(p);
return 0;
}
二级指针传参时参数部分直接设计成二级指针,可以传递一级指针的地址或二级指针或指针数组:
void test(int** ptr)
{}
void test1(int** ptr)
{}
void test2(int** ptr)
{}
int main()
{
int a = 0;
int* p = &a;
int** pp = &p;
test(pp);
test1(&p);
int* arr[] = { 0 };
test2(arr); //数组名是首元素地址,数组首元素是int*类型
return 0;
}
函数指针
函数指针是指向函数的指针,是存放函数地址的指针。 函数的地址:
int ADD(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
ADD(a, b);
printf("%d\n", ADD(a, b));
printf("%p\n",&ADD);
printf("%p\n", ADD);
return 0;
}
30
00007FF6A46913D4
00007FF6A46913D4
int ADD(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
int* p1 = &ADD;
printf("%p\n",p1);
int(*p2)(int ,int) = ADD;
printf("%p\n", p2);
return 0;
}
00007FF77A981352
00007FF77A981352
::: tip 函数地址的取出有两种形式:
int* p1 = &ADD;
int(*p2)(int ,int) = ADD;
::: “&函数名”和“函数名”都表示函数的地址。
函数指针的写法: 上图中代码表示将函数ADD存在指针p中。 函数参数部分可以写参数名int x,int y也可以不写,要把所有的函数参数类型都列出来。 函数指针:
int ADD(int a, int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
int(*p)(int, int) = ADD;
printf("%d\n", p(2, 3));
printf("%d\n", (*p)(2, 3));
return 0;
}
5
5
上述代码中第11行和12行代码的两种写法都可以。 ::: tip 两种写法都比较容易理解。 第10行代码将函数ADD存在指针变量p中,即ADD等同于p,调用函数时p(2,3)等同于ADD(2,3)。 第12行代码符合对调用指针函数时需要解引用的定义。 :::
void Print(char* ptr)
{
printf("%s\n", ptr);
}
int main()
{
void(*p)(char*) = Print;
(*p)("Hello World");
return 0;
}
Hello World
上述代码第8行将函数Print的地址存放在指针p中,第9行p中存放函数Print地址,p解引用找到地址调用传参(字符串)。 ::: warning *函数指针和数组指针一样,指针函数p和需要使用圆括号括起来,否侧指针函数p先和后面的圆括号组成函数,不能保存后面函数的地址。*
void (*p1)() = Fun - p1是函数指针
void *p2() = Fun - p2是函数
:::
两个例题
调用0地址处的,参数为无参,返回类型为void的函数:
(*(void(*)())0)();
上述代码中,void (* )()是一个函数指针类型,放入圆括号中表示强制类型转换,(void (* )())后的0表示一个函数的地址,前面加* 表示解引用,找到该函数(函数无参,返回类型为void),调用该函数(* (void()())0)(),最后一对原括号表示无参。 综上该代码的含义是:*把0强制类型转换为函数指针类型,该指针指向函数无参,返回类型为void,0变为函数地址后,解引用调用0地址处的该函数。**
上述代码的实际应用场景如:编写一个独立运行于某微处理器上的C程序,当计算机启动时,硬件调用首地址为0位置的函数。
void (*signal(int, void(*)(int)))(int);
上述代码中,signal加后面的圆括号表示是一个函数,函数有两个参数,分别是int,void(* )(int),函数的第二个参数void(* )(int)表示函数指针(指针指向函数的参数类型为int,返回类型为void)。即signal函数的两个参数类型分别是整型和函数指针类型。函数中去掉函数名和参数剩下的是函数类型,即void (* )(int)是函数signal的类型。void ()(int)是一个函数指针,函数参数的类型是int,返回类型是void。 综上该代码的含义是:*signal是一个函数声明。signal函数的参数有2个,第一个是int,第二个是函数指针:该函数指针指向的函数的参数是int,返回类型是void。signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回类型是void。**
::: warning 上述代码不能写成如下形式:
void(*)(int)signal(int, void(*)(int));
函数指针中代表指针的*必须和函数名写在同一个圆括号内。 :::
函数简化:
void (*signal(int, void(*)(int)))(int);
上述代码较难理解,可以简化为:
typedef void(*p)(int);
p signal(int, p);
上述代码第一行表示将void()(int)的函数指针类型简化为p。 ::: warning *typedef在定义简单别名时一般将别名放在最后,如:typedef unsigned int UNI中UNI就是unsigned int类型的别名。对于函数指针来说,把函数指针重新起名,新的名字在书写时应该和放在同一个圆括号中。* 上述代码中的p就是void( *)(int)类型的别名。 :::
函数指针数组
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 (*parr[4])(int, int) = { Add,Sub,Mul,Div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ", parr[i](2,3));
}
return 0;
}
5 -1 6 0
上述代码中,指针变量可以存储Add的地址,也可以存储Sub、Mul、Div的地址,但只能存储一个地址,要想把所有的地址都存储起来,就需要函数指针数组。第20行代码中,parr先和优先级更高的方括号结合形成一个4个元素的数组,去掉数组名和方括号,剩下的int (*)(int, int)是函数指针类型,即函数指针数组parr[4]中有4个元素,每个元素的类型是函数指针。最后再使用大括号对数组初始化。
写出下面函数指针和函数指针数组:
char* my_strcpy(char* dest, const char* src);
char* (*pf)(char*, const char*) = my_strcpy; //指针pf指向my_strcpy
char* (*pfrr[4])(char*, const char*) = { 0 }; //函数指针数组
函数指针数组的使用(转移表)
计算器函数:
void menu()
{
printf("#####################\n");
printf("###1.Add 2.Sub###\n");
printf("###3.Mul 4.Div###\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 main()
{
int a = 0;
int b = 0;
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入两个操作数:>");
scanf("%d %d", &a, &b);
printf("%d\n", Add(a, b));
break;
case 2:
printf("输入两个操作数:>");
scanf("%d %d", &a, &b);
printf("%d\n", Sub(a, b));
break;
case 3:
printf("输入两个操作数:>");
scanf("%d %d", &a, &b);
printf("%d\n", Mul(a, b));
break;
case 4:
printf("输入两个操作数:>");
scanf("%d %d", &a, &b);
printf("%d\n", Div(a, b));
break;
case 0:
printf("退出\n");
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
使用函数指针数组简化:
void menu()
{
printf("#####################\n");
printf("###1.Add 2.Sub###\n");
printf("###3.Mul 4.Div###\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 main()
{
int a = 0;
int b = 0;
int input = 0;
int* (*parr[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input > 0 && input < 5)
{
printf("请输入操作数:>");
scanf("%d %d", &a, &b);
int ret = parr[input](a, b);
printf("%d\n", ret);
}
else if(input == 0)
{
printf("退出\n");
}
else
{
printf("输入错误\n");
}
} while (input);
return 0;
}
上述代码使用函数指针数组进行代码优化后,日后需要增加其他运算时,可以不用增加switch-case语句,避免代码随着功能增加越来越长。 ::: tip 如上述代码中第30行代码int*(*parr[5])(int, int) = { 0,Add,Sub,Mul,Div }是通过下标找到元素,再找到元素所指向的函数。 这种函数指针数组称为转移表。 :::
指向函数指针数组的指针
将函数指针数组的地址存放在一个指针中:
int(*pfarr[10])(int, int);
int(*(*ppfarr)[10])(int, int) = &pfarr;
::: tip 思考过程: 在int( * pfarr[10])(int, int)的基础上修改,ppfarr是指向pfarr的指针,只需要将int( * pfarr[10])(int, int)中的pfarr改为ppfarr,并将ppfarr写成指针类型( * ppfarr)就可以了。 ::: 上述代码int( * ( * ppfarr)[10])(int, int) = &pfarr的类型是int( * )(int, int) ,ppfarr先和 * 结合形成指针,然后指向[4]形成有4个元素的数组,每个元素的类型就是int( * )(int, int)。
::: warning
int(*ppfarr)(int, int); //函数指针
int(*(ppfarr)[10])(int, int); //函数指针数组
int(*(*ppfarr)[10])(int, int); //指向函数指针数组的指针
如上,将第3行代码中的ppfarr后加上[],又可以将该指针存储在数组中。 :::