第3章 数据类型与变量:C语言的数据基石
在C语言中,一切数据都必须有明确的类型。类型决定了数据在内存中占据的字节数、存储格式以及可参与的运算。本章将系统讲解C语言的基本数据类型(整型、实型、字符型)、常量与变量的定义与初始化、类型转换规则以及sizeof运算符的使用。掌握这些内容,是编写健壮、高效C程序的第一步。
3.1 基本数据类型:整型、实型与字符型
3.1.1 核心原理:数据在内存中的表示
C语言的基本数据类型是构成一切复杂数据结构的原子。每种类型都有固定的存储大小和取值范围,这直接由编译器和硬件平台决定。理解这些类型的本质,是进行内存管理和性能优化的基础。
| 类型 | 关键字 | 典型字节数(32位系统) | 取值范围 | 格式化占位符 |
|---|---|---|---|---|
| 整型 | short | 2 | -32,768 ~ 32,767 | %hd |
| int | 4 | -2,147,483,648 ~ 2,147,483,647 | %d | |
| long | 4 或 8 | 取决于平台 | %ld | |
| 实型 | float | 4 | 约 ±3.4E±38(7位有效数字) | %f |
| double | 8 | 约 ±1.7E±308(15位有效数字) | %lf | |
| 字符型 | char | 1 | -128 ~ 127 或 0 ~ 255 | %c |
char 本质上是一个单字节整数。它既可以存储字符(如 'A'),也可以存储小范围整数(如 65)。当使用 %c 输出时,它显示为字符;使用 %d 输出时,它显示为对应的ASCII码值。
3.1.2 判断方法:如何选择合适的数据类型
选择数据类型时,应遵循以下判断流程:
- 先看数据是否有小数部分:有则选实型(float/double),无则选整型。
- 再看数据范围:若数据可能超过 int 的范围(约21亿),则选 long 或 long long。
- 考虑内存占用:嵌入式或内存受限场景下,优先使用 short 或 char 节省空间。
- 考虑运算精度:涉及高精度计算(如金融、科学计算)时,使用 double 而非 float。
3.1.3 案例拆解:学生成绩管理系统的类型选择
假设我们要设计一个学生成绩管理系统,需要存储学生的学号、年龄、平均成绩和姓名首字母。
// 学生信息结构体定义
struct Student {
int studentID; // 学号:范围 1~99999,int 足够
char age; // 年龄:范围 0~120,char 足够(节省内存)
float avgScore; // 平均成绩:需要小数,float 精度足够
char nameInitial; // 姓名首字母:单个字符
};
分析过程:
- 学号最大99999,远小于 int 上限,使用 int 安全且高效。
- 年龄范围0~120,char 可存储0~255(无符号),完全够用,且比 int 节省3字节。
- 平均成绩如 85.5 需要小数,float 提供7位有效数字,足够表示百分制成绩。
- 姓名首字母是单个字符,char 是唯一选择。
结果对比:如果全部使用 int 和 double,每个学生记录将占用 4+4+8+4=20 字节;而优化后仅占用 4+1+4+1=10 字节,内存占用减少50%。
3.2 常量与变量:数据的静态与动态表达
3.2.1 核心原理:常量的分类与变量的生命周期
常量是在程序运行过程中值不能被改变的量。C语言支持以下几种常量:
- 整型常量:如 100、-25、0xFF(十六进制)、075(八进制)。
- 实型常量:如 3.14、2.5e-3(科学计数法)。
- 字符常量:用单引号括起来的单个字符,如 'A'、'\n'(转义字符)。
- 字符串常量:用双引号括起来的字符序列,如 "Hello"。
- 符号常量:用
#define或const定义的具名常量。
变量是在程序运行过程中值可以改变的量。变量定义的基本语法为:
类型说明符 变量名1, 变量名2, ...;
3.2.2 操作流程:变量定义与初始化的标准步骤
- 确定类型:根据数据特征选择合适的基本数据类型。
- 命名变量:遵循标识符规则(字母、数字、下划线,不能以数字开头,不能与关键字冲突)。
- 定义变量:在函数开头或代码块开始处声明。
- 初始化:在定义时赋初值,避免使用未初始化的变量。
#include <stdio.h>
#define PI 3.14159 // 符号常量
int main() {
int count = 0; // 定义并初始化为0
double radius = 2.5; // 定义并初始化
char grade = 'A'; // 字符变量初始化
count = count + 1; // 修改变量的值
printf("PI = %.5f\n", PI);
printf("count = %d, radius = %.2f, grade = %c\n", count, radius, grade);
return 0;
}
3.2.3 实战场景:常量与变量的配合使用
在计算圆形面积和周长的程序中,我们同时使用符号常量和变量:
#include <stdio.h>
#define PI 3.14159
int main() {
double r = 5.0; // 变量:半径
double area, perimeter; // 变量:面积和周长
area = PI * r * r; // 使用符号常量PI
perimeter = 2 * PI * r;
printf("半径 = %.2f\n", r);
printf("面积 = %.2f\n", area);
printf("周长 = %.2f\n", perimeter);
return 0;
}
这里 PI 是符号常量,在编译时会被替换为 3.14159。使用符号常量的好处是:当需要提高精度时,只需修改一处定义,所有引用处都会自动更新。
3.3 类型转换规则:隐式与显式转换
3.3.1 核心原理:自动类型转换的优先级
C语言在混合类型运算时,会按照“类型提升”规则自动进行隐式转换。转换方向遵循以下优先级(从低到高):
int → unsigned int → long → unsigned long → float → double → long double
基本原则是:低精度类型自动转换为高精度类型,以避免精度损失。
3.3.2 判断方法:何时发生隐式转换
- 算术运算:如
int + float,int 先转为 float 再运算。 - 赋值运算:如
float f = 3;,整数3转为浮点数3.0。 - 函数参数传递:实参类型与形参类型不一致时。
- 函数返回值:return 表达式的类型与函数返回类型不一致时。
3.3.3 案例拆解:混合运算的精度陷阱
看下面这个经典例子:
#include <stdio.h>
int main() {
int a = 5;
int b = 2;
double result1 = a / b; // 整数除法,结果为2.0
double result2 = (double)a / b; // 强制转换,结果为2.5
printf("result1 = %.1f\n", result1); // 输出 2.0
printf("result2 = %.1f\n", result2); // 输出 2.5
return 0;
}
分析过程:
a / b中两个操作数都是 int,执行整数除法,结果为 2(小数部分被截断)。- 然后将整数2赋值给 double 变量 result1,自动转换为 2.0。
- 而
(double)a / b先将 a 强制转换为 double,此时 b 自动提升为 double,执行浮点除法,结果为 2.5。
复盘经验:在C语言中,整数除法会直接截断小数部分,不会四舍五入。若需要浮点结果,必须确保至少一个操作数是浮点类型。
3.3.4 强制类型转换的语法
(类型名) 表达式
例如:(int)3.99 结果为 3(截断);(char)65 结果为 'A'。
(double)a 只是将 a 的值转换为 double 参与运算,a 本身仍然是 int 类型。
3.4 sizeof运算符:测量数据的大小
3.4.1 核心原理:编译时计算
sizeof 是一个单目运算符,用于计算类型或变量在内存中占用的字节数。它在编译时求值,不会在运行时产生额外开销。其语法为:
sizeof(类型名) // 计算类型的大小
sizeof 变量名 // 计算变量的大小(括号可省略)
3.4.2 操作流程:使用sizeof的常见场景
- 验证平台差异:不同平台上 int 的字节数可能不同,用 sizeof 确认。
- 动态内存分配:
malloc(n * sizeof(int))计算所需字节数。 - 数组元素个数:
sizeof(arr) / sizeof(arr[0])计算数组长度。 - 结构体大小计算:了解结构体对齐规则。
#include <stdio.h>
int main() {
printf("char 大小: %zu 字节\n", sizeof(char));
printf("int 大小: %zu 字节\n", sizeof(int));
printf("float 大小: %zu 字节\n", sizeof(float));
printf("double 大小: %zu 字节\n", sizeof(double));
int arr[10];
printf("数组元素个数: %zu\n", sizeof(arr) / sizeof(arr[0]));
return 0;
}
sizeof 的返回值类型是 size_t(无符号整数),在 printf 中使用 %zu 格式化输出。不要使用 %d,否则在64位系统上可能产生警告。
3.4.3 实战场景:跨平台数据大小检测
在开发跨平台应用时,不同编译器对基本类型的大小定义可能不同。以下程序帮助开发者快速了解当前环境:
#include <stdio.h>
int main() {
printf("=== 当前平台数据类型大小 ===\n");
printf("short: %zu 字节\n", sizeof(short));
printf("int: %zu 字节\n", sizeof(int));
printf("long: %zu 字节\n", sizeof(long));
printf("long long: %zu 字节\n", sizeof(long long));
printf("float: %zu 字节\n", sizeof(float));
printf("double: %zu 字节\n", sizeof(double));
printf("long double: %zu 字节\n", sizeof(long double));
printf("============================\n");
return 0;
}
在32位系统上,long 通常为4字节;在64位Linux系统上,long 为8字节。通过 sizeof 可以避免硬编码假设。
3.5 常见误区与总结复盘
| 误区 | 错误示例 | 正确做法 |
|---|---|---|
| 未初始化变量 | int x; printf("%d", x); |
int x = 0; |
| 整数除法截断 | double r = 5/2; 得2.0 |
double r = 5.0/2; 得2.5 |
| char 与 int 混淆 | char c = 300; 溢出 |
确认 char 范围,必要时用 int |
| sizeof 用 %d 输出 | printf("%d", sizeof(int)); |
printf("%zu", sizeof(int)); |
| 隐式转换精度丢失 | int i = 3.99; 得3 |
明确使用 (int)3.99 或改用 double |