3. 基本数据类型:整型、浮点型、字符型、void 类型、常量与变量
大家好,我是你们的老朋友。今天我们来聊聊 C 语言的“地基”——基本数据类型。说实话,数据类型这东西,看着简单,但我在面试新人时,十个人里有八个会在隐式类型转换上栽跟头。咱们今天就把这块硬骨头啃透。
核心要点:数据类型决定了变量在内存中占多大空间、能存什么范围的值、以及能做什么运算。选错类型,轻则精度丢失,重则程序崩溃。
3.1 整型(int)—— 最常用的“计数器”
整型就是整数。C 语言里的整型家族其实挺庞大的,但日常开发中,int 是绝对的主力。我个人习惯,只要不是明确需要超大或超小范围,一律用 int。
| 类型 | 关键字 | 典型大小(32位系统) | 取值范围 |
|---|---|---|---|
| 基本整型 | int | 4 字节 | -2,147,483,648 ~ 2,147,483,647 |
| 短整型 | short | 2 字节 | -32,768 ~ 32,767 |
| 长整型 | long | 4 或 8 字节 | 取决于平台 |
| 无符号整型 | unsigned int | 4 字节 | 0 ~ 4,294,967,295 |
我的小技巧:在嵌入式项目中,我经常用 unsigned int 来表示数组下标或计数器。为什么?因为下标不可能为负,用无符号类型还能多出一倍的正数范围,何乐而不为?
// 整型声明与初始化
int count = 0;
short small_val = 100;
long big_val = 100000L; // 注意后缀 L
unsigned int positive = 3000;
// 我踩过的坑:用 int 存时间戳,结果溢出导致程序跑飞
// 正确做法:用 long 或 long long
long timestamp = 1699000000L;
3.2 浮点型(float/double)—— 处理“小数”的利器
浮点型用来表示带小数点的数。说白了,计算机里没有真正的“实数”,只有近似值。你想想看,1/3 在十进制里是无限循环小数,在二进制里也一样。
| 类型 | 关键字 | 大小 | 有效位数 | 典型应用 |
|---|---|---|---|---|
| 单精度 | float | 4 字节 | 约 6-7 位 | 图形学、传感器数据 |
| 双精度 | double | 8 字节 | 约 15-16 位 | 科学计算、金融 |
警告:千万不要用 float 或 double 直接比较是否相等!我曾经在金融项目中用 if (a == b) 判断两个浮点数,结果因为精度问题,明明数学上相等的两个数,程序就是不进分支。正确的做法是判断它们的差值是否小于一个很小的阈值(比如 1e-6)。
// 浮点型示例
float pi = 3.14159f; // 注意 f 后缀
double precise_pi = 3.141592653589793;
// 错误的比较方式
// if (pi == 3.14159) { ... } // 可能永远不成立
// 正确的比较方式
#include <math.h>
if (fabs(pi - 3.14159) < 1e-6) {
printf("近似相等\n");
}
3.3 字符型(char)—— 不只是“字符”
很多人以为 char 就是用来存字母的。其实在 C 语言里,char 本质上就是一个 1 字节的整数。它既可以存 'A',也可以存 65——因为 'A' 的 ASCII 码就是 65。
关键认知:char 是整型家族的一员,只是它通常被用来表示字符。你可以对 char 做加减乘除,就像对 int 一样。
// 字符型的使用
char grade = 'A';
char ascii_val = 65; // 等价于 'A'
// 字符运算:小写转大写
char lower = 'b';
char upper = lower - 32; // 结果是 'B'
printf("%c\n", upper); // 输出 B
// 注意:char 默认是有符号还是无符号?取决于编译器!
// 我在跨平台开发时,明确使用 signed char 或 unsigned char
unsigned char byte = 200; // 安全,范围 0~255
3.4 void 类型—— 特殊的“空”
void 类型很有意思。它表示“没有类型”。你不能声明一个 void 类型的变量——想想看,编译器都不知道该给它分配多少内存。
那 void 有什么用?主要有两个场景:
- 函数返回值:如果函数不需要返回任何值,就用
void。比如void print_hello()。 - 通用指针:
void*可以指向任何类型的数据。我在写通用数据结构(比如链表、栈)时,经常用void*来存储任意类型的数据。
// void 类型的使用
void say_hello() {
printf("Hello!\n");
}
// void* 通用指针
void* ptr;
int a = 10;
ptr = &a; // 可以指向 int
// 但使用前必须强制转换
int* int_ptr = (int*)ptr;
printf("%d\n", *int_ptr);
3.5 常量与变量—— 数据的两副面孔
变量就是可以改变的量,常量就是不能改变的量。这个区分在编程中至关重要。
3.5.1 变量
变量有三个要素:类型、名字、值。声明变量时,编译器会为它分配内存空间。
// 变量声明与初始化
int age = 25; // 声明并初始化
double salary; // 只声明,未初始化(危险!)
salary = 5000.0; // 赋值
// 我建议:声明变量时尽量同时初始化
// 否则变量里存的是垃圾值,用起来会出大问题
int uninitialized; // 不要这样!
printf("%d\n", uninitialized); // 输出什么?鬼知道!
避坑指南:我曾经在维护一个老项目时,发现某个变量在声明后没有初始化,直接参与运算。结果程序在 Debug 模式下运行正常,Release 模式下就崩溃。查了两天才发现是未初始化变量导致的。从那以后,我写代码必初始化。
3.5.2 常量
常量有两种定义方式:#define 和 const。
| 方式 | 示例 | 特点 |
|---|---|---|
| #define | #define PI 3.14 | 预处理阶段替换,没有类型检查 |
| const | const double PI = 3.14; | 编译阶段处理,有类型检查,更安全 |
// 常量定义
#define MAX_SIZE 100 // 宏常量
const int MIN_SIZE = 10; // const 常量
// 我个人更推荐 const,因为:
// 1. 有类型检查,避免宏替换带来的意外
// 2. 调试时可以查看 const 变量的值
// 3. 作用域规则更清晰
// 宏常量的坑:
#define SQUARE(x) x * x
int result = SQUARE(2 + 3); // 预期 25,实际是 2 + 3 * 2 + 3 = 11
// 正确写法:#define SQUARE(x) ((x) * (x))
3.6 知识体系总览
下面这张图,是我自己总结的数据类型知识结构。你把它记在脑子里,以后遇到类型相关的问题,就能快速定位。
3.7 本章小结
嗯,到这里,基本数据类型就讲完了。总结一下:
- 整型:选对范围,注意有无符号。我建议日常用
int,需要大范围用long long。 - 浮点型:精度是核心。别用
==比较浮点数,用差值比较。 - 字符型:记住它是整数,可以参与运算。跨平台时明确
signed或unsigned。 - void 类型:不能声明变量,但
void*是万能指针,写通用代码时很有用。 - 常量与变量:变量要初始化,常量推荐用
const而不是#define。
这些基础概念,就像盖楼用的砖头。砖头质量不好,楼盖得再高也会塌。下一章,我们会把这些砖头组合起来,看看运算符和表达式怎么玩。不过那是后话了,先把今天的内容消化掉。
最后送你一句话:写代码时,多花 10 秒钟选对数据类型,能省下未来 10 小时的调试时间。这是我在无数个加班夜里悟出来的道理。