引言
什么是类型系统?
类型系统(Type System) 是一组规则和机制,用于定义程序中可以使用的各种数据类型,以及这些类型如何被创建、使用和相互转换。
为什么要有类型系统?
-
提高代码可读性和可维护性:明确的数据类型让代码更易理解。
-
减少错误:类型系统可以在编译阶段捕捉很多潜在的错误,比如类型不匹配。
-
帮助编译器优化:编译器知道数据类型后,可以进行更好的内存分配与指令优化。
-
保障程序安全(相对地):良好的类型使用能避免很多内存错误和未定义行为。
事实上,我还真没见过没有类型系统的编程语言...
C语言的类型
C语言的类型大体可以分为以下几类
| 基本类型 | 构造类型 | 指针类型 | 空类型 |
|---|---|---|---|
| 字符型char | 数组 | * | void |
| 整型 | 结构&联合 | ||
| 浮点型 | 枚举类型 |
基本类型
整型
整型指的是整数类型,这种类型没有小数部分
| 类型 | 字节数 | 取值范围 | 备注 |
|---|---|---|---|
| short | 2 | -2^15~2^15-1 | 无 |
| unsigned short | 2 | 0~2^16-1 | 无 |
| int | 4 | -2^31~2^31-1 | 16位平台下通常是2字节 |
| unsigned int | 4 | 0~2^32-1 | 同int |
| long | 4 | -2^31~2^31-1 | 在部分平台也可能是8字节 |
| unsigned long | 4 | 0~2^32-1 | 同long |
| long long | 8 | -2^63~2^63-1 | C99引入 |
| unsigned long long | 8 | 0~2^64-1 | C99引入 |
注:实际大小需用 sizeof()确认,不同平台、不同编译器可能有细微调整。
C语言标准没有严格规定基本整型的字节长度,仅规定相对大小关系和最小取值范围。 其中short<=int<=long<=long long。
浮点型
浮点型(也称为实型)可以理解为小数,具有小数部分。
| 类型 | 名称 | 字节数 | 有效数字位数 | 取值范围 |
|---|---|---|---|---|
| float | 单精度浮点型 | 4 | 7 | -3.4x10^-38~3.4x10^38 |
| double | 双精度浮点型 | 8 | 15 | -1.7x10^-308~1.7x10^308 |
| long double | 长双精度浮点型 | 不定(不同平台差异大) | 同左 | 同左 |
long double的字节数由其底层存储格式决定,常见情况如下 (作为了解):
| 平台/编译器 | 存储格式 | 理论位数 | 实际字节数(sizeof) | 说明 |
|---|---|---|---|---|
| x86/x86_64(GCC/Clang/Linux/macOS) | 80 位扩展精度(x87 FPU) | 80 位 | 12 或 16 字节 | 实际存储 80 位(1 符号位 + 15 指数位 + 64 尾数位),但内存中按 16 字节对齐(如 GCC 通常返回 16)。 |
| Windows(MSVC) | 等同于 double(64 位 IEEE 754) | 64 位 | 8 字节 | MSVC 中 long double与 double无区别,仅为兼容保留。 |
| 64 位系统(部分编译器,如 GCC PowerPC) | 128 位四精度(IEEE 754-2008) | 128 位 | 16 字节 | 1 符号位 + 15 指数位 + 112 尾数位(含隐含位共 113 位)。 |
| 嵌入式系统 | 可能为 96 位或其他定制格式 | 96 位 | 12 字节 | 较少见,取决于硬件 FPU 支持。 |
实际占用需使用sizeof函数确认。
long double是 C 语言中精度最高的浮点类型,但其实现高度依赖平台,适合对精度有极致要求的场景。使用时需注意:
- 用后缀 L标记字面量;
- 用 %Lf进行 I/O;
- 通过
宏了解当前平台特性; - 权衡精度、性能与可移植性。
在大多数日常场景中,double已足够,仅在必要时才考虑 long double。
字符型
字符型指的是字符类型的数据,包括有符号字符型(char)和无符号字符型(unsigned char)。
char类型的取值范围为-128~127,unsigned char类型的取值为0~255。
一个字符型数据占用一个字节,书写形式是用单引号括起来的单个字符,例如'1','e','?'; 事实上,char类型本质上也是整型,它实际上储存的是对应的字符的编码(ASCII码以及扩展的ASCII码)。因此,你可以讲char类型理解为只占用一个字节的int类型。
例如,下面的代码片段是合法的:
char a='A';
int b=a+1;
从数学角度来看,一个数字+一个字符没有意义(不要狡辩说是方程!),但在C语言中,实际的操作是将字符的编码拿来相加,因此,结果仍然有意义('A'的ASCII值(97)+1是'B'的ASCII值(98))。
- 补充-转义字符:
有些特殊的控制字符无法直接写出,它们有特殊的写法:以反斜杠\为开头加上一个字符或一个数字序列。这类字符叫做‘转义字符’。
| 字符 | ASCII值 | 含义 |
|---|---|---|
| \a | 7 | 终端响铃 |
| \b | 8 | 退格(Backspace) |
| \n | 10 | 换行 |
| \r | 13 | 回车(Enter) |
| \\ | 92 | 反斜杠\ |
| \' | 39 | 单引号 |
| \" | 34 | 双引号 |
构造类型
数组类型
数组是一组固定数量的、类型相同的变量,它们在内存中占据一块连续的存储空间,每个元素通过整数索引(从 0开始)唯一标识。
数组按维度可分为一维数组、多维数组(二维最常见,更高维可类推);按存储方式分为静态数组(栈/全局区)和动态数组(堆区);按元素类型可分为基本类型数组、结构体数组、指针数组等。
声明与初始化:
简单的一维数组定义int a[10]; //定义一个有10个元素的int数组
访问元素:数组通过下标为每个元素编号,访问时通过数组名[下标号]的格式来访问;
数组的下标从0开始,依次递增,即第i个元素的下标为i-1。
例如 a[2]访问的是数组a的第3个元素。
有关数组的详细内容,我们另行专门讨论。
结构类型与联合类型
数组可以用于聚合相同类型数据,但当需要聚合不同类型数据时,我们需要使用结构类型(struct)。
struct people
{
char name[20];
int age;
int height;
}
这个结构体people一共有三个成员,name,age和height。
联合类型与之类似,但区别是联合类型只有最后一个写入的成员有效。
union data
{
int err_code;
float sensor_value;
}
在内存上,结构类型为每个成员都分配了各自的空间,因此一个结构体最终大小由所有成员大小之和决定;联合类型的各个成员共用内存空间,它的大小由其中最大的成员决定。
枚举类型
枚举类型是对整数序列的映射,将整数用易于理解的单词替换,可以用来提高程序的可读性。
#include<stdio.h>
//--这里是枚举类型的定义
enum week{Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday};
//--
int main()
{
printf("%d",Monday); //输出Monday的值
return 0;
}
week是枚举类型名,其中每个元素对应的值依次为0,1,2,3,4,5,6。
枚举值为整形常量,定义后无法修改!
空类型
void(无类型/空类型)通常用于对无返回值函数、无参函数、通用指针类型的声明。
#include<stdio.h>
void funa(int b){ //这是一个无返回值(return)函数
printf("%d+%d=%d",b,b,b+b);
}
int funb(void){ //这是一个无参函数
return 1;
}
void func(void){ //这个函数既无返回值也无参
printf("Hello");
}
int main(){
int a=1;
void *p=&a; //定义一个void指针p
}
指针类型
指针用于记录变量,函数等实体在内存中的地址,指针类型与其指向的对象有关。
int a=1;
int *p=&a;
*p+=a;