最近的一个项目需要进行蒙特卡洛模拟,并且需要对模拟结果进行表达式求职计算
计算的公式不固定一般的格式是这样$0+$1*2/$3*2.5且随着业务的复杂还需要
进行多次迭代计算。数据量很大最少的也要进行2-3万次计算。我试了用自己写的表达式求职程序和Mozilla的Rhino效率上都不是很理想,可能是自己
水平比较差,用Mozilla的Rhino居然比自己写的程序快一倍。我们用的jdk1.5所以不能
用jdk1.6中才支持的编译后执行脚本的方法。想过尝试动态生成Java文件然后编译执行,但是一想起在服务器环境这个程序可能需要
同时计算多个表达式......越想越觉得不可靠.大家处理这样的问题有什么好的思路,请给些指点.

解决方案 »

  1. 我不知道 BeanShell 的速度如何?
      

  2. 以脚本语言的计算能力根本就不适合做大规模数值计算。
    你可以先把一个固定的计算公式用java写好,然后跟Rhino比较一下。保守的估计至少要差几十倍以上,rhino的脚本编译运行也不能提高太多的效率,因为它依旧要依靠脚本运行引擎。如果计算公式不是经常变化的话,还是可以用java写,用javac动态编译。同时可以考虑用文件保存编译过的class。
      

  3. 现在比较麻烦的地方就是这个公式肯定是固定不下来的。我们做的那个模块不仅用了蒙特卡洛模拟还要进行杜邦分解,也就是将一个计算公式分解成一课计算树,然后从下向上计算,最下面的部分用蒙特卡洛模拟计算。将来
    有可能会在前段做一个界面出来,用户可以拖拽连线来构造计算树,然后发给后台计算。
    所以是不可能将计算过程固定的,解决办法只能有两种:
    1.用脚本
    2.将计算过程翻译成Java代码编译后执行,而这一过程的难点是编译后的文件怎么保存到哪?编译后的类如何加载。因为现实的情况很有可能是这一模块正同时处理多个不同的计算,必须要有一种策略让虚拟知道如何正确的
    加载编译文件
      

  4. 说的通俗一点就是如何高效的实现这个函数,数组的大小5000作用,
    函数会被多次调用/**
    *计算表达式的值并将结果赋值给返回数组.
    *比如eval("$0+$1*2+$3",result,d0,d1,d2,d3),最后result应
    *该能够正确的得到计算值
    *@param sExp    计算表达式,$0表示datas[0][i],$1表示datas[i][i]
    *@param result  返回结果存放数据
    *@param datas   计算相关数据
    */public void eval(String   sExp,
                     double[] result,
                     double[]...datas)throws Exception{}
      

  5. 5000 个数据计算 $0+$1*$2+$3 使用 BeanShell 的话需要 17~18 秒的时间,好像太耗时间了。
      

  6. 哈哈,改进了一下,把循环放到 BeanShell 里面用 JVM 去执行,5000 个数据计算 $0+$1*$2+$3 使用 BeanShell 的话压缩到了不到 600ms参考:import java.util.Random;import bsh.EvalError;
    import bsh.Interpreter;public class Test2 {
        
        public static void main(String[] args) throws EvalError {        
            final int count = 5000;
            final int paramCount = 4;
            
            double[][] datas = new double[paramCount][count];
            Random ran = new Random();
            for(int i = 0; i < datas.length; i++) {
                for(int j = 0; j < datas[i].length; j++) {
                    datas[i][j] = ran.nextDouble() * count;
                }
            }
            double[] results = new double[count];
            
            long t0, t1;
            t0 = System.currentTimeMillis();
            
            String exp = "$0 + $1 * $2 + $3";
            eval(exp, results, datas);
            
            t1 = System.currentTimeMillis();
            System.out.println(t1 - t0);
            
            // 测试正确性
            double[] res = new double[count];
            for(int j = 0; j < datas[0].length; j++) {
               for(int i = 0; i < datas.length; i++) {
                   res[j] = datas[0][j] + datas[1][j] * datas[2][j] + datas[3][j]; 
               }
            }
            
            for(int i = 0; i < count; i++) {
                if(results[i] != res[i]) {
                    System.out.printf("error! %.6f --> %.6f%n", results[i], res[i]);
                }
            }
        }
        
        public static void eval(String expression, double[] results, double[]... datas) throws EvalError {
            
            String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");
            
            String exp =
                " for(int j = 0; j < datas[0].length; j++) {" +
                "   for(int i = 0; i < datas.length; i++) {" +
                "     results[j] = " + ep + ";" +
                "   } " +
                " }";
            Interpreter bsh = new Interpreter();
            bsh.set("datas", datas);
            bsh.set("results", results);
            bsh.eval(exp);
            results = (double[])bsh.get("results");
        }
    }BeanShell 的官方网站:http://www.beanshell.org/
      


  7. 可能是你使用方式不当造成的,不要计算一次调用一次,把计算的过程用脚本写成循环那样只需要调用一次。
    我用Rhino是这样用的:
    double[] d1=new double[5000];
    double[] d2=new double[5000];
    double[] ret=new double[5000];
    .....
    1.将java中的数据映射到脚本
      ScriptableObject.putProperty(scope,"d1",d1);
      ScriptableObject.putProperty(scope,"d2",d2);
      ScriptableObject.putProperty(scope,"result",ret);2.设置循环脚本为如下格式
      (fucntion(){
           var i,len=d1.length<d2.length?d1.length:d2.length;
           var $0,$1....;
           for(i=0;i<len;i++){
              $0=d1[i];
              $1=d2[i];
              .....
              result=$0+$1*2;
           }
       })();
       
    3.执行脚本
    cx.evaluateString(
        scope,
        buf.toString(),
        "<GetResult>",
        0,
        null
    );
    这样效率执行一次调用一次高几个数量级,甚至比一般的表达式求值程序还要快很多.
    这个帖子没多少人关注,估计会用Rhino的人并不多。
    中午找到些资料,打算动态编译编译执行Java,回去测试下
      


  8. 学的够快的,不过用Rhino的话5000个才300ms多一些,BeanShell的效率开来没有Rhino好。
    BeanShell不知道支持的是那种语言,我比较懒还是喜欢我比较熟悉的JavaScript
      

  9. BeanShell 支持的是 Java 语言
      

  10. 哈哈,我终于知道前面为什么要 600ms 了,因为我多做了几次循环,哎,真粗心!改正后实际的工作时间在 150ms 左右(由于 JDK 的初始化问题,测试三次使用平均数)。有两种实现方式:第一种方法:
    import java.util.Random;import bsh.EvalError;
    import bsh.Interpreter;public class Test2 {
        
        public static void main(String[] args) throws EvalError {        
            final int count = 5000;
            final int paramCount = 4;
            
            double[][] datas = new double[paramCount][count];
            random(datas);
            double[] results = new double[count];
            
            long t0, t1;       
            String exp = "$0 + $1 * $2 + $3";
            t0 = System.currentTimeMillis();
            eval(exp, results, datas);        
            t1 = System.currentTimeMillis();
            System.out.println(t1 - t0);
            testCorrect(results, datas);
            
            random(datas);
            t0 = System.currentTimeMillis();
            eval(exp, results, datas);        
            t1 = System.currentTimeMillis();
            System.out.println(t1 - t0);
            testCorrect(results, datas);
            
            random(datas);
            t0 = System.currentTimeMillis();
            eval(exp, results, datas);        
            t1 = System.currentTimeMillis();
            System.out.println(t1 - t0);
            testCorrect(results, datas);
        }
        
        public static void eval(String expression, double[] results, double[]... datas) throws EvalError {
            
            String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");
            
            String exp =
                "for(int j = 0, k = datas[0].length; j < k; j++) {" +
                "  results[j] = " + ep + ";" +
                "}";
            Interpreter bsh = new Interpreter();
            bsh.set("datas", datas);
            bsh.set("results", results);
            bsh.eval(exp);
            results = (double[])bsh.get("results");
        }
        
        /**
         * 随机生成数据值
         * 
         * @param datas
         */
        private static void random(double[][] datas) {
            Random ran = new Random();
            for(int i = 0; i < datas.length; i++) {
                for(int j = 0; j < datas[i].length; j++) {
                    datas[i][j] = ran.nextDouble() * datas[i].length;
                }
            }
        }
        
        /**
         * 测试调用的结果是否正确
         * @param results
         * @param datas
         */
        private static void testCorrect(double[] results, double[][] datas) {
            double[] res = new double[results.length];
            for(int j = 0; j < datas[0].length; j++) {
               for(int i = 0; i < datas.length; i++) {
                   res[j] = datas[0][j] + datas[1][j] * datas[2][j] + datas[3][j]; 
               }
            }        
            for(int i = 0; i < results.length; i++) {
                if(results[i] != res[i]) {
                    System.out.printf("error! %.6f --> %.6f%n", results[i], res[i]);
                }
            }
        }
    }第二种方法,只是更改 eval 方法,使用字符串更像一个 Java 方法:    public static void eval(String expression, double[] results, double[]... datas) throws EvalError {
            String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");        
            String exp =
                "public void eval(double[] results, double[][] datas) {" +
                "  for(int j = 0, k = datas[0].length; j < k; j++) {" +
                "    results[j] = " + ep + ";" +
                "  }" +
                "}";
            Interpreter interpreter = new Interpreter();
            NameSpace ns = NameSpace.JAVACODE;
            interpreter.eval(exp, ns);
            BshMethod method = ns.getMethods()[0];
            method.invoke(new Object[]{ results, datas }, interpreter);        
        }
      

  11. 如果不是第一次启动 JVM 的话,那么耗时在 100ms 以下。
      

  12. 如果我是斑竹的话我会因为自己的的这次回复的含金量将此帖设置为精华。
    找到的资料非常让人振奋,不得不说脚本语言是无法和编译语言比速度的。package com.fhd.analysis.data;public interface Calculater {
    public void calculate(double[] result,double[][] d);
    }
    package com.fhd.analysis.test;import java.io.StringReader;
    import java.util.Date;import org.codehaus.janino.ExpressionEvaluator;
    import org.codehaus.janino.Scanner;import com.fhd.analysis.data.Calculater;public class TestDynamicCimpiel {
    public static void main(String[] args){
    try{
    double[] d0=new double[5000];
    double[] d1=new double[5000];
    double[] r=new double[5000];

    String code="public void calculate(double[] r,double[][] d){"
           +" for(int i=0;i<5000;i++){"
           +"      r[i]=d[0][i]+d[1][i]*2;"
           +"   }"
           +"}"; Calculater c=(Calculater)ExpressionEvaluator
    .createFastClassBodyEvaluator(
    new Scanner(null,new StringReader(code)), 
    Calculater.class, 
    (ClassLoader)null
    );

    for(int i=0;i<5000;i++){
    d0[i]=Math.floor(Math.random()*100);
    d1[i]=Math.floor(Math.random()*100);
    }

    double[][] d=new double[][]{d0,d1};
    Date ds=new Date();

    c.calculate(r,d);
    c.calculate(r,d);
    c.calculate(r,d);
    c.calculate(r,d);
    c.calculate(r,d);
    c.calculate(r,d);

    Date de=new Date();

    System.out.println(
       "耗时:"+(de.getTime()-ds.getTime())
    );

    }catch(Exception e){
    e.printStackTrace();

    }
    }
    }耗时:0赶紧去下载吧,有的时候还真得用google搜索一些英文关键字,要不这么好的资料
    是无论如何也找不到的
    http://www.janino.net/download.html
      

  13. 不知道楼主的问题急不急。
    我也不太懂 表达式求值、编译原理 之类的知识,说白了,我就是一小白。如果楼主有空的话,并且,问题仍没解决的话。
    可以看看我的以前写的一点代码,记得是关于表达式求值的。
    楼主运行一下,看看效率能否满足您的要求。
    地址是:http://blog.csdn.net/preferme/archive/2009/07/20/4365251.aspx
      

  14. 谢谢18#楼的哥们,这种表达式求值的程序肯定是不能满足需求的。
    因为即使你的效率再高效,连续执行5000次就要执行5000次调用,
    就要完成5000次解析,所以根本不可行,除非你自己写的解析程序
    能够模拟循环。所以对于这样大数据量的计算必须解决如何避免多次
    解析的问题。而解决办法就是要么用脚本,要么实现Java代码的动态
    编译动态执行。我找到的那个类库比较理想,可以实现将字符串编译
    为类然后加载使用
      

  15. 嗯 Janino 不错,改成这样的话,就能完全满足楼主的要求了    public static void eval(String expression, double[] results, double[]... datas) throws Exception {        
            String ep = expression.replaceAll("\\$(\\d+)", "datas[$1][j]");        
            String exp =
                "public void eval(double[] results, double[][] datas) {" +
                "  for(int j = 0, k = datas[0].length; j < k; j++) {" +
                "    results[j] = " + ep + ";" +
                "  }" +
                "}";
            Calculater c = (Calculater) ExpressionEvaluator.createFastClassBodyEvaluator(
                    new Scanner(null, new StringReader(exp)),
                    Calculater.class,
                    (ClassLoader) null
                );
            c.eval(results, datas);
        }
      

  16. Janino 很不错,动态编译后并使用接口,属于编译型的
    相比较而言 BeanShell 是个 Java 解释器,我优化了一下也需要 Janino 近三倍的时间
    至于 JDK 6 中的动态语言或者是 Rhino 之类的在速度上根本就没办法比的
      

  17. 这方面的文章国内好像很难找到,这么好的东西也很少有人听说。我是试着搜索java dynamic compile找到的,从论坛的回帖中了解到人家已经用了很长时间了,广泛用于电子商务OA等方面。不得不承认我们和国外是有差距
    的,人家都可以自己实现编译器了,可是我们还在为花大量时间用于解决这些琐碎的问题
      

  18. 楼主提供Janino确实很爽,无论使用还是效率。我看了一下,如果实现楼主17楼的循环计算表达式。我给的那个代码,貌似也可以实现。
    只需要把循环体的表达式进行编译,然后循环调用表达式进行结果求值就可以了。
    一次编译,多次求值,循环中的表达式结构,始终不会改变。
    但效率不知道有没有Janino高。呵呵。
      

  19. 问题解决就算了,至于精华不精华那全凭版主的意思,你要求加精华的话,我跟版主去说一下吧。如果其他人的回复没有帮到你什么的话,你可以点“无满意结帖”。至于这个帖子不是“谁都可以说上两句”这点我并不赞同,虽然发帖子提问了,对于帖主来说应该是集思广义。说到“谁都可以说上两句”之类的,楼主使用的也是在使用别人写好过的东西,并不是自己写的,因此建议楼主低调一些,毕竟诸如此类的工具,稍微会 Java 的人看一下 Cookbook,或者 API DOC 谁都会用。
      

  20. 我不知道 BeanShell 的速度如何?顺顶
      

  21. 看来我来晚了。其实要实现运行时编译用jdk1.6是最方便了,不能用的话,运行时调用javac也一样,就是要配置一下jdk路径,比较麻烦点。
    偷懒一点的话,动态生成个含表达式的jsp文件,让容器帮你编译好了,多方便。
      

  22. 才2-3万次计算啊?我有个项目一般需要计算200百万(Mega)次表达式的值,头疼啊!有谁有好的解决办法?我的项目是用VC++。
      

  23. 楼上几位说的都听不懂,:(俺的苯办法:方法类似于8楼(公式动态解析没有做)
    代码在VC下编译执行通过了
    执行时间上,函数DynCalc2()是编死的代码的10 - 15倍左右
    我怀疑switch语句上浪费了不少条件判断的时间,对汇编不了解,如果能直接jump (*pOpt)到各自的处理中,因该还能提高一些性能废话不说了,上代码
    #include <windows.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <tchar.h>//==============================================================================
    //
    //  Define Operator
    //#define DATA_1 0x81 //  Push Data 1
    #define DATA_2 0x82 //  Push Data 2
    #define DATA_3 0x83 //  Push Data 3
    #define DATA_4 0x84 //  Push Data 4
    #define DATA_5 0x85 //  Push Data 5
    #define DATA_6 0x86 //  Push Data 6
    #define DATA_7 0x87 //  Push Data 7
    #define DATA_8 0x88 //  Push Data 8#define OPT_ADD 0x01 //  + (Pop 2 Data, Push Result)
    #define OPT_SUB 0x02 //  - (Pop 2 Data, Push Result)
    #define OPT_MUL 0x03 //  * (Pop 2 Data, Push Result)
    #define OPT_DIV 0x04 //  / (Pop 2 Data, Push Result)#define OPT_DUP 0x11 //  Dup Data (Pop 1 Data, Push 2 Copy)
    #define OPT_SWAP 0x12 //  Swap Data (Pop 2 Data, Swap Data, Push 2 Data)#define OPT_END 0xFF //  Pop 1 Data, Return//==============================================================================
    //
    //  Function
    //__forceinline DOUBLE WINAPI DynCalc(BYTE * pOpt, DOUBLE * pData)
    {
    DOUBLE dStack[1024] = { 0 };
    DOUBLE * pStack = dStack; DOUBLE dTemp; for (;;)
    {
    switch (*pOpt)
    {
    case DATA_1: *pStack = pData[0]; pStack++; break;
    case DATA_2: *pStack = pData[1]; pStack++; break;
    case DATA_3: *pStack = pData[2]; pStack++; break;
    case DATA_4: *pStack = pData[3]; pStack++; break;
    case DATA_5: *pStack = pData[4]; pStack++; break;
    case DATA_6: *pStack = pData[5]; pStack++; break;
    case DATA_7: *pStack = pData[6]; pStack++; break;
    case DATA_8: *pStack = pData[7]; pStack++; break; case OPT_ADD: pStack--; *(pStack - 1) += *pStack; break;
    case OPT_SUB: pStack--; *(pStack - 1) -= *pStack; break;
    case OPT_MUL: pStack--; *(pStack - 1) *= *pStack; break;
    case OPT_DIV: pStack--; *(pStack - 1) /= *pStack; break; case OPT_DUP:
    {
    *pStack = *(pStack - 1);
    pStack ++;
    break;
    }
    case OPT_SWAP:
    {
    dTemp = *(pStack - 1);
    *(pStack - 1) = *(pStack - 2);
    *(pStack - 2) = dTemp;
    break;
    }
    case OPT_END:
    return *(pStack - 1);
    } pOpt++;
    }
    }
    VOID WINAPI DynCalc2(DWORD dwLoop, BYTE * pOpt, DOUBLE * pData, DOUBLE * pResult)
    {
    DOUBLE dStack[1024] = { 0 };
    DOUBLE * pStack = dStack;
    BYTE * pRawOpt = pOpt; DOUBLE dTemp; for (;;)
    {
    switch (*pOpt)
    {
    case DATA_1: *pStack = pData[0]; pStack++; break;
    case DATA_2: *pStack = pData[1]; pStack++; break;
    case DATA_3: *pStack = pData[2]; pStack++; break;
    case DATA_4: *pStack = pData[3]; pStack++; break;
    case DATA_5: *pStack = pData[4]; pStack++; break;
    case DATA_6: *pStack = pData[5]; pStack++; break;
    case DATA_7: *pStack = pData[6]; pStack++; break;
    case DATA_8: *pStack = pData[7]; pStack++; break; case OPT_ADD: pStack--; *(pStack - 1) += *pStack; break;
    case OPT_SUB: pStack--; *(pStack - 1) -= *pStack; break;
    case OPT_MUL: pStack--; *(pStack - 1) *= *pStack; break;
    case OPT_DIV: pStack--; *(pStack - 1) /= *pStack; break; case OPT_DUP:
    {
    *pStack = *(pStack - 1);
    pStack ++;
    break;
    }
    case OPT_SWAP:
    {
    dTemp = *(pStack - 1);
    *(pStack - 1) = *(pStack - 2);
    *(pStack - 2) = dTemp;
    break;
    }
    case OPT_END:
    {
    *pResult = *(pStack - 1); pResult ++;
    pStack --; pOpt = pRawOpt;
    dwLoop --; if (dwLoop == 0)
    return; continue;
    }
    } pOpt++;
    }
    }int main()
    {
    DOUBLE dP1 = (DOUBLE)rand() / 600.0;
    DOUBLE dP2 = (DOUBLE)rand() / 601.0;
    DOUBLE dP3 = (DOUBLE)rand() / 602.0;
    DOUBLE dP4 = (DOUBLE)rand() / 603.0;
    DOUBLE dResult; DWORD dwLoop = 1000000; BYTE btOpt[] = {DATA_1, DATA_2, DATA_3, OPT_MUL, DATA_4, OPT_DIV, DATA_5, OPT_MUL, OPT_ADD, OPT_END};
    DOUBLE dData[] = {dP1, dP2, 2.0, dP3, 2.5}; DOUBLE * pData2 = (DOUBLE *)malloc(dwLoop * sizeof(dData));
    DOUBLE * pResult = (DOUBLE *)malloc(dwLoop * sizeof(dResult)); DWORD s, e;
    DWORD i; {
    DOUBLE * pTemp = pData2; for (i = 0; i < dwLoop; i++)
    {
    memcpy(pTemp, dData, sizeof(dData));
    pTemp += (sizeof(dData) / sizeof(DOUBLE));
    }
    }
    //==============================================================================// s = GetTickCount();
    //
    // for (i = 0; i < dwLoop; i++)
    // {
    // dResult = DynCalc(btOpt, dData);
    // }
    //
    // e = GetTickCount();
    // printf("%f, %d\n", dResult, e - s);//============================================================================== s = GetTickCount(); DynCalc2(dwLoop, btOpt, pData2, pResult); e = GetTickCount();
    printf("%f, %d\n", *pResult, e - s);//============================================================================== s = GetTickCount(); for (i = 0; i < dwLoop; i++)
    {
    dResult = dP1 + dP2 * 2.0 / dP3 * 2.5;
    }

    e = GetTickCount();
    printf("%f, %d\n", dResult, e - s);//============================================================================== return 0;
    }
      

  24. 可以回复的。大家看Forcal能否满足要求,需要你的项目能加载dll。
    Forcal一级函数的计算速度约为(C/C++)或FORTRAN速度的50%左右,二级函数的速度稍有降低。FORCAL与VC的速度比较:表达式:(x,y)=cos{1-sin[1.2*[x+0.1]^(y/2-x)+cos{1-sin[1.2*[x+0.2]^(y/3-x)]}]-cos
    {1-sin[1.2*[x+0.3]^(y/4-x)]}-cos{1-sin[1.2*[x+0.4]^(y/5-x)+cos{1-sin[1.2*[x+0.5]
    ^(y/6-x)]}]-cos{1-sin[1.2*[x+0.6]^(y/7-x)]}}}FORCAL正对表达式进行循环求和计算,请等待... ...
    forcal计算结果:19160.536601703152  运行时间:1782 即: 1.782秒VC++     正对表达式进行循环求和计算,请等待... ...
    VC++     计算结果:19160.536601703152   运行时间:1328 即: 1.3280000000000001秒我的blog:http://blog.csdn.net/forcal
      

  25. 上面例子VC的代码如下: cout<<"VC++     正对表达式进行循环求和计算,请等待... ..."<<endl;
    z=0.0; old=clock();
    for(x=0.0;x<=1.0;x=x+0.0011)
    for(y=1.0;y<=2.0;y=y+0.0011)
    {
    z=z+cos(1.0-sin(1.2*pow(x+0.1,y/2.0-x)+cos(1.0-sin(1.2*pow(x+0.2,y/3.0-x))))-cos(1.0-sin(1.2*pow(x+0.3,y/4.0-x)))-cos(1.0-sin(1.2*pow(x+0.4,y/5.0-x)+cos(1.0-sin(1.2*pow(x+0.5,y/6.0-x))))-cos(1.0-sin(1.2*pow(x+0.6,y/7.0-x)))));
    }
    now=clock();
    cout<<"VC++     计算结果:"<<setprecision(20)<<z<<' ';
    cout<<"  运行时间:"<<now-old<<" 即: "<<(double)(now-old)/CLOCKS_PER_SEC<<"秒"<<endl<<endl; 
      

  26. 非常感谢Forcal,都结贴了还这样热心的提供帮助。我们的那个项目利用Janino动态编译已经可以初步解决
    我们的问题了。谢谢你提供的信息,我会把他推荐给我的搞c++的同学。因为一直做java Web开发,关注c++
    很少,看了下相关的介绍,那个库确实是个好东西。可惜我们用不上,一是因为我们的项目还没有复杂到那个
    程序,自己写个动态编译的Java程序足以应付了,再有就是我们的团队里没有人熟悉如何用JNI调用c++,而且
    那个库是需要依赖Windows的,这一点也不能不考虑。
      

  27. 好东西  长见识了   可惜都不会  stdlib   stdio  不应该是c的库吗?
      

  28. 此贴就如2年前刚上CSND,看别人帖子的感觉——我就像一张白纸
      

类似问题 »