Weijian 的个人资料Enjoy My Life照片日志列表 工具 帮助

日志


    9月1日

    很牛的顶,供大家收藏


    要顶
    必须顶
    不得不顶
    用尽全力顶
    再加上千斤顶
    总之把它顶到顶
    接着使出葵花宝顶
    就算顶到史前也要顶
    老子看了会用道德经顶
    孔子亲自拜你为师天天顶
    秦始皇站在阿房宫上使劲顶
    汉高祖挥师杀向东罗马为你顶
    吕布抛弃了貂禅而选择了帮你顶
    张三丰见了后用太极拳九式全力顶
    左冷禅召开武林盟主大会商讨如何顶
    西门吹雪从此学会了最强一招剑神一顶
    龙剑飞的如来神掌最后一招改为万佛朝顶
    陆小凤从此再也不管闲事了而专门来为你顶
    四大神捕四面出击看天下还有没有人敢不在顶

    8月11日

    使用perl识别中文的方法

    使用perl识别中文时,一种方法是根据编码字符集的范围进行识别,但是通常编码方式和相应的编码范围都不是很好确定,在网上找了一种比较通用的方法。
    基本思想是使用perl的decode(编码)和encode(解码)功能,然后使用property进行匹配:

    use strict;
    use Encode;

    my $str = "2008中国";
    my $str_c = decode('cp936', $str);
    $str_c =~ m/(\p{Han}+)/;
    print encode('cp936',$1);

    注:cp936是简体中文和Unicode的映射表。
    8月4日

    很久没有使用STL了,记录一些常用的STL常用操作

    string的使用方法
    string是一个保存字符序列的容器,除了有字符串操作的功能外,还有容器的操作功能。要列出并说明这些功能,需要太多的篇幅,这里只说明常用的字符串操作功能。
    • 说明和初值:
      string strZqdm ;
      string strZqdmJz("600446") ;
      string strZqdmZs="000003";
    • 串连接:
      可以使用+或+=连接两个或多个字符串,这些字符串,可以是const char *、char *或string型的。有多个串时,前两个串中必须有一个是string型的。
      string strSql, strSel ;
      strSel="select * ";
      strSql = strSel+" from "+" zqk";
      strSel+=" from "+" zqk";
      下面这句是错误的:
      strSql="select *"+" from zqk"; //错误
      必须连接两个char *、const char *型时,可以写成:
      strSql=string("select *")+" from zqk";
      对于两个char *或const char *数据的连接,也要有一个先转换为string。如:
      char szSel[]={"select *"};
      char szFrom[]={" from zqk"};
      strSql=string(szSel)+szFrom;
    • 串替换
      把一个串中的子串,替换为另一个串,使用replace()函数,第一个参数是替换位置,第二参数是替换长度,第三个参数是替换子串。替换串和被替换的子串长度可以不等。
      下例把一个串中的YYYYMMDD替换为一个日期串。
      int n ;
      string strFile = ".\\QS_YYYYMMDD.dat" ;
      string strDate="20070505" ;  
      n = strFile.find("YYYYMMDD") ;
      if (n >= 0)
      strFile.replace(n, 8, strDate) ;
    • 串插入
      在串的中间插入一个子串,使用insert()函数,第一个参数是插入位置,第二个是插入子串。
      strSel = "600446";
      strSel.insert(0, "ZQDM=") ;
    • 串删除
      在串的中间,删除若干个字符,使用erase()函数,第一个参数是删除位置,第二个参数是删除长度。
      strSel = "ZQDM=600446";
      strSel.erase(0, 2) ;
      注意:串的替换,插入,删除操作中,操作位置必须是0到字串长度的范围之内,超过这个范围,将会引起异常!!
    • 串查找
      string容器提供了多种查找方法,正向和反向的简单查找,第一次或最后一次出现,第一次或最后一次不出现的复杂查找,不同的查找方法函数不同。这里只介绍最简单的正反向查找。
      1. 正向查找:查找串中出现指定子串的位置,使用find()函数,第一个参数表示子串。返回一个整型值,表示出子串的位置。大于等于0(string::npos)时表示出现了子串,否则表示没有出现。
        strSel = "ZQDM=600446 446";
        int n = strSel.find("446") ;
      2. 反向查找:从后面开始,查找串中出现指定子串的位置,使用rfind()函数,第一个参数表示子串。返回一个整型值,表示出子串的位置。大于等于0(string::npos)时表示出现了子串,否则表示没有出现。
        strSel = "ZQDM=600446 446";
        int n = strSel.rfind("446") ;
    • 取子串操作
      从一个串中取子串的操作,使用substr()函数,第一个参数表示开始位置,第二个参数表示取的长度。如果不包含第二个参数,一直取到串的结束。
      strSel = "select zqdm from hqk";
      strFunc= strSel.substr(0, 6) ; //从左取6个
      strTable= strSel.substr(7, 4) ;//从第7个开始,取4个
      str1 = strSel.substr(12) ;  //从第12个开始,取到结束
      string没有提供left,right函数,来从左边和右边取子串,造成了一些不便,需要采用技巧来实现。
      取左串:strSel.substr(0, n) ;
      取右串:strSel.substr(strSel.size()-n);
    • 字串比较
      比较两个字串,使用compare函数,可以比较全字串,或串中若干个字符。相等时,返回整型值0。
      strZqdm.compare(strDm) ; //比较两个串
      strZhzj.compare(4,8,strNewZh) ;//从strZhzj位置4开始,和strNewZh比较8个字符。
      strZhzj.compare(4,8,strNewZh,2) ;//从strZhzj的位置4开始,和strNewZh的位置2开始比较8个字符。
    • 其它常用操作
      对于string,还有一些常用的操作。
      返回字串长度的size(),例:len = strSql.size();
      下标操作at(),例:c = strSql.at(1);strSql.at(2)= "5 ";
      下标操作也可以用[],例:c = strSql[1];strSql[2]= "5 ";
      返回字串C字符指针的c_str(),类型是const char *。例:
      strZqdm= "000001";
      strcpy(szDm, strZqdm.c_str()) ;
      很遗憾,string中没有提供忽略大小写的字串比较,和忽略大小写的查找。如果需要这项功能,只有使用string的c_str()函数,用C的字串操作函数来实现了。
    vector的使用方法
    容器vector称做向量,相当于可以动态改变大小的数组,使用方法简单。vector里,提供了大量的函数,其中许多函数,在STL的不同容器里,用法是基本相同的,熟悉了vector,再掌握其容器,会简单的多。
    下面说明vector的常用方法。
    • 说明和赋值
      vector <int> viCount ;//定义一个空的整型vector
      vector <int> viLen[10] ; //定义一个大小为10的整型vector
      vector <string> vsZqdm ; //定义string型的vector
      下面定义一个struct的vector:
      typedef struct
      {
      char szZqdm[7] ;
      char szZqmc[9] ;
      } ZQXX ;
      vector <ZQXX> vZqdm ;
      在定义vector <string> 后,VC6里会有4786的警告信息,可以使用#pragma warning(disable:4786)来屏蔽。
      对于vector中存在的元素,需要更改其值时,可以使用数组的方式,或成员函数at(),例如:
      viLen[1]=10;viLen.at(2)=11;
    • 迭代器iterator
      iterator,叫做迭代器,相当于一个指针,指向容器中的元素,是STL许多操作的基础,在遍历容器等场景下必须使用的数据类型。
      iterator的说明:
      vector<int>::iteratorit;
      vector的begin()成员函数返回第一个元素的iterator指针,end()成员函数返回最后一个元素下一个位置的iterator指针。* iterator返回iterator所指的元素的值。
      下例显示vector中的所有元素:
      vector <int> viCount(5) ;
      vector<int>::iteratorit;
      viCount[2]=3;
      for(it=viCount.begin();it!= viCount.end();it++)
      cout << *it << endl ;
    • 增加元素
      在vector中增加元素,分为在尾部增加,和在中间插入元素两种情况,push_back()在尾部增加元素,insert()在vector中间插入元素。使用insert插入元素时,要用到iterator定位,不太方便。
      例:
      viCount.push_back(10) ;  //在尾部增加一个元素,值是10
      it=viCount.begin();viCount.insert(it,11);//在开始处插入一个元素,值是11。
    • 删除元素
      删除vector里的所有元素,可以使用clear()函数,可以删除一个或多个元素,使用erase()。例:
      vZqdm.clear() ;  //清空vZqdm
      viCount.erase(it);  //删除迭代器it指的元素
      viCount.erase(it0,it1); //删除迭代器it0和it1之间的元素。
    • 引用元素
      对vector中的元素引用,除了[]和at()成员函数外,也可以通过迭代器引用其中的元素。
      vector <ZQXX> vZqdm ;
      vector< ZQXX >::iteratorit;
      ZQXX zq;
      it= vZqdm.begin();
      zq = *it ;
      memcpy(&zq,it,sizeof(zq));
    • 查找
      查找可以使用STL的find()函数完成,对标准类型的vector,STL已经定义了find()函数,如果数据类型是自定义的结构,需要自己编写find()函数,来完成查找。
      标准类型的vector的查找:
      it1 = find(viCount.begin(),viCount.end(),4) ;
      上句从viCount中查找值为4的元素,如果找到,it1就指向那个元素,如果找不到,it1就是viCount.end()。
      结构vector的查找,需要自己编写find函数来完成。
    • 排序
      STL提供了多种功能强大而复杂的排序功能,通过使用迭代器,可以对某vector中所有或部分元素排序。对于标准数据类型,可以使用STL的排序函数。对于自定义的数据类型,需要编写一个比较函数,由STL调用这个比较函数,完成排序功能。
      例:对int型的vector vCount做正序排序。
      sort(vCount.begin(), vCount.end());
      在VC6里,标准数据类型的降序排序也要写比较函数。例:
      sort(vCount.begin(), vCount.end(),mycomp);
      bool mycomp(const int &a, const int &b)
      {
       return a > b;
      }
      对于struct型的vector进行排序,必须写比较函数。
      例:按股票代码正序排序
      vector <ZQXX> vZqdm ;
      sort(vZqdm.begin(), vZqdm.end(), cmp_dm) ;
      bool cmp_dm(const ZQXX &a, const ZQXX &b)
      {
       return strcmp(a.szZqdm, b.szZqdm) < 0;
      }
      需要倒序排序时,把cmp_dm函数的<改为>就可以了。
    • 其它常用操作
      取大小函数int size(),如vZqdm.size();
      是否为空函数bool empty(),如vZqdm.empty();
      在尾部增加一个元素,push_back(),如vZqdm.push_back (zqdm);
      从尾部弹出一个元素,vector大小减1,pop_back(),如count=vCount.pop_back();


    5月16日

    vi中的替换命令总结

    • 全局替换
      •  :%s/AAA/BBB 替换所有行的第一个AAA为BBB
      •  :%s/AAA/BBB/g 替换全文中的AAA为BBB
    • 单行替换
      •  :s/AAA/BBB 替换当前行的第一个AAA为BBB
      •  :s/AAA/BBB/g 替换当前行的所有AAA为BBB
    • 多行替换
      •  :n,$s/AAA/BBB 替换从第n行到最后一行每行中的第一个AAA为BBB (n为.表示当前行)
      •  :n,$s/AAA/BBB/g 替换从第n行到最后一行每行中的所有AAA为BBB (n为.表示当前行)
    • 特殊分隔符
      •  :s#AAA#sky# 替换当前行的第一个AAA为BBB (AAA和BBB中可以包含/)
    3月25日

    转一篇文章,读罢后心中不禁浮现起峥嵘的读博岁月

    张启发院士给他的博士生的一封信zz

    最近我拜读了各位送交的年度工作计划,仔细推敲后,仍感到有三个方面的问题十分严重:
    第一,对课题理解不够,有的根本谈不上理解,做了不止一年,尚未进入角色。
    第二,已经完成的工作量严重偏少,博士(有的是从本室硕士上来的)做了几年还未见到可以发表的东西。
    第三,在计划中倾向于以最低工作量结束研究。即游击战法。以消耗最少的人力来解决战斗。
    这是从纸上看到的。从实验室看到的现象是,有那么几位常不到实验室照面,似乎这里是一个可来可不来的地方。而且愈未进入角色的,愈不钻研文献;工作做得愈差的,愈少见做实验。请各位想想,你不学,怎么能变内行?你不干,怎么会有结果?不钻研与自己课题密切相关的文献,不把实验室工作放在头等重要的位置上,不能算作一个名副其实的博士生。
    写一点专题读书报告,其实是一个领域内进入角色的好方法。一篇不够,可以连续写几篇,直到把与研究课题有关的方面较好地弄懂为止。如果在写读书报告的同时,加进一点自己的见解就更好了。写得好的可以送出发表。这对加强业务能力,提高写作水平,深化对自己研究工作的感情和培养事业心都大有益处。几年来我们大会小会说了很多,而至今交得很少。有人说是没有时间,我看是不愿投入时间。对博士生而言,每天工作12小时是正常的,少于这个时间就不正常了;每周工作六天半以上是正常的,少于六天半就不正常了。
    我这里是基于美国PhD四年学制所得到的概念。在我们目前三年学制还要强制性学半年英语的情况下,上述时间的投入充其量也只是一个下限。人生一世,应该追求有所建树。记得有一首诗:“朝为田舍郎,暮登天子堂。将相本无种,男儿当自强”。此诗所描述的虽有些封建意识,倒也道出了人生的真谛。想稍加以补充的是在男女完全平等的时代里,女儿也应当自强。
    走到了做科学这条路上,博士生阶段有无成就与将来有无建树关系十分密切。椐我观察,在我们这一代人中凡是后来有所成就者,大多在博士学习阶段就奠定了很好的基础。我理解的基础含三个方面的内容:一是广博的知识和不断求知的欲望;二是作为今后发展基础的工作成就;三是不断进取的奋斗精神和以工作作为第一需要的人生观。试想:要建功立业,博士生阶段不搏,更待何时?
    我们这里几乎所有的人都希望博士毕业后到国外去做博士后,我很赞成,并愿尽我之力给予支持。我很希望我们实验室出去的人将来在国际上都成为知名人士。今后在我们室毕业的博士出国做博士后可能主要有两类途径,一类是经我提名推荐,另一类是博士生本人争取,而将以后者为主。而后者却意味着要参与国际竞争。博士论文做得好,竞争性就强,我也乐意推荐,用人方也愿意接收。博士论文做得不好的,我不愿意推荐,而且推荐起来难度也大。其实,各人的路在各人自己脚下,各人的命运在各人自己手里。成败兴衰,全在各人,请各自勉。

    2月26日

    perl中实现多行注释

     ......
     ......
     =for comment
     ...... (被注释的代码)
     =cut
     ......
     ...... 
    2月25日

    mysql安装记录

    今天装了一遍mysql,把步骤记在这里,以免忘记。
    1. 在安装文件按路径下
      1) 配置安装路径
          ./configure --prefix=安装路径
      2) 编译源文件
          make
      3) 安装
          make install
    2. 到mysql安装路径下
      1) 安装mysql自身数据库
          ./bin/mysql_install_db
    3. 额外配置
      1) 把bin路径加到系统.bash_profile的PATH中
      2) 配置系统路径下的.my.cnf文件,比如端口之类

    起停mysql

    1. 启动mysql server: mysqld_safe &
    2. 打开mysql client: mysql -uroot -P端口号
    3. 停止mysql server: mysqladmin -uroot shutdown

    在安装过程中遇到的问题

    1. 如果没有install db的话,可能会没有var目录
    2月12日

    unix中使用bzip2压缩和解压

    写在这里以免又忘了
    1. 压缩多个文件,使用tar调用bzip2
      压缩:tar  -cjf  out.tar.bz2  in0.txt  in1.txt  in2.txt
      解压:tar  -xjf  out.tar.bz2 
    2. 压缩一个文件,直接调用bzip2
      压缩:bzip2  [-k]  in.txt
      解压:bzip2  -d  in.txt.bz2

    bzip2是一个号称压缩率最高的压缩方法,推荐使用。

    附使用tar打包解包的命令参数(不压缩):

    1. 打包:tar -cvf out.tar out/
    2. 解包:tar -xvf out.tar

    10月27日

    C语言获得整数类型和浮点类型的大小限制zz

    这篇文章对C语言中的基本数据类型的各种config进行了说明,很基础。

    /*c头文件limits.h和float.h分别提供有整数类型和浮点类型的大小限制的详细说明.*/

    /*可以用文本编辑器来打开和查看系统的头文件.*/

    #include <conio.h>
    #include <sio.h>
    #include <limits.h>
    #include <float.h>

    int main(void)
    {
         /* char相关 */
         printf("char的位数:%u\n",CHAR_BIT);
         printf("char类型的最大值:%d\n",CHAR_MAX);
         printf("char类型的最小值:%d\n",CHAR_MIN);
         printf("signed char类型的最大值:%d\n",SCHAR_MAX);
         printf("signed char类型的最小值:%d\n",SCHAR_MIN);
         printf("unsigned char类型的最大值:%u\n",UCHAR_MAX);
         /* short相关 */
         printf("short类型的最大值:%hd\n",SHRT_MAX);
         printf("short类型的最小值:%hd\n",SHRT_MIN);
         printf("unsigned short类型的最大值:%u\n",USHRT_MAX);
         /* int相关 */
         printf("int类型的最大值:%d\n",INT_MAX);
         printf("int类型的最小值:%d\n",INT_MIN);
         printf("unsigned int类型的最大值:%u\n",UINT_MAX);
         /* long 相关 */
         printf("long类型的最大值:%ld\n",LONG_MAX);
         printf("long类型的最小值:%ld\n",LONG_MIN);
         printf("unsigned long类型的最小值:%lu\n",ULONG_MAX);
         /* float相关 */
         printf("float类型的尾数位数:%u\n",FLT_MANT_DIG);
         printf("float类型的最小有效数字位数:%u\n",FLT_DIG);
         printf("带有全部有效数字位数的float类型的负指数的最小值:%d\n",FLT_MAX_10_EXP);
         printf("带有全部有效数字位数的float类型的正指数的最大值:%d\n",FLT_MIN_10_EXP);
         printf("保留全部精度的float类型正数的最小值:%e\n",FLT_MIN);
         printf("保留全部精度的float类型正数的最大值:%e\n",FLT_MAX);
         printf("1.00和比1.00大的最小的float类型值之间的差值:%e\n",FLT_EPSILON);
         /* double相关 */
         printf("double类型的尾数位数:%u\n",DBL_MANT_DIG);
         printf("double类型的最小有效数字位数:%u\n",DBL_DIG);
         printf("带有全部有效数字位数的double类型的负指数的最小值:%u\n",DBL_MAX_10_EXP);
         printf("带有全部有效数字位数的double类型的正指数的最大值:%d\n",DBL_MIN_10_EXP);
         printf("保留全部精度的double类型正数的最小值:%e\n",DBL_MIN);
         printf("保留全部精度的double类型正数的最小值:%e\n",DBL_MAX);
         printf("1.00和比1.00大的最小的double类型值之间的差值:%e\n",DBL_EPSILON);
         /* long double相关 */
         printf("long double类型的尾数位数:%d\n",LDBL_MANT_DIG);
         printf("long double类型的最小有效数字位数:%d\n",LDBL_DIG);
         printf("带有全部有效数字位数的long double类型的负指数的最大值:%d\n",
                   LDBL_MAX_10_EXP);
         printf("带有全部有效数字位数的long double类型的正指数的最小值:%d\n",
                   LDBL_MIN_10_EXP);
         printf("保留全部精度的long double类型正数的最小值:%le\n",LDBL_MIN);
         printf("保留全部精度的long double类型正数的最大值:%le\n",LDBL_MAX);
         printf("1.00和比1.00大的最小的long double类型值之间的差值:%le\n",LDBL_EPSILON);

         return 0;
    }

    10月14日

    在Lex&Yacc中使用内存缓冲区作为输入源

    最近在使用Lex&Yacc解析SQL语句,需要解决得两个重要问题是:1) SQL语句保存在内存缓冲区中;2) 要解析的SQL语句量非常大(90w/day)。而Lex在默认情况下以外部文件作为输入源,因此需要利用一些Lex的额外功能才行解决上面的两个问题。经过几天的探索,有两种解决方法,总结如下:
     

    1.  使用Lex提供的API(yy_scan_string, yy_delete_string)

        此时解析的主函数结构为:
            int  sql_exec(const char *str)
            {
                // load the input string to Lex buffer
                YY_BUFFER_STATE yy_buf = yy_scan_string(str);
                // parse the sql statement
                int ret = yyparse();
                // delete current Lex buffer
                yy_delete_buffer(yy_buf);
                // execute the sql
                if (!ret)
                {
                    // execute the sql 
                    ......
                }
                else    
                {
                    // deal with the parse error
                    ......
                }
            
                return 0;
            }
        其中用到了几个Lex提供的函数及结构体:
        YY_BUFFER_STATE: Lex中的buffer状态结构体,可以用来指定一个待解析的buffer;
        yy_scan_string(const char*): 创建一个新的YY_BUFFER_STATE,并把参数中的字符串拷贝到该buffer中,与之具有相似功能的函数还有yy_scan_bytes(const char*, int),它以一个指定长度的缓冲区作为输入,而不是字符串。这两个函数都需要调用malloc以创建一个新的缓冲区;
        yy_delete_buffer(YY_BUFFER_STATE): 删除当前的输入缓冲区,这个函数在底层调用free以删除一个缓冲区。
      • 优点:实现简单,代码比较直观;缓冲区根据要解析的目标自动生成,不会浪费空间。
      • 缺点:需要调用Lex的API,因此这段代码最好放在.l文件中,因此造成代码结构比较乱;每次解析都要malloc->free,容易造成碎片。

    2.  重新定义Lex的YY_INPUT

        YY_INPUT是Lex的读取待解析内容到缓冲区的函数,默认情况下从文件中读取字符,即getc(yyin)。如果我们把它重新定义为从内存缓冲区中读取字符,就可以使用我们的目标。
        首先,在.l文件中声明对YY_INPUT的重新定义:
            #undef YY_INPUT
            #define YY_INPUT(b, r, ms) { r = my_yyinput(b, ms); }
        之后,在主程序文件中使用全局变量的方式声明接收SQL语句的缓冲区:
            char g_sqlstr[SQL_LEN];  // SQL语句缓冲区
            char *g_sql_cur;              // 当前读取指针
            char *g_sql_end;             // 缓冲区结尾
            由于Lex中的YY_INPUT的使用并不灵活,因此在主程序文件中必须使用全局变量来暂存要解析的内容,SQL_LEN是要解析的SQL语句可能的最大长度。
        再后,定义my_yyinput:
            int my_yyinput(char *buf, int max_size)
            {
                long n = g_sql_end - g_sql_cur;
                if (n > max_size)           
                n = max_size;           
      
                if (n > 0)                  
                {
                    memcpy(buf, g_sql_cur, n);
                    g_sql_cur += n;         
                }
              
                return n;                   
            }
            Lex调用YY_INPUT的方式比较诡异,即使Lex和主程序文件中的buffer相比于要解析的SQL语句足够长,在一次解析中也不是只被调用一次。如果Lex内部的buffer长度(定义在#define YY_READ_BUF_SIZE 8192中)小于SQL语句的长度,YY_INPUT肯定会被多次调用。因此,在实现my_yyinput中,一定要记录当前buffer的读取位置,这样使得在一次解析中无论它被调用几次,读入的内容都不会出错。之前我使用了简单的memset(buf, g_sqlstr, SQL_LEN)结果总是出错,就是这个原因。
        最后,解析的主函数结构为:
            int sql_exec(const char *str)
            {
                // initialize the global buffer and positions
                memcpy(g_sqlstr, str, SQL_LEN);
                g_sql_cur = g_sqlstr;
                g_sql_end = g_sqlstr + strlen(g_sqlstr);
                // parse the sql statement
                int ret = yyparse();
                // flush current Lex buffer
                yy_flush_buffer(yy_buf);
                // execute the sql
                if (!ret)
                {
                    // execute the sql 
                    ......
                }
                else    
                {
                    // deal with the parse error
                    ......
                }
            
                return 0;
            }
        其中用到了Lex提供的一个函数:
        yy_flush_buffer(YY_BUFFER_STATE): 清空Lex的当前buffer,内部的主要操作是b->yy_n_chars = 0,并清空状态信息等。通过调用这个函数可以使用下次解析SQL语句时,从buffer头部开始。  
      •  优点:Lex和主程序文件中的缓冲区都是事先开辟的,在Lex中if ( ! yy_current_buffer ) yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE );,因此不用来回申请和释放,不容易造成碎片。
      • 缺点:容易造成空间浪费,在运行前必须申请一个SQL语句可能最大长度的buffer;代码不直观,因此my_yyinput是在yylex中自动调用的。

        这种方法需要强调的是在Lex中有两个和buffer长度有关的宏:YY_BUF_SIZE和YY_READ_BUF_SIZE,在默认情况下,YY_BUF_SIZE = 16384,YY_READ_BUF_SIZE = 8192。其中,YY_BUF_SIZE是在yy_create_buffer中使用,它是Lex一次申请的缓冲区长度;YY_READ_BUF_SIZE是在YY_INPUT中使用,它是Lex读一次缓冲区读取的最大程度,也就是my_yyinput中的max_len。当这两个长度小于一条SQL语句的长度时,我做了如下的实验:

    实验一:#define SQL_LEN 1024, #define YY_BUF_SIZE 10, #define YY_READ_BUF_SIZE 16,解析结果是:
    [update tb set col1 = 123|24]
    !9!7!7!1!0  execute the sql ...
    [delete from tb where col1 in (1, 2, 3) or col2 <= .1 - 1|56]
    !9!7!8!6!9!9!8!0  execute the sql ...
    [insert into tb values ('abc', 'de') (1)|39]
    !9!7!8!8!7!0  parse error !
    [update tb set col1 = 1 where (col1 = 1 or col2 = 2) and col3 = 'a'|66]
    !16!16!16!16!2!0  execute the sql ...
    [replace into tb values (1, 2, 3)|32]
    !16!16!0  execute the sql ...

    实验二::#define SQL_LEN 1024, #define YY_BUF_SIZE SQL_LEN+2, #define YY_READ_BUF_SIZE 16,解析结果是:
    [update tb set col1 = 123|24]
    !16!8!0  execute the sql ...
    [delete from tb where col1 in (1, 2, 3) or col2 <= .1 - 1|56]
    !16!16!16!8!0  execute the sql ...
    [insert into tb values ('abc', 'de') (1)|39]
    !16!16!7!0  parse error !
    [update tb set col1 = 1 where (col1 = 1 or col2 = 2) and col3 = 'a'|66]
    !16!16!16!16!2!0  execute the sql ...
    [replace into tb values (1, 2, 3)|32]
    !16!16!0  execute the sql ...

    解析结果格式说明:
    [sql_statement|sql_length]
    !each_read_len!each_read_len...parse_result

    这两个实验说明当YY_BUF_SIZE和YY_READ_BUF_SIZE 小于SQL_LEN时,Lex并没有报错(缓冲区没有溢出)。不过这个结论在代码中暂时还没有找到证据。

     

    10月10日

    一篇很有趣的关于野指针的文章(zz)

          在c中,野指针也许性子野,但是控制起来也是有章可循。然而事情在c++中出现了变化。
     
        什么是野指针?

      一个母亲有两个小孩(两个指针),一个在厨房,一个在卧室,(属于不同的代码块,其生存期不同)母亲让在厨房的小孩带一块蛋糕(指针指向的对象)给在卧室的小孩,这样在卧室的孩子才肯写作业。但这个在厨房的小孩比较淘气,他在走出厨房时自己将蛋糕吃了,没能带出来。而在卧室的没有吃到蛋糕,所以不肯完成他的作业。结果母亲却不知道卧室的孩子没有吃到蛋糕,还以为作业完了。结果第二天她就被老师召唤到办公室了。事情麻烦了。

      这样,那个在卧室的孩子就是野指针了,因为他没有得到应得的蛋糕,不能完成母亲交给他的作业。

      这就是c中所讲的野指针。上面的小剧本不过演示了一种最基本的野指针的形成过程。更容易出现的情形是coder在编码时,大意之下使用了已经free过的指针。

      对于年轻点的经验欠缺的coder来说是比较容易犯的错误,经验老到的程序员或者慎重采取成对编程的形式避免这种失误,或者使用引用计数器防止形成野指针。

      总之,在c中,野指针也许性子野,但是控制起来也是有章可循。然而事情在c++中出现了变化。

      coder们面临更大的麻烦了。c++程序员无可避免的要写很多这样那样的类。谁让c++是面向对象的呢?

      我们在写类的时候难免要用new给类的数据成员分配内存。这本来没什么,动态分配内存是一种很常见的基本操作,我们在学数据结构时经常这么做,不是么? 

      但是伙计,事情并非这么简单。类是一种高级的用户自定义数据类型,看起来和结构、枚举这样的用户自定义类型没啥太大差别。如果你这样认为....?那你会死的很惨。类太复杂了,普通情况下使用类的对象并没有太大的问题,但是,当你要复制一个对象时,问题就来了。


      比如我们知道,你要用一个对象初始化另一个对象时,c++是按位进行拷贝的,即在目标对象里创建了初始化对象的一个完全相同的拷贝。这在多数情况下已经足够了。但是,当你的类在创建时为每个对象分配内存,也就是说类中有new操作。当你的对象创建好后,类也为对象分配了一块内存。如果你用这个对象去初始化另一个对象时,被初始化的对象和初始化的对象完全一样。这意味着,他们使用同一块内存,而不是重新为被初始化的对象分配内存。

      这样麻烦就大了。如果一个对象销毁了,那么分配的内存也就销毁了(别忘了,类是有析构函数的,它负责在对象销毁时,释放动态分配的内存。难道你说你不在类中写上析构部分?那么可怜的孩子,那你就走向了另一个深渊,当你的程序运行数小时之后,系统会告诉你,内存不够用了。想象一下把你的程序用在腾讯的服务器上),另一个对象就残缺不全了,这就像一对连体婴儿,他们共用了一部分器官,心脏或者肝脏。要救活一个,就牺牲了另一个。一个得病了,另一个也要遭殃。

      可以说,这就是c++中更加变态的野指针。

      什么?你说我不用对象初始化对象?那么我们会不会将一个对象作为变元传递给函数呢?我们很多时候都这样做。有时我们不得不将对象按值传递给一个函数,但是你要知道,按值传递是什么意思?它的意思就是,把实参的一个拷贝传递给函数。这和刚才的初始化没什么两样,按位拷贝,函数体内的对象与外面的对象共用一块内存,即便在函数中的对象没有对这块内存进行过操作,但是当函数结束时。。。。析构函数将会被调用......

      还有一种与之相反的情况...... 当你想要把一个在函数内的对象值返回给外面的对象时,这时候,会自动产生一个临时对象,由它容纳函数的返回值,并在函数结束时把结果传给目标。那么这个临时对象迅速的被创建,并被迅速的释放。。。一块内存被释放了两次。其后果是不可预见的。
     

      当你把一个对象的值赋给另一个对象时,如果你没有重载赋值运算符,那么也会导致按位拷贝。最终产生一个野指针(一个隐藏在类内的毒瘤),或者释放同一块内存多次。
     

      看到了么?害怕了么?是不是感到C++到处都是陷阱呢?不但有陷阱,到处都是危险品。所有c中的疑难问题,到了c++就成了一般问题了。好了不废话了,我们继续讲讲解决之道。


      对于最后的这种赋值的情况,我们只有通过重载赋值运算符才能解决,也就是避免按位拷贝。

      至于前面的都属于初始化,概括下来就是三种情况:

      1.当一个对象初始化另一个对象时,例如在声明中;

      2.把所创建的对象拷贝(按值)传递给一个函数时;

      3.生成临时对象时,最常见的就是函数的返回值。

      解决初始化时的按位拷贝问题,我们通过创建拷贝构造函数来解决。

      基本的拷贝构造函数形式为:

            classname (const classname &o)
            {
                   //body here
            }

      拷贝构造函数就是针对这个问题而设计的。

      恩,大家都明白了吧?不要让你的对象都变成可怜的连体人啊~~~~
    6月19日

    在googling时无意遇到的一句话

    刚才在googling关于"An Introduction to Support Vector Machines and Other Kernel-based Learning Methods"这本书的时候,无意遇到一个人在书评中写的一句话,结合自己的境遇,觉得很有道理。
    “做学问需要的是一颗安宁的心,因为看不懂而产生的的急噪情绪有时也难免,但不要怨天尤人,如果首先就从思想上重视,不急于求成,踏踏实实的走下去,我想烦躁的情绪会少一些。”
    5月28日

    喧嚣与寂静zz

         报社为文的几个友人,要搞一个笔会,打电话邀我参加。当时未曾细想,便应承了下来,及至提笔,便觉出了这随口一答的尴尬,推脱不做,是言而无信也;勉强为文,应事而不敬,又为不恭也。思之再三,终也未搜到可心的题目,然又不能不为,于是也就只有“无病呻吟”式的来一段自己内心的随想,将两处看来的景观添油加醋地作一些对比,写下一点感悟,存斯于此,以为作文。
         这两处景观,一处是学校,我是天天能见的,就在我家厨房的对面。平日里炒菜做饭,便能观察到校园里热闹的景象。熙攘喧哗,蜩螗鼎沸。纵使上课的时间,操场上也是哨声连连;灯火阑珊了,也还有顽皮学生在校园内玩捉迷藏的游戏;即便是雨中,也有弃伞狂奔相互嬉戏的。我时常就为他们充沛的活力所感染,仿佛自己也年轻了。然而每逢寒暑假,校门反锁,教学楼前雪无人踩,操场边野草拱地,站在阳台上的我也若有所失,不知所措。这大概是情景交融产生的感应吧。
         另一处景观,是上月外出开会,搞了一次郊游。所到之处是个小地方,山不太高,景也谈不上秀,但有一古庙,掩映在青松翠柏之中,看起来也确有一些年代。庙依山傍水,通庙的路弯弯曲曲,属于那种走的人多了自然形成的路。庙里的和尚不多,皆木讷无语。游人进庙,捐了香资,便将双手合什,道阿弥陀佛时再偷眼瞧瞧功德箱里钞票硬币是否又多了一些。我原以为这么一个去处,应不同于别处是极清静的,哪知朝山拜庙的人也是熙熙攘攘骈肩累迹;导游们也是“你方唱罢我登场”,介绍的也是牌匾上某字为什么少了一撇某石头某棵树象什么罢了。若想寻个不被人扰安心观赏品评的角落,实属不易。更烦心的是你还没有细看,导游鸣金收兵的喇叭就响了,你不得不被撵鸭子似地又赶回车上。
         如此之游,当然是难得再有兴趣了,便倒在座椅上闭目养神。颠簸的山路忽然摇出了我一番思绪。我这样想象:夜幕降临,游客离去,僧人们清扫了佛堂殿院,是晚课诵读钻研经文佛理,还是摇着算盘计算红尘中滚来的“铜臭”,或是思考着如何与导游联手再进斗金。这我虽然不敢乱说,但有一点是可以肯定的,当一切都归于宁静后,暗夜里的庙宇一定比白天更庄严更肃穆更象庙宇。风铃叮当,松影斑驳,老僧坐定,沙弥虔诚,此时的庙才是庙,才有超凡脱俗的无为之相。
         由是我想,喧嚣与寂静实在是有趣的一对。人害怕寂寞,也不喜欢喧嚣,然喧嚣与寂寞很多时候却又是相生相伴,走出了喧嚣的场所,往往等待的就是寂寞;经历了长久的寂寞,沉淀后的脱出常常又是喧嚣。校园本是青春活泼之地,然有寒暑两假之寂寥;庙宇应是清静恬然之所,然有旅游旺季之喧嚣。虽一生中居校园做学生的时间有限,一年里聒庑殿嘈经幢的时候不多,但喧嚣与寂静则总是相陪,而且是喧嚣之短暂,寂寞却久长。婚礼上的一对新人,结婚时众星捧月,众目焦聚,待到筵席散去,漫漫岁月里生老病死、喜怒哀乐则伴随一生。做生意办工厂,开业之时,又是接匾又是纳彩,爆竹礼花,热闹非常,待祝贺的人潮退走,茫茫商海中则是苦苦拼搏。虽信誉可立但需历久而为,质量可信但要持恒追求。而领风骚者不过三五年而已,秉权政者亦不过一两届足矣。较之门可罗雀,粉墨登场时的光芒不过瞬间闪烁;较之掌声雷动,十年寒窗破冰研墨又何曾为人所知。然也正是长久寂静中的苦修苦为,才得以一朝间有短暂的喧嚣。
         一件古物,埋藏愈久,愈显价值;一坛佳酿,窖存愈久,愈见醇美。其实,于斯于彼,对人对物何尝不是如此。达摩面壁,身影印石,始悟禅机玄理。渐江山居,研习书画,终成一代宗师。可见,大凡要做成一件事情,需耐得住寂寞,持身涉世而不随境迁移,繁华面前而知割舍捐弃。如此,方能不为糜丽诱惑,方能伫立秋风而无异乡羁客之感,雪夜围炉适得乐天知命之足。然红尘滚滚,痴痴情深,连佛寺庙宇亦不能完全免俗,芸芸众生者又怎能超脱时下蔓延的浮躁轻率媚俗虚荣之风。试想,大彻大悟,醍醐灌顶若能瞬息完成,贤达睿者、俊杰先觉岂可须臾成就。于是有哲人告谓良方:孤独时聆听孤独深处的绝响,于苍凉处领略苍凉的空旷。

    5月23日

    Super Life

    武功再高,也怕菜刀;
    穿的再鸟,一砖撂倒;
    树不要皮,必死无疑;
    人不要脸,天下无敌。
    5月21日

    We are all snails

          今天下午去银行,偶遇一只毛毛虫,在怀念Miss. Spring的同时想起一个小故事。
     
          小蜗牛问妈妈:为什么我们从生下来,就要背负这个又硬又重的壳呢? 妈妈:因为我们的身体没有骨骼的支撑,只能爬,又爬不快。所以要这个壳的保护! 小蜗牛:毛虫姊姊没有骨头,也爬不快,为什么她却不用背这个又硬又重的壳呢? 妈妈:因为毛虫姊姊能变成蝴蝶,天空会保护她啊。 小蜗牛:可是蚯蚓弟弟也没骨头爬不快,也不会变成蝴蝶他什么不背这个又硬又重的壳呢? 妈妈:因为蚯蚓弟弟会钻土, 大地会保护他啊。 小蜗牛哭了起来:我们好可怜,天空不保护,大地也不保护。 蜗牛妈妈安慰他:「所以我们有壳啊!」我们不靠天,也不靠地,我们靠自己。
      
         小故事,大道理!