zoukankan      html  css  js  c++  java
  • C语言入门(15)——结构体与数据抽象


    大多数的计算机运算是对现实世界的模拟,如果想用计算机来模拟现实世界需要用到数据抽象的方法。所谓抽象是从实际的人、物、事和概念中抽取所关心的共同特征,,忽略非本质的细节,吧这些特征用各种概念精确的加以描述,从而使这些概念构成某种对现实世界进行描述的模型。

    下面以数学中的复数为实例,通过结构体讲解数据类型的组合和抽象。至于过程抽象我们已经见过最简单的形式,就是把一组语句用一个函数名封装起来,当作一个整体使用。

    现在我们用C语言表示一个复数。如果从直角座标系来看,复数由实部和虚部组成,如果从极座标系来看,复数由模和辐角组成,两种座标系可以相互转换。如下图所示

    比如用实部和虚部表示一个复数,我们可以采用两个double型组成的结构体:

    struct complex_struct {
             doublex, y;
    };

     

    这样定义了complex_struct这个标识符,既然是标识符,那么它的命名规则就和变量一样,但它不表示一个变量,而表示一个类型,struct complex_struct { double x, y; }整个可以看作一个类型名,就像int或double一样,只不过它是一个复合类型,如果用这个类型名来定义变量,可以这样写:

    struct complex_struct {
             doublex, y;
    } z1, z2;

     

    这样z1和z2就是两个变量名,变量定义后面要带个;号。这点一定要注意,结构体定义后面少;号是初学者很常犯的错误。不管是用上面两种形式的哪一种形式定义了complex_struct这个标识符,以后都可以直接用struct complex_struct来代替类型名了。例如可以这样定义另外两个复数变量:

    struct complex_struct z3, z4;

     

    结构体变量也可以在定义时初始化,例如:

    struct complex_struct z = { 3.0, 4.0 };

     

    复数加法的运算法则是实部与实部相加,虚部与虚部相加。复数相加运算的函数代码如下:

    struct complex_struct add_complex(structcomplex_struct z1, struct complex_struct z2)
    {
             z1.x= z1.x + z2.x;
             z1.y= z1.y + z2.y;
             returnz1;
    }

     

    此外,我们还提供一个函数用来构造复数变量:

    struct complex_struct make (double x,double y)
    {
             structcomplex_struct z;
             z.x= x;
             z.y= y;
             returnz;
    }

     

    现在我们来实现一个完整的复数运算的程序。在上一节我们已经定义了复数的结构体,现在需要围绕它定义一些函数。复数可以用直角座标或极座标表示,直角座标做加减法比较方便,极座标做乘除法比较方便。如果我们定义的复数结构体是直角座标的,那么应该提供极座标的转换函数,以便在需要的时候可以方便地取它的模和辐角:

    struct complex_struct {
             doublex, y;
    };
     
    double real_part(struct complex_struct z)
    {
             returnz.x;
    }
     
    double img_part(struct complex_struct z)
    {
             returnz.y;
    }
     
    double magnitude(struct complex_struct z)
    {
             returnsqrt(z.x * z.x + z.y * z.y);
    }
     
    double angle(struct complex_struct z)
    {
             doublePI = acos(-1.0);
     
             if(z.x > 0)
                       returnatan(z.y / z.x);
             else
                       returnatan(z.y / z.x) + PI;
    }

    此外,我们再提供一个可以提供极座标的函数用来构造复数变量,在函数中自动做相应的转换然后返回构造的复数变量:

    struct complex_structmake_from_mag_ang(double r, double A)
    {
             structcomplex_struct z;
             z.x= r * cos(A);
             z.y= r * sin(A);
             returnz;
    }


    在此基础上就可以实现复数的加减乘除运算了:

    struct complex_struct add_complex(structcomplex_struct z1, struct complex_struct z2)
    {
             returnmake_from_real_img(real_part(z1) + real_part(z2),
                                           img_part(z1) + img_part(z2));
    }
     
    struct complex_struct sub_complex(structcomplex_struct z1, struct complex_struct z2)
    {
             returnmake_from_real_img(real_part(z1) - real_part(z2),
                                           img_part(z1) - img_part(z2));
    }
     
    struct complex_struct mul_complex(structcomplex_struct z1, struct complex_struct z2)
    {
             returnmake_from_mag_ang(magnitude(z1) * magnitude(z2),
                                          angle(z1) + angle(z2));
    }
     
    struct complex_struct div_complex(structcomplex_struct z1, struct complex_struct z2)
    {
             returnmake_from_mag_ang(magnitude(z1) / magnitude(z2),
                                          angle(z1) - angle(z2));
    }

    可以看出,复数加减乘除运算的实现并没有直接访问结构体complex_struct的成员x和y,而是把它看成一个整体,通过调用相关函数来取它的直角座标和极座标。这样就可以非常方便地替换掉结构体complex_struct的存储表示,例如改为用极座标来存储:

    struct complex_struct {
             doubler, A;
    };
     
    double real_part(struct complex_struct z)
    {
             returnz.r * cos(z.A);
    }
     
    double img_part(struct complex_struct z)
    {
             returnz.r * sin(z.A);
    }
     
    double magnitude(struct complex_struct z)
    {
             returnz.r;
    }
     
    double angle(struct complex_struct z)
    {
             returnz.A;
    }
     
    struct complex_structmake_from_real_img(double x, double y)
    {
             structcomplex_struct z;
             doublePI = acos(-1.0);
             z.r= sqrt(x * x + y * y);
             if(x > 0)
                       z.A= atan(y / x);
             else
                       z.A= atan(y / x) + PI;
     
             returnz;
    }
     
    struct complex_structmake_from_mag_ang(double r, double A)
    {
             structcomplex_struct z;
             z.r= r;
             z.A= A;
             returnz;
    }


    虽然结构体complex_struct的存储表示做了这样的改动,add_complex、sub_complex、mul_complex、div_complex这几个复数运算的函数却不需要做任何改动,仍可以使用,原因在于这几个函数只把结构体complex_struct当作一个整体来使用,而没有直接访问它的成员,因此也不依赖于它有哪些成员。我们结合下图具体分析一下。

    这里要介绍的编程思想称为抽象。其实“抽象”这个概念并没有那么抽象,简单地说就是“提取公因式”:ab+ac=a(b+c)。如果a变了,ab和ac这两项都需要改,但如果写成a(b+c)的形式就只需要改其中一个因子。

    在我们的复数运算程序中,复数有可能用直角座标或极座标表示,我们把这个有可能变动的因素提取出来组成复数存储表示层:real_part、img_part、magnitude、angle、make_from_real_img、make_from_mag_ang。这一层看到的是数据是结构体的两个成员x和y,或者r和A,如果改变了结构体的实现就要改变这一层函数的实现,但函数接口不改变,因此调用这一层函数接口的复数运算层也不需要改变。复数运算层看到的数据只是一个抽象的“复数”的概念,知道它有直角座标和极座标,可以调用复数存储表示层的函数得到这些座标。再往上看,其它使用复数运算的程序看到的数据是一个更为抽象的“复数”的概念,只知道它是一个数,像整数、小数一样可以加减乘除,甚至连它有直角座标和极座标也不需要知道。

    这里的复数存储表示层和复数运算层称为抽象层,从底层往上层来看,“复数”这种数据越来越抽象了,把所有这些层组合在一起就是一个完整的系统。组合使得系统可以任意复杂,而抽象使得系统的复杂性是可以控制的,任何改动都只局限在某一层,而不会影响整个系统。

    我们通过一个复数存储表示抽象层把complex_struct结构体的存储格式和上层的复数运算函数隔开,complex_struct结构体既可以采用直角座标也可以采用极座标存储。但有时候需要同时支持两种存储格式,比如先前已经采集了一些数据存在计算机中,有些数据是以极座标存储的,有些数据是以直角座标存储的,如果要把这些数据都存到complex_struct结构体中怎么办?一种办法是complex_struct结构体采用直角座标格式,直角座标的数据可以直接存入complex_struct结构体,极座标的数据先用make_from_mag_ang函数转成直角座标再存,但转换总是会损失精度的。这里介绍另一种办法,complex_struct结构体由一个数据类型标志和两个浮点数组成,如果数据类型标志为0,那两个浮点数就表示直角座标,如果数据类型标志为1,那两个浮点数就表示极座标。这样,直角座标和极座标的数据都可以适配(Adapt)到complex_struct结构体中,无需转换和损失精度:

    enum coordinate_type { RECTANGULAR, POLAR};
    struct complex_struct {
             enumcoordinate_type t;
             doublea, b;
    };


    enum关键字的作用和struct关键字类似,把coordinate_type这个标识符定义为一个类型,只不过struct complex_struct表示一个结构体类型,而enum coordinate_type表示一个枚举(Enumeration)类型。枚举类型的成员是常量,它们的值编译器自动分配,例如定义了上面的枚举类型之后,RECTANGULAR就表示常量0,POLAR就表示常量1。如果不希望从0开始分配,可以这样定义:

    enum coordinate_type { RECTANGULAR = 1,POLAR };


    这样,RECTANGULAR就表示常量1,而POLAR就表示常量2,这些常量的类型就是int。有一点需要注意,结构体的成员名和变量名不在同一命名空间,但枚举的成员名和变量名却在同一命名空间,所以会出现命名冲突。例如这样是不合法的:

    int main(void)
    {
             enumcoordinate_type { RECTANGULAR = 1, POLAR };
             intRECTANGULAR;
             printf("%d%d
    ", RECTANGULAR, POLAR);
             return0;
    }


    complex_struct结构体的格式变了,就需要修改复数存储表示层的函数,但只要保持函数接口不变就不会影响到上层函数。例如:

    struct complex_structmake_from_real_img(double x, double y)
    {
             structcomplex_struct z;
             z.t= RECTANGULAR;
             z.a= x;
             z.b= y;
             returnz;
    }

    struct complex_structmake_from_mag_ang(double r, double A)

    {

             structcomplex_struct z;

             z.t= POLAR;

             z.a= r;

             z.b= A;

             returnz;

    }


  • 相关阅读:
    android 之 ListView相关
    android 之 菜单
    android 之 Dialog
    android 之 View
    android 之 service
    android 之 Intent、broadcast
    Service Broadcast简单音乐播放功能
    剑指offer面试题43:n个筛子的点数
    C# LINQ语法
    C# Linq
  • 原文地址:https://www.cnblogs.com/new0801/p/6177131.html
Copyright © 2011-2022 走看看