EasyC30,函数指针进阶
大家好,我是梁唐。
这是EasyC++系列的第30篇,继续来聊聊函数指针。
想要追求更好阅读体验的同学,可以访问github仓库: EasyLeetCode 。 函数指针进阶
简单的函数指针比较简单,但对于复杂的情况则显得有些恐怖。下面我们来看下C++ primer当中提供的一些例子: const double* f1(const double ar[], int n); const double* f2(const double [], int); const double* f3(const double *, int);
这三个函数看起来长得不一样,但其实是等价的。因为在函数参数列表当中,数组和指针是等价的。其次我们可以在函数的原型中省略掉变量名,因此 const double ar[] 可以简化成 const double [] ,也可以写成 const double * 。
有了这三个函数之后,假设我们要声明一个指针,指向这三个函数。根据我们前文当中说过的,可以将函数名替换成 (*pt) 来实现: const double* (*pt)(const double *, int) = f1;
其实这个语句看起来就有些复杂了,整个语句的可读性很差。如果不是知道这里用的是一个函数指针,乍一看想要看明白估计不太容易。我们可以分成两个部分来理解,其中 const double * 是一个整体,表示函数的返回值类型是一个 const double * 也就是一个常量浮点数的地址。其次 (*pt) 是一个整体,代替了函数名,表示这是一个指向函数的指针。
在C++11当中提供了叫做 auto 的新特性,它可以帮助变量自动识别对应的类型,可以解决一些类型特别复杂的问题,比如: auto p2 = f2;
函数指针有两种调用方法,除了可以使用 (*p2) 的方式调用之外,也可以直接使用名称调用: const double* x = p2(ar, 3); const double* y = (*p2)(ar, 3);
显然前者更好,更清楚。这里其实有一个疑问,为什么这两种方式都可以执行呢?这是因为当我们执行 auto p2 = f2 的时候,其实是执行的 auto p2 = &f2 ,C++会隐式地将函数转换成函数的地址。因为函数的值本身就是一个地址,所以这两种方式才都能正确地运行。
问题还没有结束,假如我们要定义一个指向函数的指针数组呢?这应该怎么声明?
也就是 const double* (*pt)(const double *, int) 这样一个类型的数组,它应该怎么声明,这个方括号应该放在那里?
正确答案是放在括号里: const double* (*pt[3])(const double *, int);
因为运算符 [] 的优先级高于 * ,因此 *pt[3] 表示 pt 是一个长度为3的指针数组。其他的内容表明了该指针的类型。
由于我们定义的是一个数组,所以这里不能使用 auto ,因为自动类型推断只能用于单值初始化而不能用于初始化列表。
到这里还没结束,还有更恐怖的,如果我们想要定义一个指向这个数组的指针,应该怎么办呢?如果使用 auto 可以写成: auto ptr = &pt;
如果不使用 auto 呢?首先我们可以想到,这个声明是基于 pt 的,我们需要在 pt 的声明上加上一个 * ,但问题是加在哪里呢?
进一步分析,会发现我们需要指出这是一个指针,而不是数组。意味着核心的部分应该写成 (*ptr)[3] ,表示这是一个指向长度为3的数组的指针。因为 [] 的优先级更高,所以需要使用括号。如果写成 *ptr[3] 表示这是长度为3的指针数组。
我们进一步倒推, (*ptr)[3] 这个数组当中的元素是什么类型呢?是指向函数的指针,所以写出来结果是这样: const double *(*(*ptr)[3])(const double*, int) = &pt;
很明显,这样的定义非常非常的难以理解。而且这还不是最复杂的情况,比如函数的返回类型又是一个指向一个函数的指针……明摆着告诉我们含义我们仍然要推敲一会,如果在一段不明的代码当中遇到,可能会直接抓狂吧……
也正因此,C++11当中推出了 auto 特性,可以简化这种情况。
多说一句题外话,golang语言当中将变量的类型放在变量的后面而不是前面,其中一个原因就是为了解决类似情况的复杂性。
如果是golang来定义同样的内容,会是这样的: func f2(arr []float64, n int) *float64 { // todo } // 函数指针 var p1 func([]float64, int) *float64 = f2; // 函数指针数组 var pt [3]func([]float64, int) *float64; // 函数指针数组的指针 var ptr *[3]func([]float64, int) *float64 = &pt;
很明显,虽然变量类型写在变量后面刚开始会不太习惯,但是很明显这样要清晰很多。
酒泉的小吃糊锅,为什么外地没有?说到酒泉,大家都懂的它是新中国的航天之都,地处甘肃省西部,在河西走廊中段,也是一座历史古城,自西汉置郡已有二千多年的历史,自古就是丝绸之路上重要的历史文化名城,中国西部的军事重镇。
应该如何搭配香料?所谓卤菜中的君臣佐使就是给卤菜附其香,祛其腥,增加卤制品后味的作用,曾经很多人也问过小编,卤菜配制香料跟君臣佐使有关系吗?当然有关系香辛料即是给菜肴增加香味去腥味的香料也是中草药,
你在家都做什么菜?我们老两口在家的一天三顿饭是这样的。早餐每人一个煮鸡蛋一碗牛奶麦片粥,自己烤的面包,里面有核桃仁和葡萄干,加各种果酱。中餐主食米饭馒头大饼等换着花样吃,一荤两素三个菜,一般喜欢吃红
曲靖最出名的蒸饵丝店是哪家?讲真的,在曲靖小吃中,饵丝的确好吃,但吃多了不好消化,偶尔吃一次感觉很好,天天吃就耐不住了。在曲靖这么多年,钰树庄,大富贵,老三,老街都吃过,感觉都差不多,其中北颖园是最老的,但现
兰州夜市消失了哪些美食?以前,兰州正宁路夜市挺热闹的,大街小巷各大摊位上的美食琳琅满目的,每天晚上都有很多人来夜市摊吃烧烤吃麻辣烫吃面食的,现在这种样式一去不复返了,在夜市摊位上面找不到美味可口的小吃了。
怎样做一张春饼?怎样做一张春饼?大家好,我是麟大官人,我的回答是如果只是单纯想要做一张春饼其实非常的简单,比做包子馒头都要简单很多,但是要想做出一道香软耐放不开裂的春饼,做法上就会稍微复杂一点,不
天津有那些美食?天津风味小吃三绝的狗不理包子十八街麻花和耳朵眼炸糕,特色小吃糖礅大饼鸡蛋茶汤儿果仁张崩豆张面茶杨村糕干。特色早点煎饼馃子锅巴菜老豆腐果子烫面炸糕卷圈荷包蛋糖果子果篦儿。天津特产天津
你们想要的三星GalaxyS21Ultra镜头下的故宫雪景大片,它来了北京今年雪下得好早,还赶上了周末。下雪不去故宫那简直是不可饶恕的罪过。EXIF信息2。20厂商samsung型号SMG9980光圈4。9ISO125曝光时间0。02时间202111
去香港买iPhoneX划算吗?每年新iPhone一发布很多人都会想从香港买,一来港版iPhone的价格要比大陆便宜很多,二来港版的iPhone在大陆也能获得保修,所以综合来说是应该是最值得购买的版本。刚刚发布的
台湾香港和澳门的人平时聊天都用微信吗?感谢您的阅读!台湾香港澳门可不是马化腾的粉丝,微信在港澳台可没有那么吃香。当然,有人说,他们都用Facebook,其实也是不完全对的。我们今天来聊一聊,他们都用什么软件呢?台湾用户
进博会里的双循环中国买手在进博会里选购海外品牌借电商渠道下沉大部分人眼中的进博会食品及农产品展区里都是吃的,而小部分人的眼里,这里都是生意。买手李嘉此次参加进博会的目的是看看当前市场上有什么进口水果可以合作。他认为,中国目前的海外产品消费依