本章描述数据的各种类型, 特点以及如何声明. 还描述变量的三个属性:

  1. 作用域
  2. 链接属性
  3. 存储类型

这三个属性决定了一个变量的 可视性 (可用在什么地方) 和 生命周期(值保存多久)

基本数据类型

C 语言中, 仅有 4 中基本数据类型 -- 整型, 浮点型, 指针和聚合类型(如数组和结构 等) 其他类型都是派生自这四种基本类型.

整型家族

整型包括

  1. 字符
  2. 短整型
  3. 整型
  4. 长整型

他们都分为 有符号(signed) 和 无符号(unsigned) 两种版本

长整型至少应该和整型一样长, 而整型至少应该和短整型一样长

short int 至少 16 位, long int 至少 32 位. int 则有编译器设计者决定.

头文件 limits.h 说明了各种不同整数类型的特点.

  signed signed unsigned
类型 最小值 最大值 最大值
char SCHAR_MIN SCHAR_MAX UCHAR_MAX
short SHRT_MIN SHRT_MAX USHRT_MAX
int INT_MIN INT_MAX UINT_MAX
long LONG_MIN LONG_MAX ULONG_MAX

limits.h 同时定义了下列名字: CHAR_BIT 是字符型的位数(至少8位); CHAR_MINCHAR_MAX 定义了缺省字符类型的范围, 它们应该与 SCHAR_MINSCHAR_MAX 相同, 或者应该与 0 和 UCHAR_MAX 相同

最后 MB_LEN_MAX 规定了一个多字节字符最多允许的字符数量.

整型字面值

字面值(literal) 这个属于是字面值常量的缩写--这是一种实体, 指自身的值, 并且不允许发生改变. ANSI C 允许 命名常量(named constant, 声明 const 的变量)的创建, 一旦初始化, 则值不能改变.

整数字面值后面假字符 L 或 l 是 long 整型值.

字符 U 或 u 则用于指定 unsigned 整型值.

0 开头表示8进制

0x 开头表示16进制

字符常量的类型总是int, 不能添加 unsigned 或 long 后缀

如果一个多字节的字符常量前面有一个L, 那么它就是 宽字符常量(wide character literal)

=== 枚举类型 == 枚举(enumerated)类型就是指它的值为符号常量而不是字面值的类型:

enum Jar_Type {CUP, PINT, QUART, HALF_GALLON, GALLON};

这条语句声明了一个类型, 称为 Jar_Type. 这种类型的变量按下列方式声明:

enum Jar_type milk_jug, gas_can, medicine_bottle;

如果某种特别的枚举类型变量只使用一个声明:

enum {CUP, PINT, QUART, HALF_GALLON, GALLON} milk_jug, gas_can, medicine_bottle;

这种类型的便来给你实际上是整型, 这些符号名的值都是整型值. 这里 CUP 是0, PINT 是 1, 以此类推. 可以为符号指定特定的整型值:

enum Jar_Type {CUP = 8, PINT = 16, QUART = 32, HALF_GALLON = 64, GALLON = 128};

只对部分符号名用这种放射赋值也是合法的. 如果某个符号未显示的指定一个值, 那么它的值就比前面的符号名值大1.

符号名被当做整型常量处理.

浮点类型

浮点数家族包括 float, double 和 long double 类型. 分别提供 单精度, 双精度 以及某些支持扩展精度的机器上提供扩展精度.

ANSI 标准仅仅规定 long double 至少和 double 一样长, 而 double 至少和 float 一样长. 同时规定最小范围: 所有的浮点类型至少能偶容纳 从 10的-37次方 到 10的37次方

头文件 float.h 定义了名字 FLT_MAX, DBL_MAX 和 LDBL_MAX, 分别表示 float, double 和 long double 所能存储的最大值. 而 FLT_MINT, DBL_MIN 和 LDBL_MIN 则相反.

浮点数字面值默认情况下都是 double 类型, 除非它后面跟一个 L 或 l 表示 long, 或者 F/f 表示 float.

指针

指针是 C 语言为什么如此流行的一个重要原因. 指针可以有效的实现诸如 tree 和 list 这里高级的数据结构.

变量值存储与计算机内存中, 每个变量都占据了一个特定的位置. 每个内存位置 都由 地址 唯一确定引用. 指针只是地址的另一个名字.

指针变量就是一个其值为另外一个(些)内存地址的变量.

指针和指针所指向的内容就像房子的门牌号和房子的东西一样.

指针常量(pointer constant)

内存位置无法事先知道, 所以指针常量的字面值几乎没有用处, 所以 C 语言内部 并没有特地定义这个概念

字符串常量

C 语言字符串: 一串以 '\0' 字节结尾的零个或多个字符.

字符串常量会生成一个 "指向字符的常量指针". 因此可以把字符串常量赋值给一个 "指向字符的指针"

基本声明

变量声明的基本形式是: 说明符(一个或多个) 声明表达式列表

说明符(specifier) 包含了一些关键字, 用于表述被声明的标示符的基本类型. 说明符也可以用于改变标识符的缺省存储类型和作用域.

signed 关键字一般只用于 char 类型, 因为其他整型默认都是有符号的.char 则因 编译器而异.

初始化

在声明中可以在变量名后面跟一个等号和值给一个标量变量指定一个初始值:

int j = 15;

声明简单的数组

在数组名后面根一对方括号, 方括号里面是一个证书, 指定数据中元素的个数来声明 一个一维数组:

int values[20];

数组下标总是从 0 开始.

编译器并不检查程序对数据下标的引用是否在数组的合法范围. 一个良好的法则是:

如果下标值从那些已知是正确的值计算的来, 那么无需检查它的值. 如果在一个用作下标的值是根据某种方法从用户输入的数据残生, 那么使用它之前必须进行检测, 确保他们位于有效的范围之内.

声明指针

先给出一个基本类型, 紧随其后的是一个标示符列表, 这些标示符组成表达式, 用于产生基本类型的变量:

int *a;

声明一个指向 int 的指针.

隐式声明

函数如果不显示的声明返回值的类型, 它就默认返回整型.

typedef

typedef 允许为各种类型定义新名字. 只需把 typedef 这个关键字出现在声明的前面

char *ptr_to_char

把变量 ptr_to_char 声明为一个指向字符的指针. 添加 typedef 关键字后:

typedef char *ptr_to_char;

这个声明把标识符 ptr_to_char 作为指向字符的指针类型的新名字:

ptr_to_char a;

意思是 声明 a 是一个指向字符的指针.

常量

常量的样子和变量完全一样, 只是它们的值不能修改. 可以使用 const 关键字声明:

int const a; const int a;

常量无法修改, 你可以在声明时对它机型初始化:

int const a = 15;

在函数中声明为 const 的形参在函数被调用时会得到实参的值.

当涉及指针变量时指针变量和它所指向的实体都可能成为常量:

int *pi;

pi 是一个普通的指向整型的指针, 而变量:

const int *pci;

则是一个指向整型常量的指针. 你可以修改指针的值, 但是不能修改它所指向的值:

int *const pi;

则声明 pci 为一个指向整型的常量指针, 此时指针是常量, 值无法修改, 但 可以修改它所指向的整型的值:

int const *const cpci;

最后, 在 cpci 这个例子, 两者都是常量, 都不允许修改.

当作为组数长度时不推荐声明常量 #define 可以提高可维护性.

作用域

作用域(scope) 决定了变量在程序中的可以使用的区域.

编译器可以确认 4 中不同类型的作用域

  1. 文件作用域
  2. 函数作用域
  3. 代码块作用域
  4. 原型作用域

代码块作用域

位于一对花括号之前的所有语句称之为代码块. 任何在代码块的开始位置声明的标识符都具有 代码块作用域(block scope), 表示 可以被这个代码块中的所有语句访问.

当代码块处于嵌套状态时, 声明于内层代码块的标识符的作用域到达该代码块尾部便 告终止.

如果内层代码块有一个标识符的名字与外层代码块标识符同名, 内层的那个标识符就 将隐藏外层的标识符 -- 外层的那个标识符无法在内层代码块通过名字访问.

外层代码块无法访问内层代码块声明的变量.

不是嵌套代码块, 则声明于每个代码块的变量无法被另一个代码块访问.

文件作用域

任何在所有代码块外声明的标识符都具有 文件作用域(file scope). 它表示这些 标识符的声明支出到所在源文件结尾处都可以访问.

在文件中定义的函数名也具有文件作用域, 因为函数本身并不属于任何代码块.

在头文件中编写并通过 #include 指令包含到其他文件中的声明就好像他们直接写在 那些文件中一样.

原型作用域

原型作用域(prototype scope) 只适用于在函数原型中声明的参数名. 在原型中参数的名字并非必须, 如果出现参数名, 可以随意命名, 不必与函数的 定义中的形参名匹配. 原型作用域防止参数名与其他部分名字冲突, 而且防止原型 中多次使用同一个名字.

函数作用域

函数作用域(function scope) 只适用于语句标签, 语句标签用于 goto 语句. 一个函数中的所有语句标签必须唯一.(作者希望读者永远不要用到这个知识)

链接属性

标识符的 链接属性(linkage) 决定如何处理在不同的文件出现的标识符. 标识符的作用域与它的链接数性有关, 这这里两个属性并不相同.

链接属性一共有 3 种

  1. external(外部)
  2. internal (内部)
  3. none (无)

没有链接属性的标识符总是被当做单独的个体, 也就是说该标识符的多个声明被当做 独立不同的实体.

属于 internal 链接属性的标识符在统一源文件内所有的声明中都指同一个实体, 但位于不同源文件的多个声明则分属不同的实体

属于 external 链接属性的标识符不论声明多少次, 位于几个源文件都表示同一个 实体.

typedef char *a;
int b;

int c(int d)
{
    int 3;
    int f (int g);
    ...
}

上面 b c f 的链接属性为 external, 其余为 none. f 因为是函数名, 所以也是 external

关键字 extern 和 static 用于在声明中修改标识符的属性链接. 如果正常情况下某 声明具有 external 链接属性, 在它前面加上 static 关键字可以使它的链接属性 变为 internal:

static int b;

b 现在将为源文件似有.

static 可以防止它被其他源文件调用.

static 只对默认为 external 链接属性的标识符有效.

extern 关键字的规则更为复杂, 一般它为标识符指定 external 链接属性, 这样就 可以在其他任何位置定义这个实体. 如果在函数内声明 extern 关键字, 这样函数 就可以访问在其他源文件声明的外部变量了.

当 第一次 extern 关键字声明时, 它指定该标识符具有 external 链接属性. 如果用于第二次标识符声明, 它并不会改变由第一次声明所指定的链接属性.

static int i;
int func()
{
    int j;
    extern int k;
    extern int i;
}
位于函数内的 extern int i;并不会改变 i 的链接属性

存储类型

变量的存储类型(storage class) 是指存储变量值的内存类型.它决定了变量何时 创建,何时销毁以及它的值将保存多久. 有三个地方用于存储变量:

  1. 普通内存
  2. 运行时堆栈
  3. 硬件寄存器

变量存储类型取决于声明位置. 反噬在任何代码块之外声明的变量总是存储于静态 内存中. 也就是不属于堆栈内存, 这类变量称之为静态(static)变量.并且无法为 他们制定其他的存储类型. 静态变量在程序运行之前创建, 在程序的真个执行期间 始终存在. 它之中保持原先的值, 除非给他赋一个不同的值或者程序结束

在代码块内部声明的变量的缺省存储类型是自动的(automatic), 也就是它存储于 堆栈中, 称为自动(auto)变量. 代码执行到声明自动变量的代码块时, 自动变量 才被创建, 程序离开该代码块时, 这些自动变量便自行销毁.

对于代码块内部声明的变量, 如果给它加上关键字 static, 可以使它的存储类型 从自动变为静态. 但这样不会更改该变量的作用域.

函数的形参不能声明为静态, 因为实参总是在堆栈中传递函数, 用于支持递归.

关键字 register 可以用于自动变量的声明, 提示他们应该存储于机器的硬件寄存 器而不是类型中, 这类变量称为寄存器变量. 效率比存储于内存中的变量访问效率 要高. 但是编译器不一定会理睬 register 关键字.

寄存器变量的创建和销毁时间和自动变量相同.

初始化

静态变量只在程序开始执行前初始化一次, 静态变量如不显示的制定其初始化值, 静态变量将初始化为0

#include <stdio.h>

int it;
char ct;
float ft;
double dt;

    int
main( int argc, char **argv )
{
    printf("%d %d %f %g\n", it, ct, ft, dt);
    return 0;
}
输出为:

0 0 0.000000 0

自动变量没有缺省的初始值, 而显示的初始化将在代码块的起始处插入一条隐式的 赋值语句.

这种技巧造成4中后果

  1. 自动变量初始化较赋值语句效率并无提高, 除了声明为 const 变量之外
  2. 每次执行函数时将重新对自动变量初始化
  3. 由于初始化在运行时执行, 你可以用任何表达式作为初始值
  4. 除非自动变量显示的初始化, 否则当自动变量创建时, 它们的值总是垃圾

static 关键字

当 static 用于函数定义, 或用于代码块之外的变量时,static关键字 用于修改该标 识符的链接属性, 从 external 改为 internal. 但存储类型和作用域不受影响.

当他用于代码块内部的变量声明时, static 关键字用于修改变量的存储类型, 从 自动变量修改为静态变量, 但变量的链接属性和作用域不受影响.

作用域,存储类型示例

总结

具有 external 链接属性的实体在其他语言的术语里称为全局(global)实体, 所有 源文件函数均可以访问它.

如果对代码块内部的变量使用 extern 关键字, 那么将使它所引用的是全局变量 而非局部变量