当前位置: 首页 > article >正文

C语言结构体完全指南:从基础到底层内存布局

引言在C语言中数组让我们能够存储一组相同类型的数据。但现实世界中的实体往往包含不同类型的信息——比如一个学生有姓名字符串、年龄整数、学号长整数、成绩浮点数。如何用一个变量来表示这些不同类型的数据呢结构体struct就是为了解决这个问题而生的。它允许我们将多个不同类型的变量组合成一个整体形成一个新的数据类型。今天我将从内存底层视角出发全面讲解C语言中结构体的定义、初始化、访问、内存对齐规则以及如何构建一个简单的学生管理系统。第一部分什么是结构体一、数组与结构体的对比特性数组结构体元素类型必须相同可以不同内存布局连续存储连续存储可能有对齐空隙访问方式通过下标arr[i]通过成员名stu.name类型本质相同类型的集合不同类型的集合赋值操作不能直接赋值可以直接赋值#include stdio.h #include string.h // 定义结构体类型相当于画图纸 struct student { char name[20]; // 姓名字符数组 int age; // 年龄整型 long long ID; // 学号长整型 double grade; // 成绩浮点型 }; // 注意分号不能少 int main() { // 使用结构体类型定义变量相当于按照图纸造房子 struct student stu1; struct student stu2; printf(struct student 大小: %zu 字节\n, sizeof(struct student)); return 0; }重要理解struct student是一个类型名就像int一样。struct student和int都是类型都可以用来定义变量。数组名可以退化为首元素地址但结构体变量名不能退化为地址。第二部分结构体的内存对齐核心难点一、什么是内存对齐为了提高CPU访问内存的效率编译器会在结构体成员之间插入“空隙”填充字节使每个成员都位于其自然边界上。这是C语言中一个容易被忽略但非常重要的概念。对齐规则每个成员的起始偏移量必须是其自身大小的整数倍结构体的总大小必须是最大成员大小的整数倍二、成员顺序影响结构体大小#include stdio.h // 示例1成员顺序不合理浪费空间 struct S1 { char c1; // 偏移量0占用1字节 // 偏移量1-3填充3字节为了对齐int int i; // 偏移量4-7占用4字节 char c2; // 偏移量8占用1字节 // 偏移量9-11填充3字节为了整体对齐 }; // 总大小12字节 // 示例2成员顺序合理节省空间 struct S2 { char c1; // 偏移量0占用1字节 char c2; // 偏移量1占用1字节 // 偏移量2-3填充2字节为了对齐int int i; // 偏移量4-7占用4字节 }; // 总大小8字节 // 示例3包含double的结构体 struct S3 { double d; // 偏移量0-7占用8字节 char c; // 偏移量8占用1字节 // 偏移量9-11填充3字节为了对齐int int i; // 偏移量12-15占用4字节 }; // 总大小16字节最大成员double是8字节16是8的倍数 int main() { printf(sizeof(struct S1): %zu\n, sizeof(struct S1)); // 12 printf(sizeof(struct S2): %zu\n, sizeof(struct S2)); // 8 printf(sizeof(struct S3): %zu\n, sizeof(struct S3)); // 16 // 结论将较大的成员放在前面可以节省内存 return 0; }三、复杂结构体的内存布局#include stdio.h // S5包含数组的结构体 struct s5 { char a[7]; // 偏移量0-6占用7字节对齐到1 // 偏移量7填充1字节为了对齐int数组 int b[2]; // 偏移量8-15占用8字节int是4字节数组连续 float c; // 偏移量16-19占用4字节 double d; // 偏移量24-31占用8字节对齐到8 char e[11]; // 偏移量32-42占用11字节 // 偏移量43-47填充5字节整体对齐到8的倍数48 }; // 总大小48字节 // S6包含结构体数组的嵌套结构体 struct s6 { char a; // 偏移量0占用1字节 // 偏移量1-7填充7字节为了对齐s5数组 struct s5 b[9]; // 偏移量8-4319 * 48 432字节占用432字节 struct s6* c[10]; // 偏移量440-51910 * 8 80字节占用80字节 double d; // 偏移量520-527占用8字节 short e[100]; // 偏移量528-727100 * 2 200字节占用200字节 }; // 总大小728字节 int main() { printf(sizeof(struct s5): %zu\n, sizeof(struct s5)); // 48 printf(sizeof(struct s6): %zu\n, sizeof(struct s6)); // 728 return 0; }四、手动控制对齐#pragma pack#include stdio.h // 默认对齐 struct Normal { char c; int i; char d; }; // 12字节 // 强制按1字节对齐减少空间降低访问效率 #pragma pack(1) struct Packed { char c; int i; char d; }; #pragma pack() // 强制按2字节对齐 #pragma pack(2) struct Packed2 { char c; int i; // int原本需要4字节对齐现在只按2字节对齐 char d; }; #pragma pack() int main() { printf(默认对齐: %zu\n, sizeof(struct Normal)); // 12 printf(1字节对齐: %zu\n, sizeof(struct Packed)); // 6 printf(2字节对齐: %zu\n, sizeof(struct Packed2));// 8 return 0; }第三部分结构体的定义与初始化一、三种定义方式#include stdio.h #include string.h // 方式1先定义类型再定义变量最常用 struct student { char name[20]; int age; long long ID; double grade; }; struct student stu1; // 定义变量 // 方式2定义类型的同时定义变量 struct teacher { char name[20]; int years; double salary; } t1, t2; // 同时定义了两个变量 // 方式3定义类型的同时定义变量并省略类型名不推荐 struct { char title[50]; int pages; } book1, book2; int main() { // 方式1的变量使用 strcpy(stu1.name, 张三); stu1.age 20; stu1.ID 2024001; stu1.grade 95.5; return 0; }二、初始化的多种方式#include stdio.h #include string.h struct student { char name[20]; int age; long long ID; double grade; }; int main() { // 方式1定义时直接初始化按顺序 struct student stu1 {张三, 21, 2331051646, 100}; // 方式2定义后逐个赋值 struct student stu2; strcpy(stu2.name, 李四); stu2.age 19; stu2.ID 2431272427; stu2.grade 100; // 方式3指定成员初始化C99未指定的成员初始化为0 struct student stu3 {.name 王五, .grade 100}; // 方式4全部初始化为0 struct student stu4 {0}; // 方式5通过输入赋值 struct student stu5; printf(请输入姓名: ); scanf(%s, stu5.name); // 数组名本身就是地址不需要 printf(请输入年龄: ); scanf(%d, stu5.age); printf(请输入学号: ); scanf(%lld, stu5.ID); printf(请输入成绩: ); scanf(%lf, stu5.grade); return 0; }第四部分结构体的访问与传参一、访问结构体成员#include stdio.h #include string.h struct student { char name[20]; int age; long long ID; double grade; }; int main() { struct student stu {张三, 20, 2024001, 95.5}; // 方法1通过变量名直接访问使用点运算符 . printf(姓名: %s\n, stu.name); printf(年龄: %d\n, stu.age); printf(学号: %lld\n, stu.ID); printf(成绩: %.2f\n, stu.grade); // 修改成员 stu.age 21; stu.grade 98.0; // 方法2通过指针间接访问使用箭头运算符 - struct student* p stu; printf(通过指针 - 姓名: %s\n, p-name); printf(通过指针 - 年龄: %d\n, p-age); // 等价写法不推荐太繁琐 printf(通过指针 - 学号: %lld\n, (*p).ID); return 0; }二、结构体作为函数参数传值 vs 传地址重要结构体作为函数参数时默认是值传递整个结构体被拷贝。如果结构体较大传值会消耗大量内存和时间。推荐使用指针传递。#include stdio.h #include string.h struct student { char name[20]; int age; long long ID; double grade; }; // 方式1传值会拷贝整个结构体占用40字节 void printByValue(struct student s) { printf(传值 - 姓名: %s, 年龄: %d\n, s.name, s.age); // 修改不会影响原变量 s.age 100; } // 方式2传地址只拷贝指针占8字节推荐 void printByPointer(struct student* p) { printf(传地址 - 姓名: %s, 年龄: %d\n, p-name, p-age); // 修改会影响原变量 p-age 100; } // 方式3传地址只读使用const保护 void printByConstPointer(const struct student* p) { printf(只读 - 姓名: %s, 年龄: %d\n, p-name, p-age); // p-age 100; // 错误const修饰不能修改 } int main() { struct student stu {李四, 20, 2024002, 88.5}; printByValue(stu); printf(传值后原变量年龄: %d\n, stu.age); // 仍然是20 printByPointer(stu); printf(传地址后原变量年龄: %d\n, stu.age); // 变成了100 printByConstPointer(stu); return 0; }传值与传地址对比方式语法拷贝大小能否修改原变量推荐程度传值void func(struct Student s)整个结构体不能不推荐大结构体传地址void func(struct Student* p)指针8字节能推荐传const地址void func(const struct Student* p)指针8字节不能只读时推荐第五部分结构体数组一、定义与初始化#include stdio.h struct student { char name[20]; int age; long long ID; double grade; }; int main() { // 定义并初始化结构体数组 struct student tl[10] { {张三, 21, 233533333, 100}, {吉五, 18, 20664630, 100}, {杨六, 18, 85633513, 100}, {郑七, 18, 8653233343, 100}, {齐八, 19, 9864561, 100}, {汪九, 19, 798465323, 100}, {茹十, 18, 986532, 100}, {尹11, 19, 1, 100}, }; int len sizeof(tl) / sizeof(tl[0]); printf(姓名 年龄 学号 成绩\n); for (int i 0; i len; i) { printf(%s %d %lld %.2f\n, tl[i].name, tl[i].age, tl[i].ID, tl[i].grade); } // 结构体数组的地址特性 printf(tl %p\n, tl); // 指向第一个元素 printf(tl 1 %p\n, tl 1); // 跳过一个结构体大小 printf(tl %p\n, tl); // 整个数组的地址 printf(tl 1 %p\n, tl 1); // 跳过整个数组 return 0; }二、结构体数组的遍历函数#include stdio.h #include string.h struct student { char name[20]; int age; long long ID; double grade; }; // 打印单个学生 void printStudent(const struct student* p) { printf(%-10s %-6d %-12lld %-8.2f\n, p-name, p-age, p-ID, p-grade); } // 打印所有学生 void printAllStudents(struct student arr[], int len) { printf(%-10s %-6s %-12s %-8s\n, 姓名, 年龄, 学号, 成绩); printf(----------------------------------------\n); for (int i 0; i len; i) { printStudent(arr[i]); } } // 计算平均年龄 double calcAverageAge(struct student arr[], int len) { double sum 0; for (int i 0; i len; i) { sum arr[i].age; } return sum / len; } // 统计不同长度姓名的数量注意UTF-8中文在不同平台字节数不同 void countNameLength(struct student arr[], int len) { int twoChars 0; // 2个汉字 int threeChars 0; // 3个汉字 int fourChars 0; // 4个汉字 for (int i 0; i len; i) { int byteLen strlen(arr[i].name); // VS中一个汉字2字节VSCode中一个汉字3字节 // 这里按实际字节数判断 if (byteLen 4) { twoChars; } else if (byteLen 6) { threeChars; } else if (byteLen 8) { fourChars; } } printf(两字姓名: %d人\n, twoChars); printf(三字姓名: %d人\n, threeChars); printf(四字姓名: %d人\n, fourChars); } int main() { struct student tl[10] { {张三, 21, 233533333, 100}, {吉五, 18, 20664630, 100}, {杨六, 18, 85633513, 100}, {郑七, 18, 8653233343, 100}, {齐八, 19, 9864561, 100}, {汪九, 19, 798465323, 100}, {茹十, 18, 986532, 100}, {尹11, 19, 1, 100}, }; int len sizeof(tl) / sizeof(tl[0]); printAllStudents(tl, len); printf(\n平均年龄: %.2f\n, calcAverageAge(tl, len)); printf(\n); countNameLength(tl, len); return 0; }第六部分typedef 的使用一、为什么需要 typedeftypedef用于给已有类型起别名可以简化代码书写。#include stdio.h #include string.h // 不使用 typedef每次都要写 struct student struct student { char name[20]; int age; }; // 使用 typedef可以省略 struct 关键字 typedef struct teacher { char name[20]; int years; } Teacher; // Teacher 是 struct teacher 的别名 // 更简洁的写法直接定义类型别名 typedef struct { char title[50]; int pages; } Book; int main() { // 不使用 typedef struct student stu1; strcpy(stu1.name, 张三); // 使用 typedef Teacher t1; t1.years 10; Book b1; b1.pages 300; // 也可以给基本类型起别名 typedef int Integer; typedef unsigned long long ULL; Integer num 100; ULL bigNum 123456789012345ULL; return 0; }第七部分综合示例——学生管理系统#include stdio.h #include string.h #define MAX_NAME 20 #define MAX_STUDENTS 100 // 学生结构体 typedef struct Student { char name[MAX_NAME]; int age; long long id; double score; } Student; // 系统结构体 typedef struct System { Student students[MAX_STUDENTS]; // 学生数组 int count; // 当前学生数量 int capacity; // 最大容量 } System; // 初始化系统 void initSystem(System* sys) { sys-count 0; sys-capacity MAX_STUDENTS; } // 添加学生 int addStudent(System* sys) { if (sys-count sys-capacity) { printf(系统已满无法添加\n); return -1; } Student* s sys-students[sys-count]; printf(请输入姓名: ); scanf(%s, s-name); printf(请输入年龄: ); scanf(%d, s-age); printf(请输入学号: ); scanf(%lld, s-id); printf(请输入成绩: ); scanf(%lf, s-score); sys-count; printf(添加成功当前学生人数: %d\n, sys-count); return 0; } // 打印单个学生 void printStudent(const Student* s) { printf(%-10s %-6d %-12lld %-8.2f\n, s-name, s-age, s-id, s-score); } // 打印所有学生 void printAllStudents(const System* sys) { if (sys-count 0) { printf(暂无学生数据\n); return; } printf(\n 学生列表 \n); printf(%-10s %-6s %-12s %-8s\n, 姓名, 年龄, 学号, 成绩); printf(----------------------------------------\n); for (int i 0; i sys-count; i) { printStudent(sys-students[i]); } printf(\n\n); } // 按姓名查找学生 int findStudentByName(const System* sys, const char* name) { for (int i 0; i sys-count; i) { if (strcmp(sys-students[i].name, name) 0) { return i; } } return -1; } // 删除学生 int deleteStudent(System* sys, const char* name) { int index findStudentByName(sys, name); if (index -1) { printf(未找到学生: %s\n, name); return -1; } // 将后面的元素向前移动 for (int i index; i sys-count - 1; i) { sys-students[i] sys-students[i 1]; } sys-count--; printf(删除成功\n); return 0; } // 计算平均成绩 double getAverageScore(const System* sys) { if (sys-count 0) return 0; double sum 0; for (int i 0; i sys-count; i) { sum sys-students[i].score; } return sum / sys-count; } // 菜单显示 void showMenu() { printf(\n 学生管理系统 \n); printf(1. 添加学生\n); printf(2. 查看所有学生\n); printf(3. 查找学生\n); printf(4. 删除学生\n); printf(5. 统计平均成绩\n); printf(0. 退出\n); printf(\n); printf(请选择: ); } int main() { System sys; initSystem(sys); int choice; do { showMenu(); scanf(%d, choice); getchar(); // 清除缓冲区 switch (choice) { case 1: addStudent(sys); break; case 2: printAllStudents(sys); break; case 3: { char name[MAX_NAME]; printf(请输入要查找的姓名: ); scanf(%s, name); int idx findStudentByName(sys, name); if (idx ! -1) { printf(找到学生:\n); printStudent(sys.students[idx]); } else { printf(未找到学生: %s\n, name); } break; } case 4: { char name[MAX_NAME]; printf(请输入要删除的姓名: ); scanf(%s, name); deleteStudent(sys, name); break; } case 5: { double avg getAverageScore(sys); printf(全班平均成绩: %.2f\n, avg); break; } case 0: printf(退出系统...\n); break; default: printf(无效选择请重新输入\n); } } while (choice ! 0); return 0; }第八部分结构体常见陷阱一、结构体不能直接比较#include stdio.h #include string.h struct Point { int x; int y; }; int main() { struct Point p1 {1, 2}; struct Point p2 {1, 2}; // 错误不能直接比较结构体 // if (p1 p2) { } // 编译错误 // 正确逐个成员比较 if (p1.x p2.x p1.y p2.y) { printf(两个点相同\n); } // 正确使用 memcmp注意内存对齐的填充字节 if (memcmp(p1, p2, sizeof(struct Point)) 0) { printf(内存内容相同\n); } return 0; }二、结构体赋值是深拷贝还是浅拷贝#include stdio.h #include string.h struct Person { char name[20]; int* p; // 指针成员 }; int main() { int x 10; struct Person p1 {张三, x}; struct Person p2; // 结构体赋值是浅拷贝逐字节复制 p2 p1; printf(p1.name: %s, p2.name: %s\n, p1.name, p2.name); printf(p1.p: %p, p2.p: %p\n, p1.p, p2.p); // 两个指针指向同一地址 // 修改 p2 的指针指向的内容p1 也会受影响 *(p2.p) 20; printf(p1 指向的值: %d\n, *(p1.p)); // 20 // 字符数组是深拷贝因为数组内容被复制了 strcpy(p2.name, 李四); printf(p1.name: %s, p2.name: %s\n, p1.name, p2.name); // 不同 return 0; }总结一、结构体核心要点概念说明定义不同类型数据的集合内存连续存储但有内存对齐对齐规则成员偏移量是自身大小的倍数整体大小是最大成员的倍数访问点运算符.指针用箭头-传参推荐传地址节省空间、可修改typedef给结构体起别名简化代码二、内存对齐规则总结规则说明成员偏移量必须是该成员大小的整数倍整体大小必须是最大成员大小的整数倍优化技巧将较大的成员放在前面手动控制#pragma pack(n)强制按n字节对齐三、常见错误提醒错误说明忘记分号struct Student { ... }后面的分号不能少直接比较结构体不支持需要逐个成员比较传值导致效率低大结构体用指针传递忽略内存对齐影响结构体大小和跨平台兼容性结构体自引用不能包含自身类型的非指针成员需要指针结构体是C语言中组织不同类型数据的强大工具是面向对象编程思想在C语言中的体现。掌握结构体的定义、初始化、访问、内存对齐和传参是学习链表、文件操作等高级内容的基础。学习建议理解结构体内存对齐优化结构体布局函数传参优先使用指针使用 typedef 简化代码多练习构建现实世界的数据模型

相关文章:

C语言结构体完全指南:从基础到底层内存布局

引言在C语言中,数组让我们能够存储一组相同类型的数据。但现实世界中的实体往往包含不同类型的信息——比如一个学生有姓名(字符串)、年龄(整数)、学号(长整数)、成绩(浮点数&#x…...

别再死记硬背了!用LaTeX和Python搞定希腊字母(附发音与手写体对照表)

别再死记硬背了!用LaTeX和Python搞定希腊字母(附发音与手写体对照表) 理工科写作中,希腊字母就像空气一样无处不在——从薛定谔方程中的ψ到圆周率π,从统计学中的σ到电磁学里的μ。但每次需要输入这些符号时&#xf…...

BODIPY FL-Fe₃O₄ NPs,BODIPY FL标记四氧化三铁纳米颗粒,主要应用

BODIPY FL-Fe₃O₄ NPs,BODIPY FL标记四氧化三铁纳米颗粒,主要应用BODIPY FL-Fe₃O₄ NPs(BODIPY FL标记四氧化三铁纳米颗粒)是一类将磁性无机纳米核与绿色荧光染料BODIPY FL耦合构建的多功能纳米体系,兼具磁响应与稳定…...

风险管理化技术风险预警与应急预案

风险管理化技术风险预警与应急预案:构建安全防线 在数字化与智能化快速发展的今天,技术风险已成为企业运营和项目管理中的关键挑战。无论是数据泄露、系统故障,还是网络攻击,技术风险的突发性和破坏性都可能带来巨大损失。风险管…...

从CefSharp迁移到WebView2:一个WPF老项目的真实踩坑与性能对比

从CefSharp迁移到WebView2:WPF项目实战深度解析 当微软在2020年推出基于Chromium的Edge浏览器时,很多.NET开发者就开始关注其嵌入式组件WebView2的进展。作为长期使用CefSharp的老牌WPF开发者,我在最近一个企业级项目中完成了从CefSharp到Web…...

从MFC老手到Qt新手:我是如何在VS2015上平滑过渡,搞定第一个Qt GUI项目的

从MFC老手到Qt新手:在VS2015上实现技术栈平滑迁移的实战指南 第一次双击Qt Creator图标时,那种熟悉的Visual Studio工具栏突然消失带来的不适感,让我这个用了十年MFC的老Windows开发者差点摔了鼠标。但三个月后,当我用Qt重构完一个…...

Spring Boot 多数据源配置方案

Spring Boot多数据源配置方案详解 在复杂的业务场景中,单一数据源往往无法满足需求,例如需要同时连接多个数据库或读写分离。Spring Boot通过灵活的配置支持多数据源,为开发者提供了高效解决方案。本文将深入探讨多数据源的核心配置方法&…...

Python+MediaPipe 实现实时手部关键点检测(新手避坑完整版)

PythonMediaPipe 实现实时手部关键点检测(新手避坑完整版)在计算机视觉领域,手部关键点检测是一个非常基础且实用的需求,无论是手势识别、人机交互,还是手势控制,都离不开它的支持。而 MediaPipe 作为 Goog…...

Entity Framework Core 10向量搜索集成崩溃全复盘(含Microsoft.Data.Sqlite v8.0.10+OpenAI Embedding适配陷阱)

第一章:Entity Framework Core 10向量搜索集成崩溃全复盘(含Microsoft.Data.Sqlite v8.0.10OpenAI Embedding适配陷阱)崩溃现象与根本诱因 在 Entity Framework Core 10 中启用 SQLite 向量搜索时,应用在调用 SaveChangesAsync() …...

【Dify农业知识库开发实战指南】:20年专家亲授3大核心代码模块与5个避坑要点

第一章:Dify农业知识库开发实战导论Dify 是一款开源的低代码大模型应用开发平台,专为快速构建具备对话能力、知识检索与工作流编排能力的智能应用而设计。在农业数字化转型加速的背景下,将分散的农技文档、病虫害图谱、土壤参数标准、作物生长…...

人工智能知识体系(2026版)

人工智能知识体系 (AI Knowledge Architecture) 标记说明 • ● 高确定性:成熟稳定,可直接应用,风险可控 • ◐ 中确定性:技术可用但快速演进,需持续跟进 • ○ 低确定性:前沿探索,存在路径分歧,谨慎评估 关系类型: • 依赖关系(A → B):A是B的基础,需先掌握A • 并…...

别再乱改Serial参数了!深入解读ArduPilot地面站里Serial1到Serial7每个串口的预设功能

深入解析ArduPilot串口配置:从Serial1到Serial7的功能定位与避坑指南 当你第一次打开ArduPilot地面站的"全部参数表",看到Serial1到Serial7那一排神秘的参数时,是否感到困惑?特别是当某个串口显示为-1时,你是…...

让 AI 真正“听懂业务”并“按规矩办事”

一句话回顾:为什么 Agent 需要本体?本体为 AI Agent 提供了一幅“业务地图”。有了这幅地图的导航,Agent 才能在复杂的业务环境中看清方向,减少幻觉和错误。01 误区:本体不是另一个数据库或图谱很多人第一次接触“本体…...

深入K210人脸识别核心:手把手教你解读与优化196维特征值比对算法

深入K210人脸识别核心:手把手教你解读与优化196维特征值比对算法 在嵌入式AI领域,K210凭借其低功耗、高性能的特性成为人脸识别应用的理想选择。但真正让项目从"能运行"到"好用",关键在于对196维特征值算法的深度掌控——…...

从‘换脸’到‘换物’:手把手用Attention-GAN实现图片局部精准转换(避坑指南)

从‘换脸’到‘换物’:手把手用Attention-GAN实现图片局部精准转换(避坑指南) 在数字图像处理领域,生成对抗网络(GAN)技术已经从早期的整体风格迁移发展到如今的局部精准编辑。想象这样一个场景&#xff1a…...

SketchUp动态组件实战:用onClick函数制作一个能自动开关的门(附完整公式)

SketchUp动态组件实战:用onClick函数制作自动开关门 在建筑可视化与室内设计领域,交互式组件能显著提升方案展示的专业度与趣味性。想象一下,当客户点击你模型中的门扇时,它能像真实物体一样旋转开合——这种动态演示效果远比静态…...

Go的time.Ticker与time.Timer:精准的定时任务

Go的time.Ticker与time.Timer:精准的定时任务 在现代软件开发中,定时任务是常见的需求,无论是定时数据同步、周期性任务执行,还是超时控制,都需要精准的时间管理。Go语言的标准库提供了time.Ticker和time.Timer两种强…...

开发小店简易收支台账自动生成代码,给社区团购小微店铺,做每日营收支出,智能分类汇总对账。

一个非常接地气、适合社区小店老板娘/店主使用的完整方案:基于 Python 的「社区团购小微店铺简易收支台账自动生成系统」定位:每日记账 → 自动分类 → 汇总对账 → 台账输出一、实际应用场景描述典型场景:社区团购自提点 / 小微便利店&#…...

ModTheSpire终极指南:5分钟学会安装杀戮尖塔游戏模组

ModTheSpire终极指南:5分钟学会安装杀戮尖塔游戏模组 【免费下载链接】ModTheSpire External mod loader for Slay The Spire 项目地址: https://gitcode.com/gh_mirrors/mo/ModTheSpire 你是否厌倦了杀戮尖塔的原有玩法?想要体验全新角色、卡牌和…...

Kotlin的@OptIn与@RequiresOptIn:实验性API的使用

Kotlin作为一门现代化的编程语言,不断引入新特性以提升开发体验。某些功能在稳定之前需要经过充分测试,这时实验性API(Experimental API)便成为开发者提前体验新特性的窗口。为了管理这类API的使用风险,Kotlin提供了Re…...

RT-Thread设备驱动避坑指南:eMMC块设备注册成功却挂载失败?这5个配置细节要检查

RT-Thread设备驱动避坑指南:eMMC块设备注册成功却挂载失败的深度排查 当你看到list_device命令中eMMC块设备已经成功注册,却在执行dfs_mount时遭遇失败,这种"看得见却用不了"的情况往往比完全无法识别更令人抓狂。本文将带你深入五…...

GPT、BERT、LLaMA 这些模型类别怎么区分

最核心的一句:- BERT:偏“读懂”- GPT:偏“写出来”- LLaMA:本质上也是 GPT 这一路,只是是一个重要的开源/开放权重模型家族先看本质区别| 类别 | 结构 | 训练方式 | 擅长 ||---|---|---|---|| BERT | Encoder-only | …...

企业知识库问答系统的详细架构图

离线链路:把企业知识加工进知识库- 在线链路:用户提问后,检索、推理、生成答案总架构图──离线建库链路───────────────────────────────────数据源Confluence / SharePoint / 钉盘 / 飞书文档 / Git / …...

ViGEmBus虚拟游戏控制器驱动:终极安装与完整使用指南

ViGEmBus虚拟游戏控制器驱动:终极安装与完整使用指南 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus 你是否曾经遇到过这样的烦恼?想…...

3分钟解决Minecraft语言障碍:MASA全家桶汉化包终极指南

3分钟解决Minecraft语言障碍:MASA全家桶汉化包终极指南 【免费下载链接】masa-mods-chinese 一个masa mods的汉化资源包 项目地址: https://gitcode.com/gh_mirrors/ma/masa-mods-chinese 还在为MASA模组复杂的英文界面而烦恼吗?每次打开Minecraf…...

别再死记硬背了!用Wireshark抓包,5分钟搞懂PPP链路IP地址协商(IPCP)全过程

用Wireshark透视PPP链路:IP地址协商全流程实战解析 第一次接触PPP链路的IP地址协商时,那些Config-Request、Config-Ack报文交互总让人云里雾里。直到我用Wireshark亲手抓取IPCP报文,才真正理解了为什么PPP链路两端的设备可以不在同一网段却依…...

Linux系统崩溃别慌!手把手教你用Timeshift在Deepin/UOS上快速恢复桌面(含命令行救急指南)

Linux系统崩溃急救手册:Timeshift在Deepin/UOS上的全场景恢复指南 那天下午,我正在赶一份重要文档,Deepin系统突然弹出一个更新提示。像往常一样点击"立即更新"后,屏幕却陷入了黑屏循环重启的噩梦。作为深度系统三年老用…...

别再乱用@DateTimeFormat了!Spring Boot中处理前端日期传参的3种正确姿势(附Postman测试脚本)

别再乱用DateTimeFormat了!Spring Boot中处理前端日期传参的3种正确姿势(附Postman测试脚本) 最近在团队Code Review时,发现不少同事在处理日期参数时都存在一个共性误区——把DateTimeFormat和JsonFormat混为一谈。这直接导致接口…...

保姆级教程:在Ubuntu 20.04上搞定RoboSense 16线雷达驱动与点云格式转换(附编译避坑指南)

跨平台LiDAR数据融合实战:Ubuntu 20.04/22.04下的RoboSense-Velodyne点云转换全解析 当我们在多传感器融合项目中尝试整合不同品牌的激光雷达时,数据格式的差异往往会成为第一个"拦路虎"。最近在部署RoboSense 16线雷达时,我发现许…...

从零到一:用VuePress/Hexo搭建技术博客时,你必须搞懂的SEO配置(附完整代码)

从零到一:用VuePress/Hexo搭建技术博客时,你必须搞懂的SEO配置(附完整代码) 技术博客不仅是开发者记录学习历程的载体,更是个人品牌的重要展示窗口。但很多开发者发现,即使内容优质,博客流量依然…...