文章目录
【全文大纲】 : https://blog.csdn.net/Engineer_LU/article/details/135149485
1 . 概要
- 深入浅出C语言,以下内容简洁,适合入门与加深理解
2 . C语言语法
| auto | break | case | char | const | continue | default | do |
|---|---|---|---|---|---|---|---|
| double | else | enum | extern | float | for | goto | if |
| int | long | register | return | short | signed | sizeof | static |
| struct | switch | typedef | union | unsigned | void | volatile | while |
2.1 关键字解释、
- auto :声明自动变量
- break:跳出当前循环
- case:开关语句分支
- char :声明字符型变量或函数返回值类型
- const :声明只读变量
- continue:结束当前循环,开始下一轮循环
- default:开关语句中的“默认”分支
- do :循环语句的循环体
- double :声明双精度浮点型变量或函数返回值类型
- else :条件语句否定分支(与 if 连用)
- enum :声明枚举类型
- extern:声明变量或函数是在其它文件或本文件的其他位置定义
- float:声明浮点型变量或函数返回值类型
- for:一种循环语句
- goto:无条件跳转语句
- if: 条件语句
- int: 声明整型变量或函数
- long :声明长整型变量或函数返回值类型
- register:声明寄存器变量
- return :子程序返回语句(可以带参数,也可不带参数)
- short :声明短整型变量或函数
- signed:声明有符号类型变量或函数
- sizeof:计算数据类型或变量长度(即所占字节数)
- stati` :声明静态变量
- struct:声明结构体类型
- switch :用于开关语句
- typedef:用以给数据类型取别名
- unsigned:声明无符号类型变量或函数
- union:声明共用体类型
- void :声明函数无返回值或无参数,声明无类型指针
- volatile:说明变量在程序执行中可被隐含地改变
- while :循环语句的循环条件
3 . C语言运算符优先级

4 . 本质理解
4.1 内存的本质:数字世界的生命与轮回
- 在C语言的世界里,学习语法之前,我们首先要理解一个最基础也最重要的概念——内存。
- 想象我们身处的自然界:地球上有70多亿人,每个人都是独特的生命单元。我们从自然中诞生——吸收阳光、空气、水,从自然获取物质构成身体;我们最终又回归自然——生命结束后,身体分解,元素重新进入大自然的循环。
- 每一块内存单元都像是数字宇宙中的基本粒子
- 当我们声明一个变量时,我们是在向操作系统申请一小块“自然空间”——这是内存的诞生
- 当我们为变量赋值时,我们是在用数据为这块空间赋予“生命的意义”
- 当变量超出作用域或被显式释放时,内存回归系统的“自然状态”——这是内存的回归
4.2 语法的本质:掌控数字宇宙的至高功法
- 理解了内存的本质,我们站在了数字宇宙的边缘。但仅凭领悟无法成为真正的主宰者——就像理解海洋的广阔,不等于能够驾驭惊涛骇浪。认知与掌控之间,隔着一条名为“功法”的鸿沟。
- 只有理解规律,才能驾驭万物。只有掌握方法,才能塑造世界。于是,他们倾尽智慧,创出了这部数字世界的至高功法——《C语言》。这不仅仅是一门编程语言,而是一整套掌控内存、塑造现实的道法术器。
5 . 语法应用
5.1 简单示例
int main(int argc,char* argv[]) {
unsigned char value = 0x01;
if (value == 0x01) {
;
}
return 0;
}
语法极其简洁,可能有读者不太了解 “ int argc,char* argv[] ”:这里的内容意义,由于C语言是面向过程,系统级,当系统调用一个程序时,可以执行初期给定输入数据,其中前者是数据数量,后者是具体数据
5.2 指针:时空操控的灵魂之术
5.2.1 跨越维度的力量
在C语言的修行之路上,指针是让无数初学者望而生畏,却又让大师们如鱼得水的核心功法。它不是阻碍,而是通往内存主宰之境的必经之门。当我们谈论指针时,我们谈论的是一种跨越时空的操控能力。
5.2.2 为何需要指针?时空效率的双重突破
在数字宇宙中,每一次变量操作都是在时空中留下痕迹。直接操作变量如同每次都要亲临现场:
// 传统方式:亲力亲为
int data = 100; // 创造
data = data * 2; // 修改
int backup = data; // 复制(消耗额外时空)
这种方式的局限显而易见:
- 时间消耗:大量数据复制需要CPU周期
- 空间消耗:每个副本占用额外内存
- 效率瓶颈:难以跨越函数边界传递大型数据
指针的出现,是时空操控的一次革命性突破。
5.2.3 指针的本质:坐标与存在
int reality = 42; // 在某个坐标创造存在
printf("存在本身: %d\n", reality); // 42
printf("存在坐标: %p\n", &reality); // 0x7ffeebd39a9c
// &reality 不是值,而是"这里"的坐标
// 就像"北京市朝阳区建国门外大街1号"不是建筑本身,而是位置
#### 坐标系统:&运算符
每个变量在数字宇宙中都有一个绝对坐标——内存地址。&运算符就是获取这个坐标的钥匙:
5.2.4 时空穿梭:*运算符
拥有坐标后,我们可以跨越时空直接操控。*运算符就是时空穿梭之门:
int* observer = &reality; // 获得坐标,成为观察者
printf("坐标指向的存在: %d\n", *observer); // 42
*observer = 2024; // 无需亲自到场,直接修改
printf("修改后的存在: %d\n", reality); // 2024
5.2.5 灵魂出窍:隔空取物的高阶功法
让我们通过一个精妙的示例,体会指针带来的效率革命:
int main(int argc, char* argv[]) {
u8 *consciousness = NULL; // 意识体,暂时不依附任何存在
u8 body = 1; // 肉身,初始状态为1
consciousness = &body; // 意识出窍,附身于肉身
*consciousness = 2; // 隔空操控,直接修改肉身状态
// 传统方式需要这样检查
if (body == 1) { // 此时body已经是2,无需进入
body = 2; // 这段代码永远不会执行
}
return 0;
}
灵魂出窍的哲学意义
想象一下,你有一个强大的意识体(指针):
- 没有指针:你需要走到每个人面前,亲口告诉他们该做什么
- 拥有指针:你的意识可以瞬间附身任何人,直接操控他们的行动
这种效率差异是指数级的!
5.2.6 指针功法
- 一取地址得坐标,二解引用触本质。
- 三传指针免复制,四动内存创天地。
- 五防悬空避崩溃,六置NULL归虚无。
- 七层递进掌万物,八面玲珑控时空。
结语:从畏惧到精通
指针不是C语言的难点,而是精髓所在。初学者看到的可能是语法复杂性,但修行者看到的是时空操控的自由度。当你掌握了指针,你就掌握了:
-
跨越函数边界的操控能力
-
动态创造和销毁世界的力量
-
避免不必要数据复制的智慧
-
构建复杂数据结构的基石
指针,是连接意识与存在的桥梁,是数字宇宙中最高效的沟通方式。 不要畏惧它,而要渴望掌握它。因为当你真正理解指针的那一刻,你就不再是内存的访客,而是它的主宰者。
5.3 C语言结构体:数据封装与内存管理的艺术
引言:为什么需要结构体?
在C语言编程中,当我们需要处理一组逻辑上相关但类型可能不同的数据时,简单的数组显得力不从心。数组要求所有元素类型相同,而现实世界中的数据往往是多元化的。结构体(struct)正是为了解决这一问题而设计的,它允许我们将不同类型的数据组合成一个单一的逻辑单元。
5.3.1 结构体与数组的本质区别
内存布局对比
// 数组:相同类型元素的连续集合
u8 array[10]; // 10个连续的u8类型内存单元
// 结构体:不同类型成员的组合
typedef struct {
u8 id; // 1字节
u16 value; // 2字节(注意对齐问题)
u32 count; // 4字节
} SensorData; // 总共可能需要7字节(考虑对齐后可能是8字节)
关键差异:
- 数组提供同质化数据存储,支持随机访问和迭代
- 结构体提供异质化数据封装,强调数据之间的逻辑关联性
5.3.2 结构体的专业应用
1. 数据封装与抽象
// 定义学生信息结构体
typedef struct {
u32 student_id; // 学号
char name[50]; // 姓名
u8 age; // 年龄
float gpa; // 平均绩点
u8 course_count; // 选修课程数
} Student;
2. 内存映射与硬件寄存器访问
在嵌入式系统和操作系统开发中,结构体常用于精确映射硬件寄存器:
// 映射串口控制寄存器
typedef struct {
volatile u32 DATA; // 数据寄存器
volatile u32 STATUS; // 状态寄存器
volatile u32 CONTROL; // 控制寄存器
volatile u32 BAUD; // 波特率寄存器
} UART_Registers;
// 定义UART1的寄存器映射
#define UART1_BASE 0x40001000
UART_Registers* uart1 = (UART_Registers*)UART1_BASE;
// 直接操作寄存器
uart1->BAUD = 9600; // 设置波特率
uart1->CONTROL |= 0x01; // 使能UART
3. 灵活的内存操作
结构体支持指针访问,但需注意类型安全和内存对齐:
typedef struct {
u8 status; // 状态标志
u32 data; // 数据值
u16 checksum; // 校验和
} Packet;
Packet packet;
int main() {
u8* raw_ptr = NULL;
// 获取结构体起始地址
raw_ptr = (u8*)&packet;
// 通过字节指针操作结构体内容
// 注意:这种操作需要了解结构体内存布局和对齐
*raw_ptr = 0x01; // 设置status字段
// 更安全的访问方式是通过结构体成员
packet.status = 0x01;
packet.data = 0x12345678;
packet.checksum = 0xABCD;
return 0;
}
5.3.3结构体的高级特性
1. 内存对齐与优化
编译器会对结构体进行内存对齐以提高访问效率:
// 编译器可能会在成员之间插入填充字节
typedef struct {
u8 a; // 1字节
// 编译器可能插入3字节填充(在32位系统上)
u32 b; // 4字节
u16 c; // 2字节
// 编译器可能插入2字节填充
} UnoptimizedStruct; // 总大小可能为12字节
// 优化版本:按大小降序排列减少填充
typedef struct {
u32 b; // 4字节
u16 c; // 2字节
u8 a; // 1字节
// 只插入1字节填充
} OptimizedStruct; // 总大小可能为8字节
2. 位域:紧凑存储布尔标志
// 使用位域优化存储空间
typedef struct {
u8 is_active : 1; // 1位:是否激活
u8 mode : 2; // 2位:模式选择(0-3)
u8 priority : 3; // 3位:优先级(0-7)
u8 reserved : 2; // 2位:保留位
} DeviceStatus; // 总共1字节
3. 结构体嵌套与数据层次
// 日期结构体
typedef struct {
u16 year;
u8 month;
u8 day;
} Date;
// 员工信息结构体(嵌套使用)
typedef struct {
u32 emp_id;
char name[50];
Date hire_date; // 嵌套结构体
float salary;
struct { // 匿名结构体:联系信息
char phone[15];
char email[50];
} contact;
} Employee;
5.3.4 工程实践建议
1. 命名规范
- 使用有意义的名称,避免缩写
- 结构体类型名使用sPascalCase
- 结构体变量使用PascalCase或蛇形命名随个人意愿
2. 封装与访问控制
// 头文件声明(public_interface.h)
typedef struct DataHandle DataHandle; // 不完全类型,隐藏实现细节
// 公共API
DataHandle* create_data_handle(u32 size);
void process_data(DataHandle* handle);
void destroy_data_handle(DataHandle* handle);
// 实现文件(private_implementation.c)
struct DataHandle { // 具体实现对外隐藏
u8* buffer;
u32 size;
u32 capacity;
u8 checksum;
};
3. 错误处理模式
typedef struct {
int error_code;
char error_msg[100];
void* data;
size_t data_size;
} OperationResult;
OperationResult read_file(const char* filename) {
OperationResult result = {0};
// 执行操作
FILE* file = fopen(filename, "rb");
if (!file) {
result.error_code = ERR_FILE_NOT_FOUND;
snprintf(result.error_msg, sizeof(result.error_msg),
"无法打开文件: %s", filename);
return result;
}
// ... 读取文件内容
return result;
}
5.3.5 结论
结构体是C语言中实现数据封装和抽象的核心机制。它不仅解决了异质数据集合的管理问题,还通过内存对齐、位域等特性提供了精细的内存控制。在大型工程中,合理使用结构体能够:
- 提高代码可读性:通过有意义的成员名称代替晦涩的数组索引
- 增强类型安全:编译器能够检查结构体成员的类型正确性
- 简化维护:相关数据的修改被限制在结构体定义范围内
- 优化内存访问:合理对齐可以显著提升CPU缓存效率
- 支持复杂数据结构:为链表、树、图等数据结构奠定基础
正如生物体由不同类型的细胞有序组成,良好的软件结构也需要将相关数据高内聚地组织在一起。结构体正是实现这一目标的关键工具,它让C语言在保持底层控制能力的同时,具备了构建复杂系统的表达能力。
6 . 编译过程
6.1 预处理
#define macro 0x01
void func() {
unsigned char x = macro;
}
预处理阶段时把宏macro替换为0x01
6.2 编译
unsigned char x = macro;
MOVS r0, #0x01
编译阶段把C语言转换成汇编内容, 因为这里在局部变量因此被分配到缓存寄存器R0
6.3 汇编
MOVS R0, #0x01
0x2001 汇编阶段把汇编指令根据当前平台转换成机器码
6.4 链接
程序中N条汇编机器码需要整合在一起,此时把这些机器码链接在一块输出最终可执行文件
编译过程大致如此,【Q】最终可执行文件为什么可以执行?【A】计算机最终执行权是CPU,CPU按照Flash或RAM区域地址逐步执行,而当可执行文件存到Flash或RAM区域,CPU就会根据可执行文件的内容逐步执行,这里讲解一个例子,例如当我在桌面GUI右击一下鼠标,为什么桌面会弹窗出来,步骤是这样,鼠标机械动作触发了电信号,经过USB协议与电脑主板通信,电脑主板把收到的数据反馈给CPU,由于加载了系统,系统层面会接收到鼠标这种HID设备事件,系统事件循环触发系统GUI已定逻辑,显存加载弹窗的数据,系统界面弹窗,这一切都是因为电脑系统已经集成了逻辑,这个系统逻辑就是用C语言逐步写出来的,也是经过了上述编译四个过程得出的程序
7 . 小结
学习C语言平时用C free,VS等软件验证程序,若中途学习感到困惑有阻碍,不必忧心,多看多写,自然就明白了其中的逻辑,另不必刻意背运算优先级,写着自然就记住了,印象模糊时来回顾一遍运算优先级,看到这里,相信大家已经入门。
技术交流QQ群 : 745662457
群内专注问题答疑,技术交流,项目研究
1万+

被折叠的 条评论
为什么被折叠?



