文章目录
第2章:数据表示与内存基础(基础理论+实操版)2.1 变量本质:内存地址+数据类型的”双重绑定”核心理论实操:查看变量的地址和大小
2.2 基础数据类型:大小、范围与实操验证2.2.1 核心理论:3种基础类型对比表2.2.2 实操1:验证类型的大小和范围2.2.3 实操2:验证float的精度问题(避坑重点)
2.3 格式化I/O:printf/scanf实操2.3.1 核心理论:常用格式符(一一对应类型)2.3.2 实操1:printf格式化输出(控制显示效果)2.3.3 实操2:scanf格式化输入(接收用户数据)补充:printf的缓冲区问题(实操必知)
2.4 类型转换:隐式转换风险与显式转换规范2.4.1 核心理论2.4.2 实操1:隐式转换的3大风险(必看避坑)风险1:大类型转小类型→溢出(数据截断)风险2:有符号与无符号混合运算→逻辑错误风险3:char转int→符号位扩展(负数变超大数)
2.4.3 实操2:显式转换的正确用法
2.5 基础练习(巩固实操)练习1:验证int的溢出现象练习2:用scanf输入两个整数,计算和并格式化输出练习3:避免隐式转换的逻辑错误
2.6 本章核心理论总结关键理论要点实践指导原则
第2章:数据表示与内存基础(基础理论+实操版)
程序的核心是”处理数据”,而数据的存储、表示、输入输出直接决定了程序能否正确运行。本章聚焦 初学者必须掌握的基础理论 和 可直接上手的实操案例,不深挖复杂底层原理,重点解决”数据在内存中怎么存”“怎么通过代码操作数据””常见坑怎么避”三大问题。
2.1 变量本质:内存地址+数据类型的”双重绑定”
核心理论
变量不是”数据本身”,而是 “内存存储位置”+”数据解读规则”的结合体:
内存地址:数据在内存中的”房间号”(唯一标识存储位置)数据类型:告诉编译器”这个房间里的二进制数据该怎么读”(比如是整数、字符还是小数)
举个通俗例子:内存中存着二进制(地址
01000001):
0x7ffee4b7e76f
若类型是,解读为字符’A’若类型是
char,解读为整数65地址不变,类型变了,解读结果完全不同
int
实操:查看变量的地址和大小
通过(取地址符)看变量地址,
&(关键字)看变量占用内存大小,直接验证”地址+类型”的绑定关系:
sizeof
#include <stdio.h>
int main() {
// 定义3种不同类型的变量,存储相同的"数值含义"(65对应'A')
char c = 'A'; // 字符类型
int i = 65; // 整数类型
float f = 65.0f; // 单精度浮点类型
// 1. 查看变量的内存地址(%p专门用于输出地址)
printf("字符c的地址:%p
", &c); // 输出类似:0x7ffee4b7e76f
printf("整数i的地址:%p
", &i); // 输出类似:0x7ffee4b7e770(和c差1字节)
printf("浮点数f的地址:%p
", &f); // 输出类似:0x7ffee4b7e774(和i差4字节)
// 2. 查看变量占用的内存大小(单位:字节)
printf("
char类型大小:%zu 字节
", sizeof(c)); // 固定1字节(C标准规定)
printf("int类型大小:%zu 字节
", sizeof(i)); // 通常4字节(32/64位系统通用)
printf("float类型大小:%zu 字节
", sizeof(f)); // 通常4字节
return 0;
}
运行结果(示例):
字符c的地址:0x7ffee4b7e76f
整数i的地址:0x7ffee4b7e770
浮点数f的地址:0x7ffee4b7e774
char类型大小:1 字节
int类型大小:4 字节
float类型大小:4 字节
注意事项:
地址是连续分配的:变量(1字节)→
c(4字节)→
i(4字节),地址依次递增,增量等于前一个变量的大小
f是关键字,不是函数:括号可省略(如
sizeof),计算的是”变量/类型占用的内存大小”,和变量值无关
sizeof c
2.2 基础数据类型:大小、范围与实操验证
C语言的基础类型是”操作数据的最小单位”,重点掌握(字符/单字节整数)、
char(整数)、
int(小数),核心记住”大小、范围、用途”。
float
2.2.1 核心理论:3种基础类型对比表
| 类型 | 占用大小(通用) | 取值范围(常用) | 核心用途 |
|---|---|---|---|
|
1字节 | -128 ~ 127(有符号) | 存储字符(如’A’)、单字节数据 |
|
1字节 | 0 ~ 255(无符号) | 存储非负单字节数据(如像素) |
|
4字节 | -2147483648 ~ 2147483647 | 通用整数运算(计数、编号等) |
|
4字节 | 0 ~ 4294967295 | 存储非负整数(如ID、计数) |
|
4字节 | ±3.4×10³⁸(约) | 存储小数(精度要求不高场景) |
关键补充:
有符号():默认类型,能存正数、负数(最高位是符号位:0=正,1=负)无符号(
signed):只能存非负数,取值范围更大(无符号位,所有位都是数值位)
unsigned精度有限:仅能精确表示6~7位十进制有效数字(如
float)
0.1+0.2≠0.3
2.2.2 实操1:验证类型的大小和范围
通过代码直接查看类型范围(需包含头文件,提供类型边界常量):
<limits.h>
#include <stdio.h>
#include <limits.h> // 包含int/char的范围常量
#include <float.h> // 包含float的范围常量
int main() {
// 1. 整数类型范围
printf("signed char 范围:%d ~ %d
", SCHAR_MIN, SCHAR_MAX);
printf("unsigned char 范围:%u ~ %u
", 0, UCHAR_MAX);
printf("int 范围:%d ~ %d
", INT_MIN, INT_MAX);
printf("unsigned int 范围:%u ~ %u
", 0, UINT_MAX);
// 2. 浮点类型范围(仅作了解)
printf("
float 最小值:%e,最大值:%e
", FLT_MIN, FLT_MAX);
printf("float 有效数字:%d 位
", FLT_DIG); // 6~7位有效数字
return 0;
}
运行结果(32/64位系统通用):
signed char 范围:-128 ~ 127
unsigned char 范围:0 ~ 255
int 范围:-2147483648 ~ 2147483647
unsigned int 范围:0 ~ 4294967295
float 最小值:1.175494e-38,最大值:3.402823e+38
float 有效数字:6 位
2.2.3 实操2:验证float的精度问题(避坑重点)
是”近似存储”,小数运算可能有精度损失,这是初学者最容易踩的坑:
float
#include <stdio.h>
int main() {
float a = 0.1f;
float b = 0.2f;
float sum = a + b;
printf("0.1f + 0.2f = %f
", sum); // 输出:0.300000(表面看起来正常)
printf("0.1f + 0.2f == 0.3f?%d
", sum == 0.3f); // 输出:0(0表示false,实际不相等)
// 正确比较浮点数:判断差值是否小于极小值(如1e-6)
if (sum - 0.3f < 1e-6 && 0.3f - sum < 1e-6) {
printf("浮点数比较:相等
");
} else {
printf("浮点数比较:不相等
");
}
return 0;
}
运行结果:
0.1f + 0.2f = 0.300000
0.1f + 0.2f == 0.3f?0
浮点数比较:相等
注意事项:
浮点数不能用直接比较,必须通过”差值小于极小值”判断若需高精度(如金融计算),用
==(8字节,有效数字15~16位),但同样存在精度问题,需用专门的高精度库
double
2.3 格式化I/O:printf/scanf实操
程序需要”接收外部数据”(输入)和”展示处理结果”(输出),(输出)和
printf(输入)是C语言最基础的工具,重点掌握”格式符怎么用”“常见坑怎么避”。
scanf
2.3.1 核心理论:常用格式符(一一对应类型)
| 格式符 | 对应类型 | 功能描述 | 示例 | 输出/输入结果 |
|---|---|---|---|---|
|
|
输出/输入单个字符 | |
输出:A |
|
|
输出/输入十进制整数 | |
输出:123 |
|
|
输出/输入无符号整数 | |
输出:255 |
|
|
输出/输入单精度浮点数 | |
输出:3.140000 |
|
|
输出/输入字符串 | |
输出:abc |
|
指针/变量地址 | 输出内存地址 | |
输出:0x7ffee4b7e770 |
2.3.2 实操1:printf格式化输出(控制显示效果)
通过修饰符控制输出的”宽度、精度、对齐方式”,满足实际需求(如表格对齐、保留2位小数):
#include <stdio.h>
int main() {
int num = 123;
float pi = 3.1415926f;
char str[] = "hello";
// 1. 整数输出:控制宽度(不足补空格,默认右对齐)
printf("整数默认输出:%d
", num); // 输出:123
printf("整数占5位(右对齐):%5d
", num); // 输出: 123(前面补2个空格)
printf("整数占5位(左对齐):%-5d
", num); // 输出:123 (后面补2个空格)
// 2. 浮点数输出:控制小数位数
printf("
浮点数默认输出:%f
", pi); // 输出:3.141593(默认6位小数)
printf("浮点数保留2位小数:%.2f
", pi); // 输出:3.14(四舍五入)
printf("浮点数占8位+保留2位小数:%8.2f
", pi); // 输出: 3.14(共8位,右对齐)
// 3. 字符串输出:控制长度
printf("
字符串默认输出:%s
", str); // 输出:hello
printf("字符串截取前3个字符:%.3s
", str); // 输出:hel
return 0;
}
运行结果:
整数默认输出:123
整数占5位(右对齐): 123
整数占5位(左对齐):123
浮点数默认输出:3.141593
浮点数保留2位小数:3.14
浮点数占8位+保留2位小数: 3.14
字符串默认输出:hello
字符串截取前3个字符:hel
2.3.3 实操2:scanf格式化输入(接收用户数据)
用于读取用户输入,必须传递变量地址(
scanf取地址符),否则会报错,重点避坑:
&
#include <stdio.h>
int main() {
char c;
int num;
float f;
char str[10]; // 字符串数组,最多存9个字符(留1位存结束符'')
// 1. 输入字符(%c前加空格,忽略输入中的空格/换行)
printf("输入一个字符:");
scanf(" %c", &c); // 注意:%c前有空格!
// 2. 输入整数
printf("输入一个整数:");
scanf("%d", &num);
// 3. 输入浮点数
printf("输入一个小数:");
scanf("%f", &f);
// 4. 输入字符串(遇空格/换行结束)
printf("输入一个字符串(无空格):");
scanf("%4s", str); // %4s:最多读4个字符,避免缓冲区溢出
// 输出验证
printf("
你输入的是:
");
printf("字符:%c
", c);
printf("整数:%d
", num);
printf("小数:%.2f
", f);
printf("字符串:%s
", str);
return 0;
}
运行示例(用户输入):
输入一个字符:A
输入一个整数:100
输入一个小数:3.14
输入一个字符串(无空格):test123
你输入的是:
字符:A
整数:100
小数:3.14
字符串:test
scanf避坑3大要点:
必须加(除了字符串数组
&):
char[]正确,
scanf("%d", &num)报错
scanf("%d", num)前加空格:忽略输入中的空格/换行(比如前一个
%c输入后残留的
scanf
)限制长度:如
%s(最多读4个字符),避免输入过长导致缓冲区溢出(如
%4s,最多用
char str[10])
%9s
补充:printf的缓冲区问题(实操必知)
输出的内容不会立即显示在屏幕上,而是先存在”缓冲区”,满足以下条件才会显示:
printf
遇到换行符
手动调用强制刷新程序结束
fflush(stdout)
示例(验证缓冲区):
#include <stdio.h>
#include <unistd.h> // 包含sleep函数
int main() {
printf("Hello, "); // 无
,缓冲区未刷新
sleep(2); // 休眠2秒,屏幕无输出
printf("World!
");// 有
,触发刷新,屏幕显示完整内容
return 0;
}
运行结果:休眠2秒后,一次性显示
Hello, World!
2.4 类型转换:隐式转换风险与显式转换规范
不同类型的变量运算时(如),会发生”类型转换”。重点掌握”隐式转换的坑”和”显式转换的正确用法”,避免数据出错。
int + char
2.4.1 核心理论
隐式转换:编译器自动完成(无需写代码),但可能导致错误显式转换:手动用指定(如
(目标类型)),明确意图,规避风险
(char)100
2.4.2 实操1:隐式转换的3大风险(必看避坑)
风险1:大类型转小类型→溢出(数据截断)
#include <stdio.h>
int main() {
int a = 300; // int范围:-2147483648~2147483647(300合法)
char c = a; // 隐式转换:int(4字节)→char(1字节),300超出char范围(-128~127)
printf("a=300 转char后:%d
", c); // 输出:44(300-256=44,数据被截断)
return 0;
}
运行结果:
a=300 转char后:44
风险2:有符号与无符号混合运算→逻辑错误
#include <stdio.h>
int main() {
unsigned int a = 10;
int b = 20;
// a-b=10-20=-10,但unsigned int不能存负数,解读为超大正数
if (a - b > 0) {
printf("a - b > 0(逻辑错误!)
"); // 会执行这条语句
} else {
printf("a - b <= 0
");
}
return 0;
}
运行结果:
a - b > 0(逻辑错误!)
风险3:char转int→符号位扩展(负数变超大数)
#include <stdio.h>
int main() {
signed char c = -1; // 二进制:11111111(符号位为1)
int i = c; // 隐式转换:char→int,符号位扩展为1(4字节:11111111 11111111 11111111 11111111)
printf("signed char -1 转int后:%d
", i); // 输出:-1(有符号扩展,正确)
unsigned char uc = 255; // 二进制:11111111(无符号位)
int ui = uc; // 隐式转换:unsigned char→int,高位补0
printf("unsigned char 255 转int后:%d
", ui); // 输出:255(正确)
return 0;
}
运行结果:
signed char -1 转int后:-1
unsigned char 255 转int后:255
2.4.3 实操2:显式转换的正确用法
显式转换的语法:,用于”明确知道转换安全”或”必须转换”的场景:
(目标类型) 表达式
#include <stdio.h>
int main() {
// 场景1:大类型转小类型(确认数值在目标类型范围内)
int a = 100;
char c = (char)a; // 100在char范围(-128~127),安全
printf("(char)100 = %d
", c); // 输出:100
// 场景2:整数转浮点数(避免整数除法)
int num1 = 3;
int num2 = 2;
float res1 = num1 / num2; // 隐式转换:整数除法,结果1.000000
float res2 = (float)num1 / num2; // 显式转换:num1→float,结果1.500000
printf("3/2(隐式转换):%f
", res1);
printf("(float)3/2(显式转换):%f
", res2);
return 0;
}
运行结果:
(char)100 = 100
3/2(隐式转换):1.000000
(float)3/2(显式转换):1.500000
显式转换注意事项:
转换前必须确认数值在目标类型范围内(如不安全)浮点数转整数会”直接截断小数部分”(如
(char)300→3,不是4)避免指针类型随意转换(如
(int)3.9转
int*),容易导致内存访问错误
char*
2.5 基础练习(巩固实操)
练习1:验证int的溢出现象
编写程序,让变量存储超出最大值的数,观察结果:
int
#include <stdio.h>
#include <limits.h>
int main() {
int max = INT_MAX; // int的最大值:2147483647
int overflow = max + 1; // 溢出
printf("int最大值:%d
", max);
printf("int最大值+1:%d
", overflow); // 输出:-2147483648(溢出后循环)
return 0;
}
练习2:用scanf输入两个整数,计算和并格式化输出
要求:输出时占10位,左对齐,保留0位小数(本质是整数):
#include <stdio.h>
int main() {
int a, b;
printf("输入两个整数,用空格分隔:");
scanf("%d %d", &a, &b);
int sum = a + b;
printf("和为:%-10d(占10位左对齐)
", sum);
return 0;
}
运行示例:
输入两个整数,用空格分隔:123 456
和为:579 (占10位左对齐)
练习3:避免隐式转换的逻辑错误
修改”有符号与无符号混合运算”的示例,用显式转换避免错误:
#include <stdio.h>
int main() {
unsigned int a = 10;
int b = 20;
// 显式转换:将unsigned int转为int,再运算
if ((int)a - b > 0) {
printf("a - b > 0
");
} else {
printf("a - b <= 0(正确!)
"); // 会执行这条语句
}
return 0;
}
运行结果:
a - b <= 0(正确!)
2.6 本章核心理论总结
关键理论要点
内存模型:程序视角的线性字节数组,变量是类型化的内存区域补码系统:统一的整数表示法,解决0的重复表示问题IEEE 754:浮点数的科学计数法二进制实现类型安全:隐式转换的风险源于值域和精度的不匹配缓冲区机制:I/O性能优化的核心机制
实践指导原则
内存意识:理解变量的存储形式和地址关系范围检查:在转换前验证值域兼容性精度认知:理解浮点数的精度限制显式优于隐式:使用明确的类型转换边界测试:重点测试数据类型的边界情况
本章建立了数据表示的理论基础,后续章节将基于这些概念构建更复杂的数据结构和算法。


