发布新日志

  • 软件测试基础-测试模型

    2008-04-28 16:58:55

    8 、软件测试过程模型

        V模型

          瀑布模型:人们通常把测试过程作为在需求分析、概要设计、详细设计和编码全部完成之后的一个阶段。

         V模型是软件开发瀑布模型的一个变种。

        用户需求需求分析、系统设计概要设计详细设计编码

        验收测试系统测试、确认测试集成测试单元测试

       

     

    V模型的局限性:它仅仅把测试过程作为在需求分析、概要设计、详细设计以及编码之后的一个阶段。容易使人理解测试是软件开发的最后一个阶段,主要是针对程序进行测试寻找错误,而需求分析阶段隐藏的问题一直到后期的验收测试才被发现。没有说明早期的测试,没有体现“尽早的和不间断地进行软件测试”的原则。

    W模型

      V模型的局限性在于没有明确地说明早期的测试,无法体现尽早地和不断地进行软件测试的原则。在V模型中增加软件各开发阶段应同步进行的测试,演化为W模型。在模型中不难看出,开发是“V”,测试是与此并行的“V”。基于尽早地和不断地进行软件测试的原则,在软件的需求和设计阶段的测试活动应遵循IEEE1012-1998《软件验证与确认(V&V)》的原则

     

    W模型是V模型的发展,强调的是测试伴随着整个软件开发周期,而且测试的对象不仅仅是程序,需求、功能和设计同样要测试。测试与开发是同步进行的,从而有利于尽早地发现问题。

     

    W模型的局限性:模型和V模型都把软件的开发视为需求、设计、编码等一系列串行的活动,软件测试和开发保持一种线性的前后关系,需要有严格的指令表示上一阶段完全结束,下一阶段才可以开始,无法支持迭代、自发性以及变更调整

     

    H模型

    它将测试活动完全独立出来,形成一个完全独立的流程,将测试准备活动和测试执行活动清晰地体现出来。

      

    H模型中,软件测试模型是一个独立的流程,贯穿于整个产品周期,与其他流程并发地进行。当某个测试时间点就绪时,软件测试即从测试准备阶段进入测试执行阶段。 

     

     

    X模型:目标是弥补V模型的一些缺陷

          

     

     

    模型还定位了探索性测试(右下方)。这是不进行事先计划的特殊类型的测试,诸如我这么测一下结果会怎么样?,这一方式往往能帮助有经验的测试人员在测试计划之外发现更多的软件错误

    尽管很多项目缺乏足够的需求,V模型还是从需求处理开始。V模型提示我们要对各开发阶段中已经得到的内容进行测试,但它没有规定我们要取得多少内容。如果没有任何的需求资料,开发人员知道他们要做什么吗?V模型的一个强项是它明确的需求角色的确认,而X模型没有这么做,这大概是X模型的一个不足之处。

    Marick也质疑了单元测试和集成测试的区别,因为在某些场合人们可能会跳过单元测试而热衷于直接进行集成测试。Marick担心人们盲目地跟随学院派的V模型,按照模型所指导的步骤进行工作,而实际上某些做法并不切合实用。我已经尽自己的努力把Marick的关于需要很多具有可伸缩性的行为的期望结合进了X模型,这样,X模型并不要求在进行作为创建可执行程序(图中右上方)的一个组成部分的集成测试之前,对每一个程序片段都进行单元测试(图中左侧的行为)。X模型没能提供是否要跳过单元测试的判断准则

     

    前置测试模型

    1 开发和测试相结合

    业务需求最好在设计和开发之前就被正确定义
    2
    对每一个交付内容进行测试

    源程序并不是唯一的测试内容,其他的测试对象,包括可行性报告、业务需求说明以及设计文档(V模型的扩展)

    3 在设计阶段进行测试计划和测试设计


    4
    、验收测试和技术测试相互独立

    验收测试可以在实施阶段的第一步来执行,也可以在开发阶段的最后一步执行。

    5、测试和开发结合在一起

    测试模型的使用:

     

    V模型

    强调了软件开发过程中需要经历的各个测试级别,并与每一个开发级别对应;忽略了测试不应该仅仅是程序。没有明确指出对需求和设计的测试

    W模型

    补充了V模型忽略的内容,强调了测试工作的现行和对系统需求以及系统设计的测试;V模型一样都没有对测试流程进行说明。

    H模型

    强调测试是独立的,只要测试准备完成,就可以执行测试(第三方测试)

  • 软件测试基础-基本概念

    2008-04-28 16:12:45

    软件测试基础

    1、 什么是软件测试

    测试的含义“以检验产品是否满足需求为目标“,软件测试的重要活动即发现错误

    软件测试的经典定义:在规定的条件下对软件进行操作,以发现错误,并对软件质量进行评估

    软件是由程序、数据和文档组成的,软件测试就是对软件形成过程中的文档、数据和程序进行测试,而不仅仅是对程序进行测试。

    60%以上的软件错误并不是程序错误,而是软件分析和设计的错误

     

    2、 什么是软件质量

    经典定义:软件特性的总和,软件满足规定或潜在用户需求的能力

    软件质量包括“内部质量“、“外部质量”以及“使用质量”,也就是说“软件满足规定或者潜在用户需求能力”要从“内部质量”、“外部质量”和“使用质量”三部分体现

    3、 软件测试和质量保证的区别:

    软件测试只是软件质量保证的一个工作环节

    质量保证:通过预防、检查和改进来保证软件质量。采用全面质量管理和过程改进的原理来开展质量保证工作,主要着眼于软件开发活动的过程、步骤和产物。

     

    软件测试:关心的并不是过程的活动,而是对过程的产物以及开发的软件进行剖析。通过执行软件来,对过程中的产物(开发文档和程序)进行走查,发现问题,报告质量

    4、 软件测试的目的:

    测试的目的不仅仅是为了发现软件错误和缺陷,而是也是对软件质量进行评估,以提高软件质量。

    测试是程序的执行过程,目的在于发现错误;

    一个好的测试用例在于发现了至今未发现的错误;

    一个成功的测试是发现了 至今未发现的错误的测试;

     

    5、 软件测试原则:

         所有的测试都应该追溯的用户的需求

         应该把“尽早的和不断的测试”作为软件测试者的座右铭

         完全测试是不可能的,测试需要终止

    输入量太大

    输出结果太多

    路径组合太多

    (中止测试原则)根据测试错误的概率以及软件可靠性要求,来确定测试的停止测试时间

         测试无法显示软件潜在的缺陷  

    软件测试只能证明软件存在错误而不能保证软件没有错误

         充分注意测试中的群集现象

         测试后程序中残存的错误数目和该程序中已经发现的错误数目成正比
      程序员应该避免检查自己的程序

         尽量避免测试的随意性

    6、 软件测试对象

         在整个软件生命周期中,各个阶段有不同的测试对象,形成了不同开发阶段的不同类型的测试。

         需求分析、概要设计、详细设计以及程序编码等阶段得到的不同的文档,例如需求规格说明书、概要设计规格说明书、详细设计规格说明书以及源程序,都应该成为测试对象。

    软件编码完成后,对每一模块进行测试,称为“模块测试”或“单元测试”。在模块集成后,对集成在一起的模块组件,称为“集成测试”,在集成测试后需要检测和证实软件是否满足软件需求说明书中规定的需求,这就称为“确认测试”,将整个软件集成为系统,安装在运行环境下,对硬件、网路、操作系统以及支撑平台构成的整体系统进行测试,称为“系统测试”。

    验证:保证软件正确实现特定功能的一系列活动和过程

    确认:保证软件满足用户需求的一系列活动或过程。

    7、 软件测试分类

     

    ⑴按照开发阶段划分

     

            单元测试

         又称为模块测试,检查各个程序单元能否正确的实现详细设计说明中的模块功能、性能、接口和设计约束等要求,发现各个模块可能存在的各种错误  

        集成测试

          又称为组装测试,在单元测试的基础上,将所有的程序模块进行有序的、递增的测试。集成测试是检验程序单元或部件的接口关系,逐步集成为符合概要设计要求的程序部件或系统

        确认测试

          通过检查和提供数据,证实软件是否满足特定预期用途的需求。确认测试时候检测和证实软件是否满足软件需求说明规格的需求

        系统测试

         对集成的硬件和软件进行测试。

        验收测试

    按照项目书或合同,供需双方约定的验收依据文档进行的对整个系统的测试和评审

     

    ⑵按照测试实施组织划分

     

        开发方测试

    通常也叫验收测试或a”测试,开发方在开发环境下,通过测试和提供数据,证实软件的实现是否满足规定的需求,是否满足软件设计说明或软件需求说明的需求。可以和软件的系统测试一同进行。

        用户测试

    在用户的应用环境下,用户检测与核实软件实现是否符合自己预期的要求。B测试通常被认为是用户测试,把软件有计划地免费地分发到目标市场,让用户大量使用、评价检查软件

     

        第三方测试

       由第三方测试机构来进行的测试,也称独立测试

     

    ⑶按照测试技术划分

     

    按照测试技术划分为:白盒测试、黑盒测试、灰盒测试。也可以分为静态测试和动态测试

    静态测试:不运行程序,通过人工对程序和文档进行分析和检查,静态测试实际上是对软件中的需求说明书、设计说明书、程序代码进行非运行的检查。静态测试包括走查、符号执行以及需求确认等。

    动态测试:通过人工或工具运行程序进行检查、分析程序的运行状态以及外部表现

        白盒测试

         了解程序结构和处理过程,检查是否所有的结构和路径都是正确的,检查程序的内部动作是否按照设计说明的规定正常进行

         黑盒测试

    完全不考虑程序的结构和处理过程,在程序界面进行测试,它只是检查是否按照需求规格说明书的规定正确实现

        灰盒测试

       灰盒测试只是关注输出对于输入的正确性,同时也关注内部表现

    单元测试可以应用白盒测试方法

    集成测试应用灰盒测试方法

    系统测试以及确认测试可以应用黑盒测试方法

  • 《彻底搞定C指针》函数名与函数指针

    2008-04-18 18:46:00

    一 通常的函数调用
        一个通常的函数调用的例子:
    //自行包含头文件
    void MyFun(int x);    //此处的申明也可写成:void MyFun( int );

    int main(int argc, char* argv[])
    {
       MyFun(10);     //这里是调用MyFun(10);函数

          return 0;
    }

    void MyFun(int x)  //这里定义一个MyFun函数
    {
       printf(“%d\n”,x);
    }
        这个MyFun函数是一个无返回值的函数,它并不完成什么事情。这种调用函数的格式你应该是很熟悉的吧!看主函数中调用MyFun函数的书写格式:
    MyFun(10);
        我们一开始只是从功能上或者说从数学意义上理解MyFun这个函数,知道MyFun函数名代表的是一个功能(或是说一段代码)。
        直到——
        学习到函数指针概念时。我才不得不在思考:函数名到底又是什么东西呢?
        (不要以为这是没有什么意义的事噢!呵呵,继续往下看你就知道了。)

    二 函数指针变量的申明
        就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。
        在C系列语言中,任何一个变量,总是要先申明,之后才能使用的。那么,函数指针变量也应该要先申明吧?那又是如何来申明呢?以上面的例子为例,我来申明一个可以指向MyFun函数的函数指针变量FunP。下面就是申明FunP变量的方法:
    void (*FunP)(int) ;   //也可写成void (*FunP)(int x);
        你看,整个函数指针变量的申明格式如同函数MyFun的申明处一样,只不过——我们把MyFun改成(*FunP)而已,这样就有了一个能指向MyFun函数的指针FunP了。(当然,这个FunP指针变量也可以指向所有其它具有相同参数及返回值的函数了。)

    三 通过函数指针变量调用函数
        有了FunP指针变量后,我们就可以对它赋值指向MyFun,然后通过FunP来调用MyFun函数了。看我如何通过FunP指针变量来调用MyFun函数的:
    //自行包含头文件
    void MyFun(int x);    //这个申明也可写成:void MyFun( int );
    void (*FunP)(int );   //也可申明成void(*FunP)(int x),但习惯上一般不这样。

    int main(int argc, char* argv[])
    {
       MyFun(10);     //这是直接调用MyFun函数
       FunP=&MyFun;  //将MyFun函数的地址赋给FunP变量
       (*FunP)(20);    //这是通过函数指针变量FunP来调用MyFun函数的。
    }

    void MyFun(int x)  //这里定义一个MyFun函数
    {
       printf(“%d\n”,x);
    }
        请看黑体字部分的代码及注释。
        运行看看。嗯,不错,程序运行得很好。
        哦,我的感觉是:MyFun与FunP的类型关系类似于int 与int *的关系。函数MyFun好像是一个如int的变量(或常量),而FunP则像一个如int *一样的指针变量。
    int i,*pi;
    pi=&i;    //与FunP=&MyFun比较。
        (你的感觉呢?)
        呵呵,其实不然——

    四 调用函数的其它书写格式
    函数指针也可如下使用,来完成同样的事情:
    //自行包含头文件
    void MyFun(int x);    
    void (*FunP)(int );    //申明一个用以指向同样参数,返回值函数的指针变量。

    int main(int argc, char* argv[])
    {
       MyFun(10);     //这里是调用MyFun(10);函数
       FunP=MyFun;  //将MyFun函数的地址赋给FunP变量
       FunP(20);    //这是通过函数指针变量来调用MyFun函数的。

          return 0;
    }

    void MyFun(int x)  //这里定义一个MyFun函数
    {
       printf(“%d\n”,x);
    }
        我改了黑体字部分(请自行与之前的代码比较一下)。
        运行试试,啊!一样地成功。
       咦?
    FunP=MyFun;
        可以这样将MyFun值同赋值给FunP,难道MyFun与FunP是同一数据类型(即如同的int 与int的关系),而不是如同int 与int*的关系了?(有没有一点点的糊涂了?)
        看来与之前的代码有点矛盾了,是吧!所以我说嘛!
        请容许我暂不给你解释,继续看以下几种情况(这些可都是可以正确运行的代码哟!):

    代码之三:
    int main(int argc, char* argv[])
    {
       MyFun(10);     //这里是调用MyFun(10);函数
       FunP=&MyFun;  //将MyFun函数的地址赋给FunP变量
       FunP(20);    //这是通过函数指针变量来调用MyFun函数的。

          return 0;
    }
    代码之四:
    int main(int argc, char* argv[])
    {
       MyFun(10);     //这里是调用MyFun(10);函数
       FunP=MyFun;  //将MyFun函数的地址赋给FunP变量
       (*FunP)(20);    //这是通过函数指针变量来调用MyFun函数的。

          return 0;
    }
        真的是可以这样的噢!
        (哇!真是要晕倒了!)
        还有呐!看——
    int main(int argc, char* argv[])
    {
       (*MyFun)(10);     //看,函数名MyFun也可以有这样的调用格式

          return 0;
    }
        你也许第一次见到吧:函数名调用也可以是这样写的啊!(只不过我们平常没有这样书写罢了。)
        那么,这些又说明了什么呢?
        呵呵!依据以往的知识和经验来推理本篇的“新发现”,我想就连“福尔摩斯”也必定会由此分析并推断出以下的结论:
        1. 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。
        2. 但函数名调用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许MyFun(10);这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。
        3. 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。
        4. 赋值时,即可FunP=&MyFun形式,也可FunP=MyFun。
        上述代码的写法,随便你爱怎么着!
        请这样理解吧!这可是有助于你对函数指针的应用喽!
        最后——
        补充说明一点:在函数的申明处:
    void MyFun(int );    //不能写成void (*MyFun)(int )。
    void (*FunP)(int );   //不能写成void FunP(int )。
        (请看注释)这一点是要注意的。

    五 定义某一函数的指针类型:
        就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。
        我先给你一个自定义数据类型的例子。
    typedef int* PINT;    //为int* 类型定义了一个PINT的别名
    int main()
    {
      int x;
      PINT px=&x;   //与int * px=&x;是等价的。PINT类型其实就是int * 类型
      *px=10;       //px就是int*类型的变量  
      return 0;
    }
        根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。)
        下面我们来看一下函数指针类型的定义及使用:(请与上对照!)
    //自行包含头文件
    void MyFun(int x);    //此处的申明也可写成:void MyFun( int );
    typedef void (*FunType)(int );   //这样只是定义一个函数指针类型
    FunType FunP;              //然后用FunType类型来申明全局FunP变量

    int main(int argc, char* argv[])
    {
    //FunType FunP;    //函数指针变量当然也是可以是局部的 ,那就请在这里申明了。
       MyFun(10);    
       FunP=&MyFun;  
       (*FunP)(20);    

          return 0;
    }

    void MyFun(int x)  
    {
       printf(“%d\n”,x);
    }

        看黑体部分:
        首先,在void (*FunType)(int ); 前加了一个typedef 。这样只是定义一个名为FunType函数指针类型,而不是一个FunType变量。
        然后,FunType FunP;  这句就如PINT px;一样地申明一个FunP变量。
        其它相同。整个程序完成了相同的事。
        这样做法的好处是:
        有了FunType类型后,我们就可以同样地、很方便地用FunType类型来申明多个同类型的函数指针变量了。如下:
    FunType FunP2;
    FunType FunP3;
    //……

    六 函数指针作为某个函数的参数
        既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。
        给你一个实例:
        要求:我要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。
        实现:代码如下:
    //自行包含头文件
    void MyFun1(int x);  
    void MyFun2(int x);  
    void MyFun3(int x);  
    typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函数类型一至
    void CallMyFun(FunType fp,int x);

    int main(int argc, char* argv[])
    {
       CallMyFun(MyFun1,10);   //⑤. 通过CallMyFun函数分别调用三个不同的函数
       CallMyFun(MyFun2,20);  
       CallMyFun(MyFun3,30);  
    }
    void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。
    {
      fp(x);//④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的
    }
    void MyFun1(int x) // ①. 这是个有一个参数的函数,以下两个函数也相同
    {
       printf(“函数MyFun1中输出:%d\n”,x);
    }
    void MyFun2(int x)  
    {
       printf(“函数MyFun2中输出:%d\n”,x);
    }
    void MyFun3(int x)  
    {
       printf(“函数MyFun3中输出:%d\n”,x);
    }
    输出结果:
    分析:(看我写的注释。你可按我注释的①②③④⑤顺序自行分析。)

  • 彻底搞定C指针——函数参数的传递

    2008-04-18 18:22:10

    彻底搞定C指针-——第五篇:函数参数的传递

    作者:白云小飞

    一. 三道考题
    开讲之前,我先请你做三道题目。(嘿嘿,得先把你的头脑搞昏才行……唉呀,谁扔我鸡蛋?)

    1. 考题一:程序代码如下:
    void Exchg1(int x, int y)  
    {
      int tmp;
      tmp=x;
      x=y;
      y=tmp;
      printf(“x=%d,y=%d\n”,x,y)
    }
    void main()
    {
      int a=4,b=6;
      Exchg1 (a,b) ;
      printf(“a=%d,b=%d\n”,a,b)
    }
    输出的结果:
    x=____, y=____
    a=____, b=____
    问下划线的部分应是什么,请完成。

    2. 考题二:代码如下。
    Exchg2(int *px, int *py)
    {
      int tmp=*px;
      *px=*py;
       *py=tmp;
      printf(“*px=%d,*py=%d\n”,*px,*py);
    }
    main()
    {
      int a=4;
      int b=6;
          Exchg2(&a,&b);
          printf(“a=%d,b=%d\n”, a, b);
    }
    输出的结果为:
    *px=____, *py=____
    a=____, b=____
    问下划线的部分应是什么,请完成。

    3. 考题三:
    Exchg2(int &x, int &y)
    {
       int tmp=x;
       x=y;
       y=tmp;
      printf(“x=%d,y=%d\n”,x,y);
    }
    main()
    {
      int a=4;
      int b=6;
          Exchg2(a,b);
          printf(“a=%d,b=%d\n”, a, b);
    }
    输出的结果:
    x=____, y=____
    a=____, b=____
        问下划线的部分输出的应是什么,请完成。

        你不在机子上试,能作出来吗?你对你写出的答案有多大的把握?
        正确的答案,想知道吗?(呵呵,让我慢慢地告诉你吧!)
        好,废话少说,继续我们的探索之旅了。
       我们都知道:C语言中函数参数的传递有:值传递,地址传递,引用传递这三种形式。题一为值传递,题二为地址传递,题三为引用传递。不过,正是这几种参数传递的形式,曾把我给搞得晕头转向。我相信也有很多人与我有同感吧?
    下面请让我逐个地谈谈这三种传递形式。

    二. 函数参数传递方式之一:值传递

    1. 值传递的一个错误认识
        先看题一中Exchg1函数的定义:
    void Exchg1(int x, int y)   //定义中的x,y变量被称为Exchg1函数的形式参数
    {
      int tmp;
      tmp=x;
      x=y;
      y=tmp;
      printf(“x=%d,y=%d\n”,x,y)
    }
    问:你认为这个函数是在做什么呀?
    答:好像是对参数x,y的值对调吧?
        请往下看,我想利用这个函数来完成对a,b两个变量值的对调,程序如下:
    void main()
    {
      int a=4,b=6;
      Exchg1 (a,b)     //a,b变量为Exchg1函数的实际参数。
    /  printf(“a=%d,b=%d\n”,a,b)
    }
        我问:Exchg1 ()里头的  printf(“x=%d,y=%d\n”,x,y)语句会输出什么啊?
        我再问:Exchg1 ()后的  printf(“a=%d,b=%d\n”,a,b)语句输出的是什么?
        程序输出的结果是:
    x=6 , y=4  
    a=4 , b=6  //为什么不是a=6,b=4呢?

        奇怪,明明我把a,b分别代入了x,y中,并在函数里完成了两个变量值的交换,为什么a,b变量值还是没有交换(仍然是a==4,b==6,而不是a==6,b==4)?如果你也会有这个疑问,那是因为你跟本就不知实参a,b与形参x,y的关系了。

    2. 一个预备的常识
    为了说明这个问题,我先给出一个代码:
    int a=4;
    int x;
    x=a;
    x=x+3;
        看好了没,现在我问你:最终a值是多少,x值是多少?
        (怎么搞的,给我这个小儿科的问题。还不简单,不就是a==4  x==7嘛!)
        在这个代码中,你要明白一个东西:虽然a值赋给了x,但是a变量并不是x变量哦。我们对x任何的修改,都不会改变a变量。呵呵!虽然简单,并且一看就理所当然,不过可是一个很重要的认识喔。

    3. 理解值传递的形式
    看调用Exch1函数的代码:

    main()
    {
      int a=4,b=6;
      Exchg1(a,b) //这里调用了Exchg1函数        
      printf(“a=%d,b=%d”,a,b)
    }

    Exchg1(a,b)时所完成的操作代码如下所示。
    int x=a;//←
    int y=b;//←注意这里,头两行是调用函数时的隐含操作
    int tmp;
    tmp=x;
    x=y;
    y=tmp;
        请注意在调用执行Exchg1函数的操作中我人为地加上了头两句:
    int x=a;
    int y=b;
        这是调用函数时的两个隐含动作。它确实存在,现在我只不过把它显式地写了出来而已。问题一下就清晰起来啦。(看到这里,现在你认为函数里面交换操作的是a,b变量或者只是x,y变量呢?)
        原来 ,其实函数在调用时是隐含地把实参a,b 的值分别赋值给了x,y,之后在你写的Exchg1函数体内再也没有对a,b进行任何的操作了。交换的只是x,y变量。并不是a,b。当然a,b的值没有改变啦!函数只是把a,b的值通过赋值传递给了x,y,函数里头操作的只是x,y的值并不是a,b的值。这就是所谓的参数的值传递了。
        哈哈,终于明白了,正是因为它隐含了那两个的赋值操作,才让我们产生了前述的迷惑(以为a,b已经代替了x,y,对x,y的操作就是对a,b的操作了,这是一个错误的观点啊!)。

  • 《彻底搞定C指针》const int * pi/int * const pi的区别

    2008-04-18 18:08:46

     
     
    1 从const int i 说起
        你知道我们申明一个变量时象这样int i ;这个i是可能在它处重新变赋值的。如下:
    int i=0;
    //…
    i=20;//这里重新赋值了
        不过有一天我的程序可能需要这样一个变量(暂且称它变量),在申明时就赋一个初始值。之后我的程序在其它任何处都不会再去重新对它赋值。那我又应该怎么办呢?用const 。
    //**************
    const int ic =20;
    //…
    ic=40;//这样是不可以的,编译时是无法通过,因为我们不能对const 修饰的ic重新赋值的。
    //这样我们的程序就会更早更容易发现问题了。
    //**************
        有了const修饰的ic 我们不称它为变量,而称符号常量,代表着20这个数。这就是const 的作用。ic是不能在它处重新赋新值了。
        认识了const 作用之后,另外,我们还要知道格式的写法。有两种:const int ic=20;与int const ic=20;。它们是完全相同的。这一点我们是要清楚。总之,你务必要记住const 与int哪个写前都不影响语义。有了这个概念后,我们来看这两个家伙:const int * pi与int const * pi ,按你的逻辑看,它们的语义有不同吗?呵呵,你只要记住一点,int 与const 哪个放前哪个放后都是一样的,就好比const int ic;与int const ic;一样。也就是说,它们是相同的。
        好了,我们现在已经搞定一个“双包胎”的问题。那么int * const pi与前两个式子又有什么不同呢?我下面就来具体分析它们的格式与语义吧!

    2 const int * pi的语义
        我先来说说const int * pi是什么作用 (当然int const * pi也是一样的,前面我们说过,它们实际是一样的)。看下面的例子:
    //*************代码开始***************
    int i1=30;
    int i2=40;
    const int * pi=&i1;
    pi=&i2;    //4.注意这里,pi可以在任意时候重新赋值一个新内存地址
    i2=80;    //5.想想看:这里能用*pi=80;来代替吗?当然不能
    printf( “%d”, *pi ) ;  //6.输出是80
    //*************代码结束***************
    语义分析:
        看出来了没有啊,pi的值是可以被修改的。即它可以重新指向另一个地址的,但是,不能通过*pi来修改i2的值。这个规则符合我们前面所讲的逻辑吗?当然符合了!
        首先const  修饰的是整个*pi(注意,我写的是*pi而不是pi)。所以*pi是常量,是不能被赋值的(虽然pi所指的i2是变量,不是常量)。
        其次,pi前并没有用const 修饰,所以pi是指针变量,能被赋值重新指向另一内存地址的。你可能会疑问:那我又如何用const 来修饰pi呢?其实,你注意到int * const pi中const 的位置就大概可以明白了。请记住,通过格式看语义。哈哈,你可能已经看出了规律吧?那下面的一节也就没必要看下去了。不过我还得继续我的战斗!

    3 再看int * const pi
        确实,int * const pi与前面的int const * pi会很容易给混淆的。注意:前面一句的const 是写在pi前和*号后的,而不是写在*pi前的。很显然,它是修饰限定pi的。我先让你看例子:
    //*************代码开始***************
    int i1=30;
    int i2=40;
    int * const pi=&i1;
    //pi=&i2;    4.注意这里,pi不能再这样重新赋值了,即不能再指向另一个新地址。
          //所以我已经注释了它。
    i1=80;    //5.想想看:这里能用*pi=80;来代替吗?可以,这里可以通过*pi修改i1的值。
        //请自行与前面一个例子比较。
    printf( “%d”, *pi ) ;  //6.输出是80
    //***************代码结束*********************
    语义分析:
        看了这段代码,你明白了什么?有没有发现pi值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。相反,这次你可以通过*pi来修改i1的值了。与前一个例子对照一下吧!看以下的两点分析
        1). pi因为有了const 的修饰,所以只是一个指针常量:也就是说pi值是不可修改的(即pi不可以重新指向i2这个变量了)(看第4行)。
        2). 整个*pi的前面没有const 的修饰。也就是说,*pi是变量而不是常量,所以我们可以通过*pi来修改它所指内存i1的值(看5行的注释)
        总之一句话,这次的pi是一个指向int变量类型数据的指针常量。
    我最后总结两句:
        1).如果const 修饰在*pi前则不能改的是*pi(即不能类似这样:*pi=50;赋值)而不是指pi。
        2).如果const 是直接写在pi前则pi不能改(即不能类似这样:pi=&i;赋值)。
    请你务必先记住这两点,相信你一定不会再被它们给搞糊了。现在再看这两个申明语句int const *pi和int * const pi时,呵呵,你会头昏脑胀还是很轻松惬意?它们各自申明的pi分别能修改什么,不能修改什么?再问问自己,把你的理解告诉我吧,可以发帖也可以发到我的邮箱(我的邮箱yyf977@163.com)!我一定会答复的。

    3.补充三种情况。
        这里,我再补充以下三种情况。其实只要上面的语义搞清楚了,这三种情况也就已经被包含了。不过作为三种具体的形式,我还是简单提一下吧!

    情况一:int * pi指针指向const int i常量的情况
    //**********begin*****************
    const int i1=40;
    int *pi;
    pi=&i1; //这样可以吗?不行,VC下是编译错。
        //const int 类型的i1的地址是不能赋值给指向int 类型地址的指针pi的。否则pi岂不是能修改i1的值了吗!
    pi=(int* ) &i1;  // 这样可以吗?强制类型转换可是C所支持的。
          //VC下编译通过,但是仍不能通过*pi=80来修改i1的值。去试试吧!看看具体的怎样。
    //***********end***************

    情况二:const int * pi指针指向const int i1的情况
    //*********begin****************
    const int i1=40;
    const int * pi;
    pi=&i1;//两个类型相同,可以这样赋值。很显然,i1的值无论是通过pi还是i1都不能修改的。
    //*********end*****************

    情况三:用const int * const pi申明的指针
    //***********begin****************
    int i
    const int * const pi=&i;//你能想象pi能够作什么操作吗?pi值不能改,也不能通过pi修改i的值。因为不管是*pi还是pi都是const的。
    //************end****************
  • 彻底搞定C指针》第3篇--指针与数组名

    2008-04-18 17:35:21

    《彻底搞定C指针》第3篇--指针与数组名
    1. 通过数组名访问数组元素
    看下面代码
    int i,a[]={3,4,5,6,7,3,7,4,4,6};
    for (i=0;i<=9;i++)
    {
       printf ( “%d”, a[i] );
    }
    很显然,它是显示a 数组的各元素值。
    我们还可以这样访问元素,如下
    int i,a[]={3,4,5,6,7,3,7,4,4,6};
    for (i=0;i<=9;i++)
    {
       printf ( “%d”,  *(a+i) );
    }
    它的结果和作用完全一样

    2. 通过指针访问数组元素
    int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
    pa =a  ;//请注意数组名a直接赋值给指针pa
    for (i=0;i<=9;i++)
    {
       printf ( “%d”, pa[i] );
    }
    很显然,它也是显示a 数组的各元素值。
    另外与数组名一样也可如下:
    int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
    pa =a;
    for (i=0;i<=9;i++)
    {
       printf ( “%d”, *(pa+i) );
    }
    看pa=a即数组名赋值给指针,以及通过数组名、指针对元素的访问形式看,它们并没有什么区别,从这里可以看出数组名其实也就是指针。难道它们没有任何区别?有,请继续。

    3. 数组名与指针变量的区别
    请看下面的代码:
    int i,*pa,a[]={3,4,5,6,7,3,7,4,4,6};
    pa =a;
    for (i=0;i<=9;i++)
    {
       printf ( “%d”, *pa );
        pa++ ;  //注意这里,指针值被修改
    }
    可以看出,这段代码也是将数组各元素值输出。不过,你把{}中的pa改成a试试。你会发现程序编译出错,不能成功。看来指针和数组名还是不同的。其实上面的指针是指针变量,而数组名只是一个指针常量。这个代码与上面的代码不同的是,指针pa在整个循环中,其值是不断递增的,即指针值被修改了。数组名是指针常量,其值是不能修改的,因此不能类似这样操作:a++。前面4,5节中pa[i],*(pa+i)处,指针pa的值是使终没有改变。所以变量指针pa与数组名a可以互换。

    4. 申明指针常量
    再请看下面的代码:
    int i, a[]={3,4,5,6,7,3,7,4,4,6};
    int * const pa=a;//注意const的位置:不是const int * pa,
    for (i=0;i<=9;i++)
    {
       printf ( “%d”, *pa );
        pa++ ;  //注意这里,指针值被修改
    }
    这时候的代码能成功编译吗?不能。因为pa指针被定义为常量指针了。这时与数组名a已经没有不同。这更说明了数组名就是常量指针。但是…
    int * const a={3,4,5,6,7,3,7,4,4,6};//不行
    int a[]={3,4,5,6,7,3,7,4,4,6};//可以,所以初始化数组时必定要这样。
    以上都是在VC6.0上实验。
  • 彻底搞定C指针:指向指针的指针

    2008-04-18 17:18:09

     指向另一指针的指针
     

    一. 回顾指针概念:
    早在本系列第二篇中我就对指针的实质进行了阐述。今天我们又要学习一个叫做指向另一指针地址的指针。让我们先回顾一下指针的概念吧!
    当我们程序如下申明变量:
    short int i;
    char a;
    short int * pi;
    程序会在内存某地址空间上为各变量开辟空间,如下图所示。
    内存地址→6     7      8     9     10     11    12    13     14    15
    -------------------------------------------------------------------------------------
    …  |     |      |      |      |      |       |      |      |      |  
    -------------------------------------------------------------------------------------
        |short int i |char a|      |short int * pi|
    图中所示中可看出:
    i 变量在内存地址5的位置,占两个字节。
    a变量在内存地址7的位置,占一个字节。
    pi变量在内存地址9的位置,占两个字节。(注:pi 是指针,我这里指针的宽度只有两个字节,32位系统是四个字节)
    接下来如下赋值:
    i=50;
    pi=&i;
    经过上在两句的赋值,变量的内存映象如下:
    内存地址→6     7      8     9     10     11    12    13      14     15
    --------------------------------------------------------------------------------------
    …  |    50      |      |      |    6         |      |      |       |  
    --------------------------------------------------------------------------------------
        |short int i |char a|      |short int * pi|
    看到没有:短整型指针变量pi的值为6,它就是I变量的内存起始地址。所以,这时当我们对*pi进行读写操作时,其实就是对i变量的读写操作。如:
    *pi=5;   //就是等价于I=5;

    二. 指针的地址与指向另一指针地址的指针
    在上一节中,我们看到,指针变量本身与其它变量一样也是在某个内存地址中的,如pi的内存起始地址是10。同样的,我们也可能让某个指针指向这个地址。
    看下面代码:
    short int * * ppi;    //这是一个指向指针的指针,注意有两个*号
    ppi=pi
    第一句:short int * * ppi;——申明了一个指针变量ppi,这个ppi是用来存储(或称指向)一个short int * 类型指针变量的地址。
    第二句:&pi那就是取pi的地址,ppi=pi就是把pi的地址赋给了ppi。即将地址值10赋值给ppi。如下图:
    内存地址→6     7      8     9     10     11    12    13       14    15
    ------------------------------------------------------------------------------------
    …  |    50     |      |      |      6       |       10      |      |  
    ------------------------------------------------------------------------------------
        |short int i|char a|      |short int * pi|short int ** ppi|
    从图中看出,指针变量ppi的内容就是指针变量pi的起始地址。于是……
    ppi的值是多少呢?——10。
    *ppi的值是多少呢?——6,即pi的值。
    **ppi的值是多少呢?——50,即I的值,也是*pi的值。
    呵呵!不用我说太多了,我相信你应明白这种指针了吧!

    三. 一个应用实例
    1. 设计一个函数:void find1(char array[], char search, char * pi)
    要求:这个函数参数中的数组array是以0值为结束的字符串,要求在字符串array中查找字符是参数search里的字符。如果找到,函数通过第三个参数(pa)返回值为array字符串中第一个找到的字符的地址。如果没找到,则为pa为0。
    设计:依题意,实现代码如下。
    void find1(char array[] , char search, char * pa)
    {
       int i;
       for (i=0;*(array+i)!=0;i++)
       {
          if (*(array+i)==search)
          {
            pa=array+i
            break;
          }
          else if (*(array+i)==0)
          {
            pa=0;
            break;
          }
       }
    }
    你觉得这个函数能实现所要求的功能吗?
    调试:
    我下面调用这个函数试试。
    void main()
    {
      char str[]={“afsdfsdfdf\0”};  //待查找的字符串
      char a=’d’;   //设置要查找的字符
      char * p=0;  //如果查找到后指针p将指向字符串中查找到的第一个字符的地址。
      find1(str,a,p);  //调用函数以实现所要操作。
      if (0==p )
      {
         printf (“没找到!\n”);//1.如果没找到则输出此句
      }
      else
      {
         printf(“找到了,p=%d”,p);  //如果找到则输出此句
      }
    }
    分析:
    上面代码,你认为会是输出什么呢?
    运行试试。
    唉!怎么输出的是:没有找到!
    而不是:找到了,……。
    明明a值为’d’,而str字符串的第四个字符是’d’,应该找得到呀!
    再看函数定义处:void find1(char array[] , char search, char * pa)
    看调用处:find1(str,a,p);
    依我在第五篇的分析方法,函数调用时会对每一个参数进行一个隐含的赋值操作。
    整个调用如下:
       array=str;
       search=a;
       pa=p;    //请注意:以上三句是调用时隐含的动作。
       int i;
       for (i=0;*(array+i)!=0;i++)
       {
          if (*(array+i)==search)
          {
            pa=array+i
            break;
          }
          else if (*(array+i)==0)
          {
            pa=0;
            break;
          }
       }
    哦!参数pa与参数search的传递并没有什么不同,都是值传递嘛(小语:地址传递其实就是地址值传递嘛)!所以对形参变量pa值(当然值是一个地址值)的修改并不会改变实参变量p值,因此p的值并没有改变(即p的指向并没有被改变)。
    (如果还有疑问,再看一看《函数参数的传递》了。)
    修正:
    void find2(char [] array, char search, char ** ppa)
    {
       int i;
       for (i=0;*(array+i)!=0;i++)
       {
          if (*(array+i)==search)
          {
            *ppa=array+i
            break;
          }
          else if (*(array+i)==0)
          {
            *ppa=0;
            break;
          }
       }
    }
    主函数的调用处改如下:
      find2(str,a,&p);  //调用函数以实现所要操作。
    再分析:
    这样调用函数时的整个操作变成如下:
       array=str;
       search=a;
       ppa=&p;    //请注意:以上三句是调用时隐含的动作。
       int i;
       for (i=0;*(array+i)!=0;i++)
       {
          if (*(array+i)==search)
          {
            *ppa=array+i
            break;
          }
          else if (*(array+i)==0)
          {
            *ppa=0;
            break;
          }
       }
    看明白了吗?
    ppa指向指针p的地址。
    对*ppa的修改就是对p值的修改。
    下面看一下指向指针变量的指针变量怎样正确引用。
    用指向指针的指针变量访问一维和二维数组。

    #include<stdio.h>
    #include<stdlib.h>
    main()
    {
    int a[3],b[2][2],*p1,*p2,**p3,i,j;

    printf("请输入一维数组的值:\n");
    for(i=0;i<3;i++)
    scanf("%d",&a[i]);/*一维数组的输入*/

    printf("请输入二维数组的值:\n");
    for(i=0;i<2;i++)
    for(j=0;j<2;j++)
    scanf("%d",&b[i][j]);/*二维数组输入*/

    printf("用指针输出一维数组:\n");
    for(p1=a,i=0;i<3;i++)     /* 用指针输出一维数组*/
    {
        printf("%4d",*(p1+i));
    }
    printf("\n");

    printf("用指向指针的指针变量输出一维数组(1):\n");
    for(p1=a,p3=&p1,i=0;i<3;i++)
    printf("%4d",*(*p3+i));/*用指向指针的指针变量输出一维数组*/
    printf("\n");
    printf("用指向指针的指针变量输出一维数组(2):\n");
    for(p1=a;p1-a<3;p1++)/*用指向指针的指针变量输出一维数组*/
    {
    p3=&p1;
    printf("%4d",**p3);
    }
    printf("\n");

    printf("用指针输出二维数组:\n");
    for(i=0;i<2;i++)   /*用指针输出二维数组*/
    {
       p2=b[i] ;
       for(int j=0;j<2;j++)
      {
        printf("%4d",*(p2+j)) ;
      }
    }
    printf("\n");

    printf("用指向指针的指针变量输出二维数组(1):\n");
    for(i=0;i<2;i++)/*用指向指针的指针变量输出二维数组*/
    {
    p2=b[i];
    p3=&p2;
    for(j=0;j<2;j++)
    printf("%4d",*(*p3+j));

    利用指向指针的指针变量对二维字符数组的访问。

    #include<stdio.h>
    #include<stdlib.h>
    main()
    {
    int i;
    char * ptr;
    static char c[][16]={"clanguage","fox","computer","homepage"};
    /*二维字符数组*/
    static char *cp[]={c[0],c[1],c[2],c[3]};/*指针数组*/
    static char **cpp;/*指向字符指针的指针变量*/
    cpp=cp;/*将指针数组的首地址传递给指向字符指针的指针变量*/


    for(i=0;i<4;i++)/*按行输出字符串*/
    printf("%s\n",*cpp++);
    printf("-----------\n");

    for(i=0;i<4;i++)/*按行输出字符串*/
    {
    cpp=&cp[i];
    printf("%s\n",*cpp);
    }
    printf("-----------\n");

     
     for(i=0;i<4;i++)
     {
        ptr=c[i];
        printf("%s",ptr);
        printf("\n");
     }
     

    }

    }
    printf("\n");

     printf("用指向指针的指针变量输出二维数组(2):\n");
    for(i=0;i<2;i++)/*用指向指针的指针变量输出二维数组*/
    {
    p2=b[i];
    for(p2=b[i];p2-b[i]<2;p2++)
    {
    p3=&p2;
    printf("%4d",**p3);
    }
    printf("\n");
    }

    }


  • 《彻底搞定C指针》第2篇----指针是什么

    2008-04-18 16:23:26

    转《彻底搞定C指针》第2篇----指针是什么
    二.指针是什么东西
    指针,想说弄懂你不容易啊!我们许多初学指针的人都要这样的感慨。我常常在思索它,为什么呢?其实生活中处处都有指针。我们也处处在使用它。有了它我们的生活才更加方便了。没有指针,那生活才不方便。不信?你看下面的例子。
    这是一个生活中的例子:比如说你要我借给你一本书,我到了你宿舍,但是你人不在宿舍,于是我把书放在你的2层3号的书架上,并写了一张纸条放在你的桌上。纸条上写着:你要的书在第2层3号的书架上。当你回来时,看到这张纸条。你就知道了我借与你的书放在哪了。你想想看,这张纸条的作用,纸条本身不是书,它上面也没有放着书。那么你又如何知道书的位置呢?因为纸条上写着书的位置嘛!其实这张纸条就是一个指针了。它上面的内容不是书本身,而是书的地址,你通过纸条这个指针找到了我借给你的本书。
    那么我们C,C++中的指针又是什么呢?请继续跟我来吧,看下面看一个申明一整型指针变量的语句如下:
    int * pi;
    pi是一个指针,当然我们知道啦,但是这样说,你就以为pi一定是个多么特别的东西了。其实,它也只过是一个变量而已。与上一篇中说的变量并没有实质的区别。不信你看下面图。
    内存地址→6     7       8      9     10     11      12     13     14
    --------------------------------------------------------------
    ···|    30      |  ‘t’ |      |      |      |      |      |      |...
    --------------------------------------------------------------
    变量 |→i       ←|→a   ←|             |→ pi      ←|
    (说明:这里我假设了指针只占2个字节宽度,实际上在32位系统中,指针的宽度是4个字节宽的,即32位。)由图示中可以看出,我们使用int *Pi申明指针变量; 其实是在内存的某处申明一个一定宽度的内存空间,并把它命名为Pi。你能在图中看出pi与前面的i,a 变量有什么本质区别吗,没有,当然没有!pi也只不过是一个变量而已嘛!那么它又为什么会被称为指针?关键是我们要让这个变量所存储的内容是什么。现在我要让pi成为真正有意义上的指针。请接着看下面语句:
    pi=&i;
    你应该知道 &i是什么意思吧!再次提醒你啦:这是返回i变量的地址编号。整句的意思就是把i地址的编号赋值给pi,也就是你在pi上写上i的地址编号。结果如下图所示:
    内存地址→6     7       8       9       10     11    12     13     14
    ------------------------------------------------------------------
    ···|     30      |  ‘t’  |      |      |     6      |      |      |...
    ------------------------------------------------------------------
    变量 |→i        ←|→a    ←|             |→ pi     ←|
    你看,执行完pi=&i;后,在图示中的系统中,pi的值是6。这个6就是i变量的地址编号,这样pi就指向了变量i了。你看,pi与那张纸条有什么区别?pi不就是那张纸条嘛!上面写着i的地址,而i就是那个本书。你现在看懂了吗?因此,我们就把pi称为指针。所以你要记住,指针变量所存的内容就是内存的地址编号!好了,现在我们就可以通过这个指针pi来访问到i这个变量了,不是吗?。看下面语句:
    printf(“%d”,*pi);
    那么*pi什么意思呢?你只要这样读它:pi内容所指的地址的内容(嘻嘻,看上去好像在绕口令了),就pi这张“纸条”上所写的位置上的那本 “书”---i 。你看,Pi内容是6,也就是说pi指向内存编号为6的地址。*pi嘛!就是它所指地址的内容,即地址编号6上的内容了。当然就是30的值了。所以这条语句会在屏幕上显示30。也就是说printf(“%d”,*pi);语句等价于printf( “%d”, i ) ,请结合上图好好体会吧!到此为止,你掌握了类似&i , *pi写法的含义和相关操作吗。总的一句话,我们的纸条就是我们的指针,同样我们的pi也就是我们的纸条!剩下的就是我们如何应用这张纸条了。最后我给你一道题:程序如下。
    char  a,*pa;
    a=10;
    pa=&a;
    *pa=20;
    printf( “%d”, a);
    你能直接看出输出的结果是什么吗?如果你能,我想本篇的目的就达到了。好了,就说到这了。Happy to Study!
  • 彻底搞定C指针》第一篇 变量的内存实质

    2008-04-18 16:17:48

    转帖]《彻底搞定C指针》第一篇 变量的内存实质

    一.先来理解C语言中变量的实质
        要理解C指针,我认为一定要理解C中“变量”的存储实质,所以我就从“变量”这个东西开始讲起吧!
        先来理解理解内存空间吧!请看下图:

    内存地址→  6      7       8      9       10      11      12      13
    -----------------------------------------------------------------
    ··· |       |       |       |       |       |       |       |··
    -----------------------------------------------------------------

        如图所示,内存只不过是一个存放数据的空间,就好像我的看电影时的电影院中的座位一样。每个座位都要编号,我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧!所以内存也要象座位一样进行编号了,这就是我们所说的内存编址。座位可以是按一个座位一个号码的从一号开始编号,内存则是按一个字节一个字节进行编址,如上图所示。每个字节都有个编号,我们称之为内存地址。好了,我说了这么多,现在你能理解内存空间这个概念吗?
        我们继续看看以下的C、C++语言变量申明:
    int i;
    char a;
        每次我们要使用某变量时都要事先这样申明它,它其实是内存中申请了一个名为i的整型变量宽度的空间(DOS下的16位编程中其宽度为二个字节),和一个名为a的字符型变量宽度的空间(占一个字节)。
        我们又如何来理解变量是如何存在的呢。当我们如下申明变量时:
    int i;
    char a;
        内存中的映象可能如下图:

    内存地址→   6      7       8      9      10      11    12      13
    ------------------------------------------------------------------
    ···|       |       |       |       |       |        |       |··
    ------------------------------------------------------------------  
    变量名|→i          ←|→a  ←|

        图中可看出,i在内存起始地址为6上申请了两个字节的空间(我这里假设了int的宽度为16位,不同系统中int的宽度是可能不一样的),并命名为i。 a在内存地址为8上申请了一字节的空间,并命名为a。这样我们就有两个不同类型的变量了。

    2.赋值给变量
        再看下面赋值:
    i=30
    a=’t’
        你当然知道个两个语句是将30存入i变量的内存空间中,将’t’字符存入a变量的内存空间中。我们可以这样的形象理解啦:

    内存地址→   6      7       8      9      10      11    12      13
    -----------------------------------------------------------------------
    ··· |       30      |  ‘t’  |       |       |       |       |··
    -----------------------------------------------------------------------
           |→i          ←|→a  ←|

    3.变量在哪里?(即我想知道变量的地址)
        好了,接下来我们来看看&i是什么意思?
        是取i变量所在的地址编号嘛!我们可以这样读它:返回i变量的地址编号。你记住了吗?
        我要在屏幕上显示变量的地址值的话,可以写如下代码:
    printf(“%d”,&i);
        以上图的内存映象所例,屏幕上显示的不是i值30,而是显示i的内存地址编号6了。当然实际你操作的时,i变量的地址值不会是这个数了。
        这就是我认为作为初学者们所应想象的变量存储实质了。请这样理解吧!
        最后总结代码如下:
    int main()
    {
          int i=39;
          printf(“%d\n”,i);   //①
          printf(“%d\n”,&i);  //②
        }
        现在你可知道①、②两个printf分别在屏幕上输出的是i的什么东西啊?
        好啦!下面我们就开始真正进入指针的学习了。Come on !(待续...)

  • c语言指针数组

    2008-04-17 16:25:32

    我们可以让指针指向某类变量,并替代该变量在程序中使用;我们也可以让指针指向一维、二维数组或字符数组,来替代这些数组在程序中使用,给我们在编程时带来许多方便。
    下面我们定义一种特殊的数组,这类数组存放的全部是指针,分别用于指向某类的变量,以替代这些变量在程序中的使用,增加灵活性。指针数组定义形式:
    类型标识*数组名[数组长度]
    例如: char *str[4];
    由于[ ] 比*优先权高,所以首先是数组形式str[4 ],然后才是与“*”的结合。这样一来指针数组包含4个指针s t r [ 0 ]、s t r [ 1 ]、s t r [ 2 ]、s t r [ 3 ],各自指向字符类型的变量。例如: int *p t r [ 5 ] ;
    该指针数组包含5个指针p t r [ 0 ]、p t r [ 1 ]、p t r [ 2 ]、p t r [ 3 ]、p t r [ 4 ],各自指向整型类型的变量。
    [例6-22] 针对指针数组的应用,我们分别用指针数组的各指针指向字符串数组、指向一维整型数组、指向二维整型数组。
    #include <stdlib.h>
    #include <stdio.h>
    m a i n ( )
    {
    char *ptr1[4]={"china","chengdu","sichuang","chongqin"};
    /* 指针数组p t r 1 的4个指针分别依此指向4个字符串* /
    int i,*ptr2[3],a[3]={1,2,3},b[3][2]={1,2,3,4,5,6};
    for(i=0;i<4;i++)
    printf("\n%s",ptr1[i]);/依*此输出ptr1数组4个指针指向的4个字符串*/
    printf("\n");
    for(i=0;i<3;i++)
    ptr2[i]=&a[i];/*将整型一维数组a的3个元素的地址传递给指针数组ptr2*/
    for(i=0;i<3;i++)/*依此输出ptr2所指向的3个整型变量的值*/
    printf("%4d",*ptr2[i]);
    printf("\n");
    for(i=0;i<3;i++)
    ptr2[i]=b[i];/*传递二维数组b的每行首地址给指针数组的4个指针*/
    for(i=0;i<3;i++)/*按行输出*/
    printf("%4d%4d\n",*ptr2[i],*ptr2[i]+1);
    }
    程序中指针数组与所指对象的关系如图6-12所示。


     
    ptr1指针数组中的4个指针分别指向4个字符串,如图6-11的a)所示,程序中依此输出;ptr2指针数组共有3个指针,若将整型一维数组a中各元素地址分别传递给指针数组的各指针,则ptr2[0]就指向a[0];ptr2[1]就指向a[1];ptr2[2]就指向a[2]。若将二维数组各行的首地址分别传递给指针数组的各指针,如图6-11b)所示,这样一来,ptr2[0]就指向了b数组的第0行,该行有两个元素,其地址为ptr2[0]与ptr2[0]+1;相应指针数组第i个元素ptr2[i]指向的b数组的第i行两个元素地址分别为ptr2[i]与ptr[i]+1。
     
    在处理二维字符数组时,我们可以把二维字符数组看成是由多个一维字符数组构成,也就是说看成是多个字符串构成的二维字符数组,或称为字符串数组。
    指针数组对于解决这类问题(当然也可以解决其它问题)提供了更加灵活方便的操作。
    有一点需要说明,若定义一个指针数组后,指针数组各元素的取值(即地址)要注意安全性。
    如定义指针数组:
    char*ptr[3];
    我们说该数组包含三个指针,但指针的指向是不确定的,指针现在可能指向内存的任一地址。假定现在作语句:scanf("%s",ptr[i]),则输入的字符串在内存的存放其地址由ptr[i]决定。除非给指针数组元素赋值安全的地址。
    [例6-23]定义字符指针数组,包含5个数组元素。同时再定义一个二维字符数组其数组大小为5*10,即5行10列,可存放5个字符串。若将各字符串的首地址传递给指针数组各元素,那么指针数组就成为名副其实的字符串数组。下面对各字符串进行按字典排序。
    在字符串的处理函数中,strcmp(str1,str2)函数就可以对两个字符串进行比较,函数的返回值>0、=0、<0分别表示串str1大于str2、str1等于str2、str1小于str2。再利用strcpy()函数实现两个串的复制。下面选用冒泡排序法。
    #include<stdlib.h>
    #include<string.h>
    #include<stdio.h>
    main()
    {
    char*ptr1[4],str[4][20],temp[20];
    /*定义指针数组、二维字符数组、用于交换的一维字符数组*/
    int i,j;
    for(i=0;i<4;i++)
    gets(str[i]);/*输入4个字符串*/
    printf("\n");
    for(i=0;i<4;i++)
    ptr1[i]=str[i];/*将二维字符数组各行的首地址传递给指针数组的各指针*/
    printf("original string:\n");
    for(i=0;i<4;i++)/*按行输出原始各字符串*/
    printf("%s\n",ptr1[i]);
    printf("ordinal string:\n");
    for(i=0;i<3;i++)/*冒泡排序*/
    for(j=0;j<4-i-1;j++)
    if(strcmp(ptr1[j],ptr1[j+1])>0)
    {strcpy(temp,ptr1[j]);
    strcpy(ptr1[j],ptr1[j+1]);
    strcpy(ptr1[j+1],temp);
    }
    for(i=0;i<4;i++)/*输出排序后的字符串*/
    printf("%s\n",ptr1[i]);
    }


     
    程序中一定要注意指针的正确使用。一旦将二维字符数组的各行首地址传递给指针数组的各指针,则相当于给指针分配了安全可操作的地址,地址空间大小由二维字符数组来决定。
    当然也可由编译系统为指针分配地址用于字符串的存放。
    [例6-24]利用malloc()函数为指针分配存储空间,实现字符串的排序。
    #include<stdlib.h>
    #include<string.h>
    #include<stdio.h>
    main()
    {
    char *ptr1[4],*temp;
    inti,j;
    for(i=0;i<4;i++)
    {
    ptr1[i]=malloc(20);/*为指针数组各指针分配20字节的存储空间*/
    gets(ptr1[i]);
    }
    printf("\n");
    printf("original string:\n");
    for(i=0;i<4;i++)
    printf("%s\n",ptr1[i]);
    printf("ordinal string:\n");
    for(i=0;i<3;i++)
    for(j=0;j<4-i-1;j++)
    if(strcmp(ptr1[j],ptr1[j+1])>0)
    {
    temp=ptr1[j];/*利用指向字符串的指针,进行指针地址的交换*/
    ptr1[j]=ptr1[j+1];
    ptr1[j+1]=temp;
    }
    for(i=0;i<4;i++)/*字符串输出*/
    printf("%s\n",ptr1[i]);
    }
    运行程序,其结果与上述例6-23完全相同


    [例6-25]对已排好序的字符指针数组进行指定字符串的查找。字符串按字典顺序排列,查找算法采用二分法,或称为折半查找。
    折半查找算法描述:
    1.设按开序(或降序)输入n个字符串到一个指针数组。
    2.设low指向指针数组的低端,high指向指针数组的高端,mid=(low+high)/2
    3.测试mid所指的字符串,是否为要找的字符串。
    4.若按字典顺序,mid所指的字符串大于要查找的串,表示被查字符串在low和mid之间,否则,表示被查字符串在mid和high之间。
    5.修改low式high的值,重新计算mid,继续寻找。
    #include<stdlib.h>
    #include<alloc.h>
    #include<string.h>
    #include<stdio.h>
    main()
    {
    char*binary();/*函数声明*/
    char*ptr1[5],*temp;
    inti,j;
    for(i=0;i<5;i++)
    {
    ptr1[i]=malloc(20);/*按字典顺序输入字符串*/
    gets(ptr1[i]);
    }
    printf("\n");
    printf("original string:\n");
    for(i=0;i<5;i++)
    printf("%s\n",ptr1[i]);
    printf("input search string:\n");
    temp=malloc(20);
    gets(temp);/输*入被查找字符串*/
    i=5;
    temp=binary(ptr1,temp,i);/*调用查找函数*/
    if(temp)printf("succesful-----%s\n",temp);
    elseprintf("nosuccesful!\n");
    return;
    }
    char *binary(char *ptr[],char *str,int n)定义返回字符指针的函数*/
    {/*折半查找*/
    int hig,low,mid;
    low=0;
    hig=n-1;
    while(low<=hig)
    {
    mid=(low+hig)/2;
    if(strcmp(str,ptr[mid])<0)
    hig=mid-1;
    elseif(strcmp(str,ptr[mid])>0)
    low=mid+1;
    else return(str);/*查帐成功,返回被查字符串*/
    }
    return NULL; / *查找失败,返回空指针* /
    }

    [例6-26] 在一个已排好序的字符串数组中,插入一个键盘输入的字符串,使其继续保持有序。
    在上述程序查找成功的基础上,我们将该字符串插入到字符数组中。插入的位置可以是数组头、中间或数组尾。查找的算法采用折半算法,找到插入位置后,将字符串插入。
    #include <stdlib.h>
    #include <alloc.h>
    #include <string.h>
    #include <stdio.h>
    m a i n ( )
    {
    int binary(); / *查找函数声明* /
    void insert(); / *插入函数声明* /
    char *temp,*ptr1[6];
    int i,j;
    for (i=0;i<5;i++)
    {

    ptr1[i]=malloc(20);/*为指针分配地址后*/
    gets(ptr1[i]);/*输入字符串*/
    }
    ptr1[5]=malloc(20);
    printf("\n");
    printf("original string:\n");
    for(i=0;i<5;i++)/*输出指针数组各字符串*/
    printf("%s\n",ptr1[i]);
    printf("input search string:\n");
    temp=malloc(20);
    gets(temp);/*输入被插字符串*/
    i=binary(ptr1,temp,5)/*;寻找插入位置i*/
    printf("i=%d\n",i);
    insert(ptr1,temp,5,i);/*在插入位置i处插入字符串*/
    printf("outputstrings:\n");
    for(i=0;i<6;i++)/*输出指针数组的全部字符串*/
    printf("%s\n",ptr1[i]);
    return;
    }
    int binary(char*ptr[],char*str,intn)
    {/*折半查找插入位置*/
    int hig,low,mid;
    low=0;
    hig=n-1;
    if(strcmp(str,ptr[0])<0)return0;
    /*若插入字符串比字符串数组的第0个小,则插入位置为0*/
    if(strcmp(str,ptr[hig])>0)returnn;
    /*若插入字符串比字符串数组的最后一个大,则应插入字符串数组的尾部*/
    while(low<=hig)
    {
    mid=(low+hig)/2;
    if(strcmp(str,ptr[mid])<0)
    hig=mid-1;
    else if(strcmp(str,ptr[mid])>0)
    low=mid+1;
    else return(mid);/*插入字符串与字符串数组的某个字符串相同*/
    }
    return low;/*插入的位置在字符串数组中间*/
    }
    void insert(char*ptr[],char*str,intn,inti)
    {
    int j;
    for(j=n;j>i;j--)/*将插入位置之后的字符串后移*/
    strcpy(ptr[j],ptr[j-1]);
    strcpy(ptr[i],str);将被插字符串按字典顺序插入字符串数组*/
    }
    在程序中,字符串数组的6个指针均分配存放20字节的有效地址。语句ptr1[5]=malloc(20)保证插入字符串后,也具有安全的存储空间,字符串的长度以串中最长的为基准向系统申请存储空间,以保证在串的移动中有足够的存储空间。

     

  • 函数指针和指针函数以及函数指针数组

    2008-04-17 13:49:33

    1、函数指针:

    指针函数是指带指针的函数,即本质是一个函数。我们知道函数都又返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。其定义格式如下所示:

    返回类型标识符 *返回名称(形式参数表)
    { 函数体 }

    返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。例如下面一个返回指针函数的例子:
    float *find(float(*pionter)[4],int n)/*定义指针函数*/
    {
        float *pt;
        pt=*(pionter+n);
        return(pt);
    }

    main()
    {
        static float score[][4]={{60,70,80,90},{56,89,34,45},{34,23,56,45}};
        float *p;
        int i,m;
        printf("Enter the number to be found:");
        scanf("%d",&m);
        printf("the score of NO.%d are:\n",m);
        p=find(score,m);
        for(i=0;i<4;i++)
            printf("%5.2f\t",*(p+i));
    }


    学生学号从0号算起,函数find()被定义为指针函数,起形参pointer是指针指向包含4个元素的一维数组的指针变量。pointer+1指向score的第一行。*(pointer+1)指向第一行的第0个元素。pt是一个指针变量,它指向浮点型变量。main()函数中调用find()函数,将score数组的首地址传给pointer.

     将字符串1(str1)连接字符串2(str2),并输出字符串1.

    #include "stdio.h"
     char * mystrcpy(char * str1,char * str2)
    {
        char * p;
        p=str1;
        while(*str1)
        str1++;
        while(*str1++=*str2++);
        return p;
    }
    int main(void)
    {
      char str1[]="I LOVE SHY";
      char str2[]="   chj!";
      char *p;
      p=mystrcpy(str1,str2);
      printf("%s\n",p);
    }
    例3:

    int * GetDate(int wk,int dy)
            {
                static int calendar[5][7]=
                {
                   {1,2,3,4,5,6,7},
                   {8,9,10,11,12,13,14},
                   {15,16,17,18,19,20,21},
                   {22,23,24,25,26,27,28},
                   {29,30,31,-1,0}
                };
                return (&calendar[wk-1][dy-1]);
            }

       int  main(void)
            {
                int wk,dy;
                do
                {
                    printf("Enter week(1-5)day(1-7)\n");
                    scanf("%d %d",&wk,&dy);
                }
                while(wk<1||wk>5||dy<1||dy>7);
                printf("%d",*GetDate(wk,dy));
            }

    2,函数指针:

    “函数指针”是指向函数的指针变量,因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上一致的。函数指针有两个用途:调用函数和做函数的参数。函数指针的说明方法为:

    函数类型 (*指针变量名)(形参列表);

    “函数类型”说明函数的返回类型,由于“()”的优先级高于“*”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。

    例如:

    int (*f)(int x);

    double (*ptr)(double x);

    在定义函数指针时请注意:
       
    函数指针和它指向的函数的参数个数和类型都应该是—致的;

    函数指针的类型和函数的返回值类型也必须是一致的。

    函数指针的赋值

    函数名和数组名一样代表了函数代码的首地址,因此在赋值时,直接将函数指针指向函数名就行了。

    例如,

    int func(int x);   /* 声明一个函数 */

    int (*f) (int x);    /* 声明一个函数指针 */

    f=func;            /* 将func函数的首地址赋给指针f */

    赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。

    与其他指针变量相类似,如果指针变量pi是指向某整型变量i的指针,则*p等于它所指的变量i;如果pf是指向某浮点型变量f的指针,则*pf就等价于它所指的变量f。同样地,*f是指向函数func(x)的指针,则*f就代表它所指向的函数func。所以在执行了f=func;之后,(*f)和func代表同一函数。

    由于函数指针指向存储区中的某个函数,因此可以通过函数指针调用相应的函数。现在我们就讨论如何用函数指针调用函数,它应执行下面三步:

    首先,要说明函数指针变量。

    例如:int (*f)(int x);

    其次,要对函数指针变量赋值。

    例如: f=func;    (func(x)必须先要有定义)

    最后,要用 (*指针变量)(参数表);调用函数。

    例如:    (*f)(x);(x必须先赋值)

    例1:
     int max(int x,int y)
    { return(x>y?x:y);
    }


    int  main(void)
    {

        int a,b,c;
         int (*ptr)(int,int);
        scanf("%d,%d",&a,&b);
         ptr=max;
        c=(*ptr)(a,b);
        printf("a=%d,b=%d,max=%d",a,b,c);
        return 0;
    }

    例2:

           void FileFunc()
            {
                printf("FileFunc\n");
            }

            void EditFunc()
            {
                printf("EditFunc\n");
            }

            int main(void)
            {
                void (*funcp)();
                funcp=FileFunc;
                (*funcp)();
                funcp=EditFunc;
                (*funcp)();
            }

    例3:


    ptr是指向函数的指针变量,所以可把函数max()赋给ptr作为ptr的值,即把max()的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址,不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你像怎么做了。在程序中把哪个函数的地址赋给它,它就指向哪个函数。而后用指针变量调用它,因此可以先后指向不同的函数,不过注意,指向函数的指针变量没有++和--运算,用时要小心。

    3、函数指针数组

    关于函数指针数组的定义


    关于函数指针数组的定义方法,有两种:一种是标准的方法;一种是蒙骗法。

    第一种,标准方法:

    {
    分析:函数指针数组是一个其元素是函数指针的数组。那么也就是说,此数据结构是是一个数组,且其元素是一个指向函数入口地址的指针。
    根据分析:首先说明是一个数组:数组名[]
    其次,要说明其元素的数据类型指针:*数组名[].
    再次,要明确这每一个数组元素是指向函数入口地址的指针:函数返回值类型 (*数组名[])().请注意,这里为什么要把“*数组名[]”用括号扩起来呢?因为圆括号和数组说明符的优先级是等同的,如果不用圆括号把指针数组说明表达式扩起来,根据圆括号和方括号的结合方向,那么 *数组名[]() 说明的是什么呢?是元素返回值类型为指针的函数数组。有这样的函数数祖吗?不知道。所以必须括起来,以保证数组的每一个元素是指针。

    }

    第二种,蒙骗法:

    尽管函数不是变量,但它在内存中仍有其物理地址,该地址能够赋给指针变量。获取函数方法是:用不带有括号和参数的函数名得到。
    函数名相当于一个指向其函数入口指针常量。 那么既然函数名是一个指针常量,那么就可以对其进行一些相应的处理,如强制类型转换。
    那么我们就可以把这个地址放在一个整形指针数组中,然后作为函数指针调用即可。


    完整例子:
    #include "stdio.h"
    int add1(int a1,int b1);

    int add2(int a2,int b2);

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

    {

    int numa1=1,numb1=2;

    int numa2=2,numb2=3;

    int (*op[2])(int a,int b);

    op[0]=add1;

    op[1]=add2;

    printf("%d %d\n",op[0](numa1,numb1),op[1](numa2,numb2));

    getch();

    }

    int add1(int a1,int b1)

    {

    return a1+b1;

    }

    int add2(int a2,int b2)

    {

    return a2+b2;

    }


    再给出常用的C变量的定义方式:
    a) 一个整型数(An integer)
    b) 一个指向整型数的指针(A pointer to an integer)
    c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
    d) 一个有10个整型数的数组(An array of 10 integers)
    e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)
    f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)
    g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
    h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an

    integer )

    答案是:
    a) int a; // An integer
    b) int *a; // A pointer to an integer
    c) int **a; // A pointer to a pointer to an integer
    d) int a[10]; // An array of 10 integers
    e) int *a[10]; // An array of 10 pointers to integers
    f) int (*a)[10]; // A pointer to an array of 10 integers
    g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
    h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

  • c语言字符串函数源码

    2008-04-16 17:02:17

    2、strcmp函数:

    int mystrcmp(const char *str1,const char *str2)
    {
    while((*str1==*str2)&&(*str1))
    {
    str1++;
    str2++;
    }
    if((*str1==*str2)&&(!*str1)) //Same strings
    return 0;
    else if((*str1)&&(!*str2))  //Same but str1 longer
    return 1;
    else if ((!*str1)&&(*str2))
    return -1 ;
    else
    return ((*str1>*str2)?1:-1);

    }

    3、strcat函数:

    char *mystrcat(char *target,const char *source)
    {
    char *original=target;
    while(*target) target++;     // Find the end of the string
    while(*target++=*source++);
    return(original);
    }


    int main(void)
    {

      char destination[25];
      char * cc;                                                            
    char *blank = " ", *c = "C++", *Borland = "Borland";
    strcpy(destination, Borland);
    mystrcat(destination, blank);
    cc=mystrcat(destination, c);
    printf("%s\n", cc);

    }
    4、strcpy函数

    char *mystrcpy(char *destination, const char *source)
    {
    while(*destination++=*source++);            
    return (destination);
    }

    5、strlen函数
    int mystrlen(const char *string)
    {
    int i=0;
    while(*string++)
     i++;
     return i;
    }
    5、streql函数
     int mystreql(char *str1,char *str2)
    {
    while((*str1==*str2)&&(*str1))
    {
    str1++;
    str2++;
    }
    return((*str1==NULL)&&(*str2==NULL));
    }
    6、strchr函数
    char * mystrchr(const char *string,char letter)
    {
    while((*string!=letter)&&(*string))
    string++;
    return (string);
    }

    int main(void)
    {
    char string[15];
    char *ptr, c = 'r';
    strcpy(string, "This is a string");
    ptr = mystrchr(string, c);

    if (ptr)
    printf("The character %c is at position: %d\n", c,ptr-string);
    else
    printf("The character was not found\n");
    return 0;
    }
    7、ctrcnt函数
    int mychrcnt(const char *string,char letter) /*指定计算字符串内的指定字符的个数*/
    {
    int count=0;

    while(*string)
    {
        if(*string==letter)
        count++;
        string++;
    }
    return count;
    }

  • 黑盒测试-错误推测法

    2008-04-15 16:06:00

    一.    方法简介
    1.         定义:基于经验和直觉推测程序中所有可能存在的各种错误, 从而有针对性的设计测试用例的方法。
    2.         错误推测方法的基本思想:
    列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据他们选择测试用例。
    1)        例如, 输入数据和输出数据为0的情况;输入表格为空格或输入表格只有一行。 这些都是容易发生错误的情况。可选择这些情况下的例子作为测试用例。
    2)        例如,前面例子中成绩报告的程序,采用错误推测法还可补充设计一些测试用例:
    I.          程序是否把空格作为回答
    II.       在回答记录中混有标准答案记录
    III.     除了标题记录外,还有一些的记录最后一个字符即不是2也不是3
    IV.     有两个学生的学号相同
    V.        试题数是负数。
    3)    再如,测试一个对线性表(比如数组)进行排序的程序,可推测列出以下几项需要特别测试的情况:
    I.          输入的线性表为空表;
    II.       表中只含有一个元素;
    III.     输入表中所有元素已排好序;
    IV.     输入表已按逆序排好;
    V.        输入表中部分或全部元素相同
  • 黑盒测试-边界值分析方法

    2008-04-15 15:09:02

    一.方法简介
    1.定义:边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充,这种情况下,其测试用例来自等价类的边界。
     

    2.与等价划分的区别
      1)边界值分析不是从某等价类中随便挑一个作为代表,而是使这个等价类的每个边界都要作为测试条件。
      2)边界值分析不仅考虑输入条件,还要考虑输出空间产生的测试情况。

    3.边界值分析方法的考虑:
      长期的测试工作经验告诉我们,大量的错误是发生在输入或输出范围的边界上,而不是发生在输入输出范围的内部。因此针对各种边界情况设计测试用例,可以查出更多的错误。
      使用边界值分析方法设计测试用例,首先应确定边界情况。通常输入和输出等价类的边界,就是应着重测试的边界情况。应当选取正好等于,刚刚大于或刚刚小于边界的值作为测试数据,而不是选取等价类中的典型值或任意值作为测试数据。

    4.常见的边界值
      1)对16-bit 的整数而言 32767 和 -32768 是边界
      2)屏幕上光标在最左上、最右下位置
      3)报表的第一行和最后一行
      4)数组元素的第一个和最后一个
      5)循环的第 0 次、第 1 次和倒数第 2 次、最后一次

    5.边界值分析
      1)边界值分析使用与等价类划分法相同的划分,只是边界值分析假定错误更多地存在于划分的边界上,因此在等价类的边界上以及两侧的情况设计测试用例。
        例:测试计算平方根的函数
            --输入:实数
            --输出:实数
            --规格说明:当输入一个0或比0大的数的时候,返回其正平方根;当输入一个小于0的数时,显示错误信息"平方根非法-输入值小于0"并返回0;库函数Print-Line可以用来输出错误信息。
           
      2)等价类划分:
        I.可以考虑作出如下划分:
          a、输入 (i)<0 和 (ii)>=0
          b、输出 (a)>=0 和 (b) Error
        II.测试用例有两个:
          a、输入4,输出2。对应于 (ii) 和 (a) 。
          b、输入-10,输出0和错误提示。对应于 (i) 和 (b) 。

      3)边界值分析:
        划分(ii)的边界为0和最大正实数;划分(i)的边界为最小负实数和0。由此得到以下测试用例:
        a、输入 {最小负实数}
        b、输入 {绝对值很小的负数}
        c、输入 0
        d、输入 {绝对值很小的正数}
        e、输入 {最大正实数}
       
      4)通常情况下,软件测试所包含的边界检验有几种类型:数字、字符、位置、重量、大小、速度、方位、尺寸、空间等。
      5)相应地,以上类型的边界值应该在:最大/最小、首位/末位、上/下、最快/最慢、最高/最低、  最短/最长、 空/满等情况下。
      6)利用边界值作为测试数据

     
    边界值
    测试用例的设计思路
    字符
    起始-1个字符/结束+1个字符
    假设一个文本输入区域允许输入1个到255个 字符,输入1个和255个字符作为有效等价类;输入0个和256个字符作为无效等价类,这几个数值都属于边界条件值。
    数值
    最小值-1/最大值+1
    假设某软件的数据输入域要求输入5位的数据值,可以使用10000作为最小值、99999作为最大值;然后使用刚好小于5位和大于5位的 数值来作为边界条件。
    空间
    小于空余空间一点/大于满空间一点
    例如在用U盘存储数据时,使用比剩余磁盘空间大一点(几KB)的文件作为边界条件。


      7)内部边界值分析:
        在多数情况下,边界值条件是基于应用程序的功能设计而需要考虑的因素,可以从软件的规格说明或常识中得到,也是最终用户可以很容易发现问题的。然而,在测试用例设计过程中,某些边界值条件是不需要呈现给用户的,或者说用户是很难注意到的,但同时确实属于检验范畴内的边界条件,称为内部边界值条件或子边界值条件。
        内部边界值条件主要有下面几种:
        a)数值的边界值检验:计算机是基于二进制进行工作的,因此,软件的任何数值运算都有一定的范围限制。

    范围或值
    位(bit)
    0 或 1
    字节(byte)
    0 ~ 255
    字(word)
    0~65535(单字)或 0~4294967295(双字)
    千(K)
    1024
    兆(M)
    1048576
    吉(G)
    1073741824

        b)字符的边界值检验:在计算机软件中,字符也是很重要的表示元素,其中ASCII和Unicode是常见的编码方式。下表中列出了一些常用字符对应的ASCII码值。

     

    字符
    ASCII码值
    字符
    ASCII码值
    空 (null)
    0
    A
    65
    空格 (space)
    32
    a
    97
    斜杠 ( / )
    47
    Z
    90
    0
    48
    z
    122
    冒号 ( : )
    58
    单引号 ( ‘ )
    96
    @
    64
     
     


        c)其它边界值检验
       
    6.基于边界值分析方法选择测试用例的原则
      1)如果输入条件规定了值的范围,则应取刚达到这个范围的边界的值,以及刚刚超越这个范围边界的值作为测试输入数据。
        例如,如果程序的规格说明中规定:"重量在10公斤至50公斤范围内的邮件,其邮费计算公式为……"。作为测试用例,我们应取10及50,还应取10.01,49.99,9.99及50.01等。
      2)如果输入条件规定了值的个数,则用最大个数,最小个数,比最小个数少一,比最大个数多一的数作为测试数据。
        比如,一个输入文件应包括1~255个记录,则测试用例可取1和255,还应取0及256等。
      3)将规则1)和2)应用于输出条件,即设计测试用例使输出值达到边界值及其左右的值。
        例如,某程序的规格说明要求计算出"每月保险金扣除额为0至1165.25元",其测试用例可取0.00及1165.24、还可取一0.01及1165.26等。
        再如一程序属于情报检索系统,要求每次"最少显示1条、最多显示4条情报摘要",这时我们应考虑的测试用例包括1和4,还应包括0和5等。
      4)如果程序的规格说明给出的输入域或输出域是有序集合,则应选取集合的第一个元素和最后一个元素作为测试用例。
      5)如果程序中使用了一个内部数据结构,则应当选择这个内部数据结构的边界上的值作为测试用例。
      6)分析规格说明,找出其它可能的边界条件。

    二.实战演习
    1.现有一个学生标准化考试批阅试卷,产生成绩报告的程序。其规格说明如下:程序的输入文件由一些有80个字符的记录组成,如右图所示,所有记录分为3组:

     

     


      ①标题:这一组只有一个记录,其内容为输出成绩报告的名字。
      ②试卷各题标准答案记录:每个记录均在第80个字符处标以数字"2"。该组的第一个记录的第1至第3个字符为题目编号(取值为1一999)。第10至第59个字符给出第1至第50题的答案(每个合法字符表示一个答案)。该组的第2,第3……个记录相应为第51至第100,第101至第150,…题的答案。
      ③每个学生的答卷描述:该组中每个记录的第80个字符均为数字"3"。每个学生的答卷在若干个记录中给出。如甲的首记录第1至第9字符给出学生姓名及学号,第10至第59字符列出的是甲所做的第1至第50题的答案。若试题数超过50,则第2,第3……纪录分别给出他的第51至第100,第101至第150……题的解答。然后是学生乙的答卷记录。
      ④学生人数不超过200,试题数不超过999。
      ⑤程序的输出有4个报告:
        a)按学号排列的成绩单,列出每个学生的成绩、名次。
        b)按学生成绩排序的成绩单。
        c)平均分数及标准偏差的报告。
        d)试题分析报告。按试题号排序,列出各题学生答对的百分比。
      解答:分别考虑输入条件和输出条件,以及边界条件。给出下表所示的输入条件及相应的测试用例。


      输出条件及相应的测试用例表。


     
    2.三角形问题的边界值分析测试用例
    在三角形问题描述中,除了要求边长是整数外,没有给出其它的限制条件。在此,我们将三角形每边边长的取范围值设值为[1, 100] 。
     

    测试用例
    a
    b
    c
    预期输出
    Test1
    Test2
    Test3
    Test4
    Test5
    60
    60
    60
    50
    50
    60
    60
    60
    50
    50
    1
    2
    60
    99
    100
    等腰三角形
    等腰三角形
    等边三角形
    等腰三角形
    非三角形
    Test6
    Test7
    Test8
    Test9
    60
    60
    50
    50
    1
    2
    99
    100
    60
    60
    50
    50
    等腰三角形
    等腰三角形
    等腰三角形
    非三角形
    Test10
    Test11
    Test12
    Test13
    1
    2
    99
    100
    60
    60
    50
    50
    60
    60
    50
    50
    等腰三角形
    等腰三角形
    等腰三角形
    非三角形

    3.NextDate函数的边界值分析测试用例
    在NextDate函数中,隐含规定了变量mouth和变量day的取值范围为1≤mouth≤12和1≤day≤31,并设定变量year的取值范围为1912≤year≤2050 。

    测试用例
    mouth
    day
    year
    预期输出
    Test1
    Test2
    Test3
    Test4
    Test5
    Test6
    Test7
    6
    6
    6
    6
    6
    6
    6
    15
    15
    15
    15
    15
    15
    15
    1911
    1912
    1913
    1975
    2049
    2050
    2051
    1911.6.16
    1912.6.16
    1913.6.16
    1975.6.16
    2049.6.16
    2050.6.16
    2051.6.16
    Test8
    Test9
    Test10
    Test11
    Test12
    Test13
    6
    6
    6
    6
    6
    6
    0
    1
    2
    30
    31
    32
    2001
    2001
    2001
    2001
    2001
    2001
    day超出[1…31]
    2001.6.2
    2001.6.3
    2001.7.1
    输入日期超界
    day超出[1…31]
    Test14
    Test15
    Test16
    Test17
    Test18
    Test19
    0
    1
    2
    11
    12
    13
    15
    15
    15
    15
    15
    15
    2001
    2001
    2001
    2001
    2001
    2001
    Mouth超出[1…12]
    2001.1.16
    2001.2.16
    2001.11.16
    2001.12.16
    Mouth超出[1…12]

    找零钱最佳组合

    假 设 商 店 货 品 价 格 (R) 皆 不 大 於 100 元 ( 且 为 整 数 ) , 若 顾 客 付 款 在 100 元 内 (P) , 求 找 给 顾 客 之 最 少 货币 个(张) 数 ? ( 货 币 面 值 50 元 (N50) , 10 元 (N10) , 5 元 (N5) , 1 元 (N1) 四 种 )


    一、 分 析 输 入 的 情 形 。

    R > 100

    0 < R < = 100

    R <= 0

    P > 100

    R<= P <= 100

    P < R

    二、 分 析 输 出 情 形 。

    N50 = 1

    N50 = 0

    4 > N10 >= 1

    N10 = 0

    N5 = 1

    N5 = 0

    4 > N1 >= 1

    N1 = 0

    三、 分 析 规 格 中 每 一 决 策 点 之 情 形 , 以 RR1, RR2, RR3 表 示 计 算 要 找 50, 10, 5 元 货 币 数 时 之 剩 余 金 额 。 R > 100R <= 0
    P > 100
    P < R
    RR1 >= 50
    RR2 >= 10
    RR3 >= 5

    四、 由 上 述 之 输 入 / 输 出 条 件 组 合 出 可 能 的 情 形 。

    R > 100

    R <= 0

    0 < R <= 100, P > 100
    0 < R <= 100, P < R
    0 < R <= 100, R <= P <= 100, RR = 50
    0 < R <= 100, R <= P <= 100, RR = 49
    0 < R <= 100, R <= P <= 100, RR = 10
    0 < R <= 100, R <= P <= 100, RR = 9
    0 < R <= 100, R <= P <= 100, RR = 5
    0 < R <= 100, R <= P <= 100, RR = 4
    0 < R <= 100, R <= P <= 100, RR = 1
    0 < R <= 100, R <= P <= 100, RR = 0

    五、 为 满 足 以 上 之 各 种 情 形 , 测 试 资 料 设 计 如 下 :

    1. 货品价格 = 101

    2. 货品价格 = 0

    3.货品价格 = -1

    4. 货品价格 = 100, 付款金额 = 101

    5. 货品价格 = 100, 付款金额 = 99

    6. 货品价格 = 50, 付款金额 = 100

    7. 货品价格 = 51, 付款金额 = 100

    8. 货品价格 = 90, 付款金额 = 100

    9. 货品价格 = 91, 付款金额 = 100

    10. 货品价格 = 95, 付款金额 = 100

    11. 货品价格 = 96, 付款金额 = 100

    12. 货品价格 = 99, 付款金额 = 100

    13. 货品价格 = 100, 付款金额 = 100

  • 黑盒测试-等价类划分方法

    2008-04-15 13:10:32

    一.方法简介
    1.定义
      是把所有可能的输入数据,即程序的输入域划分成若干部分(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例。该方法是一种重要的,常用的黑盒测试用例设计方法。
       
    2.划分等价类:
      等价类是指某个输入域的子集合。在该子集合中,各个输入数据对于揭露程序中的错误都是等效的,并合理地假定:测试某等价类的代表值就等于对这一类其它值的测试,因此,可以把全部输入数据合理划分为若干等价类,在每一个等价类中取一个数据作为测试的输入条件就可以用少量代表性的测试数据取得较好的测试结果。等价类划分可有两种不同的情况:有效等价类和无效等价类。
      1)有效等价类
        是指对于程序的规格说明来说是合理的、有意义的输入数据构成的集合。利用有效等价类可检验程序是否实现了规格说明中所规定的功能和性能。
      2)无效等价类
        与有效等价类的定义恰巧相反。无效等价类指对程序的规格说明是不合理的或无意义的输入数据所构成的集合。对于具体的问题,无效等价类至少应有一个,也可能有多个。
      设计测试用例时,要同时考虑这两种等价类。因为软件不仅要能接收合理的数据,也要能经受意外的考验,这样的测试才能确保软件具有更高的可靠性。
      
    3.划分等价类的标准:
      1)完备测试、避免冗余;
      2)划分等价类重要的是:集合的划分,划分为互不相交的一组子集,而子集的并是整个集合;
      3)并是整个集合:完备性;
      4)子集互不相交:保证一种形式的无冗余性;
      5)同一类中标识(选择)一个测试用例,同一等价类中,往往处理相同,相同处理映射到"相同的执行路径"。

    4.划分等价类的方法
      1)在输入条件规定了取值范围或值的个数的情况下,则可以确立一个有效等价类和两个无效等价类。如:输入值是学生成绩,范围是0~100;


      2)在输入条件规定了输入值的集合或者规定了"必须如何"的条件的情况下,可确立一个有效等价类和一个无效等价类;
      3)在输入条件是一个布尔量的情况下,可确定一个有效等价类和一个无效等价类。
      4)在规定了输入数据的一组值(假定n个),并且程序要对每一个输入值分别处理的情况下,可确立n个有效等价类和一个无效等价类。
        例:输入条件说明学历可为:专科、本科、硕士、博士四种之一,则分别取这四种这四个值作为四个有效等价类,另外把四种学历之外的任何学历作为无效等价类。
      5)在规定了输入数据必须遵守的规则的情况下,可确立一个有效等价类(符合规则)和若干个无效等价类(从不同角度违反规则);
      6)在确知已划分的等价类中各元素在程序处理中的方式不同的情况下,则应再将该等价类进一步的划分为更小的等价类。
     
    5.设计测试用例
      在确立了等价类后,可建立等价类表,列出所有划分出的等价类输入条件:有效等价类、无效等价类,然后从划分出的等价类中按以下三个原则设计测试用例:
      1)为每一个等价类规定一个唯一的编号;
      2)设计一个新的测试用例,使其尽可能多地覆盖尚未被覆盖地有效等价类,重复这一步,直到所有的有效等价类都被覆盖为止;
      3)设计一个新的测试用例,使其仅覆盖一个尚未被覆盖的无效等价类,重复这一步,直到所有的无效等价类都被覆盖为止。
     
    二.实战演习
    1.某程序规定:"输入三个整数 a 、 b 、 c 分别作为三边的边长构成三角形。通过程序判定所构成的三角形的类型,当此三角形为一般三角形、等腰三角形及等边三角形时,分别作计算 … "。用等价类划分方法为该程序进行测试用例设计。(三角形问题的复杂之处在于输入与输出之间的关系比较复杂。)
      分析题目中给出和隐含的对输入条件的要求:
      (1)整数    (2)三个数    (3)非零数   (4)正数  
      (5)两边之和大于第三边     (6)等腰     (7)等边
       如果 a 、 b 、 c 满足条件( 1 ) ~ ( 4 ),则输出下列四种情况之一:
       1)如果不满足条件(5),则程序输出为 " 非三角形 " 。
       2)如果三条边相等即满足条件(7),则程序输出为 " 等边三角形 " 。
       3)如果只有两条边相等、即满足条件(6),则程序输出为 " 等腰三角形 " 。
       4)如果三条边都不相等,则程序输出为 " 一般三角形 " 。
       列出等价类表并编号

    输入条件 有效等价类 无效等价类
    是否三角形的三条边 A>0      (1) A≤0     (7)
    B>0      (2) B≤0     (8)
    C>0      (3) C≤0     (9)
    A+B>C    (4) A+B≤C  (10)
    A+C>B    (5) A+C≤B  (11)
    B+C>A    (6) B+C≤A  (12)
    是否等腰三角形 A=B     (13) A≠B AND A≠B AND B≠C (16)
    B=C     (14)
    C=A     (15)
    是否等边三角形 A=B AND A=C AND B=C(17) A≠B   (18)
    A≠B   (19)
    A≠B   (20)
    符号 【A B C 】 覆盖等价类 输出
    1 【3、4、5】  (1)、 (2)、 (3)、 (4)、 (5)、 (6) 一般三角形
    2 【0、1、2】 (7)、 不能构成三角形
    3 【1、0、2】 (8)、
    4 【1、2、0】 (9)、
    5 【1、2、3】 (10)、
    6 【1、3、2】 (11)、
    7 【3、1、2】 (12)、
    8 【3、3、4】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(13) 等腰三角形
    9 【3、4、4】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(14)
    10 【3、4、3】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(15)
    11 【3、4、5】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(16) 非等腰三角形
    12 【3、3、3】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(17) 等边三角形
    13 【3、4、4】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(18) 非等边三角形
    14 【3、4、3】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(19)
    15 【3、3、4】  (1)、 (2)、 (3)、 (4)、 (5)、 (6)、(20)

    2.设有一个档案管理系统,要求用户输入以年月表示的日期。假设日期限定在1990年1月~2049年12月,并规定日期由6位数字字符组成,前4位表示年,后2位表示月。现用等价类划分法设计测试用例,来测试程序的"日期检查功能"。
      1)划分等价类并编号,下表等价类划分的结果

    输入等价类
    有效等价类
    无效等价类
    日期的类型及长度
    ①6位数字字符
    ②有非数字字符
    ③少于6位数字字符
    ④多于6位数字字符
    年份范围
    ⑤在1990~2049之间
    ⑥小于1990
    ⑦大于2049
    月份范围
    ⑧在01~12之间
    ⑨等于00
    ⑩大于12

      2)设计测试用例,以便覆盖所有的有效等价类在表中列出了3个有效等价类,编号分别为①、⑤、⑧,设计的测试用例如下:
        测试数据    期望结果      覆盖的有效等价类
        200211      输入有效      ①、⑤、⑧
      3)为每一个无效等价类设计一个测试用例,设计结果如下:
        测试数据   期望结果     覆盖的无效等价类
        95June     无效输入          ②
        20036      无效输入          ③
        2001006   无效输入          ④
        198912     无效输入          ⑥
        200401     无效输入          ⑦
        200100     无效输入          ⑨
        200113     无效输入          ⑩
       
    3.NextDate 函数包含三个变量:month 、 day 和 year ,函数的输出为输入日期后一天的日期。 例如,输入为 2006年3月 7日,则函数的输出为 2006年3月8日 。要求输入变量 month 、 day 和 year 均为整数值,并且满足下列条件:
      ①1≤month≤12
      ②1≤day≤31
      ③1920≤year≤2050 
      1)有效等价类为:
        M1={月份:1≤月份≤12}
        D1={日期:1≤日期≤31}
        Y1={年:1812≤年≤2012}
      2)若条件 ① ~ ③中任何一个条件失效,则 NextDate 函数都会产生一个输出,指明相应的变量超出取值范围,比如 "month 的值不在 1-12 范围当中 " 。显然还存在着大量的 year 、 month 、 day 的无效组合, NextDate 函数将这些组合作统一的输出: " 无效输入日期 " 。其无效等价类为:
        M2={月份:月份<1}
        M3={月份:月份>12}
        D2={日期:日期<1}
        D3={日期:日期>31}
        Y2={年:年<1812}
        Y3={年:年>2012}
      弱一般等价类测试用例
      月份    日期       年               预期输出
       6       15        1912           1912年6月16日
      强一般等价类测试用例同弱一般等价类测试用例
      注:弱--有单缺陷假设;健壮--考虑了无效值
     
      (一)弱健壮等价类测试
      用例ID   月份  日期    年          预期输出
      WR1      6      15    1912      1912年6月16日
      WR2     -1     15    1912      月份不在1~12中
      WR3     13     15    1912      月份不在1~12中
      WR4      6      -1    1912      日期不在1~31中
      WR5      6      32    1912      日期不在1~31中
      WR6      6      15    1811      年份不在1812~2012中
      WR7      6      15    2013      年份不在1812~2012中

      (二)强健壮等价类测试
      用例ID   月份    日期      年          预期输出
      SR1       -1      15       1912      月份不在1~12中
      SR2        6      -1        1912      日期不在1~31中
      SR3        6      15       1811      年份不在1812~2012中
      SR4       -1      -1       1912      两个无效一个有效
      SR5        6      -1        1811      两个无效一个有效
      SR6       -1      15       1811      两个无效一个有效
      SR7       -1      -1       1811      三个无效
     
    4.佣金问题等价类测试用例,它是根据佣金函数的输出值域定义等价类,来改进测试用例集合。
    输出销售额≤1000元     佣金10%
    1000<销售额≤1800     佣金=100+(销售额-1000)*15%
    销售额>1800              佣金=220+(销售额-1800)*20%
    测试用例         枪机(45)    枪托(30)      枪管(25)          销售额     佣金
        1               5             5                5                  500        50
        2              15           15              15                 1500       175
        3              25           25              25                 2500       360
    根据输出域选择输入值,使落在输出域等价类内,可以结合弱健壮测试用例结合。

352/2<12

数据统计

  • 访问量: 35948
  • 日志数: 37
  • 文件数: 1
  • 建立时间: 2008-04-03
  • 更新时间: 2008-10-23

RSS订阅

Open Toolbar