C和指针 3.1 基本数据类型

缘起

《C和指针》 3.1节

分析

C仅有四种基本数据类型

  1. 整型
  2. 浮点型
  3. 指针
  4. 数组或者结构体

其他皆由此四种派生而来.

本节介绍基本数据类型之整型和浮点型.

整型包括字符、短整型、整型、长整型——它们都分为有符号和无符号两种版本. 听上去好像长整型要比短整型大,但事实上,KR C标准仅仅规定

整型还包括枚举.

长整型范围>=整型范围>=短整型范围

ANSI C在此基础之上又加入了一条规定——说明各种整型类型的最小允许范围. 如下表所示

这相比KR C是一个巨大的进步——因为提高了程序的可移植性.

可能读者就会有问题了——短整型(short int)至少16位,长整型(long int)至少32位——那么默认int到底是16位还是32位的? 或者是其他值? 这由编译器的设计者决定. ANSI C没有规定这三种类型的范围必须要不一样——甚至可能三种类型(短整型、整型、长整型)规定的都是32位.

还有一个值得注意的事情——char类型,本质上讲就是小整型,默认的char是signed char还是 unsigned char,这取决于编译器. 这意味着不同机器上的char可能拥有不同的范围——只有所有的char类型的变量的实际值在signed char 和 unsigned char交集内(例如 ASCII字符集),这个程序才是可移植的.

在一个把char当做小整型的程序中,如果显式的声明signed char或者 unsigned char,可以提高程序的可移植性. 因为它显式指出了字符是否有符号. 但是如果有些机器处理unsigned char效率比较高的话,你强制改成unsigned char的话,就会影响效率. 所以把所有char硬生生的改成signed或者 unsigned并不是上上之策. 而且所有C标准库函数的char变量统一声明为char而不是 signed或者unsigned char,所以你在程序中显式指出unsigned char或者 char的话,可能带来程序的兼容性问题.

1
2
hint: 一般最好将char型变量的值限制在signed 和 unsigned交集内,这样既保证了程序的可移植
性,又不会牺牲效率, 而且保证了程序的兼容性.

字面量(literal)和常量是同义词. 只是表达的习惯不一致而已. -275 并不是literal,而是常量表达式. 因为负号被解释为单目运算符而不是数值的一部分. 但是实践中这个没什么歧义的——编译器总是按照我们预想的去计算.

当一个程序中出现整型字面量的时候,它是属于int,还是short int 还是 long int呢? 这取决于字面量的写法. 默认情况下,字母值的类型就是所有类型中范围最小但是足以容纳这个值的类型. 那么不默认的情况呢? 比如如果字面量后面加L或者l, 则被解释为long int,如果后面加 U或者u, 则被解释为unsigned 类型的. 如果后面加UL,则被解释为unsigned long. 一般signed、unsigned只针对char,对于其他类型默认都是signed. 至于char是signed还是unsigned,因编译器而异. 所以char可能是signed char 也可能是unsigned char.

八进制用0开头,16进制用0x开头. 如果一个多字节字符常量前面加一个L,那么它就是宽字符常量(wide character literal),如

1
L'abc'

当运行时环境支持宽字符集,就有可能使用到他们.

枚举类型

声明

1
enum Jar_Type {CPU, PINT, QUART, GALLON};

上述语句声明了一个Jar_Type的枚举类型. 实际上是以整型方式存储. 这里默认CPU是0,PINT 是1,以此类推. 当然,也可以指定代表的值

1
enum Jar_Type {CPU, PINT=9, QUART, GALLON};

则CPU是0,PINT是9,QUART是10,GALLON是11

声明枚举类型变量的方式是

1
enum Jar_Type a,b,c;

上面语句声明了三个枚举类型Jar_Type 的变量a,b,c

上面两行完全可以合并为

1
enum Jar_Type {CPU, PINT, QUART, GALLON} a,b,c;

浮点型包括float、double、long double. 分别提供单精度、双精度、在某些支持扩展精度的机器上提供扩展精度. ANSI C 仅仅规定

1
2
long double的范围>=double的范围>=float的范围,
并且所有浮点类型至少能容纳 -10^37到10^37范围的任何值.

以Windows平台下的C标准库为例

D:\vs2010\VC\include\float.h 中定义了FLT_MAX、DBL_MAX和 LDBL_MAX (自然还有相应的MIN)分别表示float、double、long double 所能存储的最大值.

1
2
3
4
5
6
7
#define FLT_MAX         3.402823466e+38F        /* max value */
#define DBL_MAX 1.7976931348623158e+308
#define LDBL_MAX DBL_MAX

#define FLT_MIN 1.175494351e-38F
#define DBL_MIN 2.2250738585072014e-308
#define LDBL_MIN DBL_MIN

浮点类型总是写成十进制,必须要有一个小数点或者一个指数.也可以两者皆有.

浮点缺省情况下是double,除非后面跟着一个L表示它是long double,或者跟着一个f表示他是一个float.

指针(常量或者字面量)是变量的地址,指针变量存储的就是指针.

C语言中没有字符串类型,但是C规定字符串常量就是一串以\0结尾(\0是字符串的休止符,碰到它,就意味着本字符串结束了,注意,即便是”” 空字符串,也存在 \0的)的字符数组.

这里说一句KR C和 ANSI C对待字符串常量(或者字面量)的区别

1
2
3
4
5
6
7
8
9
10
KR C规定程序中在不同地方出现的"abc"这种字符串常量存储在内存不同地方. 例如
char a[] = "abc";
char b[] = "abc";
则这两个"abc"存在于内存中不同地方. 所以虽然KR C并没有提及一个字符串常量中的字符是否可以被
程序修改,但是许多KR C的编译器允许程序修改字符串常量。

但是ANSI C不同,ANSI C中说了,对一个字符串常量进行修改效果是未定义的. ANSI C允许编译器
把一个字符串存在内存同一个地方(即便这个字符串在程序中多次出现,就像上面的两个"abc",在内存
中是存在一个地方的.).这样就使得ANSI C标准下修改字符串常量这种操作变得极为危险. 因此很多
ANSI C编译器不允许修改字符串常量, 或者提供相应的编译选项——让我们自己选择是否允许修改字符串常量. 如果你需要修改字符串,请把它存储于字符数组中.

因为我们在表达式中使用字符串常量其实是在使用存储这些字符的地址而不是字符本身,所以我们可以将字符串常量赋予一个指向字符的指针,就像下面一样

1
char *p = "abc";

ANSI C 并不规定编译器对数组越界进行检查——避免了额外的性能消耗.

声明指针变量的时候要注意

int a和 int\ a 是等价的. 因为C是自由的语言(所谓自由,指的是并没有规定什么地方可以书写语句,一行中可以出现多少条语句,什么地方该留空白以及应该出现多少个空白等)

而且如果有java背景的童鞋可能认为

1
int* a 比  int *a更易读.

但是 int* a这种写法其实是容易混淆的

1
int* a,b,c;

容易让人误以为是声明了三个指向整型的指针变量, 但实际上只有a是指向整型的指针变量,b和c仅仅是整型变量而已. 如果要声明三个指向整型变量的指针变量的话,则正确的写法是

1
int *a, *b, *c;

声明并定义指针变量可以

1
char *message = "Hello World!";