写点什么

Android C++ 系列:C++11 函数特殊特性

作者:轻口味
  • 2023-04-24
    北京
  • 本文字数:2521 字

    阅读完需:约 8 分钟

Android C++系列:C++11函数特殊特性

1. 背景

在 Python 中函数有默认参数等,在 C++11 中我们发现 C++也支持了默认参数;还有 C++特有的内联函数、constexpr 函数等知识都有不少细节,本文对这些知识做详细介绍。

2. 默认参数

我们在 Java 中重载函数时经常有这样的形式:


public CustomView(Context context) {    super(context);  }
public CustomView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context, attrs); }
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); }
复制代码


同一个函数因为参数个数不同最后定义了好几种形式,有些场景我们可以这样理解,参数较少的函数只是参数较多的函数的某个参数是个特定值,比如上面第二个构造函数可以理解成第一个构造函数的 defStyleAttr 为 0,我们不去使用这个参数。这样就面临一个问题,明明一个函数可以解决的事情,最后搞出好几个函数来,有没有一种简便方法呢?在 Python 中我们用了默认参数实现,C++11 也提供了这种特性。


某些函数有这样一种形参,在函数的很多次调用中它们都被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认实参(default argument)。调用含有默认实参的函数时,可以包含该实参,也可以省略该实参。


实际的代码工程中经常看到这种(随便从工程代码截取一段):


std::future<bool> notifyOfWakeWord(        capabilityAgents::aip::AudioProvider wakeWordAudioProvider,        avsCommon::avs::AudioInputStream::Index beginIndex,        avsCommon::avs::AudioInputStream::Index endIndex,        std::string keyword,        std::chrono::steady_clock::time_point startOfSpeechTimestamp,        std::shared_ptr<const std::vector<char>> KWDMetadata = nullptr) override;
std::future<bool> notifyOfTapToTalk( capabilityAgents::aip::AudioProvider tapToTalkAudioProvider, avsCommon::avs::AudioInputStream::Index beginIndex = capabilityAgents::aip::AudioInputProcessor::INVALID_INDEX, std::chrono::steady_clock::time_point startOfSpeechTimestamp = std::chrono::steady_clock::now(), capabilityAgents::aip::Initiator initiator = capabilityAgents::aip::Initiator::TAP, std::string keyword = "", float wakeUpConfidence = 0.f) override;
复制代码


再举个例子说明:


void setPosition(int x, int y, int z = 0);
复制代码


我们设置坐标值,如果是二维空间 z 坐标都为 0,这时候我们只需要这样调用:


setPosition(1,1);
复制代码


如果是三维需要设置 z 坐标:


setPosition(1,1,1);
复制代码


我们可以定义一个或者多个形参的默认值,但是要注意:


  • 一旦一个形参被赋予了默认值,它后面的所有形参都必须有默认值,换句话说就是默认参数必须放在参数列表的最后面。

  • 调用函数时,其中一个参数使用了默认值,这个参数对应形参里面后面的所有参数都必须使用默认值。


**最佳实现:**设计含有默认实参的函数时,尽量让不怎么用到默认值的形参出现在前面,让经常使用默认值的形参出现在后面。


对于函数的声明来说,我们的习惯是将其放在头文件中,并且一个函数只声明一次,但是 C++中多次声明同一个函数也是合法的。我们在给定的作用域中一个形参只能被赋予一次默认实参。意味着,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,而且该形参右侧的所有形参必须都有默认值


通常我们使用的默认实参都是字面值,其实只要表达式的类型能转换成形参所需的类型,这个表达式就能作为默认实参。注意:局部变量不能作为默认实参。


int dz= 100;void setPosition(int x, int y, int z = dz);
复制代码


用作默认实参的名字在函数声明所在的作用域内解析,而这些名字的求值过程发生在函数调用时:


int dz = 100;void setPosition(int x,int y int z = dz);void fun(){  dz = 200;  setPosition(100,100);}
复制代码


我们在 fun 内部改变了 dz 值,所以调用 setPosition 时传递的是更新过的值。

3. 内联函数

我们一般的函数调用会有以下动作:


  1. 调用前先保存寄存器;

  2. 在返回时恢复;

  3. 如果需要会拷贝实参

  4. 程序转向一个新的位置继续执行。


总结下来就是函数调用是有不小开销的。有没有办法优化这块呢?答案是内联函数


内联函数实际就是在每个调用点上”内联的“展开。


比如我们定义了一个内联函数:


inline int min(int x , int y){  return x > y ? y : x;}
复制代码


调用函数:


std::cout << min(1,2) << std::end;
复制代码


再编译过程会展开成下面形式:


std::cout << (x > y ? y :x) << std::endl;
复制代码


上面也看到了,内联函数的定义是在函数返回类型前面加上关键字 inline


内联关键字是想编译器发出请求,编译器具体实现时也可以忽略这个请求。


内联机制用于优化规模较小,流程直接,频繁调用的函数。对于大部分编译器,不支持内联递归函数,对于 75 行的函数也大概不允许在调用节点内联展开。

4. constexpr 函数

constexpr 函数是指能用于常量表达式的函数。定义 constexpr 函数的方法与其他函数类似,不过要遵循以下几项规定:


  1. 函数的返回类型及所有形参的类型都得是字面值类型

  2. 函数体中必须有且只有一条 return 语句。


constexpr int getX(){  return 20;}constexpr int x = getX();
复制代码


执行初始化任务时,编译器把对 constexpr 函数的调用替换成其结果值。和 inline 一样都是在编译时,所以为了能在编译过程中随时展开,constexpr 函数被隐式地指定为内联函数。


constexpr 函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。例如,constexpr 函数中可以有空语句、类型别名以及 using 声明。


最佳实践:


内联函数和 constexpr 函数可以在程序中多次定义。因为编译器要想展开函数仅有函数声明是不够的,还需要函数的定义。但是对于某个给定的内联函数或者 constexpr 函数来说,它的多个定义必须完全一致。基于这个原因,内联函数和 constexpr 函数通常定义在头文件中。

5. 总结

本文介绍了 C++函数的特殊语法:默认参数、内联函数、constexpr 函数。其中默认参数是 C++11 新特性,constexpr 默认也是内联的,内联是在调用节点展开,有点类似宏展开。

发布于: 刚刚阅读数: 3
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017-10-17 加入

Android、音视频、AI相关领域从业者。 欢迎加我微信wodekouwei拉您进InfoQ音视频沟通群 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
Android C++系列:C++11函数特殊特性_c++_轻口味_InfoQ写作社区