C 语言的数据模型

数据模型


任何数学概念都可称为数据模型, 计算机科学领域, 数据模型通常包含以下两个方面:

  • 对象可以采用的值
  • 数据模型这一方面是静态的, 它告诉我们对象能接受哪些值.
    编程语言数据模型的这一静态部分通常被称为类型系统.

  • 数据的运算
  • 模型的这一方面是动态的, 它告诉我们改变值和创建新值的方式.

编程语言数据模型


每种编程语言都有自己的数据模型, 这些数据模型通常有相当大的差异.
多数编程语言处理数据所遵循的基本原则是, 每个程序都可以访问我们用于表示存储区域的"框".
每个框具有一个类型, 比如 int 或 char.
框中可以存储类型对应的值, 通常将可以存储到这些框中的值称为数据对象.
我们还要为这些框命名, 一般来说, 框的名称可以是任何指示该框的表述性词语.
我们通常会将框的名称视作该程序的变量, 但也并非完全如此.
例如, 如果 x 是递归函数 F 的局部变量, 那么就可能会有很多名为 x 的框, 每个 x 都与 F 的不同调用相关联.
这样的话, 这种框的真实名称就是 x 与 对 F 的某次调用的组合.

C 语言的数据模型


C 语言数据模型的静态部分, 类型系统, 它描述了数据可能拥有的值.
C 语言数据模型的动态部分, 描述可以对数据进行的操作.

C 语言类型系统


C 语言中, 有着类型构成的无限集合, 其中任意元素都可以成为与某个特定变量相关联的类型.
这些类型以及构成类型的规则就形成了 C 语言的类型系统.
类型系统包含整数这样的基本类型以及一些类型构成规则, 利用这些规则, 就可以用已知的类型逐步构建更为复杂的类型.

C 语言的基本类型包括:

  • 字符 char, signed char, unsigned char
  • 整数 int, short int, long int, unsigned
  • 浮点 float, double, long double
  • 枚举 enum

整数和浮点数称为算术类型.

C 语言中的一些类型构成规则:

  • 数组类型

    1. T A[n];

    该语句声明了包含 n 个元素的数组 A, 其中每个元素都是 T 类型.
    C 语言中, 数组下标从 0 开始, 第一个元素是 A[0], 最后一个元素是 A[n-1].
    数组可由字符, 算术类型, 指针, 结构体, 共用体或其他数组构成.

  • 结构体类型
  • C 语言中, 结构体是由称为成员或字段的变量构成的分组.
    结构体中, 不同的成员可以具有不同的类型, 但每个成员必须具有某一个类型的元素.

    1. /* T1, T2, ..., Tn 是类型, 而 M1, M2, ..., Mn 是成员名称 */
    2. struct S {
    3. T1 M1;
    4. T2 M2;
    5. ...
    6. Tn Mn;
    7. };

    该声明就定义了标记为 S 而且具有 n 个成员的结构体.
    对于 i = 1, 2, ..., n 来说, 第 i 个成员名称为 Mi, 且其值为 Ti 类型.

    结构体标记(或其类型名称) S 是可选的, 它可以在随后的声明中为表示该类型提供方便的简写.

    1. struct S myRecord;

    该声明定义了变量 myRecord 是一个类型为 S 的结构体.

  • 共用体类型
  • 共用体类型允许一个变量在程序执行的不同时期具有不同的类型.

    1. union {
    2. T1 M1;
    3. T2 M2;
    4. ...
    5. Tn Mn;
    6. } x;

    该声明定义了变量 x, 可以存放类型为 T1, T2, ..., Tn 中任意一种的值.
    成员名称 M1, M2, ..., Mn 用来指示 x 的值现在应该是哪种类型.
    也就是说, x.Mi 就表明 x 的值是类型为 Ti 的值.

  • 指针类型
  • C 语言的独特之处在于对指针的依赖.
    指针类型的变量包含某个存储区域的地址, 通过指针可以间接地访问另一个变量.

    1. T *p;

    该声明定义了变量 p 是指向某个 T 类型变量的指针.
    用 p 来表示指向 T 类型的指针的框, 框 p 的值就是个指针, 真正出现在 p 框中的是 T 类型对象在计算机中存储的地址(或位置).

    1. int x, y, *p;
    2. p = &x; /* 将 x 的地址赋值给 p, 或说, 让 p 指向 x */
    3. y = *p; /* 将 p 指向的内容赋值给 y, 这两句等价于 y = x */

  • 函数
  • 函数也有与之关联的类型, 即使我们没有像处理程序变量那样将框或值与函数相关联.
    对任意的一系列类型 T1, T2, ..., Tn, 我们可以定义一个函数, 具有 n 个类型依次为这些类型的参数.
    这一系列类型后面带上函数返回值的类型, 就是这个函数的类型.
    如果函数没有返回值, 那么该函数就是 void 类型.
    一般来说, 可以应用类型构成规则任意地构建类型, 不过也有一些限制.
    比如, 不能构建"函数数组", 不过, 构建由指向函数的指针构成的数组是可以的.

C 语言数据模型中的操作


C 语言数据模型中的数据操作可分为三类:

  • 创建或销毁数据对象的操作
  • 访问或修改数据对象某些部分的操作
  • 将若干数据对象的值组合起来, 为某个数据对象生成新值的操作

数据对象的创建和销毁


  • 对于数据对象的创建
  • 在函数被调用时, C 语言会创建对应每个局部参数的框, 这些框用来存放参数的值.
    另一种数据创建机制是使用程序库例程 malloc(n).
    该例程可以返回一个指针, 指向 n 个未使用的连续字符位置, 调用者可以在这一存储区域中创建数据对象.

  • 对于数据对象的销毁
  • 当函数返回时, 该函数调用的局部参数将不复存在.
    例程 free 会释放 malloc 创建的存储空间.
    特别的, free(p) 的效果是释放 p 指向的存储空间, 如果使用 free 去销毁非 malloc 创建的对象, 会造成灾难性后果.

数据的访问和修改


使用 a[i] 访问数组 a 的第 i 个元素.
使用 x.m 访问结构 x 的成员 m.
使用 *p 访问指针 p 指向的对象.
表达式 (*p).f[2] 和 p->f[2] 是等价的, 都表示指针 p 指向的结构体中 f 字段数组的第 2 个元素.
修改值(或说写值)主要是由赋值运算符完成.

  1. /*
  2. 示例
  3. C 语言的 typedef 结构可用来创建类型名称的同义字
  4. */
  5.  
  6. typedef int Distance;
  7. typedef int type1[10];
  8. typedef type1 *type2;
  9. typedef struct {
  10. int field1;
  11. type2 field2;
  12. } type3;
  13. typedef type3 type4[5];

如果变量 a 的类型是 type4, 那么:

  1. (*a[0].field2)[3] = 99;

就是把值 99 赋给了数组 a 第 0 个元素所代表的结构体中 field2 指向的数组的第 3 个元素.

数据的组合


C 语言有丰富的运算符, 可用来对值进行操作和组合.

  • 算术运算符

    • 用于整数和浮点数的常规二元算术运算符 + - * /, 整数除法会取整(4/3 = 1)
    • 一元的 + 和 - 运算符
    • 取模运算符 %, i%j 的结果是 i 除以 j 的余数
    • 递增和递减运算符 ++ 和 --, 可以出现在他们的操作数之前, 也可以出现在他们的操作数之后, 取决于是想要在改变变量的值之前还是之后计算该表达式的值

  • 逻辑运算符
  • C 语言中没有布尔类型, 使用 "0" 表示逻辑值假, 使用 "非 0" 表示逻辑值真.

    • 表示 AND 运算 &&
    • 表达式 x&&y 在两个操作数都非 0 的情况下返回 1, 否则返回 0, 如果 x 的值为 0, 就不考虑 y 的值了.

    • 表示 OR 运算 ||
    • 表达式 x||y 在 x 或 y 非 0 的情况下会返回 1, 否则返回 0, 如果 x 的值 为 非 0, 就不考虑 y 的值了.

    • 一元的否定运算符 !
    • !x 在 x 非 0 时返回 0, 在 x 等于 0 时返回 1.

    • 三元的条件运算符, 一个 ? 和一个 :
    • 表达式 x ? y : z, 在 x 为真时(即 x 非 0)会返回 y 的值, 在 x 为假时(即 x=0)会返回 z 的值.

  • 比较运算符
  • 对整数或浮点数使用 6 种关系比较运算符之一(==, !=, <, >, <=, >=), 如果关系不成立, 结果为 0, 否则结果为 1.

  • 位运算运算符
  • C 语言提供的位逻辑运算符将整数当作与他们的二进制形式相同的位串.

    • 按位与: &
    • 按位或: |
    • 按位异或: ^
    • 按位取非: ~
    • 左移位: <<
    • 右移位: >>

  • 赋值运算符
  • C 语言使用 = 作为赋值运算符.
    还允许将 (x = x+y) 这样的表达式简写成 (x += y).
    类似的格式也可以用于其他二元算术运算符.

  • 强制转换运算符
  • 强制转换指将某个类型的值转换成另一个类型的等价值的过程 x = (double) y.
    如 x 是浮点数, y 是整数, 那么 x=y 会导致 y 的整数值被转换成等值的浮点数.
    这里强制转换运算符并未显式出现, C 语言编译器会判断从整数到浮点数的转换是必要的, 并会自动执行转换步骤.

《计算机科学的基础》