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

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

现在我们用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\n", 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;

}

C语言入门(15)——结构体与数据抽象的更多相关文章

  1. golang | Go语言入门教程——结构体初始化与继承

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是golang专题第10篇文章,我们继续来看golang当中的面向对象部分. 在上一篇文章当中我们一起学习了怎么创建一个结构体,以及怎么 ...

  2. 4-17疑难点 c语言之【结构体对齐】

    今天学习了结构体这一章节,了解到了结构体在分配内存的时候采取的是对齐的方式 例如: #include<stdio.h> struct test1 { int a; char b; shor ...

  3. Swift入门篇-结构体

    前面主要是介绍swift语言中基本类型的用法,今天给大家介绍的是swift的结构体的用法,swift中结构体的用法和其他语言的用法,还有不太一样,不过您多敲几遍,就可以理解结构体,结构体在ios开发中 ...

  4. C语言中的结构体,结构体数组

    C语言中的结构体是一个小难点,下面我们详细来讲一下:至于什么是结构体,结构体为什么会产生,我就不说了,原因很简单,但是要注意到是结构体也是连续存储的,但要注意的是结构体里面类型各异,所以必然会产生内存 ...

  5. Go语言基础之结构体

    Go语言基础之结构体 Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念.Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性. 类型别名和自定义类型 自定义类型 在G ...

  6. C语言第九讲,结构体

    C语言第九讲,结构体 一丶结构体的定义 在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据.结构体的定义形式为: struct 结构体名{ 结构体所包含的变量或数组 }; 结构体是一种 ...

  7. C 语言实例 - 使用结构体(struct)

    C 语言实例 - 使用结构体(struct) C 语言实例 C 语言实例 使用结构体(struct)存储学生信息. 实例 #include <stdio.h> struct student ...

  8. Verilog缺少一个复合数据类型,如C语言中的结构体

    https://mp.weixin.qq.com/s/_9UsgUQv-MfLe8nS938cfQ Verilog中的数据类型(Data Type)是分散的,缺少一个复合数据类型:把多个wire, r ...

  9. GO学习-(13) Go语言基础之结构体

    Go语言基础之结构体 Go语言中没有"类"的概念,也不支持"类"的继承等面向对象的概念.Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性. ...

随机推荐

  1. 微软源代码管理工具TFS2013安装与使用图文教程

    微软源代码管理工具TFS2013安装与使用图文教程 这篇文章主要介绍了微软源代码管理工具TFS2013安装与使用图文教程,本文详细的给出了TFS2013的安装配置过程.使用教程,需要的朋友可以参考下 ...

  2. 从HCE的各种问题 讨论未来趋势

    为了能让NFC手机支持NFC支付,维萨公司和万事达公司宣布了对HCE的研发,并且将很快推出最新的HCE规范.从2012年末,我一直在关注关于HCE的相关信息,其原因是由于我们公司参与了名为Simply ...

  3. 解决C/C++程序执行一闪而过的方法(三种办法)

    简述 在VS编写控制台程序的时候,包括使用其他IDE(Visual C++)编写C/C++程序,经常会看到程序的执行结果一闪而过,要解决这个问题,可以在代码的最后加上system(“pause”).g ...

  4. (六)boost库之内存管理shared_ptr

    (六)boost库之内存管理shared_ptr 1.shared_ptr的基本用法 boost::shared_ptr<int> sp(new int(10)); //一个指向整数的sh ...

  5. yum lamp for Centos6.4

    1,Centos6.4  yum lamp#!/bin/sh<<cat#echo Ruiy!#Create by Ruiy on 2015-03-27----cat yum install ...

  6. OMNeT++安装教程

    前提及注意事项: 1) 安装之前首先要确定已经安装好GCC编译环境(例如:MinGW.Cygwin,选择一种安装); (否则OMNeT++会安装不成功),具体安装教程详见另一篇文章 MinGW安装教程 ...

  7. hdu 1279 验证角谷猜想(简单的模拟)

    Problem Description 数论中有许多猜想尚未解决,其中有一个被称为“角谷猜想”的问题,该问题在五.六十年代的美国多个著名高校中曾风行一时,这个问题是这样描述的:任何一个大于一的自然数, ...

  8. iOS 各种传值方式

    属性传值 将A页面所拥有的信息通过属性传递到B页面使用 B页面定义了一个naviTitle属性,在A页面中直接通过属性赋值将A页面中的值传到B页面. A页面DetailViewController.h ...

  9. windows+Ubuntu双系统 windows引导修复

    我的博客:http://blog.csdn.net/muyang_ren 装完windows+Ubuntu麒麟双系统后,发现引导是Ubuntu的. Ubuntu的引导是GRUP windows的引导是 ...

  10. 基于HTML5多图片Ajax上传可预览

    html5多图控件<input id="fileImage" type="file" size="30" name="fil ...