引言:为什么需要数据类型?
C 语言是一种 “强类型语言”,所有数据必须先声明类型才能使用。数据类型的本质是规定数据的存储方式(占多少内存)、取值范围(能存哪些值)和操作方式(能做什么运算)。例如:
- 整型(int)占 4 字节(常见环境),只能存 - 2147483648 到 2147483647 之间的整数;
- 字符型(char)占 1 字节,存的是 ASCII 码(如 'A' 对应 65);
- 指针(*)占 8 字节(64 位系统),存的是内存地址。
一、基本类型(Primitive Types)
基本类型是 C 语言内置的、不可再分解的类型,共 3 大类:整型、浮点型、字符型。
1.1 整型(Integer Types)
整型用于存储整数,根据取值范围和符号分为不同子类型:
类型关键字 | 存储大小(常见环境) | 取值范围 | 说明 |
---|---|---|---|
char | 1 字节 | -128 ~ 127(有符号) | 本质是小整型,常用来存字符 |
unsigned char | 1 字节 | 0 ~ 255(无符号) | 纯数值,不存字符时使用 |
short (或short int ) | 2 字节 | -32768 ~ 32767 | 短整型,节省内存 |
unsigned short | 2 字节 | 0 ~ 65535 | 无符号短整型 |
int | 4 字节 | -2147483648 ~ 2147483647 | 最常用的整型 |
unsigned int | 4 字节 | 0 ~ 4294967295 | 无符号整型(只能存非负数) |
long (或long int ) | 4/8 字节(取决于系统) | -2^31 ~ 2^31-1(32 位) | 长整型,用于大整数 |
unsigned long | 4/8 字节 | 0 ~ 2^32-1(32 位) | 无符号长整型 |
注意:
- C 标准未规定具体存储大小(只要求
short ≤ int ≤ long
),实际大小由编译器和系统决定(可用sizeof(int)
查看); - 无符号类型(
unsigned
)只能存非负数,适合表示 “数量”(如年龄、次数); char
严格来说是整型的一种,但通常用于存储字符(通过 ASCII 码映射,如'A'
=65,'0'
=48)。
1.2 浮点型(Floating-Point Types)
浮点型用于存储小数(实数),通过 “科学计数法” 表示(如 123.45=1.2345×10²),分为单精度(float)和双精度(double):
类型关键字 | 存储大小 | 有效位数 | 取值范围(近似) | 说明 |
---|---|---|---|---|
float | 4 字节 | 6~7 位 | ±3.4×10^-38 ~ ±3.4×10^38 | 单精度,适合一般计算 |
double | 8 字节 | 15~17 位 | ±1.7×10^-308 ~ ±1.7×10^308 | 双精度,精度更高,更常用 |
注意:
- 浮点型有 “精度丢失” 问题(如
0.1
无法精确表示为二进制小数),比较两个浮点数时需用 “误差范围”(如|a - b| < 1e-6
); - 浮点型变量声明时,
float
需加f
后缀(如float x = 3.14f
),否则默认是double
类型。
1.3 字符型(Character Types)
字符型(char
)本质是 1 字节的整型,用于存储 ASCII 字符(共 128 个,包括字母、数字、符号、控制符)。
示例代码:
#include <stdio.h>
int main() {
char c1 = 'A'; // 存储大写字母A(ASCII码65)
char c2 = 65; // 直接存ASCII码,等价于c1
char c3 = '\n'; // 存储换行符(控制符)
printf("c1: %c(ASCII码:%d)\n", c1, c1); // 输出:c1: A(ASCII码:65)
printf("c2: %c\n", c2); // 输出:c2: A
printf("c3: 换行成功!"); // 输出时c3会换行
return 0;
}
二、构造类型(Derived Types)
构造类型是通过基本类型组合或扩展得到的类型,包括数组、结构体、联合体、枚举。
2.1 数组(Array)
数组是相同类型数据的连续存储集合,通过 “下标”(从 0 开始)访问元素。
定义方式:
类型 数组名[元素个数];
(例:int scores[5];
定义一个包含 5 个整型的数组)
关键特性:
- 内存连续:数组元素在内存中 “紧挨着” 存放(如
int arr[3]
占 12 字节,地址依次为 0x1000、0x1004、0x1008); - 下标越界:C 语言不检查下标是否越界(如
arr[5]
访问第 6 个元素会导致 “未定义行为”,可能崩溃); - 初始化:可在定义时赋值(如
int arr[] = {1,2,3};
自动确定长度为 3)。
示例代码:
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50}; // 初始化数组
int len = sizeof(arr) / sizeof(arr[0]); // 计算数组长度(5)
// 遍历数组
for (int i = 0; i < len; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
2.2 结构体(Struct)
结构体是不同类型数据的集合,用于表示一个 “复合对象”(如学生、日期)。
定义方式:
struct 结构体名 {
类型 成员1;
类型 成员2;
// ...其他成员
};
(例:struct Student { char name[20]; int age; float score; };
)
关键特性:
- 成员访问:通过
.
操作符(如Student s; s.age = 20;
); - 内存对齐:结构体成员存储时会 “对齐” 到特定地址(如
char
占 1 字节,但后面的int
可能从 4 的倍数地址开始),导致结构体总大小可能大于成员大小之和; - 结构体指针:通过
->
操作符访问成员(如struct Student *p = &s; p->age = 20;
)。
示例代码:
#include <stdio.h>
#include <string.h>
struct Student {
char name[20];
int age;
float score;
};
int main() {
struct Student s1;
strcpy(s1.name, "张三"); // 字符串赋值需用strcpy
s1.age = 20;
s1.score = 90.5f;
printf("姓名:%s,年龄:%d,分数:%.1f\n", s1.name, s1.age, s1.score);
return 0;
}
2.3 联合体(Union)
联合体是多个成员共享同一块内存的类型,每次只能使用其中一个成员(最后赋值的成员有效)。
定义方式:
union 联合体名 {
类型 成员1;
类型 成员2;
// ...其他成员
};
关键特性:
- 内存共享:联合体的大小等于其最大成员的大小(如
union U { char c; int i; }
占 4 字节,c
和i
共用这 4 字节); - 用途:节省内存(当多个成员不会同时使用时),或用于 “类型转换”(如通过联合体观察整型的字节组成)。
示例代码:
#include <stdio.h>
union Data {
int i;
float f;
char str[4];
};
int main() {
union Data data;
data.i = 10; // 赋值整型成员
printf("data.i: %d\n", data.i); // 输出:10
data.f = 22.5f; // 覆盖之前的整型数据
printf("data.f: %.1f\n", data.f); // 输出:22.5
strcpy(data.str, "ABC"); // 覆盖浮点数据
printf("data.str: %s\n", data.str); // 输出:ABC
return 0;
}
2.4 枚举(Enum)
枚举是为一组整数常量命名的类型,用于提高代码可读性(如表示 “星期”“状态”)。
定义方式:
enum 枚举名 {
枚举常量1,
枚举常量2,
// ...其他常量
};
(枚举常量默认从 0 开始递增,也可手动赋值,如enum Color { RED=1, GREEN=2, BLUE=4 };
)
关键特性:
- 本质是整型:枚举变量存储的是整数(如
enum Color c = RED;
中c
的值是 1); - 用途:替代 “魔法数字”(如用
RED
代替 1,代码更易理解)。
示例代码:
#include <stdio.h>
enum Week {
MON=1, TUE, WED, THU, FRI, SAT, SUN // MON=1,后续自动递增(TUE=2,...,SUN=7)
};
int main() {
enum Week today = WED;
printf("今天是星期%d\n", today); // 输出:今天是星期3
return 0;
}
三、指针(Pointer):指向内存地址的类型
指针是 C 语言的核心特性,用于直接操作内存。指针变量存储的是 “内存地址”(类似于 “门牌号”),通过 “解引用” 可以访问地址上的数据。
3.1 指针的基本操作
- 声明指针:
类型* 指针名;
(例:int* p;
声明一个指向整型的指针); - 取地址:
&变量名
(例:int a=10; p=&a;
让p
存储a
的地址); - 解引用:
*指针名
(例:*p
表示p
指向的地址上的数据,即a
的值 10)。
示例代码:
#include <stdio.h>
int main() {
int a = 10;
int* p = &a; // p存储a的地址
printf("a的值:%d\n", a); // 输出:10
printf("a的地址:%p\n", &a); // 输出:0x7ffd...(具体地址)
printf("p存储的地址:%p\n", p); // 输出:与&a相同
printf("*p的值:%d\n", *p); // 输出:10(解引用)
return 0;
}
3.2 指针与数组
数组名本质是 “指向首元素的指针”(如int arr[3]
中arr
等价于&arr[0]
)。
示例代码:
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30};
int* p = arr; // p指向数组首元素(arr[0])
// 指针运算:p+1指向arr[1](地址+4字节,因int占4字节)
printf("arr[0] = %d\n", *p); // 输出:10
printf("arr[1] = %d\n", *(p+1)); // 输出:20
printf("arr[2] = %d\n", *(p+2)); // 输出:30
return 0;
}
3.3 指针与结构体
通过指针访问结构体成员时,用->
操作符(等价于(*指针).成员
)。
示例代码:
#include <stdio.h>
struct Student {
char name[20];
int age;
};
int main() {
struct Student s = {"李四", 22};
struct Student* p = &s;
// 两种方式访问成员
printf("姓名:%s\n", (*p).name); // 等价于s.name
printf("年龄:%d\n", p->age); // 更简洁的写法
return 0;
}
3.4 指针的高级应用
- 动态内存分配:通过
malloc
/calloc
函数在堆内存中分配空间(如int* arr = (int*)malloc(5*sizeof(int));
); - 函数指针:指向函数的指针(如
int (*func)(int, int);
可指向加法函数); - 指针数组与数组指针:
- 指针数组:
int* arr[5];
(数组元素是指针); - 数组指针:
int (*arr)[5];
(指针指向一个包含 5 个整型的数组)。
- 指针数组:
四、空类型(Void Type)
空类型(void
)表示 “无类型”,用于以下场景:
4.1 无返回值的函数
函数声明为void
表示没有返回值(如void print_hello() { printf("Hello"); }
)。
4.2 无类型指针(void*)
void*
是 “万能指针”,可以指向任何类型的数据(但使用时需强制转换类型)。
示例代码:
#include <stdio.h>
void print_value(void* ptr, char type) {
if (type == 'i') {
int* int_ptr = (int*)ptr; // 强制转换为int*
printf("整型值:%d\n", *int_ptr);
} else if (type == 'f') {
float* float_ptr = (float*)ptr; // 强制转换为float*
printf("浮点值:%.1f\n", *float_ptr);
}
}
int main() {
int a = 10;
float b = 22.5f;
print_value(&a, 'i'); // 输出:整型值:10
print_value(&b, 'f'); // 输出:浮点值:22.5
return 0;
}
4.3 无参数的函数
函数参数声明为void
表示没有参数(如int func(void) { return 0; }
)。
五、总结:数据类型的核心作用
数据类型是 C 语言的 “基石”,它:
- 规定内存大小:不同类型占不同字节(如
int
占 4 字节,double
占 8 字节); - 限制操作方式:整型可以做取模运算(
%
),但浮点型不行; - 提高可读性:用
struct Student
代替 “姓名 + 年龄 + 分数” 的零散变量,代码更清晰; - 保证内存安全:通过类型检查避免 “错误数据操作”(如用字符型存大整数会溢出)。
形象生动的 “数据类型分类” 解释(用生活比喻帮你记住)
我们可以把 C 语言的数据类型想象成 “数据的家具风格”—— 不同的家具(数据)有不同的 “形状”“功能” 和 “摆放方式”,而 “数据类型” 就是给这些 “家具” 分类的规则。
1. 基本类型:最基础的 “单人家具”
特点:像 “单人椅”“小桌子” 一样,是最基础、不可再拆分的类型。
C 语言里的基本类型有 3 种核心 “款式”:
- 整型(int):专门装 “整数” 的 “盒子”,比如年龄(20)、数量(5)。
(类比:单人椅只能坐 1 个人,整型盒子只能装整数,装不了小数) - 浮点型(float/double):专门装 “小数” 的 “精密盒子”,比如身高(1.75)、价格(9.99)。
(类比:带刻度的量杯,能装更细的数据) - 字符型(char):专门装 “单个字符” 的 “小格子”,比如字母('A')、符号('!')。
(类比:邮票盒,每个格子只能放一张邮票,字符型只能存 1 个字符)
2. 构造类型:“组合家具”—— 用基本类型拼出来的新款式
特点:像 “沙发床”(沙发 + 床)、“衣柜书桌组合” 一样,用基本类型 “拼” 出来的新类型。
C 语言里的构造类型有 4 种 “组合方式”:
- 数组(Array):“一排相同款式的单人椅”—— 把多个相同类型的基本数据排在一起。
(例:int scores[5]
是 5 个整型组成的数组,像 5 个并排的整数盒子) - 结构体(struct):“多功能家具组合”—— 把不同类型的数据打包成一个整体。
(例:描述 “学生” 时,用struct Student { char name[20]; int age; float score; }
,把姓名(字符数组)、年龄(整型)、分数(浮点型)装在一起) - 联合体(union):“共享空间的家具”—— 多个数据共用同一块空间,每次只能用其中一个。
(类比:一个抽屉,今天放袜子,明天放手套,但不能同时放) - 枚举(enum):“给选项贴标签”—— 把有限的可选值列出来,比如 “星期” 只有周一到周日。
3. 指针:“地址标签”—— 指向数据的 “导航图”
特点:像 “快递单上的地址”—— 不直接存数据,而是存数据的 “位置”(内存地址)。
(例:你有一个盒子(变量int a=10
),指针int *p
存的是盒子的 “门牌号”(&a
),通过指针可以 “找到” 盒子里的内容(*p
就是 10))
4. 空类型(void):“万能工具”—— 没有具体类型的 “空白区”
特点:像 “万能接口”—— 不指定具体类型,用于通用场景。
(例:void main()
表示主函数 “没有返回值”;void *
是 “万能指针”,可以指向任何类型的数据)