精华内容
下载资源
问答
  • function cloneObject... // 只有object和array里,for-in才能获取到参数 // 注意返回的是'[object Array]'后面大写 if (Object.prototype.toString.call(src).slice(8, -1) === 'Object' || Object.prototype.toS
    function cloneObject(src) {
        // 只有object和array里,for-in才能获取到参数
        // 注意返回的是'[object Array]'后面大写
        if (Object.prototype.toString.call(src).slice(8, -1) === 'Object'
            || Object.prototype.toString.call(src).slice(8, -1) === 'Array') {
            // 判断是Object还是Array
            var result = src instanceof Object?{}:[];
            for (var i in src) {
                if (src.hasOwnProperty(i)) {
                    // 对象或数组里面,继续遍历来获取,直到属性值为非Object或Array
                    result[i] = cloneObject(src[i]);
                }
            }
            return result;
        }
        // 原始类型和Date, Boolean等对象类型直接返回就好了
        else {
            return src;
        }
    }
    var srcObj = {
        a: 1,
        b: {
            b1: ["hello", "hi"],
            b2: "JavaScript"
        }
    };
    // 浅拷贝
    var abObj = srcObj;
    // 深拷贝
    var tarObj = cloneObject(srcObj);
    
    srcObj.a = 2;
    srcObj.b.b1[0] = "Hello";
    
    console.log(abObj.a);       // 2
    console.log(abObj.b.b1[0]);     // 'Hello'
    
    console.log(tarObj.a);      // 1
    console.log(tarObj.b.b1[0]);    // "hello"


    var clone_abd = new Function(abd.toString());// 可以拷贝函数


    展开全文
  • ##Random 通常用来作为随机数生成器,它有两个构造方法: Random random = new Random(); Random random2 = new Random...1.不含参构造方法: public Random() { setSeed(System.nanoTime() + seedBase); ...

    ##Random 通常用来作为随机数生成器,它有两个构造方法:

            Random random = new Random();
            Random random2 = new Random(50);
    

    1.不含参构造方法:

    public Random() {
        setSeed(System.nanoTime() + seedBase);
        ++seedBase;
    }
    

    2.含参构造方法:

    public Random(long seed) {
        setSeed(seed);
    }
    

    都调用的 setSeed 方法:

    public synchronized void setSeed(long seed) {
        this.seed = (seed ^ multiplier) & ((1L << 48) - 1);
        haveNextNextGaussian = false;
    }
    

    ###可以看到,不含参构造方法每次都使用当前时间作为种子,而含参构造方法是以一个固定值作为种子

    ##什么是种子 seed 呢?

    seed 是 Random 生成随机数时使用的参数:

    Random 中最重要的就是 next(int) 方法,使用 seed 进行计算:

    protected synchronized int next(int bits) {
        seed = (seed * multiplier + 0xbL) & ((1L << 48) - 1);
        return (int) (seed >>> (48 - bits));
    }
    

    其他 nextXXX 方法都是调用的 next()。

    比如 nextInt(int):

    public int nextInt(int n) {
        if (n <= 0) {
            throw new IllegalArgumentException("n <= 0: " + n);
        }
        if ((n & -n) == n) {
    		//调用 next()
            return (int) ((n * (long) next(31)) >> 31);
        }
        int bits, val;
        do {
            bits = next(31);
            val = bits % n;
        } while (bits - val + (n - 1) < 0);
        return val;
    }
    

    再比如 nextBoolean():

    //也是调用的 next()
    public boolean nextBoolean() {
        return next(1) != 0;
    }
    

    ##举个栗子:

    @Test
    public void testRandomParameter(){
        System.out.println("Random 不含参构造方法:");
        for (int i = 0; i < 5; i++) {
            Random random = new Random();
            for (int j = 0; j < 8; j++) {
                System.out.print(" " + random.nextInt(100) + ", ");
            }
    
            System.out.println("");
        }
    
        System.out.println("");
    
        System.out.println("Random 含参构造方法:");
        for (int i = 0; i < 5; i++) {
            Random random = new Random(50);
            for (int j = 0; j < 8; j++) {
                System.out.print(" " + random.nextInt(100) + ", ");
            }
            System.out.println("");
        }
    }
    

    分别用含参构造方法和不含参构造方法创建 5 个随机生成器对象,每个随机生成器再生产 8 个随机数,对比下结果:

    这里写图片描述

    再运行一次:

    这里写图片描述

    ##总结:

    通过上述例子可以发现:

    随机数是种子经过计算生成的。

    • 不含参的构造函数每次都使用当前时间作为种子,随机性更强
    • 而含参的构造函数其实是伪随机,更有可预见性

    转载于:https://www.cnblogs.com/shizhijie/p/8258668.html

    展开全文
  • Random 通常用来作为随机数生成器,它有两个构造方法: Random random = new Random(); Random random2 = new...1.不含参构造方法:public Random() { setSeed(System.nanoTime() + seedBase); ++seedBase; } 2.含参

    Random 通常用来作为随机数生成器,它有两个构造方法:

            Random random = new Random();
            Random random2 = new Random(50);
    

    1.不含参构造方法:

    public Random() {
        setSeed(System.nanoTime() + seedBase);
        ++seedBase;
    }
    

    2.含参构造方法:

    public Random(long seed) {
        setSeed(seed);
    }
    

    都调用的 setSeed 方法:

    public synchronized void setSeed(long seed) {
        this.seed = (seed ^ multiplier) & ((1L << 48) - 1);
        haveNextNextGaussian = false;
    }
    

    可以看到,不含参构造方法每次都使用当前时间作为种子,而含参构造方法是以一个固定值作为种子

    什么是种子 seed 呢?

    seed 是 Random 生成随机数时使用的参数:

    Random 中最重要的就是 next(int) 方法,使用 seed 进行计算:

    protected synchronized int next(int bits) {
        seed = (seed * multiplier + 0xbL) & ((1L << 48) - 1);
        return (int) (seed >>> (48 - bits));
    }
    

    其他 nextXXX 方法都是调用的 next()。

    比如 nextInt(int):

    public int nextInt(int n) {
        if (n <= 0) {
            throw new IllegalArgumentException("n <= 0: " + n);
        }
        if ((n & -n) == n) {
            //调用 next()
            return (int) ((n * (long) next(31)) >> 31);
        }
        int bits, val;
        do {
            bits = next(31);
            val = bits % n;
        } while (bits - val + (n - 1) < 0);
        return val;
    }
    

    再比如 nextBoolean():

    //也是调用的 next()
    public boolean nextBoolean() {
        return next(1) != 0;
    }
    

    举个栗子:

    @Test
    public void testRandomParameter(){
        System.out.println("Random 不含参构造方法:");
        for (int i = 0; i < 5; i++) {
            Random random = new Random();
            for (int j = 0; j < 8; j++) {
                System.out.print(" " + random.nextInt(100) + ", ");
            }
    
            System.out.println("");
        }
    
        System.out.println("");
    
        System.out.println("Random 含参构造方法:");
        for (int i = 0; i < 5; i++) {
            Random random = new Random(50);
            for (int j = 0; j < 8; j++) {
                System.out.print(" " + random.nextInt(100) + ", ");
            }
            System.out.println("");
        }
    }
    

    分别用含参构造方法和不含参构造方法创建 5 个随机生成器对象,每个随机生成器再生产 8 个随机数,对比下结果:

    这里写图片描述

    再运行一次:

    这里写图片描述

    总结:

    通过上述例子可以发现:

    随机数是种子经过计算生成的

    • 不含参的构造函数每次都使用当前时间作为种子,随机性更强
    • 而含参的构造函数其实是伪随机,更有可预见性
    展开全文
  • 利用已建立的Lie代数构造了一类新的含复数的谱问题,再用一个不含参数的规范变换将该谱问题转化为著名的Zakharov-Shabat特征值问题,由此得到了新的位势函数与原来的位势函数间的代数运算关系,也就是说,如果取...
  • 可变参数函数详解

    千次阅读 2019-04-08 13:00:39
    可变参数函数又称参数个数可变函数(本文也简称变参函数),即函数参数数目可变。原型声明格式为: type VarArgFunc(type FixedArg1, type FixedArg2, …); 其中,参数可分为两部分:数目确定的固定参数...

    https://www.cnblogs.com/clover-toeic/p/3736748.html

       可变参数函数又称参数个数可变函数(本文也简称变参函数),即函数参数数目可变。原型声明格式为:

    type VarArgFunc(type FixedArg1, type FixedArg2, …);

         其中,参数可分为两部分:数目确定的固定参数和数目可变的可选参数。函数至少需要一个固定参数,其声明与普通函数参数相同;可选参数由于数目不定(0个或以上),声明时用"…"表示(“…”用作参数占位符)。固定参数和可选参数共同构成可变参数函数的参数列表。

         由于参数数目不定,使用可变参数函数通常能缩短编码,灵活性和易用性较高。

         典型的变参函数如printf(及其家族),其函数原型为:

    int printf(const char* format, ...);

         printf函数除参数format固定外,后续参数的数目和类型均可变。实际调用时可有以下形式:

    printf("string"); 

    printf("%d", i); 

    printf("%s", s); 

    printf("number is %d, string is:%s", i, s);

    ……

     

    1 变参函数实现原理

        C调用约定下可使用va_list系列变参宏实现变参函数,此处va意为variable-argument(可变参数)。典型用法如下:

    #include <stdarg.h>

    int VarArgFunc(int dwFixedArg, ...){ //以固定参数的地址为起点依次确定各变参的内存起始地址

        va_list pArgs = NULL;  //定义va_list类型的指针pArgs,用于存储参数地址

        va_start(pArgs, dwFixedArg); //初始化pArgs指针,使其指向第一个可变参数。该宏第二个参数是变参列表的前一个参数,即最后一个固定参数

        int dwVarArg = va_arg(pArgs, int); //该宏返回变参列表中的当前变参值并使pArgs指向列表中的下个变参。该宏第二个参数是要返回的当前变参类型

        //若函数有多个可变参数,则依次调用va_arg宏获取各个变参

        va_end(pArgs);  //将指针pArgs置为无效,结束变参的获取

        /* Code Block using variable arguments */

    }

    //可在头文件中声明函数为extern int VarArgFunc(int dwFixedArg, ...);,调用时用VarArgFunc(FixedArg, VarArg);

         变参宏根据堆栈生长方向和参数入栈特点,从最靠近第一个可变参数的固定参数开始,依次获取每个可变参数的地址。

         变参宏的定义和实现因操作系统、硬件平台及编译器而异(但原理相似)。System V Unix在varargs.h头文件中定义va_start宏为va_start(va_list arg_ptr),而ANSI C则在stdarg.h头文件中定义va_start宏为va_start(va_list arg_ptr, prev_param)。两种宏并不兼容,为便于程序移植通常采用ANSI C定义。

         gcc编译器使用内置宏间接实现变参宏,如#define va_start(v,l)  __builtin_va_start(v,l)。因为gcc编译器需要考虑跨平台处理,而其实现因平台而异。例如x86-64或PowerPC处理器下,参数不全都通过堆栈传递,变参宏的实现相比x86处理器更为复杂。

         x86平台VC6.0编译器中,stdarg.h头文件内变参宏定义如下:

    typedef char * va_list;

    #define _INTSIZEOF(n)       ( (sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1) )

    #define va_start(ap,v)        ( ap = (va_list)&v + _INTSIZEOF(v) )

    #define va_arg(ap, type)    ( *(type *)((ap += _INTSIZEOF(type)) - _INTSIZEOF(type)) )

    #define va_end(ap)             ( ap = (va_list)0 )

         各宏的含义如下:

         ①_INTSIZEOF宏考虑到某些系统需要内存地址对齐。从宏名看应按照sizeof(int)即堆栈粒度对齐,即参数在内存中的地址均为sizeof(int)=4的倍数。例如,若在1≤sizeof(n)≤4,则_INTSIZEOF(n)=4;若5≤sizeof(n)≤8,则_INTSIZEOF(n)=8。

         为便于理解,简化该宏为

    #define _INTSIZEOF(n)  ((sizeof(n) + x) & ~(x))

    x = sizeof(int) - 1 = 3 = 0b’0000 0000 0000 0011

    ~x = 0b’1111 1111 1111 1100

         一个数与(~x)相与的结果是sizeof(int)的倍数,即_INTSIZEOF(n)将n圆整为sizeof(int)的倍数。

         ②va_start宏根据(va_list)&v得到第一个可变参数前的一个固定参数在堆栈中的内存地址,加上_INTSIZEOF(v)即v所占内存大小后,使ap指向固定参数后下个参数(第一个可变参数地址)。

         固定参数的地址用于va_start宏,因此不能声明为寄存器变量(地址无效)或作为数组类型(长度难定)。

         ③va_arg宏取得type类型的可变参数值。首先ap+=_INTSIZEOF(type),即ap跳过当前可变参数而指向下个变参的地址;然后ap-_INTSIZEOF(type)得到当前变参的内存地址,类型转换后返回当前变参值。

         va_arg宏的等效实现如下

    //将指针移动至下个变参,并返回左移的值[-1](数组下标表示偏移量),即当前变参值

    #define va_arg(ap,type)  ((type *)((ap) += _INTSIZEOF(type)))[-1]

         ④va_end宏使ap不再指向有效的内存地址。该宏的某些实现定义为((void*)0),编译时不会为其产生代码,调用与否并无区别。但某些实现中va_end宏用于函数返回前完成一些必要的清理工作:如va_start宏可能以某种方式修改堆栈,导致返回操作无法完成,va_end宏可将有关修改复原;又如va_start宏可能对参数列表动态分配内存以便于遍历va_list,va_end宏可释放此前动态分配的内存。因此,从使用va_start宏的函数中退出之前,必须调用一次va_end宏。

         函数内可多次遍历可变参数,但每次必须以va_start宏开始,因为遍历后ap指针不再指向首个变参。

         下图给出基于变参宏的可变参数在堆栈中的分布:

         变参宏无法智能识别可变参数的数目和类型,因此实现变参函数时需自行判断可变参数的数目和类型。前者可显式提供变参数目或设定遍历结束条件(如-1、'\0'或回车符等)。后者可显式提供变参类型枚举值,或在固定参数中包含足够的类型信息(如printf函数通过分析format字符串即可确定各变参类型),甚至主调函数和被调函数可约定变参的类型组织等。

     

    2 变参函数代码示例

         本节给出若干遵循ANSI C标准形式的简单可变参数函数,基于这些示例可构造更为复杂实用的功能。

         示例函数必须包含stdio.h和stdarg.h头文件,并按需包含string.h头文件。

        【示例1】函数接受一个整型固定参数和一个整型可变参数,并打印这两个参数值。

    1 void IntegerVarArgFunc(int i, ...){
    2     va_list pArgs = NULL;
    3     va_start(pArgs, i);
    4     int j = va_arg(pArgs, int);
    5     va_end(pArgs);
    6     printf("i=%d, j=%d\n", i, j);
    7 }

         分别采用以下三种方法调用:

         1) IntegerVarArgFunc(10);

         输出i=10, j=6803972(形参i的堆栈上方内容)

         2) IntegerVarArgFunc(10, 20);

         输出i=10, j=20,符合期望。

         3) IntegerVarArgFunc(10, 20, 30);

         输出i=10, j=20,多余的变参被忽略。

     

        【示例2】函数通过固定参数指定可变参数个数,循环打印所有变参值。

     1 //第一个参数定义可变参数个数,用于循环获取变参内容
     2 void ParseVarArgByNum(int dwArgNum, ...){
     3     va_list pArgs = NULL;
     4     va_start(pArgs, dwArgNum);
     5     int dwArgIdx;
     6     int dwArgVal = 0;
     7     for(dwArgIdx = 1; dwArgIdx <= dwArgNum; dwArgIdx++){
     8         dwArgVal = va_arg(pArgs, int);
     9         printf("The %dth Argument: %d\n",dwArgIdx, dwArgVal);
    10     }
    11     va_end(pArgs);
    12 }

         调用方式为ParseVarArgByNum(3, 11, 22, 33);,输出:

         The 1th Argument: 11

         The 2th Argument: 22

         The 3th Argument: 33

     

        【示例3】函数定义一个结束标记,调用时通过最后一个参数传递该标记,以结束变参的遍历打印。

     1 //最后一个参数作为变参结束符(-1),用于循环获取变参内容
     2 void ParseVarArgByEnd(int dwStart, ...){
     3     va_list pArgs = NULL;
     4     va_start(pArgs, dwStart);
     5     int dwArgIdx = 0;
     6     int dwArgVal = dwStart;
     7     while(dwArgVal != -1){
     8         ++dwArgIdx;
     9         printf("The %dth Argument: %d\n",dwArgIdx, dwArgVal);
    10         dwArgVal = va_arg(pArgs, int); //得到下个变参值
    11     }
    12     va_end(pArgs);
    13 }

         调用方式为ParseVarArgByEnd(44, 55, -1);,输出:

         The 1th Argument: 44

         The 2th Argument: 55

     

        【示例4】函数自定义一些可能出现的参数类型,在变参列表中显式指定变参类型。可这样传递参数:参数数目,可变参数类型1,可变参数值1,可变参数类型2,可变参数值2,....。

     1 //可变参数采用<ArgType, ArgValue>的形式传递,以处理不同的变参类型
     2 typedef enum{
     3     CHAR_TYPE = 1,
     4     INT_TYPE,
     5     LONG_TYPE,
     6     FLOAT_TYPE,
     7     DOUBLE_TYPE,
     8     STR_TYPE
     9 }E_VAR_TYPE;
    10 void ParseVarArgType(int dwArgNum, ...){
    11     va_list pArgs = NULL;
    12     va_start(pArgs, dwArgNum);
    13 
    14     int i = 0;
    15     for(i = 0; i < dwArgNum; i++){
    16         E_VAR_TYPE eArgType = va_arg(pArgs, int);
    17         switch(eArgType){
    18             case INT_TYPE:
    19                 printf("The %dth Argument: %d\n", i+1, va_arg(pArgs, int));
    20                 break;
    21             case STR_TYPE:
    22                 printf("The %dth Argument: %s\n", i+1, va_arg(pArgs, char*));
    23                 break;
    24             default:
    25                 break;
    26         }
    27     }
    28     va_end(pArgs);
    29 }

         调用方式为ParseVarArgType(2, INT_TYPE, 222, STR_TYPE, "HelloWorld!");,输出:

         The 1th Argument: 222

         The 2th Argument: HelloWorld!

     

        【示例5】实现简易的MyPrintf函数。该函数无返回值,即不记录输出的字符数目;接受"%d"按整数输出、"%c"按字符输出、"%b"按二进制输出,"%%"输出'%'本身。

     1 char *MyItoa(int iValue, char *pszResBuf, unsigned int uiRadix){
     2     //If pszResBuf is NULL, string "Nil" is returned.
     3     if(NULL == pszResBuf){
     4         //May add more trace/log output here
     5         return "Nil";
     6     }
     7     
     8     //If uiRadix(Base of Number) is out of range[2,36],
     9      //empty resulting string is returned.
    10     if((uiRadix < 2) || (uiRadix > 36)){
    11         //May add more trace/log output here
    12         *pszResBuf = '\0';
    13         return pszResBuf;
    14     }
    15 
    16     char *pStr = pszResBuf; //Pointer to traverse string
    17     char *pFirstDig = pszResBuf; //Pointer to first digit
    18     if((10 == uiRadix) && (iValue < 0)){ //Negative decimal number
    19         iValue = (unsigned int)-iValue;
    20         *pStr++ = '-';
    21         pFirstDig++;  //Skip negative sign
    22     }
    23 
    24     int iTmpValue = 0;
    25     do{
    26         iTmpValue = iValue;
    27         iValue /= uiRadix;
    28         //Calculating the modulus operator(%) by hand saving a division
    29         *pStr++ = "0123456789abcdefghijklmnopqrstuvwxyz"[iTmpValue - iValue * uiRadix];
    30     }while(iValue);
    31     *pStr-- = '\0';  //Terminate string, pStr points to last digit(or negative sign)
    32     //Now have a string of number in reverse order
    33 
    34     //Swap *pStr and *pFirstDig for reversing the string of number
    35     while(pFirstDig < pStr){ //Repeat until halfway
    36         char cTmpChar = *pStr;
    37         *pStr--= *pFirstDig;
    38         *pFirstDig++ = cTmpChar;
    39     }
    40     return pszResBuf;
    41 }
    42 
    43 void MyPrintf(const char *pszFmt, ... ){
    44     va_list pArgs = NULL;
    45     va_start(pArgs, pszFmt);
    46 
    47     for(; *pszFmt != '\0'; ++pszFmt){
    48         //若不是控制字符则原样输出字符
    49         if(*pszFmt != '%'){
    50             putchar(*pszFmt);
    51             continue;
    52         }
    53 
    54         //若是控制字符则查看下一字符
    55         switch(*++pszFmt){
    56             case '%': //连续两个'%'输出单个'%'
    57                 putchar('%');
    58                 break;
    59             case 'd': //按照整型输出
    60                 printf("%d", va_arg(pArgs, int));
    61                 break;
    62             case 'c': //按照字符输出
    63                 printf("%c", va_arg(pArgs, int)); //不可写为...va_arg(pArgs, char);
    64                 break;
    65             case 'b': {//按照二进制输出
    66                 char aucStr[sizeof(int)*8 + 1] = {0};
    67                 fputs(MyItoa(va_arg(pArgs, int), aucStr, 2), stdout);
    68                 //printf(MyItoa(va_arg(pArgs, int), aucStr, 2));
    69                 break;
    70             }
    71             default:
    72                 vprintf(--pszFmt, pArgs);
    73                 return;
    74         }
    75     }//end of for-loop
    76     va_end(pArgs);
    77 }

         调用方式为MyPrintf("Binary string of number %d is = %b!\n", 9999, 9999);,输出:

         Binary string of number 9999 is = 10011100001111!

         注意,MyPrintf函数for循环语句段旨在自定义格式化输出(如%b),而非实现printf库函数本身;否则直接使用vprintf(pszFmt, pArgs);即可。此外该函数存在一处明显缺陷,即%b前若出现case匹配项外的控制字符(如%x),则会调用vprintf函数处理该字符及其后的格式串,%b将会原样输出"%b"(而非转换为二进制)。

         本示例中也附带实现了MyItoa函数。该函数与非标准C语言扩展函数itoa功能相同。该函数将整数iValue转换为uiRadix 所指定的进制数字符串,并将其存入pszResBuf字符数组。

     

        【示例6】可变参数数目不多时,可用数组或结构体数组变相实现可变参数函数。

    #define VAR_ARG_MAX_NUM    (unsigned char)10
    #define VAR_ARG_MAX_LEN     (unsigned char)20
    //可变参数信息
    typedef struct{
        E_VAR_TYPE eArgType;
        unsigned char aucArgVal[VAR_ARG_MAX_LEN];
    }VAR_ARG_ENTRY;
    typedef struct{
        unsigned char ucArgNum;
        VAR_ARG_ENTRY aucVarArg[VAR_ARG_MAX_NUM];
    }VAR_ARG_LIST;
    
    void ParseStructArrayArg(VAR_ARG_LIST *ptVarArgList){
        int i = 0;
        for(i = 0; i < ptVarArgList->ucArgNum; i++){
            E_VAR_TYPE eArgType = ptVarArgList->aucVarArg[i].eArgType;
            switch(eArgType){
                case CHAR_TYPE:
                    printf("The %dth Argument: %c\n", i+1, ptVarArgList->aucVarArg[i].aucArgVal[0]);
                    break;
                case STR_TYPE:
                    printf("The %dth Argument: %s\n", i+1, ptVarArgList->aucVarArg[i].aucArgVal);
                    break;
                default:
                    break;
            }
        }
    }

         调用方式为

    VAR_ARG_LIST tVarArgList = {2, {{CHAR_TYPE, {'H'}}, {STR_TYPE, "TEST"}}};

    ParseStructArrayArg(&tVarArgList);

         输出:

         The 1th Argument: H

         The 2th Argument: TEST

         本示例函数原型稍加改造,显式声明参数数目如下:

    void ParseStructArrayArg(unsigned char ucArgNum, VAR_ARG_ENTRY aucVarArg[]);或

    void ParseStructArrayArg(unsigned char ucArgNum, VAR_ARG_ENTRY *aucVarArg);

         改造后的原型与main函数的带参原型非常相似!

    int main(int argc, char *argv[]);或

    int main(int argc, char **argv);

         若VAR_ARG_ENTRY内的变参数目和类型固定,则主调函数和被调函数双方约定后可采用char型数组替代VAR_ARG_ENTRY结构体数组。

         通过数组可替代某些不必要的变参函数实现,如对整数求和:

    实现方式

    可变参数函数

    数组替代

    函数代码

    int SumVarArg(int dwStart, ...){

        va_list pArgs = NULL;

        va_start(pArgs, dwStart);

        int dwArgVal = dwStart, dwSum = 0;

        while(dwArgVal != 0){ //0为结束标志

            dwSum += dwArgVal;

            dwArgVal = va_arg(pArgs, int);

        };

        va_end(pArgs);

        return dwSum;

    }

    int SumArray(int aucArr[], int dwSize){

        int i = 0, dwSum = 0;

        for(i = 0; i < dwSize; i++){

            dwSum += aucArr[i];

        }

        return dwSum;

    }

    调用方式

    SumVarArg(7, 2, 7, 11, -2, 0);

    int aucArr[] = {7, 2, 7, 11, -2};

    SumArrayArg(aucArr, sizeof(aucArr)/sizeof(aucArr[0]));

         数组方式调用时可方便地指定求和项的起止,如SumArrayArg(&aucArr[1], 3)将从数组aucArr的第2个元素开始累加3个元素,即2+7+11=20。而这是变参函数SumVarArg无法做到的。

     

    3 变参函数注意事项

         可变参数函数在编程中应注意以下问题:

         1) 编译器对可变参数函数的原型检查不够严格,不利于编程查错。

         调用变参函数时,传递的变参数目应不少于该函数所期望的变参数目(该数目由主调函数实参指定或由变参函数内部实现决定),否则会访问到函数参数以外的堆栈区域,可能导致堆栈错误。

         如示例1中可变参数为char*类型(用%s打印) 时,若使用整型变参调用该函数,可能会出现段错误(Linux)或页面非法错误(Windows),也可能出现难以觉察的细微错误。

         printf函数格式化字符串参数所指定的类型与后面变参的类型不匹配时,也可能造成程序崩溃(尤其以%s打印整型参数值时)。

         gcc编译器提供attribute 机制用以编译时检查某些变参函数调用情况,如声明函数为

    void OmciLog(LOG_TYPE eLogType, const char *pFmt, ...) __attribute__((format(printf,2,3)));

         表示函数原型中第2个参数(pFmt)为格式化字符串,从参数列表中第3个参数(即首个变参)开始与pFmt形式比较。该声明将对OmciLog(LOG_PON, "%s", 1)的调用产生编译警告:

    VarArgs.c:204: warning: format '%s' expects type 'char *', but argument 3 has type 'int'

         但该机制主要针对类似scanf/printf的变参函数,此类函数可根据格式化字符串确定变参数目和类型。

         2) va_arg(ap, type)宏获取变参时,type不可指定为以下类型:

    • char、signed char、unsigned char
    • short、unsigned short
    • signed shortshort int、signed short int、unsigned short int
    • float

         在C语言中,调用不带原型声明或声明为变参的函数时,主调函数会在传递未显式声明的参数前对其执行“缺省参数提升(default argument promotions)”,将提升后的参数值传递给被调函数。

         提升操作如下:

    • float类型的参数提升为double类型
    • char、short和相应的signed、unsigned类型参数提升为int类型
    • 若int类型不能存储原值,则提升为unsigned int类型

         在gcc 编译器中,若type使用char或unsigned short int等需提升的类型,可能会得到严重警告。 

         因此,若要获取变参数列表中float类型的实参,则变参函数中应使用double dVar = va_arg(ap, double)或float fVar = (float)va_arg(ap, double)。char和short类型实参处理方式与之类似。

         3) 使用va_arg宏获取变参列表中类型为函数指针的参数时,可能需要将函数指针用typedef定义为新的数据类型,以便通过编译(与va_arg宏的实现有关)。

         对于VC6.0的va_arg宏实现,若用该宏从变参列表中提取函数指针类型的参数,如

    va_arg(argp, int(*)());

         被扩展为以下形式(为缩减长度直接写出_INTSIZEOF宏值)

    ( *(int (*)() *)((pArgs += 4) - 4) );

         显然,(int (*)() *)无意义。

         解决方法如下

    typedef int (*pFunc)();

         va_arg(argp, pFunc)被扩展为(*(pFunc *)((pArgs += 4) - 4)),即可通过编译检查。

         而在gcc编译器下,va_arg宏可直接使用函数指针类型。

     1 //for Gcc Compiler
     2 int DummyFunc(void){printf("Here!!!\n"); return 0; }
     3 void ParseFuncPtrVarArg(int i, ...){
     4     va_list pArgs = NULL;
     5     va_start(pArgs, i);
     6     char *sVal = va_arg(pArgs, char*);
     7     va_end(pArgs);
     8     printf("%d %s ", i, sVal);
     9 
    10     int (*pf)() = va_arg(pArgs, int (*)());
    11     pf();
    12 }

         以ParseFuncPtrVarArg(1, "Welcome", DummyFunc);方式调用,输出为1 Welcome Here!!!。

         4) C语言层面上无法将函数A的可变参数直接传递给函数B。只能定义被调函数的参数为va_list类型,在主调函数中将可变参数列表转换为va_list,再进行可变参数的传递。这种技巧常用于定制打印函数:

     1 INT32S OmciLog(E_LOG_TYPE eLogType, const CHAR *pszFmt, ...){
     2     CHECK_SINGLE_POINTER(pFormat, RETURN_VOID);
     3 
     4     if(0 == GET_BIT(gOmciLogCtrl, eLogType))
     5         return;
     6 
     7     CHAR aucLogBuf[OMCI_LOG_BUF_LEN] = {0};
     8     va_list pArgs = NULL;
     9     va_start(pArgs, pszFmt);
    10     INT32S dwRetVal = vsnprintf(aucLogBuf, sizeof(aucLogBuf), pszFmt, pArgs);
    11     va_end(pArgs);
    12 
    13     OUTPUT_LOG(aucLogBuf);
    14     return dwRetVal;
    15 }

         其中被调函数vsnprintf可根据va_arg(pszFmt, pArgs)依次取出所需的变参。

         以OmciLog("%d %f %s\n", 10, 20.3, "ABC");方式调用,输出为10 20.300000 ABC。

         5) 可变参数必须从头到尾按照顺序逐个访问。可访问几个变参后中止,但不能一开始就访问变参列表中间的参数。

         6) ANSI C要求至少定义一个固定参数(ISO C requires a named argument before '...'),该参数将传递给va_start宏以查找参数列表的可变部分。故不可定义void func(...)这样的函数。

         7) 变参宏实现与堆栈相关,在参数入寄存器的处理器下实现可能异常复杂(gcc中va_start宏会将所有可能用于变参传递的寄存器均保存在栈中)。因此如非必要,应尽量避免使用变参宏。C语言中除示例6中数组或结构体数组替代方式外,还可采用回调函数方式"抛出"变化部分,如:

     1 /**********************************************************************
     2 * 函数名称: OmciLocateListNode
     3 * 功能描述: 查找链表首个与pData满足函数fCompareNode判定关系的结点
     4 * 输入参数: T_OMCI_LIST* pList           :链表指针
     5 *            VOID* pData                  :待比较数据指针
     6 *            CompareNodeFunc fCompareNode :比较回调函数指针
     7 * 输出参数: NA
     8 * 返 回 值: T_OMCI_LIST_NODE* 链表结点指针(未找到时返回NULL)
     9 ***********************************************************************/
    10 T_OMCI_LIST_NODE* OmciLocateListNode(T_OMCI_LIST *pList, VOID *pData, CompareNodeFunc fCompareNode)
    11 {
    12     CHECK_TRIPLE_POINTER(pList, pData, fCompareNode, NULL);
    13     CHECK_SINGLE_POINTER(pList->pHead, NULL);
    14     CHECK_SINGLE_POINTER(pList->pHead->pNext, NULL);
    15 
    16     if(0 == pList->dwNodeNum)
    17     {
    18         return NULL;
    19     }
    20 
    21     T_OMCI_LIST_NODE *pListNode = pList->pHead->pNext;
    22     while(pListNode != pList->pHead)
    23     {
    24         if(0 == fCompareNode(pListNode->pNodeData, pData, pList->dwNodeDataSize))
    25             return pListNode;
    26 
    27         pListNode = pListNode->pNext;
    28     }
    29 
    30     return NULL;
    31 }

         OmciLocateListNode函数是下面Omci_List_Query函数的另一实现。主调函数提供fCompareNode回调函数以比较链表结点,从而简化代码实现,并增强可读性。

     1 /***************************************************************
     2  * Function: Omci_List_Query
     3  * Description -
     4  *     根据给定的KEY偏移和KEY长度,查找目标节点
     5  * Input: 
     6  *     pList: 链表
     7  *     可变参数: 三个参数为一组,第一个为key value,第二个为key
     8  *               偏移,第三个为key长度,以LIST_END表示参数结束。
     9  * Output: 
    10  * Returns: 
    11  * 
    12  * modification history
    13  * -------------------------------
    14  * Created : 2011-5-25 by xxx
    15  * ------------------------------
    16  ***************************************************************/
    17 OMCI_LIST_NODE* Omci_List_Query(OMCI_LIST *pList, ...)
    18 {
    19     OMCI_LIST_NODE_KEY  aKeyGroup[MAX_LIST_NODE_KEYS_NUM];
    20     OMCI_LIST_NODE *pNode=NULL;
    21     INT8U *pData=NULL, *pKeyValue=NULL;
    22     INT8U ucKeyNum=0, i;
    23     INT32U iKeyOffset=0, iKeyLen=0;
    24     VA_LIST tArgList;
    25 
    26     if(NULL==pList)
    27         return NULL;
    28     memset((INT8U*)aKeyGroup, 0, sizeof(OMCI_LIST_NODE_KEY)*MAX_LIST_NODE_KEYS_NUM);
    29     VA_START(tArgList, pList);
    30     while(TRUE)
    31     {
    32         pKeyValue=VA_ARG(tArgList, INT8U*);
    33         if(LIST_END==pKeyValue)
    34             break;
    35         iKeyOffset=VA_ARG(tArgList, INT32U);
    36         iKeyLen=VA_ARG(tArgList, INT32U);
    37         if(0==iKeyLen)
    38         {
    39             VA_END(tArgList);
    40             return NULL;
    41         }
    42         if(ucKeyNum>=MAX_LIST_NODE_KEYS_NUM)
    43         {
    44             VA_END(tArgList);
    45             return NULL;
    46         }
    47         aKeyGroup[ucKeyNum].pKeyValue=pKeyValue;
    48         aKeyGroup[ucKeyNum].iKeyOffset=iKeyOffset;
    49         aKeyGroup[ucKeyNum++].iKeyLen=iKeyLen;
    50     }
    51     VA_END(tArgList);
    52 
    53     pNode=Omci_List_First(pList);
    54     while(NULL!=pNode)
    55     {
    56         pData=(INT8U*)pNode->pNodeData;
    57         for(i=0; i<ucKeyNum; i++)
    58         {
    59             if(0!=memcmp(&pData[aKeyGroup[i].iKeyOffset], aKeyGroup[i].pKeyValue, aKeyGroup[i].iKeyLen))
    60                 break;
    61         }
    62         if(i>=ucKeyNum)
    63         {
    64             break;
    65         }
    66         pNode=pNode->pNext;
    67     }
    68     return pNode;
    69 }

         在C++语言里,可利用多态性来实现可变参数的功能(但灵活性有所下降)。 

     

    【扩展阅读】vsnprintf函数

    vsnprintf函数原型为:int vsnprintf(char *str, size_t size, const char *format, va_list ap)。

    该函数将根据format字符串来转换并格式化ap所指向的可变参数列表,并将结果字符串以不超过size字节(包括字符串结束符'\0')的长度写入str所指向的字符串缓冲区(该缓冲区大小至少为size字节)。若结果字符串超过size-1个字符,则丢弃多余字节,但将其计入函数返回值。若函数执行成功,则返回实际或本该写入的字符数目(包括字符串结束符);否则将返回负值。因此,仅当返回值为小于size的非负值时,表明结果字符串被完全写入(大于等于size则意味着字符串被截断)。snprintf函数的返回值规则与之相同。

    注意,当目的缓冲区不够大时会截断字符串,但vsnprintf/snprintf函数确保缓冲区中存放的字符串以NULL结尾,而stncpy函数处理后的字符串不含结束符。

     

     

    展开全文
  • 本文学习Python函数,包括:函数调用 定义函数 函数参数 递归函数我们知道圆的面积计算公式为:S = π r*r当我们知道半径r的值时,就可以根据公式计算出面积。假设我们需要计算3个不同大小的圆的面积:r1 = 12.34 ...
  • C语言中用有参数来调用无参数函数

    千次阅读 2008-04-18 10:33:00
    昨天在读一段代码时,突然发现有一个函数,使用参数调用了一个定义时没参数函数,但在函数的定义中没有用到这个参数,奇怪的是它能够正确地编译, 不报错。后来我把参数去掉,程序没有受到影响。后来请教坤哥,...
  • SQL 之 存储过程、参数函数

    千次阅读 2016-10-06 19:04:59
    存储过程和参数 区别于之前的用单独一条语句检索数据,现在把多条语句保存...2. 把参数和SQL语句结合使用(也可以不含参数)   创建存储过程 CREATE PROCEDURE ProcedureName, AS [OptionalParameterDeclarations] BE
  • 默认参数指的是当函数调用中省略了实参时,自动使用一个值。这里首先需要注意的是: 对于带参数列表的函数,必须从右向左添加默认值。 也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值。...
  • 1、函数基础 一个典型的函数包括:返回类型(return type)、函数名字、由0个或多个形参组成的列表以及函数体。其中形参列表位于一对括号内,以逗号隔开。 形参和实参 实参是形参的初始值。实参初始化形参要一一...
  • Python中的函数参数

    2019-05-08 16:32:44
    Python中的函数8.1 内置函数8.2 函数的定义8.2.1 关键字pass的使用8.2.2 函数参数8.3 参数类型8.3.1 位置的参数8.3.2. 默认值参数8.3.3. 命名关键字参数8.3.4 可变参数8.3.5 关键字参数8.3.6 参数组合 函数可以...
  • 可以看到,在带参数的构造函数里打印出来的对象地址 和对象obj的地址不一致。 实际上 代码13行的调用只是在栈上生成了一个临时对象 对于自己本身毫无影响 还可以发现 构造函数的互相调用引起的后果不是死循环 而是栈...
  • 参数数量不同的函数 编写求最大值的函数 1.C++和Java都允许在有效范围相同的区域内出现同名函数 重载函数-overloaded function 2.编译时,根据调用命令中的实际参数选择相匹配的版本作为被调函数 不允许函数形参...
  • main函数可以含参可以不含参,main函数参数规定为:argc、char* argv[ ] 、char* envp(环境变量)。 1>:在这里主要理解一下argc、char* arg两个参数。 C语言规定argc为整型变量,argv为指向字符串的指针数组。加上...
  • Python:函数参数列表(1位置参数,2默认参数,3可变参数,4关键字参数,5可变关键字参数)何为关键字参数?在定义函数时,函数体形参列表中,可变参数(*args)后不带默认值的参数,为调用函数时必须赋值的关键字...
  • 当我们跑机器学习程序时,尤其是调节网络参数时,通常待调节的参数有很多,参数之间的组合更是繁复。依照注意力>时间>金钱的原则,人力手动调节注意力成本太高,非常不值得。For循环或类似于for循环的方法受限...
  • C# 不包含采用0个参数的构造函数

    万次阅读 热门讨论 2017-02-05 11:32:47
    内容: 一、构造函数是什么?...不带参数的构造函数称为:“默认构造函数”。无论何时,只要使用 new 运算符实例化对象,并且不为 new 提供任何参数,就会调用默认构造函数。除非类是 static 的
  • 本来今天吧,想写一个proxy class的范例,写着写着出了个问题,见如下代码 const int _cap = 10;... 同时我为Array1D这个类写了一个构造函数来顶替编译器自动生成的 Array1D(int inx) ...
  • 函数为开放计算机视觉(OpenCV)库库函数,用来检测图像中的目标 定义: typedef struct CvAvgComp { CvRect rect; int neighbors; }CvAvgComp;CvSeq* cvHaarDetectObjects( const CvArr* image,...
  • 如果箭头函数不需要参数或者需要多个参数,则需要使用小括号,下面是不含参数的情况,多参数参照上图 如果箭头函数代码块部分多于一条语句,则需要使用大括号括起来,并可以使用return语句返回 因为大括号被解释为...
  • chomp是一个函数,它直接对参数进行修改。作为一个函数,它有一个返回值,为移除的字符的个数: $food = <STDIN>; $betty = chomp $food; #得到值1 通常我们这么写: chomp ($text = <STDIN>); #...
  • C#中调用C函数,除了需要在C#中声明被调函数之外,还要考虑到参数传递的问题。虽然我在之前两篇文章中已经提到过如在C#中向C函数传递参数,但是在调用OpenGL函数时,仍然遇到不少难题,特别是关于指针方面。我试图在...
  • 函数名其实就是指向一个函数...在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。 #千万注意传参 在Python交互环...
  • findContours方法中各参数的含义及用法,比如要求只检测最外层轮廓该怎么办?contours里边的数据结构是怎样 的?hierarchy到底是什么鬼?Point()有什么用? 先从findContours函数原型看起: findContours( Input...
  • 用指针做参数 #include &lt;stdio.h&gt; void swap(int *a, int* b) { int t = *a; *a = *b; *b = t; } int main() { int a = 3, b = 4; swap(&amp;a, &amp;b); printf("%d %d",...
  • 这个函数自己的作用就是:需要一个c++程序作为输入,然后它可以将其中注释部分(就是\后的内容)的内容删掉,然后输出不含注释的文件。 另外还有一个问题:我们可以直接将c++程序的源文件输入进来进行处理吗?还是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 49,006
精华内容 19,602
关键字:

不含参数的函数