第2章:数据表示与内存基础(基础理论+实操版)【20251121】

文章目录

第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
):

若类型是
char
,解读为字符’A’若类型是
int
,解读为整数65地址不变,类型变了,解读结果完全不同

实操:查看变量的地址和大小

通过
&
(取地址符)看变量地址,
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 字节

注意事项

地址是连续分配的:变量
c
(1字节)→
i
(4字节)→
f
(4字节),地址依次递增,增量等于前一个变量的大小
sizeof
是关键字,不是函数:括号可省略(如
sizeof c
),计算的是”变量/类型占用的内存大小”,和变量值无关

2.2 基础数据类型:大小、范围与实操验证

C语言的基础类型是”操作数据的最小单位”,重点掌握
char
(字符/单字节整数)、
int
(整数)、
float
(小数),核心记住”大小、范围、用途”。

2.2.1 核心理论:3种基础类型对比表

类型 占用大小(通用) 取值范围(常用) 核心用途

char
1字节 -128 ~ 127(有符号) 存储字符(如’A’)、单字节数据

unsigned char
1字节 0 ~ 255(无符号) 存储非负单字节数据(如像素)

int
4字节 -2147483648 ~ 2147483647 通用整数运算(计数、编号等)

unsigned int
4字节 0 ~ 4294967295 存储非负整数(如ID、计数)

float
4字节 ±3.4×10³⁸(约) 存储小数(精度要求不高场景)

关键补充

有符号(
signed
):默认类型,能存正数、负数(最高位是符号位:0=正,1=负)无符号(
unsigned
):只能存非负数,取值范围更大(无符号位,所有位都是数值位)
float
精度有限:仅能精确表示6~7位十进制有效数字(如
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
浮点数比较:相等

注意事项

浮点数不能用
==
直接比较,必须通过”差值小于极小值”判断若需高精度(如金融计算),用
double
(8字节,有效数字15~16位),但同样存在精度问题,需用专门的高精度库

2.3 格式化I/O:printf/scanf实操

程序需要”接收外部数据”(输入)和”展示处理结果”(输出),
printf
(输出)和
scanf
(输入)是C语言最基础的工具,重点掌握”格式符怎么用”“常见坑怎么避”。

2.3.1 核心理论:常用格式符(一一对应类型)

格式符 对应类型 功能描述 示例 输出/输入结果

%c

char
输出/输入单个字符
printf("%c", 'A')
输出:A

%d

int
输出/输入十进制整数
printf("%d", 123)
输出:123

%u

unsigned int
输出/输入无符号整数
printf("%u", 255)
输出:255

%f

float
输出/输入单精度浮点数
printf("%f", 3.14)
输出:3.140000

%s

char[]
输出/输入字符串
printf("%s", "abc")
输出:abc

%p
指针/变量地址 输出内存地址
printf("%p", &a)
输出: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
限制长度:如
%4s
(最多读4个字符),避免输入过长导致缓冲区溢出(如
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
不安全)浮点数转整数会”直接截断小数部分”(如
(int)3.9
→3,不是4)避免指针类型随意转换(如
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性能优化的核心机制

实践指导原则

内存意识:理解变量的存储形式和地址关系范围检查:在转换前验证值域兼容性精度认知:理解浮点数的精度限制显式优于隐式:使用明确的类型转换边界测试:重点测试数据类型的边界情况

本章建立了数据表示的理论基础,后续章节将基于这些概念构建更复杂的数据结构和算法。

© 版权声明

相关文章

暂无评论

none
暂无评论...