在C语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。
这里强调:
C 是一种静态类型语言。在实际使用变量之前,必须明确声明变量的名称以及要存储的数据类型。
C 也是一种强类型语言,这意味着不允许将一种数据类型自动或隐式地转换为另一种数据类型。
C 中的类型可分为以下几种:
序号 | 类型与描述 |
---|---|
1 | 基本数据类型 它们是算术类型,包括整型(int)、字符型(char)、浮点型(float)和双精度浮点型(double)。 |
2 | 枚举类型: 它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。 |
3 | void 类型: 类型说明符 void 表示没有值的数据类型,通常用于函数返回值。 |
4 | 派生类型: 包括数组类型、指针类型和结构体类型。 |
基础数据类型
注意:以下是典型的数据位长和范围。编译器可能使用不同的数据位长和范围。请参考具体的参考。
在标准头文件limits.h 和 float.h中说明了基础数据的长度。float,double和long double的范围就是在IEEE 754标准中提及的典型数据。另外,C99添加了新的复数类型,C11添加了原子类型,它们不在本条目讨论范围内。关于这些类型的具体含义和部分细节,参见资料类型,最后一列写出了这些类型在硬件层面的类型(x86&x86_64实现)
关键字 | 字节(字节) | 范围 | 格式化字符串 | 硬件层面的类型 | 备注 |
---|---|---|---|---|---|
char | 1bytes | 通常为-128至127或0至255,与体系结构相关 | %c | 字节(Byte) | 大多数情况下即signed char; 在极少数1byte != 8bit或不使用ASCII字符集的机器类型上范围可能会更大或更小。其它类型同理。 |
unsigned char | 1bytes | 通常为0至255 | %c、%hhu | 字节 | |
signed char | 1bytes | 通常为-128至127 | %c、%hhd、%hhi | 字节 | |
int | 2bytes(16位系统) 或 4bytes | -32768至32767或 -2147483648至2147483647 | %i、%d | 字(Word)或双字(Double Word) | 即signed int (但用于bit-field时,int可能被视为signed int,也可能被视为unsigned int) |
unsigned int | 2bytes 或 4bytes | 0至65535 或 0至4294967295 | %u | 字或双字 | |
signed int | 2bytes 或 4bytes | -32768至32767 或 -2147483648至2147483647 | %i、%d | 字或双字 | |
short int | 2bytes | -32768至32767 | %hi、%hd | 字 | 即signed short |
unsigned short | 2bytes | 0至65535 | %hu | 字 | |
signed short | 2bytes | -32768至32767 | %hi、%hd | 字 | |
long int | 4bytes 或 8bytes[1] | -2147483648至2147483647 或 -9223372036854775808至9223372036854775807 | %li、%ld | 长整数(Long Integer) | 即signed long |
unsigned long | 4bytes 或 8bytes | 0至4294967295 或 0至18446744073709551615 | %lu | 整数(Unsigned Integer)或 长整数(Unsigned Long Integer) | 依赖于实现 |
signed long | 4bytes或 8bytes | -2147483648至2147483647 或 -9223372036854775808至9223372036854775807 | %li、%ld | 整数(Signed Integer)或 长整数(Signed Long Integer) | 依赖于实现 |
long long | 8bytes | -9223372036854775808至9223372036854775807 | %lli、%lld | 长整数(Long Integer) | |
unsigned long long | 8bytes | 0至18446744073709551615 | %llu | 长整数(Unsigned Long Integer) | |
float | 4bytes | 2.939x10−38至3.403x10+38 (7 sf) | %f、%e、%g | 浮点数(Float) | |
double | 8bytes | 5.563x10−309至1.798x10+308 (15 sf) | %lf、%e、%g | 双精度浮点型(Double Float) | |
long double | 10bytes或 16bytes | 7.065x10-9865至1.415x109864 (18 sf或33 sf) | %lf、%le、%lg | 双精度浮点型(Double Float) | 在大多数平台上的实现与double 相同,实现由编译器定义。 |
_Bool | 1byte | 0或1 | %i、%d | 布尔型(Boolean) |
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <float.h>
int main(int argc, char** argv) {
printf("CHAR_BIT : %d\n", CHAR_BIT);
printf("CHAR_MAX : %d\n", CHAR_MAX);
printf("CHAR_MIN : %d\n", CHAR_MIN);
printf("INT_MAX : %d\n", INT_MAX);
printf("INT_MIN : %d\n", INT_MIN);
printf("LONG_MAX : %ld\n", (long) LONG_MAX);
printf("LONG_MIN : %ld\n", (long) LONG_MIN);
printf("SCHAR_MAX : %d\n", SCHAR_MAX);
printf("SCHAR_MIN : %d\n", SCHAR_MIN);
printf("SHRT_MAX : %d\n", SHRT_MAX);
printf("SHRT_MIN : %d\n", SHRT_MIN);
printf("UCHAR_MAX : %d\n", UCHAR_MAX);
printf("UINT_MAX : %u\n", (unsigned int) UINT_MAX);
printf("ULONG_MAX : %lu\n", (unsigned long) ULONG_MAX);
printf("USHRT_MAX : %d\n", (unsigned short) USHRT_MAX);
return 0;
}
当你编译并执行上述程序时,它会产生以下结果
CHAR_BIT : 8 CHAR_MAX : 127 CHAR_MIN : -128 INT_MAX : 2147483647 INT_MIN : -2147483648 LONG_MAX : 9223372036854775807 LONG_MIN : -9223372036854775808 SCHAR_MAX : 127 SCHAR_MIN : -128 SHRT_MAX : 32767 SHRT_MIN : -32768 UCHAR_MAX : 255 UINT_MAX : 4294967295 ULONG_MAX : 18446744073709551615 USHRT_MAX : 65535
结构
结构(structure variable)允许构造由多个基础数据类型组合而成的复杂结构[2]。结构为面向对象编程的蓝本。以下示例通过结构和结构体里的指针实现了二叉树结构:
typedef struct Bintree { int data; struct bintree *lchild; // left child of the node struct bintree *rchild; // right child of the node } bintree; // 自定义 bintree 类型
为结构定义变量时通常会用到动态内存分配:
#define mktree() (bintree *)malloc(sizeof(bintree)) // 分配该结构所需的内存单元数量 bintree *tree; tree = mktree(); // 分配到 tree 指针 tree->data = 1; tree->lchild = mktree(); ...
由于C语言不具备自动垃圾收集(Garbage Collection)功能,使用完毕后调用free(treePtr)
来释放之前通过malloc(size)
分配的内存。详见这里。 在C99标准中,还添加了名为伸缩型数组成员的特性[3],若需了解更多信息可参见文档或其它材料。
联合
联合(union)与结构相类似,但不同的是,联合在某一特定时刻只有最后被使用的成员的值是确定的,因此一个联合只使用所有成员中所占空间最大的成员所使用的内存。因此,一次只能使用一个元素。
然而,一些编译器可以通过编译参数或#pragma的方式强制联合使用与所有成员所占储存空间的和相等的储存空间,在这种情况下,除最后被使用的成员外,其余成员的值是未定义的[2]。以下给出了联合的一个声明:
union foo{ int bar; double foobar; }; foo.bar = 8; foo.foobar = 3.14;
在这个例子中,假设使用32位平台编译,一个double变量占8字节,一个int变量占2字节(由上表得),则该联合所占大小即为double类型的大小——8字节。在这段程序执行完毕后,foo.bar的值是未定义的,而foo.foobar的值为3.14。
枚举
枚举(enumerated type)用来声明一组整数常量。默认情况下,枚举声明格式为enum type {value1,value2,...,valuen};此时value1,value2分别为0,1,直到n-1。事实上,枚举类型在C语言实现中是以int类型储存的[2]。以下是枚举的一个声明:
enum a { b , c , d };
在此之后,便可以以如下方式使用:
enum a foo; foo = b; if(foo != c) //等同于if(foo != 1) { do_something(); }
而此时的b,c,d分别为0,1,2。 另外,也可以手动为枚举列表中的常量赋值。下面是一个例子:
enum colour {red = 100,blue = 700,yellow = 200};
则此时red,blue,yellow的值分别为100,700,200.
需要注意的是,枚举在C和C++中所表现的行为有一些细微的差异。参见C与C++的兼容性。
C 语言中的数组数据类型
数组是存储在连续内存位置的多个相同数据类型的值的集合。数组的大小在方括号 [] 中提及。例如,
数组可以在声明时初始化。要分配的值放在括号中。
C 还支持多维数组。要了解有关数组的更多信息,后面会介绍。
C 语言中的指针数据类型
指针是一种特殊的变量,用于在内存中存储另一个变量/对象的地址或引用。指针变量的名称以星号 (*) 为前缀。指针变量的类型和要指向的变量/对象必须相同。
这里,“y”是一个指针变量,它存储了“int”类型变量“x”的地址。
类型转换
类型转换是将一个数据类型的值转换为另一种数据类型的值。
C 语言中有两种类型转换:
-
隐式类型转换:隐式类型转换是在表达式中自动发生的,无需进行任何明确的指令或函数调用。它通常是将一种较小的类型自动转换为较大的类型,例如,将int类型转换为long类型或float类型转换为double类型。隐式类型转换也可能会导致数据精度丢失或数据截断。
-
显式类型转换:显式类型转换需要使用强制类型转换运算符(type casting operator),它可以将一个数据类型的值强制转换为另一种数据类型的值。强制类型转换可以使程序员在必要时对数据类型进行更精确的控制,但也可能会导致数据丢失或截断。
隐式类型转换实例:
实例
int i = 10;
float f = 3.14;
double d = i + f; // 隐式将int类型转换为double类型
显式类型转换实例:
实例
double d = 3.14159;
int i = (int)d; // 显式将double类型转换为int类型,此时i = 3.
布尔类型
在C99之前,C语言的标准没有提供布尔类型,但是这不意味着C90不能表示布尔值的概念。C中的所有布尔运算(&&, ||)以及条件声明(if, while)都以非零值代表真,零值代表假。这样,在其他类型如一个整数或一个枚举中保存布尔值就变得很平常。为了方便,常常为布尔类型创建一个typedef来和一些已存在的数据类型相关联。C99标准也提供了一个内置的布尔类型。
为了说明C中的布尔型,注意以下C代码:
if (my_variable) { printf("True!\n"); } else { printf("False!\n"); }
等价于:
if (my_variable != 0) { printf("True!\n"); } else { printf("False!\n"); }
简单来说这就是一个整数类型。由于C标准要求0
用在指针上下文中时要代表空指针,上面的概念也可以用来检查一个指针是否为空,虽然一些程序风格不建议这样用。这种情况同样适用于浮点值,当比较它们的时候要特别小心,因为它们通常包含四舍五入的结果。通常,整型用来包含布尔变量。
虽然为了测试一个变量的真假值时没必要来命名真或假,但是在给它们分配值的时候却是需要的。(一种方法是使用零值和一,这样做的好处是语言独立。)其他方法,enum
关键字允许在语言中根据你的选择来命名元素,例如:
typedef enum { FALSE, TRUE } boolean; ... boolean b;
如下典型的预处理宏经常被使用。
#define FALSE 0 #define TRUE 1 ... int f = FALSE;
有时TRUE
可能被定义为-1或~0(位运算0的补)。这意味着在现在常见的二补数计算机架构的整型中所有的位都被设置为1。
但是,在C中任意非零值都代表真就带来了问题,因为TRUE
由一个特定的值来表示,因此在其他语言中if (foo == TRUE) ...
只不过是多余的,而在C中,就是错误的代码。
C99中有bool
类型,取值为true
和false
,定义在<stdbool.h>
头文件中:
#include <stdbool.h> bool b = false; ... b = true;