有趣的参数收集

  周五面试的时候面试官提到了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\nname:%s\nage:%s\nother\tweight:%d\theight:%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\t'%(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\n",i,*(++p));
        _sum+=*p;
    }
    printf("\n 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\n",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

 

posted on 2013-09-20 17:15  无为在歧路  阅读(450)  评论(0编辑  收藏  举报

导航