第3章 数据类型与变量:C语言的数据基石

在C语言中,一切数据都必须有明确的类型。类型决定了数据在内存中占据的字节数、存储格式以及可参与的运算。本章将系统讲解C语言的基本数据类型(整型、实型、字符型)、常量与变量的定义与初始化、类型转换规则以及sizeof运算符的使用。掌握这些内容,是编写健壮、高效C程序的第一步。

C语言数据类型与变量知识结构图 数据类型与变量 基本数据类型 整型 int / short / long 实型 float / double 字符型 char 常量与变量 定义 / 初始化 / 作用域 类型转换规则 隐式转换 / 强制转换 sizeof 运算符 计算类型或变量在内存中的字节数

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
经验提醒:在C语言中,char 本质上是一个单字节整数。它既可以存储字符(如 'A'),也可以存储小范围整数(如 65)。当使用 %c 输出时,它显示为字符;使用 %d 输出时,它显示为对应的ASCII码值。

3.1.2 判断方法:如何选择合适的数据类型

选择数据类型时,应遵循以下判断流程:

  1. 先看数据是否有小数部分:有则选实型(float/double),无则选整型。
  2. 再看数据范围:若数据可能超过 int 的范围(约21亿),则选 long 或 long long。
  3. 考虑内存占用:嵌入式或内存受限场景下,优先使用 short 或 char 节省空间。
  4. 考虑运算精度:涉及高精度计算(如金融、科学计算)时,使用 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 操作流程:变量定义与初始化的标准步骤

  1. 确定类型:根据数据特征选择合适的基本数据类型。
  2. 命名变量:遵循标识符规则(字母、数字、下划线,不能以数字开头,不能与关键字冲突)。
  3. 定义变量:在函数开头或代码块开始处声明。
  4. 初始化:在定义时赋初值,避免使用未初始化的变量。
#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;
}
风险提示:未初始化的局部变量存储的是不确定的“垃圾值”。使用未初始化的变量是C语言中最常见的错误之一,可能导致程序行为不可预测。建议在定义变量时立即初始化。

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的常见场景

  1. 验证平台差异:不同平台上 int 的字节数可能不同,用 sizeof 确认。
  2. 动态内存分配:malloc(n * sizeof(int)) 计算所需字节数。
  3. 数组元素个数:sizeof(arr) / sizeof(arr[0]) 计算数组长度。
  4. 结构体大小计算:了解结构体对齐规则。
#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
核心总结:数据类型是C语言的基石。选择正确的类型、初始化所有变量、理解类型转换规则、善用 sizeof 进行跨平台开发,这四项能力是编写可靠C程序的基本功。建议在每次编写代码时,都先问自己:这个数据是什么类型?范围是多少?需要多高的精度?是否已初始化?