发布新日志

  • 如何使用场景法设计测试用例

    2008-10-16 09:22:51

    你可以通过这个链接引用该篇文章:http://shmilyfan.bokee.com/viewdiary.16709505.html

    这次来讲将如何使用场景法设计测试用例。在将具体例子前,我们先来了解下。场景法的基本概念。

    通过运用场景来对系统的功能点或业务流程的描述,从而提高测试效果。场景法一般包含基本流和备用流,从一个流程开始,通过描述经过的路径来确定的过程,经过遍历所有的基本流和备用流来完成整个场景。

    为什么场景法能如此清晰的描述整个事件?因为,现在的系统基本上都是由事件来触发控制流程的。如:我们申请一个项目,需先提交审批单据,再由部门经理审批,审核通过后由总经理来最终审批,如果部门经理审核不通过,就直接退回。每个事件触发时的情景便形成了场景。而同一事件不同的触发顺序和处理结果形成事件流。这一系列的过程我们利用场景法可以清晰的描述清楚。

    下图来展示一下网上最长见的场景法基本情况的一个实例图。

     

    在这个图中,有一个基本流和四个备选流。

    每个经过用例的可能路径,可以确定不同的用例场景。从基本流开始,再将基本流和备选流结合起来,可以确定以下用例场景:

    场景 1 基本流

    场景 2 基本流 备选流 1

    场景 3 基本流 备选流 1 备选流 2

    场景 4 基本流 备选流 3

    场景 5 基本流 备选流 3 备选流 1

    场景 6 基本流 备选流 3 备选流 1 备选流 2

    场景 7 基本流 备选流 4

    场景 8 基本流 备选流 3 备选流 4

    从上面的实例我们就可以了解场景是如何利用基本流和备用流来确定的。

     

    基本流:采用直黑线表示,是经过用例的最简单的路径(无任何差错,程序从开始直接执行到结束)

    备选流:采用不同颜色表示,一个备选流可能从基本流开始,在某个特定条件下执行,然后重新加入基本流中,也可以起源于另一个备选流,或终止用例,不在加入到基本流中;(各种错误情况)

     

    下面是场景法的基本设计步骤

    1.         根据说明,描述出程序的基本流及各项备选流

    2.         根据基本流和各项备选流生成不同的场景

    3.         对每一个场景生成相应的测试用例

    4.         对生成的所有测试用例重新复审,去掉多余的测试用例,测试用例确定后,对每一个测试用例确定测试数据值

     

    好了。说了一些场景法的基本概念和设计方法。想必大家已经有了一些了解了。再举一个简单例子来讲解下。这里,我就不用网上很流行的ATM的例子了。我结合以前项目中遇到的情况。设计一个简单的例子来讲解下。

     

    有一个在线购物的实例,用户进入一个在线购物网站进行购物,选购物品后,进行在线购买,这时需要使用帐号登录,登录成功后,进行付钱交易,交易成功后,生成订购单,完成整个购物过程。

     

    第一步我们来确定基本流和备选流:

    基本流

    登录在线购物网站,选择物品,登录帐号,付钱交易,生成订购单

    备选流1

    帐号不存在

    备选流2

    帐号或密码错误

    备选流3

    用户帐号余额不足

    备选流4

    用户帐号没有钱

    备选流x

    用户退出系统

    第二步我们根据基本流和备选流来确定场景:

    场景1-成功购物

    基本流

     

    场景2-帐号不存在

    基本流

    备选流1

    场景3-帐号或密码错误

    基本流

    备选流2

    场景4-用户帐号余额不足

    基本流

    备选流3

    场景5-用户帐号没有钱

    基本流

    备选流4

    第三步我们来设计用例

    对于每一个场景都需要确定测试用例。可以采用矩阵或决策表来确定和管理测试用例。

    下面显示了一种通用格式,其中各行代表各个测试用例,而各列则代表测试用例的信息。

    本例中,对于每个测试用例,存在一个测试用例ID、条件(或说明)、测试用例中涉及的所有数据元素(作为输入或已经存在于数据库中)以及预期结果。

    通过从确定执行用例场景所需的数据元素入手构建矩阵。然后,对于每个场景,至少要确定包含执行场景所需的适当条件的测试用例。例如,在下面的矩阵中,V(有效)用于表明这个条件必须是 VALID(有效的)才可执行基本流,而 I(无效)用于表明这种条件下将激活所需备选流。下表中使用的“n/a”(不适用)表明这个条件不适用于测试用例。

    测试用例ID

    场景/条件

    帐号

    密码

    用户帐号余额

    预期结果

    1

    场景1:成功购物

    V

    V

    V

    成功购物

    2

    场景2:帐号不存在

    I

    n/a

    n/a

    提示帐号不存在

    3

    场景3:帐号或密码错误(帐号正确,密码错误)

    V

    I

    n/a

    提示帐号或密码错误,返回基本流步骤3

    4

    场景3:帐号或密码错误(帐号错误,密码正确)

    V

    I

    n/a

    提示帐号或密码错误,返回基本流步骤3

    5

    场景4:用户帐号余额不足

    V

    V

    I

    提示帐号余额不足请充值

    查看(1336) 评论(0) 收藏 分享 管理

  • C语言之指针、数组和函数

    2008-07-13 11:50:11

    http://www.chinaitlab.com/www/techspecial/ncre_2/zdfx-hs.htm

    基本解释
      1、指针的本质是一个与地址相关的复合类型,它的值是数据存放的位置(地址);数组的本质则是一系列的变量。
      2、数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。
      3、当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
      问题:指针与数组
      听说char a[]与char *a是一致的,是不是这样呢?
      答案与分析:
      指针和数组存在着一些本质的区别。当然,在某种情况下,比如数组作为函数的参数进行传递时,由于该数组自动退化为同类型的指针,所以在函数内部,作为函数参数传递进来的指针与数组确实具有一定的一致性,但这只是一种比较特殊的情况而已,在本质上,两者是有区别的。请看以下的例子:
    char a[] = "Hi, pig!";
    char *p = "Hi, pig!";
    上述两个变量的内存布局分别如下:
      数组a需要在内存中占用8个字节的空间,这段内存区通过名字a来标志。指针p则需要4个字节的空间来存放地址,这4个字节用名字p来标志。其中存放的地址几乎可以指向任何地方,也可以哪里都不指,即空指针。目前这个p指向某地连续的8个字节,即字符串“Hi, pig!”。
      另外,例如:对于a[2]和p[2],二者都返回字符‘i’,但是编译器产生的执行代码却不一样。对于a[2],执行代码是从a的位置开始,向后移 动2两个字节,然后取出其中的字符。对于p[2],执行代码是从p的位置取出一个地址,在其上加2,然后取出对应内存中的字符。
      问题:数组指针
      为什么在有些时候我们需要定义指向数组而不是指向数组元素的指针?如何定义?
      答案与分析:
      使用指针,目的是用来保存某个元素的地址,从而来利用指针独有的优点,那么在元素需要是数组的情况下,就理所当然要用到指向数组的指针,比如在高维需要动态生成情况下的多维数组。
      定义例子如下: int (*pElement)[2]。
      下面是一个例子:
    int array[2][3] = {{1,2,3},{4,5,6}};
    int (*pa)[3]; //定义一个指向数组的指针
    pa = &array[0]; // '&'符号能够体现pa的含义,表示是指向数组的指针
    printf ("%d", (*pa)[0]); //将打印array[0][0],即1
    pa++; // 猜一猜,它指向谁?array[1]?对了!
    printf ("%d", (*pa)[0]); // 将打印array[1][0],即4
    上述这个例子充分说明了数组指针—一种指向整个数组的指针的定义和使用。
      需要说明的是,按照我们在第四篇讨论过的,指针的步进是参照其所指对象的大小的,因此,pa++将整个向后移 动一个数组的尺寸,而不是仅仅向后移 动一个数组元素的尺寸。
      问题:指针数组
      有如下定义:
    struct UT_TEST_STRUCT *pTo[2][MAX_NUM];
      请分析这个定义的意义,并尝试说明这样的定义可能有哪些好处?
      答案与分析:
      前面我们谈了数组指针,现在又提到了指针数组,两者形式很相似,那么,如何区分两者的定义呢?分析如下:
      数组指针是:指向数组的指针,比如 int (*pA)[5]。
      指针数组是:指针构成的数组,比如int *pA[5]。
      至于上述指针数组的好处,大致有如下两个很普遍的原因:
      a)、各个指针内容可以按需要动态生成,避免了空间浪费。
      b)、各个指针呈数组形式排列,索引起来非常方便。
      在实际编程中,选择使用指针数组大多都是想要获得如上两个好处。
    问题:指向指针的指针
      在做一个文本处理程序的时候,有这样一个问题:什么样的数据结构适合于按行存储文本?
      答案与分析:
      首先,我们来分析文本的特点,文本的主要特征是具有很强的动态性,一行文本的字符个数或多或少不确定,整个文本所拥有的文本行数也是不确定的。这样的特征决定了用固定的二维数组存放文本行必然限制多多,缺乏灵活性。这种场合,使用指向指针的指针有很大的优越性。
      现实中我们尝试用动态二维数组(本质就是指向指针的指针)来解决此问题:
      图示是一个指针数组。所谓动态性指横向(对应每行文本的字符个数)和纵向(对应整个文本的行数)两个方向都可以变化。
      就横向而言,因为指针的灵活性,它可以指向随意大小的字符数组,实现了横向动态性。
      就竖向而言,可以动态生成及扩展需要的指针数组的大小。
      下面的代码演示了这种动态数组的用途:
    // 用于从文件中读取以 '\0'结尾的字符串的函数
    extern char *getline(FILE *pFile);
    FILE *pFile;
    char **ppText = NULL; // 二维动态数组指针
    char *pCurrText = NULL; // 指向当前输入字符串的指针
    ULONG ulCurrLines = 0;
    ULONG ulAllocedLines = 0;

    while (p = getline(pFile))
    {
     if (ulCurrLines >= ulAllocedLines)
     {
      // * 当前竖向空间已经不够了,通过realloc对其进行扩展。
     ulAllocedLines += 50; // 每次扩展50行。
      ppText = realloc (ppText, ulAllocedLines * (char *));
      if (NULL == ppText)
      {
       return; // 内存分配失败,返回
      }
     }
     ppText[ulCurrLines++] = p; // 横向“扩展”,指向不定长字符串
    }
    问题:指针数组与数组指针与指向指针的指针
      指针和数组分别有如下的特征:
      指针:动态分配,初始空间小
      数组:索引方便,初始空间大
      下面使用高维数组来说明指针数组、数组指针、指向指针的指针各自的适合场合。
       多维静态数组:各维均确定,适用于整体空间需求不大的场合,此结构可方便索引,例a[10][40]。
       数组指针:低维确定,高维需要动态生成的场合,例a[x][40]。
       指针数组:高维确定,低维需要动态生成的场合,例a[10][y]。
       指向指针的指针:高、低维均需要动态生成的场合,例a[x][y]。
      问题:数组名相关问题
      假设有一个整数数组a,a和&a的区别是什么?
      答案与分析:
      a == &a == &a[0],数组名a不占用存储空间。需要引用数组(非字符串)首地址的地方,我一般使用&a[0],使用a容易和指针混淆,使用&a容易和非指针变量混淆。
      区别在于二者的类型。对数组a的直接引用将产生一个指向数组第一个元素的指针,而&a的结果则产生一个指向全部数组的指针。例如:
    int a[2] = {1, 2};
    int *p = 0;
    p = a; /* p指向a[0]所在的地方 */
    x = *p; /* x = a[0] = 1*/
    p = &a; /* 编译器会提示你错误,*/
    /*显示整数指针与整数数组指针不一样 */
    问题:函数指针与指针函数
       请问:如下定义是什么意思:
    int *pF1();
    int (*pF2)();
      答案与分析:
      首先清楚它们的定义:
       指针函数,返回一个指针的函数。
       函数指针,指向一个函数的指针。
      可知:
       pF1是一个指针函数,它返回一个指向int型数据的指针。
       pF2是一个函数指针,它指向一个参数为空的函数,这个函数返回一个整数。

  • 了解指针数组,数组指针,以及函数指针,以及堆中的分配规则

    2008-07-13 11:35:31

    文章来源:http://blog.163.com/dlx1986@126/blog/static/26250549200732052822579/

    一 :关于指针和堆的内存分配

    先来介绍一下指针 : 指针一种类型,理论上来说它包含其他变量的地址,因此有的书上也叫它:地址变量。既然指针是一个类型,是类型就有大小,在达内的服务器上或者普通的PC机上,都是4个字节大小,里边只是存储了一个变量的地址而已。不管什么类型的指针,char * ,int * ,int (*) ,string * ,float * ,都是说明了本指针所指向的地址空间是什么类型而已,了解了这个基本上所有的问题都好象都变的合理了。

    在C++中,申请和释放堆中分配的存贮空间,分别使用new和delete的两个运算符来完成:

    指针类型 指针变量名=new 指针类型 (初始化);

            delete 指针名;

    例如:1、 int *p=new int(0);

         它与下列代码序列大体等价:

    2、int tmp=0, *p=&tmp;

    区别:p所指向的变量是由库操作符new()分配的,位于内存的堆区中,并且该对象未命名。

      

    下面是关于new 操作的说明 : 部分引自<<C++面向对象开发>>

    1、new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有名字。

    2、一般定义变量和对象时要用标识符命名,称命名对象,而动态的称无名对象(请注意与栈区中的临时对象的区别,两者完全不同:生命期不同,操作方法不同,临时变量对程序员是透明的)。

    3、堆区是不会在分配时做自动初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。new表达式的操作序列如下:从堆区分配对象,然后用括号中的值初始化该对象。

    下面是从堆中申请数组

    1、申请数组空间:

    指针变量名=new 类型名[下标表达式];

    注意:“下标表达式”不是常量表达式,即它的值不必在编译时确定,可以在运行时确定。这就是堆的一个非常显著的特点,有的时候程序员本身都不知道要申请能够多少内存的时候,堆就变的格外有用。

    2、释放数组空间:

    delete [ ]指向该数组的指针变量名;

    注意:方括号非常重要的,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的,会产生回收不彻底的问题(只回收了第一个元素所占空间),我们通常叫它“内存泄露”,加了方括号后就转化为指向数组的指针,回收整个数组。delete [ ]的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。<<Think in c++>>上说过以前的delete []方括号中是必须添加个数的,后来由于很容易出错,所以后来的版本就改进了这个缺陷。

    下面是个例子,VC上编译通过

    #include<iostream>

    using namespace std;

    //#include <iostream.h>  //for VC

    #include <string.h>

    void main(){

    int n;

    char *p;

    cout<<"请输入动态数组的元素个数"<<endl;

    cin>>n; //n在运行时确定,可输入17

    p=new char[n]; //申请17个字符(可装8个汉字和一个结束符)的内存空间strcpy(pc,“堆内存的动态分配”);//

    cout<<p<<endl;

    delete []p;//释放pc所指向的n个字符的内存空间return ; }

    通过指针使堆空间,编程中的几个可能问题

    1.动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。

       data = new double [m]; //申请空间

    if ((data ) == 0)…… //或者==NULL

    2.指针删除与堆空间释放。删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针,不能再通过p使用该空间,在重新给p赋值前,也不能再直接使用p。

    3.内存泄漏(memory leak)和重复释放。new与delete 是配对使用的, delete只能释放堆空间。如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,而这个时候又去释放的话,会导致一个很难查出来的运行时错误。所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。

    4.动态分配的变量或对象的生命期。无名变量的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为自由空间(free store)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错,所以永远不要在函数体内申请空间,让调用者释放,这是一个很差的做法。你再怎么小心翼翼也可能会带来错误。

    类在堆中申请内存 :

    通过new建立的对象要调用构造函数,通过deletee删除对象要调用析构函数。

    CGoods *pc;

    pc=new CGoods;  //分配堆空间,并构造一个无名对象

                                  //的CGoods对象;

    …….

    delete pc;  //先析构,然后将内存空间返回给堆;        堆对象的生命期并不依赖于建立它的作用域,所以除非程序结束,堆对象(无名对象)的生命期不会到期,并且需要显式地用delete语句析构堆对象,上面的堆对象在执行delete语句时,C++自动调用其析构函数。

    正因为构造函数可以有参数,所以new后面类(class)类型也可以有参数。这些参数即构造函数的参数。

    但对创建数组,则无参数,并只调用缺省的构造函数。见下例类说明:

    class CGoods{

              char Name[21];

              int  Amount;

              float Price;

              float Total_value;

    public:

     CGoods(){}; //缺省构造函数。因已有其他构造函数,系统不会再自动生成缺省构造,必须显式声明。   CGoods(char* name,int amount ,float price){

               strcpy(Name,name);

               Amount=amount;

               Price=price;

               Total_value=price*amount;  }

               ……};//类声明结束

    下面是调用机制 :

    void main(){

     int n;

     CGoods *pc,*pc1,*pc2;

     pc=new CGoods(“hello”,10,118000);

     //调用三参数构造函数   pc1=new CGoods();  //调用缺省构造函数  cout<<”输入商品类数组元素数”<<endl;

     cin>>n;

     pc2=new CGoods[n];

    //动态建立数组,不能初始化,调用n次缺省构造函数  

     ……

     delete pc;

     delete pc1;

     delete []pc2;  }

    申请堆空间之后构造函数运行;

    释放堆空间之前析构函数运行;

    再次强调:由堆区创建对象数组,只能调用缺省的构造函数,不能调用其他任何构造函数。如果没有缺省的构造函数,则不能创建对象数组。

    ---------------------下面我们再来看一下指针数组和数组指针―――――――――――――

    如果你想了解指针最好理解以下的公式 :

    (1)int*ptr;//指针所指向的类型是int

      (2)char*ptr;//指针所指向的的类型是char

      (3)int**ptr;//指针所指向的的类型是int* (也就是一个int * 型指针)

      (4)int(*ptr)[3];//指针所指向的的类型是int()[3] //二维指针的声明

    (1)指针数组:一个数组里存放的都是同一个类型的指针,通常我们把他叫做指针数组。

    比如 int * a[10];它里边放了10个int * 型变量,由于它是一个数组,已经在栈区分配了10个(int * )的空间,也就是32位机上是40个byte,每个空间都可以存放一个int型变量的地址,这个时候你可以为这个数组的每一个元素初始化,在,或者单独做个循环去初始化它。

    例子:

    int * a[2]={ new int(3),new int(4) };     //在栈区里声明一个int * 数组,它的每一个元素都在堆区里申请了一个无名变量,并初始化他们为3和4,注意此种声明方式具有缺陷,VC下会报错

    例如 :

    int * a[2]={new int[3],new int[3]};

    delete a[0];

    delet a[10];

    但是我不建议达内的学生这么写,可能会造成歧义,不是好的风格,并且在VC中会报错,应该写成如下 :

    int * a[2];

    a[0]= new int[3];

    a[1]=new int[3];

    delete a[0];

    delet a[10];

    这样申请内存的风格感觉比较符合大家的习惯;由于是数组,所以就不可以delete a;编译会出警告.delete  a[1];

    注意这里 是一个数组,不能delete [] ;

    ( 2 ) 数组指针 : 一个指向一维或者多维数组的指针;

    int * b=new int[10]; 指向一维数组的指针b ;

    注意,这个时候释放空间一定要delete [] ,否则会造成内存泄露, b 就成为了空悬指针.

    int (*b2)[10]=new int[10][10]; 注意,这里的b2指向了一个二维int型数组的首地址.

    注意:在这里,b2等效于二维数组名,但没有指出其边界,即最高维的元素数量,但是它的最低维数的元素数量必须要指定!就像指向字符的指针,即等效一个字符串,不要把指向字符的指针说成指向字符串的指针。这与数组的嵌套定义相一致。

    int(*b3) [30] [20];  //三级指针――>指向三维数组的指针;

    int (*b2) [20];     //二级指针;

    b3=new int [1] [20] [30];

    b2=new int [30] [20];

          两个数组都是由600个整数组成,前者是只有一个元素的三维数组,每个元素为30行20列的二维数组,而另一个是有30个元素的二维数组,每个元素为20个元素的一维数组。

          删除这两个动态数组可用下式:

    delete [] b3;  //删除(释放)三维数组;

    delete [] b2;  //删除(释放)二维数组;

    再次重申:这里的b2的类型是int (*) ,这样表示一个指向二维数组的指针。

    b3表示一个指向(指向二维数组的指针)的指针,也就是三级指针.

    ( 3 ) 二级指针的指针

    看下例 :

    int (**p)[2]=new (int(*)[3])[2];

           p[0]=new int[2][2];

           p[1]=new int[2][2];

           p[2]=new int[2][2];

           delete [] p[0];

           delete [] p[1];

           delete [] p[2];

           delete [] p;

    注意此地方的指针类型为int (*),碰到这种问题就把外边的[2]先去掉,然后回头先把int ** p=new int(*)[n]申请出来,然后再把外边的[2]附加上去;

    p代表了一个指向二级指针的指针,在它申请空间的时候要注意指针的类型,那就是int (*)代表二级指针,而int (**)顾名思义就是代表指向二级指针的指针了。既然是指针要在堆里申请空间,那首先要定义它的范围:(int(*)[n])[2],n 个这样的二级指针,其中的每一个二级指针的最低维是2个元素.(因为要确定一个二级指针的话,它的最低维数是必须指定的,上边已经提到)。然后我们又分别为p[0],p[1],p[2]…在堆里分配了空间,尤其要注意的是:在释放内存的时候一定要为p[0],p[1],p[2],单独delete[] ,否则又会造成内存泄露,在delete[]p 的时候一定先delete p[0]; delete p[1],然后再把给p申请的空间释放掉 delete [] p ……这样会防止内存泄露。

    (3)指针的指针;

    int ** cc=new (int*)[10]; 声明一个10个元素的数组,数组每个元素都是一个int *指针,每个元素还可以单独申请空间,因为cc的类型是int*型的指针,所以你要在堆里申请的话就要用int *来申请;

    看下边的例子  (vc & GNU编译器都已经通过);

           int ** a= new int * [2];     //申请两个int * 型的空间

           a[1]=new int[3];        //为a的第二个元素又申请了3个int 型空间,a[1]指向了此空间首地址处

           a[0]=new int[4];        ////为a的第一个元素又申请了4个int 型空间,a[0] 指向了此空间的首地址处

           int * b;

           a[0][0]=0;

           a[0][1]=1;

           b=a[0];

      delete [] a[0]       //一定要先释放a[0],a[1]的空间,否则会造成内存泄露.;

           delete [] a[1];

      delete [] a;

           b++;

           cout<<*b<<endl;       //随机数

    注意 :因为a 是在堆里申请的无名变量数组,所以在delete 的时候要用delete [] 来释放内存,但是a的每一个元素又单独申请了空间,所以在delete [] a之前要先delete [] 掉 a[0],a[1],否则又会造成内存泄露.

    (4) 指针数组 :

       

    我们再来看看第二种 :二维指针数组

    int *(*c)[3]=new int *[3][2];

    如果你对上边的介绍的个种指针类型很熟悉的话,你一眼就能看出来c是个二级指针,只不过指向了一个二维int * 型的数组而已,也就是二维指针数组。

    例子 :

     int *(*b)[10]=new int*[2][10];//

    b[0][0]=new int[100];

    b[0][1]=new int[100];

    *b[0][0]=1;

    cout <<*b[0][0]<<endl;    //打印结果为1

    delete [] b[0][0];

    delete [] b[0][1];

    delete [] b;

    cout<<*b[0][0]<<endl;    //打印随机数

     这里只为大家还是要注意内存泄露的问题,在这里就不再多说了。

    如果看了上边的文章,大家估计就会很熟悉,这个b是一个二维指针,它指向了一个指针数组

    第二种 :

    int **d[2];表示一个拥有两个元素数组,每一个元素都是int ** 型,这个指向指针的指针:)

       d不管怎样变终究也是个数组,呵呵,

       如果你读懂了上边的,那下边的声明就很简单了:

       d[0]=new int *[10];

       d[1]=new int * [10];

    delete [] d[0];

    delete [] d[1];

    具体的就不再多说了 :)

    二 : 函数指针 

    关于函数指针,我想在我们可能需要写个函数,这个函数体内要调用另一个函数,可是由于项目的进度有限,我们不知道要调用什么样的函数,这个时候可能就需要一个函数指针;

    int a();这个一个函数的声明;

    ing (*b)();这是一个函数指针的声明;

    让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

    unsigned psize = sizeof (int (*) ()); 获得函数指针的大小

    // 为函数指针声明类型定义

    typedef int (*PFUNC) ();

    PFUNC是一个函数指针,它指向的函数没有输入参数,返回int。使用这个类型定义名可以隐藏复杂的函数指针语法,就我本人强烈建议我们大内弟子使用这种方式来定义;

    下面是一个例子,一个简单函数指针的回调(在GNU编译器上通过,在VC上需要改变一个头文件就OK了)

    #include<iostream>              //GNU 编译器 g++ 实现

    using namespace std;

    /*                              //vc 的实现

    #include "stdafx.h"

    #include <iostream.h>

    */

    #define DF(F) int F(){  cout<<"this is in function "<<#F<<endl;\

          return 0;       \

    }

    //声明定义DF(F)替代 int F();函数;

    DF(a); DF(b); DF(c); DF(d); DF(e); DF(f); DF(g); DF(h); DF(i);     //声明定义函数 a b c d e f g h i

    // int (*pfunc)();              //一个简单函数指针的声明

    typedef int(*FUNC)();   //一个函数指针类型的声明

    FUNC ff[] = {a,b,c,d,e,f,g,h,i};   //声明一个函数指针数组,并初始化为以上声明的a,b,c,d,e,f,g,h,i函数

    FUNC func3(FUNC vv){    //定义函数func3,传入一个函数指针,并且返回一个同样类型的函数指针

          vv();

          return vv;

    }

    /*FUNC func4(int (*vv)()){      //func3的另一种实现

          vv();

          return vv;

    }*/

    int main(){

          for(int i=0;i<sizeof(ff)/sizeof (FUNC);i++){  //循环调用函数指针

                  FUNC r=func3(ff[ i ]);

                  cout<<r()<<endl;                //输出返回值,只是返回了0

          }

          return 0;

    }

    到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

    好了,先到此为止吧,写这篇文章耗费了基本上快半天的时间了,很多事情还没有做,等改天有时间再回来整理,所有的源程序都放在openlab3服务器上我的目录下lib/cpp下,大家可以去拿。不知道的登陆openlab3 然后cd ~chengx/lib/cpp就可以看到了。

    还有很复杂的声明可能也是一种挑战 比如<<Think in c++>>里的

    int (*(*f4())[10]();的声明,f4是一个返回指针的函数,该指针指向了含有10个函数指针的数组,这些函数返回整形值;不是这个函数有特别之处,而是Bruce Eckel 说的“从右到左的辨认规则”是一种很好的方法,值得我们去学习,感谢他:)

    最后我想应该跟大家说一下,写程序应该就象JERRY所说的:简单就是美;我们应该遵循一个原则 : KISS (Keep It Simple,Stupid ,尽量保持程序简单 出自 :《Practical C programming》),把自己的程序尽量的简单明了,这是个非常非常好的习惯。

    由于写的匆忙,可能其中有遗漏的地方,大家发现希望能指正:)

    GOOD LUCK !

  • 第一次软件测试笔试+面试题总结

    2008-07-13 11:21:37

    2008年7月11日参加了泰豪公司的笔试+面试,以下是我的一些总结!
    笔试:
    0.你对软件测试这个职业的认识?
    软件测试这个职业是一份具有挑战性的工作,我喜欢在有压力的工作环境下工作,因为只有在压力下我才能够进步,能够成长,其次,他要求从事这个行业的人员不仅要懂得测试方面的技术,同时还要能够了解软件产品所能涉及到的各行各业的知识,并且由于这个职业特有的属性,他还要求从事这个行业的人员有好的沟通能力,坚持能力,学习能力,领悟能力,以及创新能力,我十分喜欢这份工作。
    1.为什么要选择从事软件测试?
    我认为要想在一个职业里有所成功,有所建树,首先要热爱你所从事的这个职业,而能够在软件测试这个领域里工作和发展一直以来都是我的梦想,毕业之后由于某些原因没有能进入这一领域,我感到很遗憾,但是现在机会来了!我参加了软件评测中心的这个培训之后,使我对软件测试这个职业有了更深的了解,从而也更加确定了我要从事这个职业的信心。
    2.数据库查询,插入,更新操作
    selcet * from table where condition group by asc/desc
    insert into table (colume1,colume2...) values(values1,values2...) where condition
    update table set(colume1,colume2...) values('values1','values'...) where condition
    3.写一个冒泡排序或者选择排序算法
    /*bumble*/
    void main()
    {
    int i,j,temp,a[10];
    for(i=1;i<n-1;i++)
      for(j=0;j<n-i;j++)
      {
        if(a[j]>a[j+1])
          {
            temp=a[j+1];
            a[j+1]=a[j];
            a[j]=temp;
          } 
      }
     for(i=0;i<n;i++)
     printf("%d",a[i]);
    }
    /*select*/
    void sort(int *array,int n)
    {
       int i,k,t;
       for(i=0;i<n-1;i++)
          for(k=i+1;k<n;k++)
             if(a[i]>a[k]){t=a[i];a[i]=a[k];a[k]=t}
     
    }
    4.计算n!,数字排序,哪些常见排序算法
    void main()
    {
    int i,n,sum;
    sum=1;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    sum=sum*i;
    printf("sum=%d",sum);
    }
    5.HTTP和TCP/IP的区别
    Http协议是超文本传输协议;
    TCP/IP是传输控制协议/网际协议,是一种标准通信协议
    1 HTTP协议简介
    HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(NextGenerationofHTTP)的建议已经提出。
    HTTP协议的主要特点可概括如下:
    1.支持客户/服务器模式。
    2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。
    由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
    3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
    4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
    5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

    2.2 HTTP协议的几个重要概念
    1.连接(Connection):一个传输层的实际环流,它是建立在两个相互通讯的应用程序之间。
    2.消息(Message):HTTP通讯的基本单位,包括一个结构化的八元组序列并通过连接传输。
    3.请求(Request):一个从客户端到服务器的请求信息包括应用于资源的方法、资源的标识符和协议的版本号
    4.响应(Response):一个从服务器返回的信息包括HTTP协议的版本号、请求的状态(例如“成功”或“没找到”)和文档的MIME类型。
    5.资源(Resource):由URI标识的网络数据对象或服务。
    6.实体(Entity):数据资源或来自服务资源的回映的一种特殊表示方法,它可能被包围在一个请求或响应信息中。一个实体包括实体头信息和实体的本身内容。
    7.客户机(Client):一个为发送请求目的而建立连接的应用程序。
    8.用户代理(Useragent):初始化一个请求的客户机。它们是浏览器、编辑器或其它用户工具。
    9.服务器(Server):一个接受连接并对请求返回信息的应用程序。
    10.源服务器(Originserver):是一个给定资源可以在其上驻留或被创建的服务器。
    11.代理(Proxy):一个中间程序,它可以充当一个服务器,也可以充当一个客户机,为其它客户机建立请求。请求是通过可能的翻译在内部或经过传递到其它的服务器中。一个代理在发送请求信息之前,必须解释并且如果可能重写它。
    代理经常作为通过防火墙的客户机端的门户,代理还可以作为一个帮助应用来通过协议处理没有被用户代理完成的请求。
    12.网关(Gateway):一个作为其它服务器中间媒介的服务器。与代理不同的是,网关接受请求就好象对被请求的资源来说它就是源服务器;发出请求的客户机并没有意识到它在同网关打交道。
    网关经常作为通过防火墙的服务器端的门户,网关还可以作为一个协议翻译器以便存取那些存储在非HTTP系统中的资源。
    13.通道(Tunnel):是作为两个连接中继的中介程序。一旦激活,通道便被认为不属于HTTP通讯,尽管通道可能是被一个HTTP请求初始化的。当被中继的连接两端关闭时,通道便消失。当一个门户(Portal)必须存在或中介(Intermediary)不能解释中继的通讯时通道被经常使用。
    14.缓存(Cache):反应信息的局域存储。

    2.3 HTTP协议的运作方式
    HTTP协议是基于请求/响应范式的。一个客户机与服务器建立连接后,发送一个请求给服务器,请求方式的格式为,统一资源标识符、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。服务器接到请求后,给予相应的响应信息,其格式为一个状态行包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
    许多HTTP通讯是由一个用户代理初始化的并且包括一个申请在源服务器上资源的请求。最简单的情况可能是在用户代理(UA)和源服务器(O)之间通过一个单独的连接来完成(见图2-1)。
    图2-1
    当一个或多个中介出现在请求/响应链中时,情况就变得复杂一些。中介由三种:代理(Proxy)、网关(Gateway)和通道(Tunnel)。一个代理根据URI的绝对格式来接受请求,重写全部或部分消息,通过URI的标识把已格式化过的请求发送到服务器。网关是一个接收代理,作为一些其它服务器的上层,并且如果必须的话,可以把请求翻译给下层的服务器协议。一个通道作为不改变消息的两个连接之间的中继点。当通讯需要通过一个中介(例如:防火墙等)或者是中介不能识别消息的内容时,通道经常被使用。图2-2
    上面的图2-2表明了在用户代理(UA)和源服务器(O)之间有三个中介(A,B和C)。一个通过整个链的请求或响应消息必须经过四个连接段。这个区别是重要的,因为一些HTTP通讯选择可能应用于最近的连接、没有通道的邻居,应用于链的终点或应用于沿链的所有连接。尽管图2-2是线性的,每个参与者都可能从事多重的、并发的通讯。例如,B可能从许多客户机接收请求而不通过A,并且/或者不通过C把请求送到A,在同时它还可能处理A的请求。
    任何针对不作为通道的汇聚可能为处理请求启用一个内部缓存。缓存的效果是请求/响应链被缩短,条件是沿链的参与者之一具有一个缓存的响应作用于那个请求。下图说明结果链,其条件是针对一个未被UA或A加缓存的请求,B有一个经过C来自O的一个前期响应的缓存拷贝。
    图2-3
    在Internet上,HTTP通讯通常发生在TCP/IP连接之上。缺省端口是TCP80,但其它的端口也是可用的。但这并不预示着HTTP协议在Internet或其它网络的其它协议之上才能完成。HTTP只预示着一个可靠的传输。
    以上简要介绍了HTTP协议的宏观运作方式,下面介绍一下HTTP协议的内部操作过程。
    首先,简单介绍基于HTTP协议的客户/服务器模式的信息交换过程,如图2-4所示,它分四个过程,建立连接、发送请求信息、发送响应信息、关闭连接。
    图2-4
    在WWW中,“客户”与“服务器”是一个相对的概念,只存在于一个特定的连接期间,即在某个连接中的客户在另一个连接中可能作为服务器。WWW服务器运行时,一直在TCP80端口(WWW的缺省端口)监听,等待连接的出现。
    下面,讨论HTTP协议下客户/服务器模式中信息交换的实现。 1.建立连接 连接的建立是通过申请套接字(Socket)实现的。客户打开一个套接字并把它约束在一个端口上,如果成功,就相当于建立了一个虚拟文件。以后就可以在该虚拟文件上写数据并通过网络向外传送。
    2.发送请求
    打开一个连接后,客户机把请求消息送到服务器的停留端口上,完成提出请求动作。
    HTTP/1.0 请求消息的格式为:
    请求消息=请求行(通用信息|请求头|实体头)CRLF[实体内容]
    请求 行=方法 请求URL HTTP版本号 CRLF
    方 法=GET|HEAD|POST|扩展方法
    U R L=协议名称+宿主名+目录与文件名
    请求行中的方法描述指定资源中应该执行的动作,常用的方法有GET、HEAD和POST。不同的请求对象对应GET的结果是不同的,对应关系如下:
    对象 GET的结果
    文件 文件的内容
    程序 该程序的执行结果
    数据库查询 查询结果
    HEAD——要求服务器查找某对象的元信息,而不是对象本身。
    POST——从客户机向服务器传送数据,在要求服务器和CGI做进一步处理时会用到POST方法。POST主要用于发送HTML文本中FORM的内容,让CGI程序处理。
    一个请求的例子为:
    GEThttp://networking.zju.edu.cn/zju/index.htmHTTP/1.0
    头信息又称为元信息,即信息的信息,利用元信息可以实现有条件的请求或应答。
    请求头——告诉服务器怎样解释本次请求,主要包括用户可以接受的数据类型、压缩方法和语言等。
    实体头——实体信息类型、长度、压缩方法、最后一次修改时间、数据有效期等。
    实体——请求或应答对象本身。
    3.发送响应
    服务器在处理完客户的请求之后,要向客户机发送响应消息。
    HTTP/1.0的响应消息格式如下:
    响应消息=状态行(通用信息头|响应头|实体头) CRLF 〔实体内容〕
    状态行=HTTP版本号 状态码 原因叙述
    状态码表示响应类型
    1×× 保留
    2×× 表示请求成功地接收
    3×× 为完成请求客户需进一步细化请求
    4×× 客户错误
    5×× 服务器错误
    响应头的信息包括:服务程序名,通知客户请求的URL需要认证,请求的资源何时能使用。
    4.关闭连接
    客户和服务器双方都可以通过关闭套接字来结束TCP/IP对话



    2TCP/IP
    就像人类的语言一样,要使计算机连成的网络能够互通信息,需要有一组共同遵守的通信标准,这就是网络协议,不同的计算机之间必须使用相同的通讯协议才能进行通信。在Internet中TCP/IP协议是使用最为广泛的通讯协议。TCP/IP是英文Transmission Control Protocol/Internet Protocol的缩写,意思是“传输控制协议/网际协议”。 TCP/IP是Internet使用的一组协议(Protocol)。

    在Internet上传输控制协议和网际协议是配合进行工作的。网际协议(IP)负责将消息从一个主机传送到另一个主机。为了安全消息在传送的过程中被分割成一个个的小包。

    传输控制协议(TCP)负责收集这些信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确地还原。传输协议保证了数据包在传送中准确无误。

    http://site.dcjy.net/qise/6/jxzyk/My%20Webs/z4.htm
    尽管计算机通过安装IP软件,从而保证了计算机之间可以发送和接收数据,但IP协议还不能解决数据分组在传输过程中可能出现的问题。因此,若要解决可能出现的问题,连上 Internet 的计算机还需要安装TCP协议来提供可靠的并且无差错的通信服务。
    TCP协议被称作一种端对端协议。这是因为它为两台计算机之间的连接起了重要作用:当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接、发送和接收数据以及终止连接。

    传输控制协议TCP协议利用重发技术和拥塞控制机制,向应用程序提供可靠的通信连接,使它能够自动适应网上的各种变化。即使在 Internet 暂时出现堵塞的情况下,TCP也能够保证通信的可靠。

    众所周知, Internet 是一个庞大的国际性网络,网路上的拥挤和空闲时间总是交替不定的,加上传送的距离也远近不同,所以传输数据所用时间也会变化不定。TCP协议具有自动调整"超时值"的功能,能很好地适应 Internet 上各种各样的变化,确保传输数值的正确。

    因此,从上面我们可以了解到:IP协议只保证计算机能发送和接收分组数据,而TCP协议则可提供一个可靠的、可流控的、全双工的信息流传输服务。

    综上所述,虽然IP和TCP这两个协议的功能不尽相同,也可以分开单独使用,但它们是在同一时期作为一个协议来设计的,并且在功能上也是互补的。只有两者的结合,才能保证 Internet 在复杂的环境下正常运行。凡是要连接到 Internet 的计算机,都必须同时安装和使用这两个协议,因此在实际中常把这两个协议统称作TCP/IP协议。
    6.常见web服务器有哪些,运行环境?
    WEB服务器也称为WWW(WORLD WIDE WEB)服务器,主要功能是提供网上信息浏览服务。
    (1)应用层使用HTTP协议。
    (2)HTML文档格式。
    (3)浏览器统一资源定位器(URL)。
    WWW代表万维网的意思

    WWW 是 Internet 的多媒体信息查询工具,是 Internet 上近年才发展起来的服务,也是发展最快和目前用的最广泛的服务。正是因为有了WWW工具,才使得近年来 Internet 迅速发展,且用户数量飞速增长。

    1、WWW简介

    WWW 是 World Wide Web (环球信息网)的缩写,也可以简称为 Web,中文名字为“万维网”。它起源于1989年3月,由欧洲量子物理实验室 CERN(the European Laboratory for Particle Physics)所发展出来的主从结构分布式超媒体系统。通过万维网,人们只要通过使用简单的方法,就可以很迅速方便地取得丰富的信息资料。 由于用户在通过 Web 浏览器访问信息资源的过程中,无需再关心一些技术性的细节,而且界面非常友好,因而 Web 在Internet 上一推出就受到了热烈的欢迎,走红全球,并迅速得到了爆炸性的发展。

    2、WWW的发展和特点

    长期以来,人们只是通过传统的媒体(如电视、报纸、杂志和广播等)获得信息。但随着计算机网络的发展,人们想要获取信息,已不再满足于传统媒体那种单方面传输和获取的方式,而希望有一种主观的选择性。现在,网络上提供各种类别的数据库系统,如文献期刊、产业信息、气象信息、论文检索等等。由于计算机网络的发展,信息的获取变得非常及时、迅速和便捷。

    到了1993年,WWW 的技术有了突破性的进展,它解决了远程信息服务中的文字显示、数据连接以及图像传递的问题,使得 WWW 成为 Internet 上最为流行的信息传播方式。 现在,Web 服务器成为 Internet 上最大的计算机群,Web 文档之多、链接的网络之广,令人难以想象。可以说,Web 为 Internet 的普及迈出了开创性的一步,是近年来 Internet 上取得的最激动人心的成就。

    WWW 采用的是客户/服务器结构,其作用是整理和储存各种WWW资源,并响应客户端软件的请求,把客户所需的资源传送到 Windows 95(或Windows98)、Windows NT、UNIX 或 Linux 等平台上。

    使用最多的 web server 服务器软件 有两个:微软的信息服务器(iis),和Apache。
    通俗的讲,Web服务器传送(serves)页面使浏览器可以浏览,然而应用程序服务器提供的是客户端应用程序可以调用(call)的方法(methods)。确切一点,你可以说:Web服务器专门处理HTTP请求(request),但是应用程序服务器是通过很多协议来为应用程序提供(serves)商业逻辑(business logic)。

    Web服务器可以解析(handles)HTTP协议。当Web服务器接收到一个HTTP请求(request),会返回一个HTTP响应(response),例如送回一个HTML页面。为了处理一个请求(request),Web服务器可以响应(response)一个静态页面或图片,进行页面跳转(redirect),或者把动态响应(dynamic response)的产生委托(delegate)给一些其它的程序例如CGI脚本,JSP(JavaServer Pages)脚本,servlets,ASP(Active Server Pages)脚本,服务器端(server-side)Javascrīpt,或者一些其它的服务器端(server-side)技术。无论它们(译者注:脚本)的目的如何,这些服务器端(server-side)的程序通常产生一个HTML的响应(response)来让浏览器可以浏览。

    要知道,Web服务器的代理模型(delegation model)非常简单。当一个请求(request)被送到Web服务器里来时,它只单纯的把请求(request)传递给可以很好的处理请求(request)的程序(译者注:服务器端脚本)。Web服务器仅仅提供一个可以执行服务器端(server-side)程序和返回(程序所产生的)响应(response)的环境,而不会超出职能范围。服务器端(server-side)程序通常具有事务处理(transaction processing),数据库连接(database connectivity)和消息(messaging)等功能。

    虽然Web服务器不支持事务处理或数据库连接池,但它可以配置(employ)各种策略(strategies)来实现容错性(fault tolerance)和可扩展性(scalability),例如负载平衡(load balancing),缓冲(caching)。集群特征(clustering—features)经常被误认为仅仅是应用程序服务器专有的特征。

    应用程序服务器(The Application Server)

    根据我们的定义,作为应用程序服务器,它通过各种协议,可以包括HTTP,把商业逻辑暴露给(expose)客户端应用程序。Web服务器主要是处理向浏览器发送HTML以供浏览,而应用程序服务器提供访问商业逻辑的途径以供客户端应用程序使用。应用程序使用此商业逻辑就象你调用对象的一个方法(或过程语言中的一个函数)一样。
    应用程序服务器的客户端(包含有图形用户界面(GUI)的)可能会运行在一台PC、一个Web服务器或者甚至是其它的应用程序服务器上。在应用程序服务器与其客户端之间来回穿梭(traveling)的信息不仅仅局限于简单的显示标记。相反,这种信息就是程序逻辑(program logic)。 正是由于这种逻辑取得了(takes)数据和方法调用(calls)的形式而不是静态HTML,所以客户端才可以随心所欲的使用这种被暴露的商业逻辑。

    在大多数情形下,应用程序服务器是通过组件(component)的应用程序接口(API)把商业逻辑暴露(expose)(给客户端应用程序)的,例如基于J2EE(Java 2 Platform, Enterprise Edition)应用程序服务器的EJB(Enterprise JavaBean)组件模型。此外,应用程序服务器可以管理自己的资源,例如看大门的工作(gate-keeping duties)包括安全(security),事务处理(transaction processing),资源池(resource pooling), 和消息(messaging)。就象Web服务器一样,应用程序服务器配置了多种可扩展(scalability)和容错(fault tolerance)技术。  

    例如,设想一个在线商店(网站)提供实时定价(real-time pricing)和有效性(availability)信息。这个站点(site)很可能会提供一个表单(form)让你来选择产品。当你提交查询(query)后,网站会进行查找(lookup)并把结果内嵌在HTML页面中返回。网站可以有很多种方式来实现这种功能。我要介绍一个不使用应用程序服务器的情景和一个使用应用程序服务器的情景。观察一下这两中情景的不同会有助于你了解应用程序服务器的功能。
    情景1:不带应用程序服务器的Web服务器
    在此种情景下,一个Web服务器独立提供在线商店的功能。Web服务器获得你的请求(request),然后发送给服务器端(server-side)可以处理请求(request)的程序。此程序从数据库或文本文件(flat file,译者注:flat file是指没有特殊格式的非二进制的文件,如properties和XML文件等)中查找定价信息。一旦找到,服务器端(server-side)程序把结果信息表示成(formulate)HTML形式,最后Web服务器把会它发送到你的Web浏览器。
    简而言之,Web服务器只是简单的通过响应(response)HTML页面来处理HTTP请求(request)。
    情景2:带应用程序服务器的Web服务器
    情景2和情景1相同的是Web服务器还是把响应(response)的产生委托(delegates)给脚本(译者注:服务器端(server-side)程序)。然而,你可以把查找定价的商业逻辑(business logic)放到应用程序服务器上。由于这种变化,此脚本只是简单的调用应用程序服务器的查找服务(lookup service),而不是已经知道如何查找数据然后表示为(formulate)一个响应(response)。 这时当该脚本程序产生HTML响应(response)时就可以使用该服务的返回结果了。
    在此情景中,应用程序服务器提供(serves)了用于查询产品的定价信息的商业逻辑。(服务器的)这种功能(functionality)没有指出有关显示和客户端如何使用此信息的细节,相反客户端和应用程序服务器只是来回传送数据。当有客户端调用应用程序服务器的查找服务(lookup service)时,此服务只是简单的查找并返回结果给客户端。
    通过从响应产生(response-generating)HTML的代码中分离出来,在应用程序之中该定价(查找)逻辑的可重用性更强了。其他的客户端,例如收款机,也可以调用同样的服务(service)来作为一个店员给客户结帐。相反,在情景1中的定价查找服务是不可重用的因为信息内嵌在HTML页中了。
    总而言之,在情景2的模型中,在Web服务器通过回应HTML页面来处理HTTP请求(request),而应用程序服务器则是通过处理定价和有效性(availability)请求(request)来提供应用程序逻辑的。
    警告(Caveats)
    现在,XML Web Services已经使应用程序服务器和Web服务器的界线混淆了。通过传送一个XML有效载荷(payload)给服务器,Web服务器现在可以处理数据和响应(response)的能力与以前的应用程序服务器同样多了。
    另外,现在大多数应用程序服务器也包含了Web服务器,这就意味着可以把Web服务器当作是应用程序服务器的一个子集(subset)。虽然应用程序服务器包含了Web服务器的功能,但是开发者很少把应用程序服务器部署(deploy)成这种功能(capacity)(译者注:这种功能是指既有应用程序服务器的功能又有Web服务器的功能)。相反,如果需要,他们通常会把Web服务器独立配置,和应用程序服务器一前一后。这种功能的分离有助于提高性能(简单的Web请求(request)就不会影响应用程序服务器了),分开配置(专门的Web服务器,集群(clustering)等等),而且给最佳产品的选取留有余地。

    常用的WEB服务器

      在UNIX和LINUX平台下使用最广泛的免费HTTP服务器是W3C、NCSA和APACHE服务器,而Windows平台NT/2000/2003使用IIS的WEB服务器。在选择使用WEB服务器应考虑的本身特性因素有:性能、安全性、日志和统计、虚拟主机、代理服务器、缓冲服务和集成应用程序等,下面介绍几种常用的WEB服务器。
      Microsoft IIS
      Microsoft的Web服务器产品为Internet Information Server (IIS), IIS 是允许在公共Intranet或Internet上发布信息的Web服务器。IIS是目前最流行的Web服务器产品之一,很多著名的网站都是建立在IIS的平台上。IIS提供了一个图形界面的管理工具,称为 Internet服务管理器,可用于监视配置和控制Internet服务。
      IIS是一种Web服务组件,其中包括Web服务器、FTP服务器、NNTP服务器和SMTP服务器,分别用于网页浏览、文件传输、新闻服务和邮件发送等方面,它使得在网络(包括互联网和局域网)上发布信息成了一件很容易的事。它提供ISAPI(Intranet Server API)作为扩展Web服务器功能的编程接口;同时,它还提供一个Internet数据库连接器,可以实现对数据库的查询和更新。
      IBM WebSphere
      WebSphere Application Server 是 一 种功能完善、开放的Web应用程序服务器,是IBM电子商务计划的核心部分,它是基于 Java 的应用环境,用于建立、部署和管理 Internet 和 Intranet Web 应用程序。 这一整套产品进行了扩展,以适应 Web 应用程序服务器的需要,范围从简单到高级直到企业级。
      WebSphere 针对以 Web 为中心的开发人员,他们都是在基本 HTTP服务器和 CGI 编程技术上成长起来的。IBM 将提供 WebSphere 产品系列,通过提供综合资源、可重复使用的组件、功能强大并易于使用的工具、以及支持 HTTP 和 IIOP 通信的可伸缩运行时环境,来帮助这些用户从简单的 Web 应用程序转移到电子商务世界。
      BEA WebLogic
      BEA WebLogic Server 是一种多功能、基于标准的web应用服务器,为企业构建自己的应用提供了坚实的基础。各种应用开发、部署所有关键性的任务,无论是集成各种系统和数据库,还是提交服务、跨 Internet 协作,起始点都是 BEA WebLogic Server。由于 它具有全面的功能、对开放标准的遵从性、多层架构、支持基于组件的开发,基于 Internet 的企业都选择它来开发、部署最佳的应用。
      BEA WebLogic Server 在使应用服务器成为企业应用架构的基础方面继续处于领先地位。BEA WebLogic Server 为构建集成化的企业级应用提供了稳固的基础,它们以 Internet 的容量和速度,在连网的企业之间共享信息、提交服务,实现协作自动化。
           APACHE
      apache仍然是世界上用的最多的Web服务器,市场占有率达60%左右。它源于NCSAhttpd服务器,当NCSA WWW服务器项目停止后,那些使用NCSA WWW服务器的人们开始交换用于此服务器的补丁,这也是apache名称的由来(pache 补丁)。世界上很多著名的网站都是Apache的产物,它的成功之处主要在于它的源代码开放、有一支开放的开发队伍、支持跨平台的应用(可以运行在几乎所有的Unix、Windows、Linux系统平台上)以及它的可移植性等方面。
      Tomcat
      Tomcat是一个开放源代码、运行servlet和JSP Web应用软件的基于Java的Web应用软件容器。Tomcat Server是根据servlet和JSP规范进行执行的,因此我们就可以说Tomcat Server也实行了Apache-Jakarta规范且比绝大多数商业应用软件服务器要好。
      Tomcat是Java Servlet 2.2和JavaServer Pages 1.1技术的标准实现,是基于Apache许可证下开发的自由软件。Tomcat是完全重写的Servlet API 2.2和JSP 1.1兼容的Servlet/JSP容器。Tomcat使用了JServ的一些代码,特别是Apache服务适配器。随着Catalina Servlet引擎的出现,Tomcat第四版号的性能得到提升,使得它成为一个值得考虑的Servlet/JSP容器,因此目前许多WEB服务器都是采用Tomcat。
    7.常见浏览器版本有哪些?
    FireFox,Maxthon,IE,MyIE
    8.上传文件的测试用例
    9.描述测试缺陷报告,ATM取款机的例子
    10.英文翻译中文
    面试:
    自我介绍
  • SQL基本语句

    2008-07-12 23:11:40

    文章来源:http://www.yfdmt.com/webmedia/build/SQL.htm

    掌握SQL四条最基本的数据操作语句:Insert,Select,Update和Delete。

       练掌握SQL是
    数据库用户的宝贵财 富。在本文中,我们将引导你掌握四条最基本的数据操作语句—SQL的核心功能—来依次介绍比较操作符、选择断言以及三值逻辑。当你完成这些学习后,显然你已经开始算是精通SQL了。

      在我们开始之前,先使用CREATE TABLE语句来创建一个表(如图1所示)。DDL语句对数据库对象如表、列和视进行定义。它们并不对表中的行进行处理,这是因为DDL语句并不处理数据库中实际的数据。这些
    工作由另一类SQL语句—数据操作语言(DML)语句进行处理。

      SQL中有四种基本的DML操作:INSERT,SELECT,UPDATE和DELETE。由于这是大多数SQL用户经常用到的,我们有必要在此对它们进行一一说明。在图1中我们给出了一个名为EMPLOYEES的表。其中的每一行对应一个特定的雇员记录。请熟悉这张表,我们在后面的例子中将要用到它。

      INSERT语句

      用户可以用INSERT语句将一行记录插入到指定的一个表中。例如,要将雇员John Smith的记录插入到本例的表中,可以使用如下语句:

      INSERT INTO EMPLOYEES VALUES

       ('Smith','John','1980-06-10',

       'Los Angles',16,45000);

      通过这样的INSERT语句,系统将试着将这些值填入到相应的列中。这些列按照我们创建表时定义的顺序排列。在本例中,第一个值“Smith”将填到第一个列LAST_NAME中;第二个值“John”将填到第二列FIRST_NAME中……以此类推。

      我们说过系统会“试着”将值填入,除了执行规则之外它还要进行类型检查。如果类型不符(如将一个字符串填入到类型为数字的列中),系统将拒绝这一次操作并返回一个错误信息。

      如果SQL拒绝了你所填入的一列值,语句中
    其他各列的值也不会填入。这是因为SQL提供对事务的支持。一次事务将数据库从一种一致性转移到另一种一致性。如果事务的某一部分失败,则整个事务都会失败,系统将会被恢复(或称之为回退)到此事务之前的状态。

       回到原来的INSERT的例子,请注意所有的整形十进制数都不需要用单引号引起来,而字符串和日期类型的值都要用单引号来区别。为了增加可读性而在数字间插入逗号将会引起错误。记住,在SQL中逗号是元素的分隔符。

      同样要注意输入文字值时要使用单引号。双引号用来封装限界标识符。

      对于日期类型,我们必须使用SQL标准日期格式(yyyy-mm-dd),但是在系统中可以进行定义,以接受其他的格式。当然,2000年临近,请你最好还是使用四位来表示年份。

      既然你已经理解了INSERT语句是怎样工作的了,让我们转到EMPLOYEES表中的其他部分:

      INSERT INTO EMPLOYEES VALUES

       ('Bunyan','Paul','1970-07-04',

       'Boston',12,70000);

      INSERT INTO EMPLOYEES VALUES

       ('John','Adams','1992-01-21',

       'Boston',20,100000);

      INSERT INTO EMPLOYEES VALUES

       ('Smith','Pocahontas','1976-04-06',

       'Los Angles',12,100000);

      INSERT INTO EMPLOYEES VALUES

       ('Smith','Bessie','1940-05-02',

       'Boston',5,200000);

      INSERT INTO EMPLOYEES VALUES

       ('Jones','Davy','1970-10-10',

       'Boston',8,45000);

      INSERT INTO EMPLOYEES VALUES

       ('Jones','Indiana','1992-02-01',

       'Chicago',NULL,NULL);

      在最后一项中,我们不知道Jones先生的工薪级别和年薪,所以我们输入NULL(不要引号)。NULL是SQL中的一种特殊情况,我们以后将进行详细的讨论。现在我们只需认为NULL表示一种未知的值。

      有时,像我们刚才所讨论的情况,我们可能希望对某一些而不是全部的列进行赋值。除了对要省略的列输入NULL外,还可以采用另外一种INSERT语句,如下:

      INSERT INTO EMPLOYEES(

       FIRST_NAME, LAST_NAME,

       HIRE_DATE, BRANCH_OFFICE)

      VALUE(

       'Indiana','Jones',

       '1992-02-01','Indianapolis');

      这样,我们先在表名之后列出一系列列名。未列出的列中将自动填入缺省值,如果没有设置缺省值则填入NULL。请注意我们改变了列的顺序,而值的顺序要对应新的列的顺序。如果该语句中省略了FIRST_NAME和LAST_NAME项(这两项规定不能为空),SQL操作将失败。

      让我们来看一看上述INSERT语句的语法图:

      INSERT INTO table

       [(column { ,column})]

      VALUES

       (columnvalue [{,columnvalue}]);

      和前一篇文章中一样,我们用方括号来表示可选项,大括号表示可以重复任意次数的项(不能在实际的SQL语句中使用这些特殊字符)。VALUE子句和可选的列名列表中必须使用圆括号。

      SELECT语句

      SELECT语句可以从一个或多个表中选取特定的行和列。因为查询和检索数据是数据库管理中最重要的功能,所以SELECT语句在SQL中是工作量最大的部分。实际上,仅仅是访问数据库来分析数据并生成报表的人可以对其他SQL语句一窍不通。

      SELECT语句的结果通常是生成另外一个表。在执行过程中系统根据用户的标准从数据库中选出匹配的行和列,并将结果放到临时的表中。在直接SQL(direct SQL)中,它将结果显示在终端的显示屏上,或者将结果送到打印机或文件中。也可以结合其他SQL语句来将结果放到一个已知名称的表中。

      SELECT语句功能强大。虽然表面上看来它只用来完成本文第一部分中提到的关系代数运算“选择”(或称“限制”),但实际上它也可以完成其他两种关系运算—“投影”和“连接”,SELECT语句还可以完成聚合计算并对数据进行排序。

      SELECT语句最简单的语法如下:

      SELECT columns FROM tables;

      当我们以这种形式执行一条SELECT语句时,系统返回由所选择的列以及用户选择的表中所有指定的行组成的一个结果表。这就是实现关系投影运算的一个形式。

      让我们看一下使用图1中EMPLOYEES表的一些例子(这个表是我们以后所有SELECT语句实例都要使用的。而我们在图2和图3中给出了查询的实际结果。我们将在其他的例子中使用这些结果)。

      假设你想查看雇员工作部门的列表。那下面就是你所需要编写的SQL查询:

      SELECT BRANCH_OFFICE FROM EMPLOYEES;

      以上SELECT语句的执行将产生如图2中表2所示的结果。

      由于我们在SELECT语句中只指定了一个列,所以我们的结果表中也只有一个列。注意结果表中具有重复的行,这是因为有多个雇员在同一部门工作(记住SQL从所选的所有行中将值返回)。要消除结果中的重复行,只要在SELECT语句中加上DISTINCT子句:

      SELECT DISTINCT BRANCH_OFFICE

      FROM EMPLOYEES;

      这次查询的结果如表3所示。

      现在已经消除了重复的行,但结果并不是按照顺序排列的。如果你希望以字母表顺序将结果列出又该怎么做呢?只要使用ORDER BY子句就可以按照升序或降序来排列结果:

      SELECT DISTINCT BRANCH_OFFICE

      FROM EMPLOYEES

      ORDER BY BRANCH_OFFICE ASC;

      这一查询的结果如表4所示。请注意在ORDER BY之后是如何放置列名BRANCH _OFFICE的,这就是我们想要对其进行排序的列。为什么即使是结果表中只有一个列时我们也必须指出列名呢?这是因为我们还能够按照表中其他列进行排序,即使它们并不显示出来。列名BRANCH_ OFFICE之后的关键字ASC表示按照升序排列。如果你希望以降序排列,那么可以用关键字DESC。

      同样我们应该指出ORDER BY子句只将临时表中的结果进行排序;并不影响原来的表。

      假设我们希望得到按部门排序并从工资最高的雇员到工资最低的雇员排列的列表。除了工资括号中的内容,我们还希望看到按照聘用时间从最近聘用的雇员开始列出的列表。以下是你将要用到的语句:

      SELECT BRANCH_OFFICE,FIRST_NAME,

       LAST_NAME,SALARY,HIRE_DATE

      FROM EMPLOYEES

      ORDER BY SALARY DESC,

       HIRE_DATE DESC;

      这里我们进行了多列的选择和排序。排序的优先级由语句中的列名顺序所决定。SQL将先对列出的第一个列进行排序。如果在第一个列中出现了重复的行时,这些行将被按照第二列进行排序,如果在第二列中又出现了重复的行时,这些行又将被按照第三列进行排序……如此类推。这次查询的结果如表5所示。

      将一个很长的表中的所有列名写出来是一件相当麻烦的事,所以SQL允许在选择表中所有的列时使用*号:

      SELECT * FROM EMPLOYEES;

      这次查询返回整个EMPLOYEES表,如表1所示。

       下面我们对开始时给出的SELECT语句的语法进行一下更新(竖直线表示一个可选项,允许在其中选择一项。):

      SELECT [DISTINCT]

       (column [{, columns}])| *

      FROM table [ {, table}]

      [ORDER BY column [ASC] | DESC

       [ {, column [ASC] | DESC }]];

      定义选择标准

      在我们目前所介绍的SELECT语句中,我们对结果表中的列作出了选择但返回的是表中所有的行。让我们看一下如何对SELECT语句进行限制使得它只返回希望得到的行:

      SELECT columns FROM tables [WHERE predicates];

      WHERE子句对条件进行了设置,只有满足条件的行才被包括到结果表中。这些条件由断言(predicate)进行指定(断言指出了关于某件事情的一种可能的事实)。如果该断言对于某个给定的行成立,该行将被包括到结果表中,否则该行被忽略。在SQL语句中断言通常通过比较来表示。例如,假如你需要查询所有姓为Jones的职员,则可以使用以下SELECT语句:

      SELECT * FROM EMPLOYEES

      WHERE LAST_NAME = 'Jones';

      LAST_NAME = 'Jones'部分就是断言。在执行该语句时,SQL将每一行的LAST_NAME列与“Jones”进行比较。如果某一职员的姓为“Jones”,即断言成立,该职员的信息将被包括到结果表中(见表6)。

      使用最多的六种比较

      我们上例中的断言包括一种基于“等值”的比较(LAST_NAME = 'Jones'),但是SQL断言还可以包含其他几种类型的比较。其中最常用的为:

      等于 =

      不等于 <>

      小于 <

      大于 >

      小于或等于 <=

      大于或等于 >=

      下面给出了不是基于等值比较的一个例子:

      SELECT * FROM EMPLOYEES

      WHERE SALARY > 50000;

      这一查询将返回年薪高于$50,000.00的职员(参见表7)。

      逻辑连接符

      有时我们需要定义一条不止一种断言的SELECT语句。举例来说,如果你仅仅想查看Davy Jones的信息的话,表6中的结果将是不正确的。为了进一步定义一个WHERE子句,用户可以使用逻辑连接符AND,OR和NOT。为了只得到职员Davy Jones的记录,用户可以输入如下语句:

      SELECT * FROM EMPLOYEES

      WHERE LAST_NAME = 'Jones' AND FIRST_NAME = 'Davy';

      在本例中,我们通过逻辑连接符AND将两个断言连接起来。只有两个断言都满足时整个表达式才会满足。如果用户需要定义一个SELECT语句来使得当其中任何一项成立就满足条件时,可以使用OR连接符:

      SELECT * FROM EMPLOYEES

      WHERE LAST_NAME = 'Jones' OR LAST_NAME = 'Smith';

      有时定义一个断言的最好方法是通过相反的描述来说明。如果你想要查看除了Boston办事处的职员以外的其他所有职员的信息时,你可以进行如下的查询:

      SELECT * FROM EMPLOYEES

      WHERE NOT(BRANCH_OFFICE = 'Boston');

      关键字NOT后面跟着用圆括号括起来的比较表达式。其结果是对结果取否定。如果某一职员所在部门的办事处在Boston,括号内的表达式返回true,但是NOT操作符将该值取反,所以该行将不被选中。

      断言可以与其他的断言嵌套使用。为了保证它们以正确的顺序进行求值,可以用括号将它们括起来:

      SELECT * FROM EMPLOYEES

      WHERE (LAST_NAME = 'Jones'

      AND FIRST_NAME = 'Indiana')

      OR (LAST_NAME = 'Smith'

      AND FIRST_NAME = 'Bessie');

      SQL沿用数学上标准的表达式求值的约定—圆括号内的表达式将最先进行求值,其他表达式将从左到右进行求值。

      以上对逻辑连接符进行了说明,在对下面的内容进行说明之前,我们再一次对SELECT语句的语法进行更新:

      SELECT [DISTINCT]

       (column [{, column } ] )| *

      FROM table [ { , table} ]

      [ORDER BY column [ASC] | [DESC

      [{ , column [ASC] | [DESC } ] ]

      WHERE predicate [ { logical-connector predicate } ];

      NULL和三值逻辑

      在SQL中NULL是一个复杂的话题,关于NULL的详细描述更适合于在SQL的高级教程而不是现在的入门教程中进行介绍。但由于NULL需要进行特殊处理,并且你也很可能会遇到它,所以我们还是简略地进行一下说明。

      首先,在断言中进行NULL判断时需要特殊的语法。例如,如果用户需要显示所有年薪未知的职员的全部信息,用户可以使用如下SELECT语句:

      SELECT * FROM EMPLOYEES

      WHERE SALARY IS NULL;

      相反,如果用户需要所有已知年薪数据的职员的信息,你可以使用以下语句:

      SELECT * FROM EMPLOYEES

      WHERE SALARY IS NOT NULL;

      请注意我们在列名之后使用了关键字IS NULL或IS NOT NULL,而不是标准的比较形式:COLUMN = NULL、COLUMN <> NULL或是逻辑操作符NOT(NULL)。

      这种形式相当简单。但当你不明确地测试NULL(而它们确实存在)时,事情会变得很混乱。

      例如,回过头来看我们图1中的EM-PLOYEES表,可以看到Indiana Jones的工薪等级或年薪值都是未知的。这两个列都包含NULL。可以想象运行如下的查询:

      SELECT * FROM EMPLOYEES

      WHERE GRADE <= SALARY;

      此时,Indiana Jones应该出现在结果表中。因为NULL都是相等的,所以可以想象它们是能够通过GRADE小于等于SALARY的检查的。这其实是一个毫无疑义的查询,但是并没有关系。SQL允许进行这样的比较,只要两个列都是数字类型的。然而,Indiana Jones并没有出现在查询的结果中,为什么?

      正如我们早先提到过的,NULL表示未知的值(而不是象某些人所想象的那样表示一个为NULL的值)。对于SQL来说意味着这个值是未知的,而只要这个值为未知,就不能将其与其他值比较(即使其他值也是NULL)。所以SQL允许除了在true 和false之外还有第三种类型的真值,称之为“非确定”(unknown)值。

      如果比较的两边都是NULL,整个断言就被认为是非确定的。将一个非确定断言取反或使用AND或OR与其他断言进行合并之后,其结果仍是非确定的。由于结果表中只包括断言值为“真”的行,所以NULL不可能满足该检查。从而需要使用特殊的操作符IS NULL和IS NOT NULL。

      UPDATE语句

      UPDATE语句允许用户在已知的表中对现有的行进行修改。

      例如,我们刚刚发现Indiana Jones的等级为16,工资为$40,000.00,我们可以通过下面的SQL语句对数据库进行更新(并清除那些烦人的NULL)。

      UPDATE EMPLOYEES

      SET GRADE = 16, SALARY = 40000

      WHERE FIRST_NAME = 'Indiana'

       AND LAST_NAME = 'Jones';

      上面的例子说明了一个单行更新,但是UPDATE语句可以对多行进行操作。满足WHERE条件的所有行都将被更新。如果,你想让Boston办事处中的所有职员搬到New York,你可以使用如下语句:

      UPDATE EMPLOYEES

      SET BRANCH_OFFICE = 'New York'

      WHERE BRANCH_OFFICE = 'Boston';

      如果忽略WHERE子句,表中所有行中的部门值都将被更新为'New York'。

      UPDATE语句的语法流图如下面所示:

      UPDATE table

      SET column = value [{, column = value}]

      [ WHERE predicate [ { logical-connector predicate}]];

      DELETE语句

      DELETE语句用来删除已知表中的行。如同UPDATE语句中一样,所有满足WHERE子句中条件的行都将被删除。由于SQL中没有UNDO语句或是“你确认删除吗?”之类的警告,在执行这条语句时千万要小心。如果决定取消Los Angeles办事处并解雇办事处的所有职员,这一卑鄙的工作可以由以下这条语句来实现:

      DELETE FROM EMPLOYEES

      WHERE BRANCH_OFFICE = 'Los Angeles';

      如同UPDATE语句中一样,省略WHERE子句将使得操作施加到表中所有的行。

      DELETE语句的语法流图如下面所示:

      DELETE FROM table

      [WHERE predicate [ { logical-connector predicate} ] ];

      现在我们完成了数据操作语言(DML)的主要语句的介绍。我们并没有对SQL能完成的所有功能进行说明。SQL还提供了许多的功能,如求平均值、求和以及其他对表中数据的计算,此外SQL还能完成从多个表中进行查询(多表查询,或称之为连接)的工作。这种语言还允许你使用GRANT和REVOKE命令控制使用者的数据访问权限。

  • 数据统计

    • 访问量: 6345
    • 日志数: 6
    • 建立时间: 2008-07-12
    • 更新时间: 2008-10-16

    RSS订阅