第3章 数据
3.1 简介
描述它数据的各种类型,描述它的特点以及如何申明,变量的三个属性——作用域、链接属性和存储类型
- 数据类型
- 整型
- 浮点型
- 指针
- 聚合类型(数组和结构等)
- 数据的声明:
- 说明符(一个或多个) 说明表达式列表
- 变量的三个属性
- 作用域:生命周期
- 链接属性:内部static、外部external、None
- 存储类型:静态、动态
3.1.1 基本数据类型
A 整型家族
A.1 整型基本信息
包括:字符char、短整型short、整型int和长整型long,分别分为有符号(signed)和无符号(unsigned)
规定整型值相互之间大小的规则:长整型至少应该和整型一样长,而整型至少应该和短整型一样长
A.2 整型字面值
字面值(literal)1是字面值常量的缩写——这是一种变体,指定了自身的值,并且不允许发生改变。就是const
啦。
字符长两难。它们的类型总是int。你不能再它们后面添加unsigned或long后缀。字符常量就是一个用单引号包围起来的单个字符(或字符转义序列):'M'
\n
1 | //对于字符运算,不合适的代码 |
A.3 枚举类型
枚举(enumerated)类型就是指它的值为符号常量而不是字面值的类型:
1 | enum { CUP, PINT, QUART, HALF_GALLON, GALLON} |
这种类型的变量实际上以整型的方式存储,这些符号名的实际值都是整型值。这里CUP是0,PINT是1,依次类推。
注:最好不要把枚举变量同整数无差别地混合在一起使用。
A.3 浮点类型
所有浮点类型至少能够容纳从10^-37^到10^37^之间的任何值
浮点类型除了long double之外,short、signed、unsigned都是不可用。
A.4 指针
通过地址而不是名字来访问数据的想法常常会引起混淆。事实上你不该被搞混,因为生活中,很多东西都是这样大的。比如用门牌号码来标识一条街道上的房子就是如此,没有人会把房子和门牌号码和房子里面的东西搞混。
变量的值存储于计算机的内存中,每个变量都占据一个特定的位置。每个位置包含一个值,这和它的地址是独立且显著不同的,即使它们都是数字。
字符串常量(string literal)
C语言不存在字符串类型,但是C语言提供了字符串常量。
C语言存在字符串的概念:它就是一串以NUL
字节结尾的零个或多个字符。之所以选择NUL作为字符串的终止符,是因为它不是一个可打印的字符。
之所以把字符串常量和指针放在一起讨论,是因为在程序中使用字符串常量会生成一个“指向字符的常量指针”。当一个字符串常量出现于一个表达式时,表达式使用的值就是这些字符存储的地址,而不是这些字符本身。因此,你可以把字符串常量赋值给一个“指向字符的指针”,后者指向这些字符所存储的地址。但是,你不能把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。
3.1.2 基本声明
说明符(一个或多个) 声明表达式列表
说明符包含了一些关键字,用于描述被声明的标识符的基本类型。说明符也可以用于改变标识符的缺省存储类型和作用域。
A 初始化
1 | int j = 15; |
B 声明简单数组
1 | int values[20]; |
C 声明指针
1 | int *a; //这条语句表示表达式*a产生的结果类型是int |
知道了操作符执行的是*间接访问操作以后,我们可以推断a肯定是一个指向int的指针。
D typedef
使用typedef
而不是#define来创建新的类型名。
1 | typedef char *ptr_to_char; //这个声明把标识符ptr_to_char作为指向字符的指针类型的新名字。 |
3.1.3 常量
1 | int const a; |
当指针变量碰到常量
1 | int *pi; //pi是一个指向整型的指针 |
3.1.4 作用域
编译器可以确认4种不同类型的作用域:
- 文本作用域
- 函数作用域
- 代码块作用域
- 原型作用域:函数原型申明中使用
A 代码块作用域
如果内层代码块有一个标识符的名字与外层代码块的一个标识符同名,内层的那个标识符就将隐藏外层的标识符——外层的那个标识符无法在内层代码块中通过名字访问。声明9的f和声明6的 f是不同的变量,后者无法在内层代码块中通过名字来访问
B 文件作用域
任何在所有代码块之外声明的标识符都具有文件作用域(file scope),它表示从标识符从它们的声明之处直到它所在的源文件结尾处都是可以访问的。图3.1中的声明1和声明2就是这一类
C 原型作用域
原型作用域只适用于函数原型中声明的参数,图3.1中对的声明3和声明8。在原型中,参数可以没有名字。
D 函数作用域
只适用于语句标签,语句标签用于goto语句
3.1.5 链接属性
链接属性——external(外部)、internal(内部)和none(无)
属于internal链接属性的标识符在同一个源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。external链接属性的标识符不论声明多少次,位于多个源文件都表示同一个实体。
关键字extern
和static
用于在声明中修改标识符的链接属性。
3.1.6 存储类型
存储变量的地方:普通内存、运行时堆栈、硬件寄存器。
变量的缺省存储类型取决于它的声明位置,凡是在任何代码块之外声明的变量总是存储于静态内存中,也就是不属于堆栈的内存,这类变量称为静态(static)变量。静态变量在程序运行之前创建,始终保持原先的值,除非给它赋一个不同的值或者程序结束。
代码块内部声明的变量缺省存储类型是自动的(automatic),也就是说它存储于堆栈中,称为auto变量。代码执行完后就消失。
如果给它加上关键字static
,可以使它的存储类型从自动变为静态。注。修改变量的存储类型并不表示修改该变量的作用域,它仍然只能在该代码块内部按名字访问。
3.1.7 static关键字
3.1.8 作用域、存储类型示例
3.1.8 external
具有external链接属性的实体在其他语言的术语里称为全局(global)实体,所有源文件中的所有函数均可以访问它。只要变量并非声明于代码块或函数定义内部,它在缺省情况下的链接属性即为external。如果一个变量声明于代码块内部,在它前面添加extern关键字将使它所引用的是全局变量而非局部变量。
具有external链接属性的实体总是具有静态存储类型。全局变量在程序开始执行前创建,并在程序整个执行过程中始终存在。从属于函数的局部变量在函数开始执行时创建,在函数执行完毕后销毁,但用于执行函数的机器指令在程序的生命期内一直存在。
3.1.9 总结
1. 在本书中,literal这个词有时翻译为字面值,有时翻译为常量,它们的含义相同,只是表达的习惯不一。其中,string literal和char literal分别翻译wie字符串常量和字符常量,其他的literal一般翻译为字面值 ↩
3.2 收获
3.2.1 字符
尽管char类型变量的目的是为了让它们容纳字符型值,但字符在本质上是小整型值。缺省的char要么是signed char,要么是unsigned char,这取决于编译器。
- 提示:当可移植问题比较重要时,字符是unsigned还是signed就会带来两难的境地。最佳妥协方案就是把存储于char型变量的值限制在signed char和unsigned char的交集内。如ASCII码
3.2.2 字符串常量修改
许多编译器都允许程序改变字符串常量(因为K&R C清楚地表明具有相同的值的不同字符串常量在内存中是分开存储的)。
但是不推荐修改,因为如果对一个字符串常量进行修改,其效果是未定义的。
如果需要修改字符串,请把它存储于数组中。
3.2.3 数组下标检测
如果下标值是从那些已知是正确的值计算得来, 那么就无需检查它的值。如果一个用作下标的值是根据某种方法从用户愉入的数据产生而来的, 那么在使用它之前必须进行检测, 确保它们位于有效的范围之内。
3.2.4 指针声明
声明指针变量时采用容易误导的写法:
在指针声明的时候,不要这样写成int* a
。
难怪昨天的myLinkedList * a, b, c中只有a是指向结构体的指针,而b,c是结构体。
警告:
1 | // 它声明了一个指针,并用一个字符串常量对其进行初始化: |
这种类型的声明所面临的一个危险是你容易误解它的意思。在前面一个声明中,看上去初始值似乎是赋给表达式*message,事实上它是赋给message本身的,换句话说,前面一个声明相当于:
1 | char *message; |
3.2.5 什么时候用#define不用const
const也不是一直比#define要好,使用”名字常量“的地方,用#define要好,const变量只能用于允许使用变量的地方.
3.2.6 避免在嵌套的代码块中出现相同的变量名
3.2.7 初始化
静态变量的初始化,初始化时已经提前放好了数据于执行过程中欲调用位置。
- 自动变量的初始化较之赋值语句效率并无提高。除了声明为const的变量之外,在声明变量的同时进行初始化和先声明后赋值只有风格之差,并无效率之别
- 其次,这条隐式的赋值语句使自动变量在程序执行到它们所声明的函数(或代码块)时,每次都将重新初始化。这个行为与静态变量大不相同,后者只是在程序开始执行前初始化一次。
- 初始化在运行时执行,你可以用任何表达式作为初始化值:
1 | int func( int a ) |
- 除非你对自动变量进行显式的初始化,否则当自动变量创建时,它们的值总是垃圾值。