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

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

image.png


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默认也是内联的,内联是在调用节点展开,有点类似宏展开。

目录
相关文章
|
1月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
103 59
|
27天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6
|
1月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
23 0
C++ 多线程之线程管理函数
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
154 1
|
1月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
30 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
26天前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
32 0