• C++中的默认参数规则


    C++中的默认参数规则

    C++的默认参数规则其实是一个非常容易掉坑的规则,尤其是当一个函数拥有多个声明的时候,每个声明的默认参数可以各不相同,在调用时又可能与每个声明都不同;这篇博客稍微列举一下C++中的默认参数规则。

    前置

    在开始之前,我们先来复习一下,函数可以有多个声明,定义 是有函数体的声明。默认参数则是函数声明中使用特殊语法(decl-specifier-seq declarator = initializer)为某个参数提供的默认值。这种语法就像在参数列表中写参数对象的拷贝初始化一样。

    void foo(int a, int b = 0);
    void func(int a = 1, int b = 2, int c = 3);
    

    默认参数语法存在的目的,是为用户在函数调用时,可以不提供尾随参数。

    func();
    func(1);
    func(1,2);
    

    非常容易理解,由于C++目前还没有像python那样指定参数的语法,因而需要提供默认值的参数必须放到参数列表的后面。实质上,这相当于编译器替用户向函数中传递参数。

    那么,这里就有这么几个问题,什么地方可以有默认参数?哪些东西可以作为默认参数?当函数有多个声明时,默认参数如何工作?

    什么地方可以有默认参数?

    到我开始写这篇文章为止,C++允许普通的函数声明(包括类成员函数)、lambda表达式中使用默认参数,而不允许函数指针、函数引用以及typedef声明中出现默认参数。具体可见这里。所以,在需要管理各种包装计算函数的对象的场景中(algorithm factory),由于大部分手法使用函数指针进行,保存默认参数需要使用额外的空间并在运行时完成。

    这里可以稍微留个问题,std::function支持默认参数吗?如果不,为什么?

    什么可以作为默认参数?

    从文法上说,所有能做initializer的东西都能作为默认参数,但问题在于,有些东西出现在initializer中是不被允许的,这个范围还挺广。

    1. 局部变量不能在默认参数中
    2. this指针不能在默认参数中
    3. 其他的参数不能在默认参数中
    4. 非静态成员不能在默认参数中
    5. 有捕获内容的lambda表达式不能在默认参数中
    int main(int argc = 0,char** argc = {0});  // ok
    int foo(int a1, int a2, int a3 = a1 + a2); // bad
    int func(int a1, int a2, int a3 = 1 + 2); // ok
    void helper()
    {
        int n = 1;
        int func(int a1 = n);//bad
    }
    //C is a class with copy constructor
    C::foo(C p = *this); // bad
    
    class C
    {
        static int s;
        int a;
        //void func(int n = a);//bad
        void func(int n = s);//OK
    }
    
    //after C++11
    int globalV;
    void defaultFuncs(int a1 = ([]()->int {return 0; })(), int a2 = 1, int a3 = 3 + 2);//ok
    //void defaultFuncs(int a1 = ([]()->int {return globalV; })(), int a2 = 1, int a3 = 2);//bad
    

    并且,由于默认参数实质上就是编译器替用户填参数,而函数调用时会发生argument到parameter的拷贝初始化,因而这个initializer必须要能够满足到相应parameter的拷贝初始化。

    除此之外,其它的东西都可以做默认参数。

    多个声明与默认参数

    如果每个函数都只有一个声明兼定义,那么事情会简单很多,但是,在C++中,一个函数可以有多个声明,但只能有一个定义,这和名称查找规则共同协作,构成了C++的分离编译特性。随之而来的,默认参数在多个声明之间有很有趣的工作特性。

    多个声明之间的默认参数组合

    第一个特性,就是不同声明之间的默认参数是能组合的。

    void multi(int a1, int a2, int a3)
    {
    	std::cout << a1 << a2 << a3 << '
    ';
    }
    
    void multi(int a1,int a2, int a3 = 3);
    
    void multi(int a1, int a2 = 2, int a3);
    
    void multi(int a1 = 1, int a2, int a3);
    
    int main()
    {
        multi();//ok ,call multi(1,2,3);
    }
    

    这是一个很隐蔽的特性,隐蔽到笔者的VS2017intelliScene会对它报错,然而编译还是能通过。事实上,标准中有这么一句规定:

    所有的有默认参数的形参后面,所有的形参都必须在这个声明或者在先前的声明提供默认参数,或者是参数包。

    在函数调用点,实际上的默认参数是函数所有可见声明的默认参数的联合。但是相应的,在这个可见集合中,不能有对于同一个形参重复的默认参数声明,即使是同一个值也不行。

    int foo(int,int);
    int foo(int a1, int a2 = 0);
    int foo(int a1, int a2 = 0);//bad
    

    内部作用域

    简单而言,内部作用域中可以重新声明一个函数,并且可以忽视外部声明的默认参数。在这个内部作用域中的函数调用,其默认参数集合也是这个这个作用域中所有声明的默认参数联合。当然,这仅仅是可见声明这个概念,也就是名称查找的小游戏而已。

    同理,如果你外部作用域与内部作用域均有默认参数定义,那么using会同时把默认参数导入进来。

    其它的小规则

    默认参数还有一些其它的小规则。

    在类外定义的函数,可以将其默认参数与声明组合,但不能将成员函数变成构造函数。

    虚函数的默认参数由静态类型决定。

    除了调用运算符以外,其它的运算符都不能有默认参数。

    对于在不同翻译单元内定义的内联函数,那么在每个翻译单元末尾,默认参数集都需要相同。

    友元函数声明如果有默认参数,那么这个声明必须是定义并且在这个翻译单元中不会再有别的声明

  • 相关阅读:
    HTML5中的audio在手机端和 微信端的自动播放
    vue框架
    购物车原理
    angular前端框架
    -webkit-line-clamp超过两行就出现省略号
    jQuery事件委托
    淘宝橱窗
    选字游戏
    大众点评订单分库分表实践
    业界难题-“跨库分页”的四种方案
  • 原文地址:https://www.cnblogs.com/eadle/p/8819381.html
Copyright © 2020-2023  润新知