本文是读完《c和指针》后记录的一些关于函数和指针中值得注意的细节,如有错误,还望指正;你还可以阅读指针,指针与数组了解更多关于指针的内容
递归
递归函数就是直接或间接调用自身的函数,如下例:
void val_to_ascii(int num)
{
int tmp = num/10;
if( tmp != 0 )
val_to_ascii(tmp);
putchar('0' + num%10);
}
int main(void)
{
val_to_ascii(12375);
return 0;
}
输出为:
- 递归分为两个阶段:
- 递推 : 把复杂问题的求解往简单推
- 回归 : 当获得最简单的情况后,逐步往回得到复杂的解- - 递归需要两个条件:
- 限制条件,当符合这个条件时便不再继续递推
- 每次递推后都要越来越接近这个限制条件 - 当递归函数调用时,每进行一次调用,都将创建一批变量,为其分配内存空间,他们将掩盖递归函数前一次调用时创建的变量,递推阶段就相当于这样一层层往上叠,直到最后一层调用返回,便开始往下释放,变量从堆栈中销毁;当递归的优点可以弥补他的效率开销时,可以使用这个工具
区分指针数组 & 数组指针
线来看看指针数组与数组指针的区别:
首先我们了解一下几个操作符的优先级,然后再看如何区分;下面操作符至上往下优先级降低
1 ( ) 聚组
2 [ ] 下标引用
3 * 间接访问
- 声明1:
char *array[];
:根据操作符优先级,先执行下标引用符[ ],故array是一个数组,元素为 char* 类型,故array是一个指针数组 - 声明2:
char (*array)[10];
首先执行的是聚组操作符,则array是一个指针,它指向包含10个char型元素的数组,故array是一个数组指针
区分指针函数 & 函数指针
同样,首先我们了解一下几个操作符的优先级,然后再看如何区分;下面操作符至上往下优先级降低
1 ( ):聚组----------(表达式)
2 ( ):函数调用----函数名(参数)
3 * :间接访问----*指针变量名
注:上面两个()功能是不同的
- 声明1:
int *func();
根据操作符优先级,先执行函数调用符(),故func是一个函数,返回值为 int* 类型,故func是一个指针函数 - 声明2:
int (*func)();
这里首先要执行的是聚组操作符,则func是一个指针,它指向一个函数,故func是一个函数指针 - 声明3:
int *(*func)();
我想了解了声明1,2,这个声明应该也不难了,func是一个指向返回值为int*类型的函数指针
两个错误用法
- 声明1:
int func()[];
按照正常的理解,定义的func是一个函数,其返回值为一个int型数组,相当于数组函数
- 但是:函数只能返回标量值,不能返回数组,故该用法是非法的 - 声明2:
int func[]();
按照正常的理解,定义的func是一个数组,其中的元素是返回值为int型的函数,相当于函数数组
- 但是:数组元素的长度必须是相等的,但你很难使多个函数的大小相等,故该用法是非法的
其他正确用法
- 声明1:
int (*func[])();
函数指针数组
- 先执行聚组符内的表达式
- func首先执行下标引用,显然func是一个数组
- 元素是指针类型
- 该指针指向的是一个函数,该函数返回int型数据 - 声明2:
int *(*func[])();
- 这个声明于第一个相比只多了一个 * 操作符,代表该指针指向函数的返回值是 int* 型
关于函数指针
此处会讲到函数指针的初始化以及如何通过函数指针来进行函数的调用
初始化示例
int test(char *a,char *b);
int main(void)
{
int (*p0)(char * , char * ) = test;
int (*p1)(char * , char * ) = &test;
int (*p2)(char *a, char *b) = test;
/* .
.
.
*/
}
int test(char *a,char *b)
{
return ( (a==b) ? 1 : 0 );
}
初始化说明
- 在进行函数指针定义时,必须有一个模板,该模板就是它将要指向的函数,返回值类型,参数类型都要与将要指向的函数一致
- 在函数指针初始化之前需要有指向的函数原型,如上第一条语句
- 以上p0,p1,p2除了名字不同其他都是一致的,参数中的具体变量名可以省略,如p0和p2
- 函数与数组不同:函数名 / &函数名均表示指向该函数的指针
- 编译器会将函数名隐式转换为函数指针
- &函数名只是显式说明编译器将执行转换
函数调用示例
int test(char *a,char *b);
int main(void)
{
int (*p)(char * , char * ) = test;
printf("%d\n", test("gfg","gfg") );
printf("%d\n", (*p)("gfg","gfg") );
printf("%d\n", p("gfg","gfg") );
return 0;
}
int test(char *a,char *b)
{
return ( (a==b) ? 1 : 0 );
}
函数调用说明
编译器在编译函数部分时,都会先将函数名转变为函数指针,再加入调用指令
- 以上给出了三种调用方式,结果都是相同的
- 第一种:直接使用函数名调用,执行了2步
- 函数名转为指针
- 指针调用 - 第二种:间接访问函数指针,执行了3步
- 间接访问操作,得到函数名
- 函数名转为指针
- 指针调用 - 第三种:直接调用函数指针,执行了1步
- 指针调用
回调函数 & 普通函数
普通函数大家应该都耳熟能详了,但回调函数可能用的相对较少,那什么是回调函数?
- 来自百度百科:如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数
- 来自维基百科:把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调,而先被当做指针传入、后面又被回调的函数就是回调函数
感受下面示例:
/********回调函数*******/
void Callback_1(char *str)
{
printf("%s \n",str);
}
void Callback_2(char *str)
{
printf("%s \n",str);
}
/********处理函数*******/
void Handle( void (*Callback)(char*), char *str )
{
Callback(str);
}
/*********主函数********/
int main(void)
{
Handle(Callback_1,"first" );
Handle(Callback_2,"second");
return 0;
}
结果为:
转移表
转移表也是函数指针中常用的,它通常与函数指针数组联系起来,如下计算器示例:
switch(oper)
{
case ADD:
result = add(a,b); break;
case SUB:
result = sub(a,b); break;
case MUL:
result = mul(a,b); break;
case DIV:
result = div(a,b); break;
...
}
//===》可以用转移表代替,用到函数指针数组,如下
double add(double,double);
double sub(double,double);
double mul(double,double);
double div(double,double);
...
double (*oper_fun[])(double,double)={add, sub, mul, div, ...};
result = oper_fun[oper](a,b);
注:使用转移表时,应该始终验证下标的有效性