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

  • 相关阅读:
    C++对象内存布局③测试多继承中派生类的虚函数在哪一张虚函数表中
    C++对象内存布局⑨VS编译器虚拟继承菱形继承
    C++对象内存布局⑥GCC编译器虚拟继承的虚基类表可能有两个
    C++对象内存布局⑦VS编译器虚拟继承多个基类
    C++对象内存布局⑧GCC编译器虚拟继承多个基类
    C++对象内存布局②测试派生类跟基类的虚函数表
    C++对象内存布局④VS编译器单个虚拟继承
    关于gridview绑定数据为空时的界面设计
    防止浏览器不小心被关闭的方法
    javascript 继承之原型链
  • 原文地址:https://www.cnblogs.com/keily/p/3330591.html
Copyright © 2011-2022 走看看