我花了 3 个月时间,写了一个 C 语言电子书,以非常通俗的语言跟大家讲解 C 语言,把复杂的技术讲得连小学生都能听得懂,绝不是 AI 生成那种晦涩难懂的电子垃圾。
在我们的日常生活中,我们会遇到各种各样的信息:数字、文字、图片、声音等等。比如你的年龄是一个数字,你的姓名是一段文字,你的照片是图像信息。不同类型的信息需要用不同的方式来处理和存储。
数据类型就像是给数据贴上的"标签",告诉计算机这个数据是什么类型的,应该如何处理。就像超市里的商品都有标签一样,食品类商品有食品标签,电子产品有电子产品标签,不同的标签决定了商品的处理方式。
2.1.1 数据类型分类
整个C语言数据类型家族可以分为两大主要分支:基本数据类型和构造数据类型。这就像一个大家族分为"原生家庭成员"和"通过结合组成的新家庭"一样。
基本数据类型是C语言中最基础、最原始的数据类型,就像化学中的原子一样,它们是构成其他复杂数据类型的基础。基本数据类型又可以细分为几个小类:
整型数据类型专门用来存储整数,就像我们数学中学习的整数一样:...,-3,-2,-1,0,1,2,3,...
每种整型还可以加上unsigned修饰符,表示"无符号",也就是只能存储非负数(0和正数)。这就像把负数的存储空间也用来存储正数,所以无符号类型能存储的正数范围会翻倍。
浮点型用来存储小数,比如3.14,2.718,0.5等等。为什么叫"浮点"呢?这是因为小数点的位置是"浮动"的,可以在数字中的任何位置。
- float:单精度浮点数,就像用普通的尺子测量长度,精度有限但够用。它通常能提供大约6-7位有效数字的精度。
- double:双精度浮点数,就像用精密的游标卡尺测量,精度更高。它通常能提供大约15-16位有效数字的精度。大多数情况下,我们使用double来处理小数。
- long double:扩展精度浮点数,精度最高,但在不同系统中的具体实现可能不同。
char类型用来存储单个字符,比如字母'A',数字'5',标点符号'!'等等。需要注意的是,字符要用单引号括起来,比如'A',而不是"A"。
2. 构造数据类型详解
数组类型
数组有一维数组、二维数组、多维数组等。一维数组像是一排柜子,二维数组像是一个柜子矩阵(行和列),三维数组像是一个立体的柜子组合。
指针是C语言中一个非常重要但也比较难理解的概念。指针就像是地址标签,它不直接存储数据,而是存储数据的地址。
结构体类型
联合体类型
枚举类型
3. 自定义数据类型
比如,我们可以定义:
typedef int StudentAge; // 定义学生年龄类型typedef float StudentHeight; // 定义学生身高类型1. 内存的基本概念
在深入了解数据存储之前,我们需要理解两个基本概念:位(bit)和字节(byte)。
字节(byte)由8个位组成,是计算机中基本的存储单位。一个字节可以存储256种不同的值(从00000000到11111111,也就是十进制的0到255)。为什么是8个位呢?这是历史上形成的标准,8个位恰好可以表示一个英文字符。
不同的数据类型在内存中占用的空间是不同的,这就像不同大小的物品需要不同大小的盒子来装一样。
char类型通常占用1个字节的空间。一个字节的8个位可以表示256种不同的值,这足够表示所有的ASCII字符(包括大小写字母、数字、标点符号等)。
整型
这就像我们有不同大小的盒子:小盒子装小物品,大盒子装大物品。如果我们知道要装的物品不大,就不需要浪费空间使用大盒子。
浮点数的存储比整数复杂得多,它分为三个部分:符号位、指数位和尾数位。这就像科学计数法一样,比如3.14×10^2,其中3.14是尾数,2是指数,符号是正号。
计算机内部所有数据都是以二进制形式存储的,也就是只用0和1来表示。这就像用莫尔斯电码来传递信息一样,只用"滴"和"嗒"两种符号就能表示所有的文字。
正整数的二进制表示比较直观,就是将十进制数转换为二进制数。比如:
- 十进制的5在二进制中是101
- 十进制的10在二进制中是1010
字符的二进制表示
注意,字符'0'和数字0是不同的。字符'0'是一个显示符号,它的ASCII码是48;而数字0的二进制表示就是00000000。
在实际的内存存储中,还有一个重要的概念叫做"内存对齐"。这是为了提高内存访问效率而采用的策略。
想象一下,如果你要从书架上取书,整齐摆放的书比杂乱摆放的书更容易找到和取出。内存对齐就是这样,它让数据在内存中按照一定的规则整齐摆放。
这样做虽然可能浪费一些内存空间,但可以大大提高数据访问的速度。在结构体中,编译器会自动进行内存对齐,有时候结构体的实际大小会比各个成员大小的总和要大。
程序中的变量根据定义方式的不同,会被存储在内存的不同区域:
局部变量(在函数内部定义的变量)通常存储在栈区。栈区就像一摞盘子,后放的盘子在上面,先拿走的也是上面的盘子,这叫做"后进先出"。
堆区存储
全局区存储
1. 什么是字节序?
想象一下,你要在纸上写下数字"1234"。你会从左到右写,先写1,再写2,然后3,最后4。但是,如果有些人习惯从右到左写字,他们可能会先写4,再写3,然后2,最后1,最终在纸上呈现的可能是"4321"。
2. 大端序与小端序
大端序(Big Endian)
举个例子,十六进制数0x12345678在大端序的32位系统中会这样存储:
- 地址1000: 0x12(最高位字节)
- 地址1001: 0x34
- 地址1002: 0x56
- 地址1003: 0x78(最低位字节)
小端序(Little Endian)
同样的十六进制数0x12345678在小端序的32位系统中会这样存储:
- 地址1000: 0x78(最低位字节)
- 地址1001: 0x56
- 地址1002: 0x34
- 地址1003: 0x12(最高位字节)
4. 为什么会有不同的字节序?
历史原因
技术考虑
大端序的优势是比较直观,符合人类的阅读习惯。在网络传输中,大端序被广泛采用,所以也被称为"网络字节序"。
5. 不同系统的字节序
网络字节序
6. 字节序的影响
在大多数情况下,程序员不需要关心字节序问题,因为:
- 在同一台机器上运行的程序,字节序是一致的
- C语言的编译器会自动处理大部分字节序问题
- 高级语言通常会屏蔽这些底层细节
文件存储
比如,数字1234在小端序文件中可能存储为D2 04(十六进制),但在大端序系统读取时可能被解释为1234(十六进制),这完全是错误的值。
在网络编程中,经常需要在本地字节序和网络字节序之间转换。C语言提供了专门的函数来处理这种转换:
- htons():主机字节序转网络字节序(短整型)
- htonl():主机字节序转网络字节序(长整型)
- ntohs():网络字节序转主机字节序(短整型)
- ntohl():网络字节序转主机字节序(长整型)
在嵌入式系统开发中,特别是当需要与其他系统通信或处理特定格式的数据时,字节序问题就变得很重要。程序员需要明确知道数据的字节序,并进行正确的处理。
我们可以用一个简单的C程序来检测当前系统的字节序:
#include <stdio.h>int main() { int test = 1; char *p = (char*)&test; if (*p == 1) { printf("当前系统是小端序\n"); } else { printf("当前系统是大端序\n"); } return 0;}8. 字节序转换的实现
这个函数通过位运算来交换高低字节的位置,从而实现字节序转换。

