zoukankan      html  css  js  c++  java
  • 有趣的参数收集

      周五面试的时候面试官提到了javascript是如何来实现参数收集的一个问题,记忆中模糊记得好像又该argument这个关键词是可以实现函数对象的参数收集的,回来后查询的先关资料,由于平时喜欢用python来实现不同的语言特性和算法,同时又想:既然高级语言或者说python和javascript这样的解释性语言实现了参数收集的特性,那设计者或者底层开发语言C是如何实现的啦。一下是我简单的理解和总结:

    一、python的中参数收集

      其实python中是有对应的语言特性来实现参数收集的,而且还有参数展开的效果。其中收集参数分为:

    1.位置参数收集,语法为函数的形参格式为 *params,返回值为tuple(元组def args_collect1(*param):

    print param
    a=(1,3)
    args_collect1(a)
    ((1, 3),)#返回为元组

    位置参数搜集的函数形参必须在参数的最后一个位置,否则为语法错误

    def args_collect2(*param,preAgr):
        print preAgr,param
    SyntaxError: invalid syntax
    应该为:
    def args_collect2(preAgr,*param):
        print preAgr,param
    args_collect2('head',a)
    output:('head', (1, 3))
    当然你可以这样调用
    args_collect1('head','man',(175,50),23)
    output:('head', 'man', (175, 50), 23)

    2.关键字收集,语法为函数的形参格式为 **params,返回值为dict(字典)

    所谓关键字收集就是在函数形参中需要声明关键字,然后对字典进行解包处理

    def args_keywds_collect1(**param):
        personInfo= param
        print 'person info
    name:%s
    age:%s
    other	weight:%d	height:%d'%(personInfo['name'],personInfo['age'],personInfo['other']['weight'],personInfo['other']['height'])
    args_keywds_collect1(name='roy',age=26,other={'weight':50,'height':176})
    output:

    roy
    person info
    name:roy
    age:26
    other weight:50 tall:176

    那混合使用的效果啦?

    def args_mix(x,y,z=0,*param1,**param2):
        print x,y,z
        print param1
        print param2
    args_mix(1,2,3,4,5,p1='6',p2='7')
    output:1 2 3
    (4, 5)
    {'p2': '7', 'p1': '6'}

    可以看出python先对参数扫描后对其处理的

    3.参数展开-解包

    前面调用函数的时候实参接收一个dict的对象,形参通过**param的方式参数收集进行inpack。事实上有时候我们需要传入一个类似json格式的对象,用函数来进行onpack,还是用person那个例子

    def args_keywds_extend(name,age,other):
        print 'name:%s,age:%s'%(name,age)
        for o in other:print '%s:%s	'%(o,other[o])
    person={'name':'roy','age':26,'other':{'weight':50,'height':176}}
    args_keywds_extend(**person)
    output: name:roy,age:
    26 weight:50 height:176

    好,python的参数收集到此告一个段落,下面我们来看javascript是如何实现收集参数的

    二、javascript的参数收集

    javascript的参数收集可以利用ECMAScript arguments对象来实现。

    注释:与其他程序设计语言不同,ECMAScript 不会验证传递给函数的参数个数是否等于函数定义的参数个数。开发者定义的函数都可以接受任意个数的参数(根据 Netscape 的文档,最多可接受 25 个),而不会引发任何错误。任何遗漏的参数都会以 undefined 传递给函数,多余的函数将忽略。
    from http://www.w3cschool.cn/pro_js_functions_arguments_object.html

    根据w3c的文档arguments的length最多可以接受25个参数。

    来看一个比较经典的arguments参数收集实现

    function format(string) {
        var args = arguments;
        var pattern = new RegExp("%([1-" + (arguments.length-1) + "])", "g");
        return String(string).replace(pattern, function (match, index) {
            return args[index];
        });
    };
    var aformat = format("And the %1 want to know whose %2 you %3", "papers", "shirt", "wear");
    output:And the papers want to know whose shirt you wear

    这里arguments不是一个数组(arguments instanceof Array为false),但可以按数组下标进行访问.对于arguments不是数组类型,网友寻水的鱼给出了解决方案进行转换,以下为摘抄:

    通过

    var args = Array.prototype.slice.call(arguments);

    call(obj,当前函数使用的参数列表)
    call方法第一个参数为一个对象,这个传进去的对象将调用slice函数.因为arguments不是一个数组,所以不能直接调用slice方法,所以只能使用''对象冒充''方法了
    这样,数组变量 args 包含了所有 arguments 对象包含的值。

    对上面的format函数进行改造一下,使用 arguments 对象能够简短我们编写的 Javascript 代码量。下面有个名为 makeFunc 的函数,它根据你提供的函数名称以及其他任意数目的参数,然后返回个匿名函数。此匿名函数被调用时,合并的原先被调用的参数,并交给指定的函数运行然后返回其返回值。

    function makeFunc() {
      var args = Array.prototype.slice.call(arguments);
      var func = args.shift();
      return function() {
        return func.apply(null, args.concat(Array.prototype.slice.call(arguments)));
      };
    }

    arguments有一个不可枚举的属性callee(不能用for in读出,可用HasOwnProterty(name)来判断),arguments.callee为正被执行的 Function 对象。slice时己把当前函数指针copy了过去,所以args的第一个元素为函数类型。

    makeFunc 的第一个参数指定需要调用的函数名称(是的,在这个简单的例子中没有错误检查),获取以后从 args 中删除。makeFunc 返回一个匿名函数,它使用函数对象的(Function Object)apply 方法调用指定的函数。

    apply 方法的第一个参数指定了作用域,基本上的作用域是被调用的函数。不过这样在这个例子中看起来会有点复杂,所以我们将其设定成 null ;其第二个参数是个数组,它指定了其调用函数的参数。makeFunc 转换其自身的 arguments 并连接匿名函数的 arguments,然后传递到被调用的函数。

    有种情况就是总是要有个输出的模板是相同的,为了节省每次是使用上面提到的 format 函数并指定重复的参数,我们可以使用 makeFunc 这个工具。它将返回一个匿名函数,并自动生成已经指定模板后的内容:

    var majorTom = makeFunc(format, "This is Major Tom to ground control. I'm %1.");
    你可以像这样重复指定 majorTom 函数:
    majorTom("stepping through the door");
    majorTom("floating in a most peculiar way");
    那么当每次调用 majorTom 函数时,它都会使用第一个指定的参数填写已经指定的模板。例如上述的代码返回:
    "This is Major Tom to ground control. I'm stepping through the door."
    "This is Major Tom to ground control. I'm floating in a most peculiar way."

    三、c语言中的参数收集

    c/c++中参数收集是通过可变参数实现的,但可变参数只有__cdecl的函数调用约定才支持,而__stdcall是不支持的。对于两者之间的区别可见_stdcall与_cdecl的区别。比价常见的函数printf就是通过可变参数实现接收数量不定类型不同的参数。

    比如:printf("%d",n);
    printf("%s","hello word");
    而其原型为
    int printf ( const char *format, ... );
    从函数原型可以看出,其除了接收一个固定的参数format以外,后面的参数用"…"表示(当然是可以shennue的)。在C/C++语言中,"…"表示可以接受不定数量的参数,理论上来讲,可以是0或0以上的n个参数。

    另外,c中函数参数参数传递是需压入堆栈,如下图,从右至左将参数从高地址到底地址压入堆栈(知道更多)顺序依次是被调函数-返回地址-参数1-参数n。

    为了简单理解,我们先不分析printf函数,自己来实现一个求和和字符串相加的函数。

    void sum(int argsNum,...){
        int _sum=0;
        int* p=&argsNum;
        for(int i=0;i<argsNum;i++){
            printf("the %d argument is %d
    ",i,*(++p));
            _sum+=*p;
        }
        printf("
     the sum is %d",_sum); 
    }
    void join(int argsNum,...){
        int* p=&argsNum;
        for(int i=0;i<argsNum;i++){
            char* pchr = (char*)(++p);
            printf("the %d argument is %s
    ",i,pchr);
        } 
    }
    int main()
    {
        sum(3,2,3,5);
        join(3,'dd','s','e');
        system("pause");
        return 0;
    }

    argsNum参数传入被调函数的参数个数,本例中为2,3,5,申明一个指针p指向第一个参数3的地址,由上图可知,第一个参数3处于栈帧中栈顶位置的低地址中,++p指针跳向了下一个参数的地址,解引用后取得参数值。

    output:

    the 0 argument is 2
    the 1 argument is 3
    the 2 argument is 5

    the sum is 10the 0 argument is dd
    the 1 argument is s
    the 2 argument is e

    事实上对于c中的格式化输出函数printf实现远比这个复杂,主要通过stdarg.h中的宏来实现

    typedef char *  va_list;
    #define _ADDRESSOF(v)   ( &reinterpret_cast<const char &>(v) )
    #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
    #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
    #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    #define _crt_va_end(ap)      ( ap = (va_list)0 )
    #define va_start _crt_va_start
    #define va_arg _crt_va_arg
    #define va_end _crt_va_end

    va_list为char的指针类型表示可变参数;arg_ptr是指向可变参数表的指针;prev_param则指可变参数表的前一个固定参数;type为可变参数的类型。va_list也是一个宏,其定义为typedef char * va_list,实质上是一char型指针。char型指针的特点是++、--操作对其作用的结果是增1和减1(因为sizeof(char)为1),与之不同的是int等其它类型指针的++、--操作对其作用的结果是增sizeof(type)或减sizeof(type),而且sizeof(type)大于1。

    参考文献

    1.http://www.cnblogs.com/PandaBamboo/archive/2013/02/05/2892731.html

    2.http://blog.sina.com.cn/s/blog_63544da30100hg10.html

    3.http://www.cnblogs.com/devilmsg/archive/2009/07/23/1529504.html

  • 相关阅读:
    51nod 1179 最大的最大公约数 (数论)
    POJ 3685 二分套二分
    POJ 3045 贪心
    LIC
    HDU 1029 Ignatius and the Princess IV
    HDU 1024 Max Sum Plus Plus
    HDU 2389 Rain on your Parade
    HDU 2819 Swap
    HDU 1281 棋盘游戏
    HDU 1083 Courses
  • 原文地址:https://www.cnblogs.com/keily/p/3330591.html
Copyright © 2011-2022 走看看