正文
4.数组参数和指指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?
4.1一维数组传参
如果我们要将如下两个数组作为参数传递给test函数
#include<stdio.h> void test1()//参数该如何设计? void test2()//参数该如何设立? int main() { int arr1[10] = { 0 }; int* arr2[20] = { 0 }; test1(arr1); test2(arr2); }
下面展示的几种设计方式都是正确的:
void test(int arr[])//可以省略10 {} void test(int arr[10]) {} void test(int* arr) {} void test2(int* arr[20]) {} void test2(int** arr) {}
有的小伙伴可能不明白为何可以用指针作为参数来接收一维数组。
是因为,我们调用test函数时,都将数组名传递过去,而数组名又是首地址,当然可以用一个指针
变量来接收。
对于arr1,是一个整型数组,元素类型为int ,所以用int*的指针来接收;
对于arr2,是一个指针数组,元素类型为int* ,所以用int**的二级指针来接收。
4.2 二维数组传参
#include<stdio.h> int main() { int arr[3][5] = {0}; test(arr); return 0; }
如果我们要将二维数组arr传递给test函数,那么test函数又该如何如何设计呢?
void test(int arr[3][5]) {} //void test(int arr[][]) //{} void test(int arr[][5]) {} //void test(int* arr) //{} //void test(int* arr[5]) //{} void test(int(*arr)[5]) {} void test(int** arr) {}
如上面代码所示,除了被注释掉的代码,其他的都是行得通的。
对于第二种格式为什么是错的,是因为有这样的规定:
总结:二维数组传参,函数形参的设计只能省略第一个 [] 的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 这样才方便运算。
第四、五两种形式是接收一维数组的设计。
那么最后两种又为什么行呢?
参照第三节最后的二维数组与数组指针就不难理解了。
原因就是,二维数组的数组名就是首元素地址。而其实二维数组的每个元素又是一个数组。所以
我们可以用数组指针 int(*arr)[5] 或是 二级指针来接收。
4.3一级指针传参
一级指针传参相对简单,就用一个指针变量接收即可。
一级指针运用实例:
#include <stdio.h> 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; }
4.4二级指针传参
同样的,二级指针传参,也只需一个二级指针即可。
例如:
#include <stdio.h> void test(int** ptr) { printf("num = %d\n", **ptr); } int main() { int n = 10; int*p = &n; int **pp = &p; test(pp); test(&p); return 0; }
思考一下:
通过前面的二级指针与二维数组的关系,分析一下当函数的参数为二级指针时,可以接收什么参
数?大致有这么几种:
void test(char** p) { } int main() { char c = 'b'; char* pc = &c; char** ppc = &pc; char* arr[10]; test(&pc); test(ppc); test(arr);//Ok? return 0; }
5.函数指针
之前学到,&+变量名可以得到变量的地址,&+数组名可以取出数组的地址(其实是数组首元素的地址)。那么当我们第一次听到函数指针这个概念时,有没有首先想到函数也会有地址吗?怎么得到函数的地址?难道&+函数名就可以得到函数的地址吗?
其实还真是,看以下操作:
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
结果如下:
我们发现,不仅&+函数名可以得到函数的地址,就连函数名本身也是函数的地址。
既然得到了函数的地址,我们如何将函数的地址保存起来呢?
这就要用到函数指针了。
5.1函数指针的定义
下面我们看代码:
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void* pfun2();
首先,能给存储地址,就要求 pfun1 或者 pfun2 是指针,那哪个是指针?
答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
数,返回值类型为void。
下面我们多练习如何认识函数指针:
int (*pfun3)(int a, int b); //pfun3先和*结合,说明pfun3是一个指针,指向一个函数, //函数有两个参数,参数的类型都是int,函数的返回值是int类型 char* (*pfun4)(int* pa, int* (*parr)[10]); //pfun4先和*结合,说明pfun4是一个指针,指向一个函数, //函数有两个参数,参数的类型一个是int*,一个是数组指针,函数的返回值是char*类型
6.函数指针数组
首先提问,函数指针数组是一个指针还是数组?答案是是一个数组。
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int *arr[10]; //数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
//首先,函数指针数组是数组 parr[]; //数组的每个元素是函数指针,以无参函数,且无返回值的函数为例 void (*) (); //结合后 void (*) () parr[]; //规范的写法 void (*parr[])()
再多举几个例子吧:
int (*parr1[5]) (int a,int b); char (*parr2[10]) (int* a,int* b); double (*parr3[10]) (double a.double* b); int* (*parr4[10]) (int (*pfun) (int a,int b).int* a); //....
函数指针数组有什么用途呢?
例如,我们现在要实现一个计算器,包含加减乘除四种功能。我们可以将这四种功能分别用四个函数来实现。然后将四个函数对应的函数指针保存到一个函数指针数组里。当我们运行计算器的程序时,可以根据选择不同功能的选项,来找到函数指针数组不同下标所对应的不同函数,进行运算。
由于代码太长,将计算器的代码实现放在文章末尾。
7.指向函数指针数组的指针
首先,指向函数指针数组的指针是一个指针。
指针指向一个数组,数组的每个元素都是函数指针。
那么该如何定义一个指向函数指针数组的指针呢?
#include<stdio.h> 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; }
如果给我们一个指向函数数组的指针,我们不会辨别怎么办?
没关系,我们逐步来分解:
//指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; //首先我们看到*与ppfunArr结合 //所以ppfunArr是一个指针,我们将*ppfunArr记作a void (*a[])(const char*) //现在我们看到,a与[]先结合 //说明指针a指向的是一个数组 //接下来将a[]移除 void (*) (const char*) //剩下的部分我们已经学过,是一个函数指针 //说明数组的元素类型是函数指针 //总结,ppfunArr是一个指针,指向一个数组,数组的每个元素都是函数指针 //所以,ppfunArr称为指向函数指针数组的指针
8.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。具体的使用我们以库函数qsort为例。
附:
计算器一般实现:
#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("*************************\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("输入操作数:"); scanf("%d %d", &x, &y); ret = add(x, y); printf("ret = %d\n", ret); break; case 2: printf("输入操作数:"); scanf("%d %d", &x, &y); ret = sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("输入操作数:"); scanf("%d %d", &x, &y); ret = mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("输入操作数:"); scanf("%d %d", &x, &y); ret = div(x, y); printf("ret = %d\n", ret); break; case 0: printf("退出程序\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
计算器函数指针数组的实现:
#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; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 while (input) { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); if ((input <= 4 && input >= 1)) { printf("输入操作数:"); scanf("%d %d", &x, &y); ret = (*p[input])(x, y); } else printf("输入有误\n"); printf("ret = %d\n", ret); } return 0; }