做网站图片和文字字体侵权/南召seo快速排名价格
专栏内容:
- 手写数据库toadb
本专栏主要介绍如何从零开发,开发的步骤,以及开发过程中的涉及的原理,遇到的问题等,让大家能跟上并且可以一起开发,让每个需要的人成为参与者。
本专栏会定期更新,对应的代码也会定期更新,每个阶段的代码会打上tag,方便阶段学习。
开源贡献:
- toadb开源库
个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
文章目录
- 前言
- 概述
- 结构体定义
- 结构体别名
- 结构体指针
- 结构体嵌套定义
- 可变长结构体定义
- 结构体大小
- 字节大小端
- 结构体大小
- 结构体紧凑格式
- 结构体地址
- 结构体成员首地址
- 获取成员地址
- 结构体赋值
- 结构体变量赋值
- 结体体指针成员
- 结构体类型转换
- 总结
- 结尾
前言
经过前面几个专栏,我们了解了数据库作为基础软件,类似于操作系统,几乎涉及到数据的应用都会使用;我们也通过手写数据库内核,开源了一款数据库,名叫toadb,它是一个轻量级的、开源的关系型数据库,它提供了基本的SQL支持和数据存储管理功能。相比于其他成熟的数据库产品,toadb更加简单和易于理解,适合初学者和数据库内核开发人员使用。通过学习和使用toadb,我们可以更好地理解数据库的基本原理,掌握数据库的核心技术,为以后的数据库设计和优化工作打下坚实的基础。
toadb是使用C语言编写,在内核开发过程中,我们发现一些初学者,对于数据库中使用C语言方法和技巧,阅读代码时需要学习。本专栏就特别将这些方法和技巧整理出来,方便初学者系统的了解和学习,以便很快能上手数据库内核的开发,不致于在开发语言层面遇到很多障碍,更多精力在数据库理论的实践。
本专栏建议为学习过C语言基础知识的读者,可以进一步深入学习,更贴进实际项目的开发应用。
概述
本文主要分享一下,C语言中最常用的数据结构常用的使用方法和技巧。C语言为了定义复杂的数据类型,引入了数据结构 struct,可以通过对基础数据类型的组合,自定义符合现实的组合类型。因为是对于多个基础数据类型的组合,所以引出了很多问题,如数据结构的大小如何计算,成员的地址是多少,字节大小端带来的影响如何消除等等。
通过以下四部分来系统的了解结构的知识:
- 结结体定义
- 结构体地址
- 结构体大小
- 结构体赋值
- 结构体类型转换
结构体定义
如何定义出一个符合我们代码要求的结构体类型,同时在使用中可以简单明了,下面我们一起来看一下实际中如何定义。
结构体别名
在C语言中结构体的定义很简单,如下:
#define NAME_MAX_LEN 64
struct ColumnDefInfo
{char colName[NAME_MAX_LEN];int type;int options;
};
这样就定义了一个名为ColumnDefInfo
的结构定,当我们定义该类型的变量时,会如下使用
struct ColumnDefInfo stColumn;
每次都要多写struct
这个单词,当写上几十上百遍时,是不是也很烦的;这就用到C语言的一个特性,给这个结构体定义一个别名,平常使用别名就可以
typedef struct ColumnDefInfo
{char colName[NAME_MAX_LEN];int type;int options;
}ColumnDefInfo; ColumnDefInfo stColumn; // 定义变量
在定义结构体struct ColumnDefInfo
的同时定义别名为ColumnDefInfo
,这样在定义变量或引用结构体类型的地方,就可以直接使用别名即可,是不是看这简洁很多,当然为了区分结构体类型,可以加上st
等前缀,统一命名。
结构体指针
C语言的实际使用中,避免不了指针类型,结构体类型的指针也是我们常用的,当函数参数需要传递结构体时,需要动态分配空间时等等,普通写法如下:
ColumnDefInfo *pstColumn = NULL; // 定义变量
每次都会像普通类型定义指针一样,当然也没有错,因为结构体名已经是复杂类型了,如何通过类型就能区分是值还是指针类型呢? 高手一般会如下定义。
typedef struct ColumnDefInfo *PColumnDefInfo;
或者在结构体定义时,同时定义好对应的指针类型。
typedef struct ColumnDefInfo
{char colName[NAME_MAX_LEN];int type;int options;
}ColumnDefInfo, *PColumnDefInfo;PColumnDefInfo pstColumn = NULL; // 定义变量
这时定义结构体指针,直接使用对应的指针类型PColumnDefInfo
,这样是不是又可以简洁一些,在函数入参中,看到这样的结构体名,我们立马就可以知道它是指针类型了。
结构体嵌套定义
结构体可以定义出来很复杂的类型,但是现实世界更复杂,很多事务都有层次关系,这就必须用到嵌套的结构体定义。
比如表是有行数据组成,那么表的结构体定义中,嵌套有行的结构体定义,如下:
#define FLEXIBLE_SIZE 10
typedef struct TableMetaInfo
{int tableId;int tableType;char tableName[NAME_MAX_LEN];int colNum;ColumnDefInfo column[FLEXIBLE_SIZE];
}TableMetaInfo, *PTableMetaInfo;
这次在定义时,就直接使用了上面介绍的技巧,别名,指针类型定义。我们定义了一个表的结构体TableMetaInfo
,表有名字,ID等,还有行数量,以及行的数据结构定义,因为行的数量不确定,所以这里定义是一个数组。
对于嵌套结构体,在引用成员时,就有一些麻烦,如果在几层的嵌套,可以写一长串。
PTableMetaInfo stTblInfo;
int i; // 其它代码 stTblInfo->colum[i].type = 1;
这里需要注意的是,在嵌套结构体时,要注意内层成员结构体是值类型,还是指针类型,如果是值类型就要用.
来引用成员,如果是指针定类的话用->
引用成员,在实际使用中,我们可以看到在一条语句中两个混合使用的情况,这就是根据不同的类型进行选择。
可变长结构体定义
每一个表中的数据行,在结构体定义时,我们是不能预知的,它可以有一行,也可以有一万行,那如何定义这个数据结构呢,这就是可变长结构体定义;可变长的数据结构定义中,有一个成员来记录变长部分的大小,如行的数量colNum
,而column
是行数据,它的数量在每个表中都是不一样的,由动态决定大小。
使用变长结构体方法来定义,如下
#define FLEXIBLE_SIZE
typedef struct TableMetaInfo
{int tableId;int tableType;char tableName[NAME_MAX_LEN];int colNum;ColumnDefInfo column[FLEXIBLE_SIZE];
}TableMetaInfo, *PTableMetaInfo;
其中,行数据数组 column[FLEXIBLE_SIZE]
的维度定义FLEXIBLE_SIZE
并没有给出明确的值,这里相当于可变数组的定义
int array[] = {1,2,3};
此时,TableMetaInfo
结构体默认大小中,其实没有包括行的结构定义大小,我们通过程序简单输出它们的size。
printf("table size=%d, column size=%d\n", sizeof(TableMetaInfo), sizeof(ColumnDefInfo));
得到的结果如下
table size=76, column size=72
可以看到TableMetaInfo
结构体默认大小只有前四个成员的大小,并不包括行数据结构的大小。那么问题来了,如何定义变量呢?
在定义变量时,我们一般动态申请内存,再通过成员数组来访问。
结构体大小
不管是动态申请内容,还是局部变量的定义,我们都需要知道结构体占多少内存空间,尤其是在多并发之间进行交互时,要尽量减少交互数据量。
下面介绍一下结构体大小,在实际应用中的那些事儿。
字节大小端
在介绍结构体大小时,我们首先要知道计算机存储我们的变量值时,并不是按照从左到右完成从高位到低位的存储,而是不同操作系统规定了自己的一个字节顺序。
在常用的X86 CPU架构中,常用的就是小端存储,即0x1234, 在内存中低位是0x34,高位是0x12,进行了反转。
这在一些结构体转为其它类型时,常常会遇到字节序问题,还有一些网络数据转为结构体数据时,明明看似没有问题,但是成员的值就是不对,这就是不同数据对应的字节序在作怪。
结构体大小
对于结构体这一复杂的自定义类型,计算机对访问内存做了一定的优化,也就是字节对齐。如下结构体,
typedef struct A
{char a;int b;double c;
}st_A;
这个结构体st_A
中只有三个成员,sizeof(st_A)
算出来是16字节,符合你的预期吗? 单从代码看,只有13字节,如何多出了3字节呢? 这就是计算机内部优化的结果,成员b
的地址被对齐到了四字节上,也就是成员a
与b
的地址相差4,而不是字面上的1字节,这样就多出了3字节。
如果定义了一个结构体类型的唯一标识,而其中成员的类型不同时,将这个标识按字节进行计算hash值时,就会存在问题,因为多出来的3字节,永远不知道它的值到底是什么,那么虽然成员的值都是一样的,但是算出来的hash却有可能不同。
结构体紧凑格式
上面介绍了,计算机会对结构体采用字节对齐的优化,当然这是一种空间换时间的方式。如果我们对于空间比较敏感时,就要放弃这种默认的优化了,这就定义成紧凑格式。
typedef struct __attribute__((packed)) A
{char a;int b;double c;
}st_A;
这样就告诉编译器,不要在成员间加多余的字节。有多种写法,也可以用 __attribute__((aligned(1)))
。
结构体地址
C语言中经常使用地址来访问内存,如结构体的指针,也即地址,那么对于结构体类型的变量,它会有几种地址需要我们注意了。
结构体成员首地址
想必大家会有疑问,结构体的首地址,就是结构体指针内容嘛,不是很简单吗?
没错,是的,我们举个例子来说明。
/* 10个table ,平均每个table 中有4行数据 */
PTableMetaInfo tbl = (PTableMetaInfo)malloc(sizeof(TableMetaInfo) * 10 + sizeof(ColumnDefInfo) * 40);PTableMetaInfo pstTbl = tbl;
这里用指针pstTbl
来遍历数组tbl
,那么pstTbl++
都会移动sizeof(TableMetaInfo)
字节,这样使用是正确的吗?
前面我们介绍了变长结构体,这里的sizeof(TableMetaInfo)
中,是不包括最后一个成员的长度的,所以下一个数据结构的首地址不是通过默认的偏移得到的,这里就需要计算了,根据成员colNum
来计算需要偏移多少了。
#define GET_NEXT_TABLE(addr) ((addr) + sizeof(TableMetaInfo) + (addr)->colNum * sizeof(ColumnDefInfo))
GET_NEXT_TABLE
这个宏定义,就是进行可变长结构体的数组偏移计算,而不是简单的通过默认运算得到。
获取成员地址
结构体成员的地址,可以通过->
或 .
引用的方式获得,当然也可以计算获得,比如ColumnDefInfo
结构体中,成员type
与结构体首地址相差64字节,就可以通过首地址来计算。
通过计算方式获取成员的地址时,尤其在非紧凑格式的定义的结构体时,就需要特别注意结构体成员并不一定是基础类型的字节数,要根据结构体类型字节对齐规则进行计算;对于可变长结构体,不能使用指针的默认+1移动方式,需要自己计算偏移,这在另一篇博客《C语言可变数组 嵌套的可变数组》中有详细介绍。
结构体赋值
结构体的赋值方法不同于基础类型,也有很多方式进行赋值,需要正确的使用。
结构体变量赋值
一般结构体类型的变量,我们都会清零操作,有两种方法进行初始化为零,如下示例:
struct ColumnDefInfo stColumn = {0}; memset(&stColumn, 0x00, sizeof(stColumn));
- 在定义时,使用初始化方式进行置零,这种方式如果只写一个0,所有内容都会置零,也可以根据成员数量和类型分别写出初始化的值;
- 使用内存操作方式,初始化为0,这种方式要能正确计算结构体的大小;
结体体指针成员
当结构体中有指针成员时,在结构体拷贝时就会存在深拷贝和浅拷贝的问题。当一处结构体直接赋值给另一个结构体变量时,它们的指针成员指向的地址是一样的,所以释放内存时需要判空,非空时才释放。
当结构体中有可变长成员时,与指针成员一样,赋值时需要特别注意,两个结构体变量内存大小是否可以容纳新值。
结构体类型转换
在数据库中,尤其执行计划,执行器处理等地方,为了方便统一使用相同的函数调用,将不同类型的结构体会强转成统一的类型,如下所示:
typedef struct Node
{NodeType type;
}Node, *PNode;typedef struct NestLoop
{NodeType type;PNode leftplan;PNode rightplan;PNode expr; /* join expr */int isJoin;int mergeType;PList targetList; /* result columns */
}NestLoop, *PNestLoop;PNestLoop nl = NewNode(NestLoop);
PNode node = (PNode)nl;
为了达到可以相互转换,如示例所示,在结构体NestLoop
的第一个成员为type
, 与结构体Node
的成员是一致的,这样由NestLoop
强制转换为Node
类型时,就只能看到成员type
了。
这样类似的其它节点类型,都可以转为结构体Node
,然后根据节点类型选择不同的处理调用,进行执行,这样就可以达到统计处理调用的目的。
总结
在我们进行C语言学习时,只是学习了基础的结构体使用,需要在实际使用中不断加深对它的理解,从内存部局,成员地址对齐,拷贝赋值等各方面进行探索,在数据库中,对于C语言结构体的使用方法非常丰富,在学习数据库内核过程,我们对于C语言的驾驭也会精进。
结尾
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。
相关文章:

【手写数据库所需C语言基础】可变结构体,结构体成员计算,类型强制转换为统一类型,数据库中使用C语言方法和技巧
专栏内容: 手写数据库toadb 本专栏主要介绍如何从零开发,开发的步骤,以及开发过程中的涉及的原理,遇到的问题等,让大家能跟上并且可以一起开发,让每个需要的人成为参与者。 本专栏会定期更新,…...

Android Studio(适配器Adapter)
认识适配器 在学完并且在做了一个自主项目后,我对适配器有了以下认识:1. 适配器的作用: 数据驱动的动态页面列表渲染,所以适配器主要就做了两件事:遍历数据,渲染页面(列表项)。比…...

携程AI布局:三重创新引领旅游行业智能化升级
2023年10月24日,携程全球合作伙伴峰会在新加坡召开,携程集团联合创始人、董事局主席梁建章做了名为《旅游业是独一无二的最好的行业》的演讲,梁建章在演讲中宣布了携程生成式 AI、内容榜单、ESG 低碳酒店标准三重创新的战略方向。这些创新将为…...

IOS手机耗电量测试
1. 耗电量原始测试方法 1.1 方法原理: 根据iPhone手机右上角的电池百分比变化来计算耗电量。 1.2实际操作: 在iOS通用设置中打开电池百分比数值显示,然后操作30分钟,60分钟,90分钟,看开始时和结束时电池…...

LeetCode.6 N字形变换
一开始想的是真的创建一个数组 去按照题目所给的要求填入数据 最后输出不为空的数组项 但是不仅时间复杂度高 而且错误频繁出现 最终也没有提交成功 查阅题解后发现数组并不重要 假设我们忽略掉数组中的那些空白项 最终输出的结果就是numRows行的字符串的拼接 string conver…...

第一章 03Java入门-编写第一个Java程序HelloWorld以及JVM、JDK和JRE概念
文章目录 前言一、下载和安装JDK二、第一个程序HelloWorld1.用记事本编写程序2.编译文件3.运行程序三、HelloWorld案例常见问题四、环境变量五、Notepad软件的安装和使用六、Java语言的发展七、Java为什么这么火八、JRE和JDK总结前言 上次我们学习了常见的CMD指令以及环境变量…...

windbg的常见调试命令
windbg的常见调试命令 1).break:在指定的条件下停止调试。 2).bt:显示调用堆栈信息。 3).catch:设置异常捕获,可以用来捕获程序中的异常并进行调试。 4).continue:继续执…...

VBA之正则表达式(44)-- 拆分商品和规格
实例需求:商品组清单保存在A列中,现需要将其拆分为商品名称,保存在从B列开始的后续单元格中,部分商品包含规格,并且多种规格属性使用了逗号分隔,因此无法直接使用Excel分列功能完成数据拆分。 示例代码如下…...

听GPT 讲Rust源代码--library/std(13)
题图来自 Decoding Rust: Everything You Need to Know About the Programming Language[1] File: rust/library/std/src/os/horizon/raw.rs 在Rust源代码中,rust/library/std/src/os/horizon/raw.rs这个文件的作用是为Rust的标准库提供与Horizon操作系统相关的原始…...

计算机视觉任务图像预处理之去除图像中的背景区域-------使用连通域分析算法(包含完整代码)
原理 通过连通域分析算法能够找到最大的连通域,即图片的主体部分,然后保存该连通域的最小外接矩阵,即可去除掉无关的背景区域 代码 使用连通域分析算法去除图像中的空白部分 并将图像变为统一大小的正方形 from skimage import measure imp…...

SurfaceFlinger的硬件Vsync深入分析-千里马android framework车机手机系统开发
背景: 学过或者你看过surfaceflinger相关文章同学都知道,vsync其实都是由surfaceflinger软件层面进行模拟的,但是软件模拟有可能会有误差或偏差,这个时候就需要有个硬件vsync帮忙校准。 故才会在surfaceflinger的systrace出现如下…...

力扣160. 相交链表
目录 1.解题思路2.代码实现 1.解题思路 首先分析,如果两个链表的长度不一,假设他们有交点,那么他们的最后一定是相同的,也即是后面为相同的部分,但前面不好说,而又因为长度不一又没法简便的一一对比&#…...

操作系统学习与思考
x86体系架构 x86是因特尔8086代芯片的CPU总线位数以及寄存器种类的规范,大部分操作系统都是以该规范作为基准来生产的 计算机组成 CPU,可以根据程序计数器进行取指令操作,并根据指令执行运算(加、减、乘、除)。运算所…...

C++笔记之动态数组的申请和手动实现一个简单的vector
C笔记之动态数组的申请和手动实现一个简单的vector code review! 文章目录 C笔记之动态数组的申请和手动实现一个简单的vector1.C语言中动态数组的申请与使用1.动态数组的申请使用new和delete使用std::vector 1.std::vector的底层实现2.手动实现一个简单的vector:使用一个指向…...

答题测评考试小程序的效果如何
在线答题系统是一种在线练习、考试、测评的智能答题系统,适用于企业培训、测评考试、知识竞赛、模拟考试等场景,管理员可任意组题、随机出题,答题者成功提交后,系统自动判分。 多种题目类型,两种答题模式 练习模式&a…...

树上贪心+生成树贪心:1104T3
<47.92.197.167:5283/contest/425/problem/3> 根据 n n n 奇偶性可以推断答案 合法解只需要在任何一棵生成树上构造即可 贪心肯定要在最大生成树上 然后从前往后看一条未选的边能不能选即可 #include<bits/stdc.h> using namespace std; #ifdef LOCAL#define …...

MySQL进阶之性能优化与调优技巧
数据库开发-MySQL 1. 多表查询1.1 概述1.1.2 介绍1.1.3 分类 1.2 内连接1.3 外连接1.4 子查询1.4.1 介绍1.4.2 标量子查询1.4.3 列子查询1.4.4 行子查询1.4.5 表子查询 2. 事务2.1 介绍2.2 操作2.3 四大特性 3. 索引3.1 介绍3.2 结构3.3 语法 1. 多表查询 1.1 概述 1.1.2 介绍…...

MySQL EXPLAIN查看执行计划
MySQL 执⾏计划是 MySQL 查询优化器分析 SQL 查询时⽣成的⼀份详细计划,包括表如何连 接、是否⾛索引、表扫描⾏数等。通过这份执⾏计划,我们可以分析这条 SQL 查询中存在的 问题(如是否出现全表扫描),从⽽进⾏针对优化…...

目标检测YOLO系列从入门到精通技术详解100篇-【目标检测】机器视觉(最终篇)
目录 知识储备 杂散光 结构光 ■ 被动测距 ■ 主动结构光 图像分类技巧 增强...

redis教程 二 redis客户端Jedis使用
文章目录 Redis的Java客户端-JedisJedis快速入门创建工程:引入依赖:建立连接测试:释放资源Jedis连接池创建Jedis的连接池改造原始代码 Redis的Java客户端-SpringDataRedis快速入门导入pom坐标配置文件测试代码 数据序列化器StringRedisTempla…...

【数据开发】大数据平台架构,Hive / THive介绍
1、大数据引擎 大数据引擎是用于处理大规模数据的软件系统, 常用的大数据引擎包括Hadoop、Spark、Hive、Pig、Flink、Storm等。 其中,Hive是一种基于Hadoop的数据仓库工具,可以将结构化的数据映射到Hadoop的分布式文件系统上,并提…...

SOEM源码解析——ecx_init_context(初始化句柄)
0 工具准备 1.SOEM-master-1.4.0源码1 ecx_init_context函数总览 /*** @brief 初始化句柄* @param context 句柄*/ void ecx_init_context(ecx_contextt *context) {int lp;*(context->slavecount) = 0;/* clean ec_slave array */...

11.Z-Stack协议栈使用
f8wConfig.cfg文件 选择信道、设置PAN ID 选择信道 #define DEFAULT_CHANLIST 0x00000800 DEFAULT_CHANLIST 表明Zigbee模块要工作的网络,当有多个信道参数值进行或操作之后,把结果作为 DEFAULT_CHANLIST值 对于路由器、终端、协调器的意义࿱…...

设计模式—结构型模式之适配器模式
设计模式—结构型模式之适配器模式 将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,适配器模式分为类结构型模式(继承)和对象结构型模式(组合)两种,前者&a…...

【LeetCode】187. 重复的DNA序列
187. 重复的DNA序列 难度:中等 题目 DNA序列 由一系列核苷酸组成,缩写为 A, C, G 和 T.。 例如,"ACGAATTCCG" 是一个 DNA序列 。 在研究 DNA 时,识别 DNA 中的重复序列非常有用。 给定一个表示 DNA序列 的字符串 …...

C++17中std::any的使用
类sdk:any提供类型安全的容器来存储任何类型的单个值。通俗地说,std::any是一个容器,可以在其中存储任何值(或用户数据),而无需担心类型安全。void*的功能有限,仅存储指针类型,被视为不安全模式。std::any可以被视为vo…...

携手ChainGPT 人工智能基础设施 波场TRON革新 Web3 版图
近日,波场TRON与 Web3 人工智能基础设施服务商 ChainGPT 正式达成合作。通过本次合作,双方将进一步推动人工智能和区块链技术的融合,在实现优势互补的同时,真正惠及日常生活。 作为一站式的加密AI中心,ChainGPT 的人工智能工具需要进行大量计算,能耗高,而波场TRON采用的创新型…...

pdfH5实现pdf预览功能
1.引入 npm install pdfh5 2.使用 <view id"pdfBox" class""></view> showPdf(url) {this.pdfh5 new Pdfh5("", {URIenable: false,zoomEnanle: true,maxZoom: 2,pdfurl: url})this.pdfh5.on("complete", function(st…...

Redis的持久化机制
多级缓存使用到了一个装饰设计模式:相当于我不影响我之前缓存本身的代码,但是我可以对我的缓存去做增强,因此多级缓存就是采用装饰模式去实现的~! 多级缓存可以采用装饰模式去重构~! Redis当中的持久化机制ÿ…...