使用纯C语言定义通用型数据结构的方法和示例
文章目录
- 前言
- 以实现优先队列来描述实现思想
- 基本类型的包装类型
- 比较函数
- 演示
- 总结
前言
最近一段时间在复习数据结构和算法,用的C语言,不得不说,不学个高级语言再回头看C语言根本不知道C语言的强大和完美,不过相比之下也有许多不便利的地方,尤其是以下两个方面:
- 没有异常处理机制
- 没有泛型
其中第一方面之前就解决了,详情请看在C语言中实现类似面向对象语言的异常处理机制,今天下午有空来实现一下泛型。不得不说,通过异常处理机制和泛型的实现,既让我C语言使用的得心应手,又让我对高级语言的设计有了亲身般体验。
以实现优先队列来描述实现思想
首先C语言本身不支持泛型,这意味着实现泛型有以下两个困难(解决这两个困难也就意味着成功):
- ①:类型信息在编译前就已经确定了
- ②:类型信息不能像参数一样传递
有了目标就轻松多了,于是我立刻就想到了函数的可变参数<stdarg.h>
,请看下面的DEMO:
PackagingTypeList intBatchValueOf(int size, ...) {PackagingTypeList list = calloc(size, sizeof(PackagingType *));va_list argList;va_start(argList, size);for (int i = 0; i < size; ++i) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->intValue = va_arg(argList, int);*(list + i) = pack;}va_end(argList);return list;
}
在使用va_arg
取可变参数时我们确实直接将int
类型作为参数传递了,这就意味着困难②克服了,那么困难①呢?于是我继续研究,我发现函数的参数存储在一个GCC内置的数据结构中:
typedef struct {void *__stack; /* __stack 记录下一个匿名栈参数的存储位置, 随着va_arg的调用可能变化 */void *__gr_top; /* __gr_top 记录最后一个匿名通用寄存器参数的尾地址, 其不随va_arg调用变化 */void *__vr_top; /* __vr_top 记录最后一个匿名浮点寄存器参数的尾地址, 其不随va_arg调用变化 */int __gr_offs; /* __gr_offs 记录下一个匿名通用寄存器参数到__gr_top的偏移(负数),随着va_arg的调用可能变化 */int __vr_offs; /* __vr_offs 记录下一个匿名浮点寄存器参数到__vr_top的偏移(负数),随着va_arg的调用可能变化 */
} __builtin_va_list;
这就意味着要想克服困难①就必须得到编译器的支持,显然这是不可能的,于是我果断放弃了,但困难②的克服给我了灵感,va_arg
是一个宏定义,强大的预处理器赋予了C语言元编程的能力,这就是我想到的第一种方法:
- 克服困难①:使用宏定义在编译时定义可以存储指定类型的优先队列,
- 克服困难②:使用带参数的宏传递类型信息
于是第一种方案诞生了:
#define PriorityQueueNode(TYPE) \
{ \typedef struct PriorityQueueNode_##TYPE{ \TYPE data; \struct PriorityQueueNode *next; \struct PriorityQueueNode *prior; \}PriorityQueueNode_##TYPE; \
}while(false)#define priorityQueueEnQueue(TYPE) \
{ \void priorityQueueEnQueue_##TYPE(struct PriorityQueue_##TYPE queue,TYPE data){ \... \} \
}while(false)#define PriorityQueue(TYPE, NAME) \
{ \PriorityQueueNode(TYPE); \priorityQueueEnQueue(TYPE) \PriorityQueueNode_##TYPE head={.next=NULL,.prior=NULL}; \struct PriorityQueue_##TYPE{ \PriorityQueueNode *front; \PriorityQueueNode *rear; \void (* priorityQueueEnQueue)(struct PriorityQueue_##TYPE,TYPE); \} NAME={ \.front=&head, \.rear=&head \.priorityQueueEnQueue=priorityQueueEnQueue_##TYPE \}; \
}while(false)
不过还没等写完我就放弃了,因为这太不优雅了,这其实和单独为每种类型定义一个优先队列没什么区别,于是我又想到了void*
指针,这就是我想到的第二个方法,也是最终实现的方法:
- 克服困难①:使用
void*
指针存储任意数据类型的指针,实际存储数据的空间由调用者分配 - 克服困难②:在数据结构内部需要类型信息的地方通过传入的函数完成,这个函数也由调用者提供
//PriorityQueue.h#ifndef INC_2023_PRIORITYQUEUE_H
#define INC_2023_PRIORITYQUEUE_H#include "../../../util/Util.h"typedef struct PriorityQueueNode PriorityQueueNode;
typedef struct PriorityQueue *PriorityQueue;/*** 构造带头结点的优先队列* @param compare* @return*/
PriorityQueue priorityQueueConstructor(int (*compare)(void *, void *)) throws NULL_POINTER_EXCEPTION;/*** 销毁优先队列* @param queue*/
void priorityQueueFinalize(PriorityQueue queue) throws NULL_POINTER_EXCEPTION;/*** 优先队列是否为空* @param queue* @return*/
bool priorityQueueIsEmpty(PriorityQueue queue) throws NULL_POINTER_EXCEPTION;/*** 入队* @param queue* @param element*/
void priorityQueueEnQueue(PriorityQueue queue, void *element) throws NULL_POINTER_EXCEPTION;/*** 出队* @param queue* @return*/
void *priorityQueueDeQueue(PriorityQueue queue) throws NULL_POINTER_EXCEPTION;#endif //INC_2023_PRIORITYQUEUE_H
//PriorityQueue.c#include "PriorityQueue.h"struct PriorityQueueNode {void *data;PriorityQueueNode *next;PriorityQueueNode *prior;
};struct PriorityQueue {PriorityQueueNode *front;PriorityQueueNode *rear;int (*compare)(void *, void *);
};/*** 构造带头结点的优先队列* @param compare* @return*/
PriorityQueue priorityQueueConstructor(int (*compare)(void *, void *)) throws NULL_POINTER_EXCEPTION {if (compare == NULL) {throw Error(NULL_POINTER_EXCEPTION, "比较函数不能为空");}PriorityQueue queue = malloc(sizeof(struct PriorityQueue));//头结点queue->front = queue->rear = malloc(sizeof(PriorityQueueNode));queue->front->next = NULL;queue->front->prior = NULL;queue->compare = compare;return queue;
}/*** 销毁优先队列* @param queue*/
void priorityQueueFinalize(PriorityQueue queue) throws NULL_POINTER_EXCEPTION {if (queue == NULL) {throw Error(NULL_POINTER_EXCEPTION, "优先队列不能为空");}for (; !priorityQueueIsEmpty(queue);) {priorityQueueDeQueue(queue);}free(queue->front);free(queue);
}/*** 优先队列是否为空* @param queue* @return*/
bool priorityQueueIsEmpty(PriorityQueue queue) throws NULL_POINTER_EXCEPTION {if (queue == NULL) {throw Error(NULL_POINTER_EXCEPTION, "优先队列不能为空");}if (queue->front == queue->rear) {return true;} else {return false;}
}/*** 入队* @param queue* @param element*/
void priorityQueueEnQueue(PriorityQueue queue, void *element) throws NULL_POINTER_EXCEPTION {if (queue == NULL) {throw Error(NULL_POINTER_EXCEPTION, "优先队列不能为空");}PriorityQueueNode *node = malloc(sizeof(PriorityQueueNode));node->data = element;//如果新加入元素优先级比队尾元素优先级小则直接插入队尾,否则就遍历优先队列找到合适的插入位置if (priorityQueueIsEmpty(queue) || queue->compare(queue->rear->data, node->data) > 0) {node->next = NULL;node->prior = queue->rear;queue->rear->next = node;queue->rear = node;} else {for (PriorityQueueNode *temp = queue->front->next; temp != NULL; temp = temp->next) {if (queue->compare(temp->data, node->data) <= 0) {node->next = temp;node->prior = temp->prior;temp->prior->next = node;temp->prior = node;break;}}}
}/*** 出队* @param queue* @return*/
void *priorityQueueDeQueue(PriorityQueue queue) throws NULL_POINTER_EXCEPTION {if (queue == NULL) {throw Error(NULL_POINTER_EXCEPTION, "优先队列不能为空");}if (!priorityQueueIsEmpty(queue)) {PriorityQueueNode *node = queue->front->next;void *data = node->data;if (queue->rear == node) {queue->rear = queue->front;queue->front->next = NULL;} else {queue->front->next = node->next;node->next->prior = queue->front;}free(node);return data;} else {return NULL;}
}
基本类型的包装类型
为了方便基本类型指针的获取,我定义了基本类型的包装类型:
//PackagingType.h#ifndef DSA_PACKAGINGTYPE_H
#define DSA_PACKAGINGTYPE_H#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>typedef union PackagingType PackagingType, **PackagingTypeList;int getIntValue(void *element);float getFloatValue(void *element);double getDoubleValue(void *element);char getCharValue(void *element);bool getBoolValue(void *element);PackagingType *intValueOf(int value);PackagingTypeList intBatchValueOf(int size, ...);PackagingType *floatValueOf(float value);PackagingTypeList floatBatchValueOf(int size, ...);PackagingType *doubleValueOf(double value);PackagingTypeList doubleBatchValueOf(int size, ...);PackagingType *charValueOf(char value);PackagingTypeList charBatchValueOf(int size, ...);PackagingType *boolValueOf(bool value);PackagingTypeList boolBatchValueOf(int size, ...);#endif //DSA_PACKAGINGTYPE_H
//PackagingType.cunion PackagingType {int intValue;float floatValue;double doubleValue;char charValue;bool boolValue;
};int getIntValue(void *element) {return ((PackagingType *) element)->intValue;
}float getFloatValue(void *element) {return ((PackagingType *) element)->floatValue;
}double getDoubleValue(void *element) {return ((PackagingType *) element)->doubleValue;
}char getCharValue(void *element) {return ((PackagingType *) element)->charValue;
}bool getBoolValue(void *element) {return ((PackagingType *) element)->boolValue;
}PackagingType *intValueOf(int value) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->intValue = value;return pack;
}PackagingTypeList intBatchValueOf(int size, ...) {PackagingTypeList list = calloc(size, sizeof(PackagingType *));va_list argList;va_start(argList, size);for (int i = 0; i < size; ++i) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->intValue = va_arg(argList, int);*(list + i) = pack;}va_end(argList);return list;
}PackagingType *floatValueOf(float value) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->floatValue = value;return pack;
}PackagingTypeList floatBatchValueOf(int size, ...) {PackagingTypeList list = calloc(size, sizeof(PackagingType *));va_list argList;va_start(argList, size);for (int i = 0; i < size; ++i) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->intValue = va_arg(argList, double);*(list + i) = pack;}va_end(argList);return list;
}PackagingType *doubleValueOf(double value) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->doubleValue = value;return pack;
}PackagingTypeList doubleBatchValueOf(int size, ...) {PackagingTypeList list = calloc(size, sizeof(PackagingType *));va_list argList;va_start(argList, size);for (int i = 0; i < size; ++i) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->intValue = va_arg(argList, double);*(list + i) = pack;}va_end(argList);return list;
}PackagingType *charValueOf(char value) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->charValue = value;return pack;
}PackagingTypeList charBatchValueOf(int size, ...) {PackagingTypeList list = calloc(size, sizeof(PackagingType *));va_list argList;va_start(argList, size);for (int i = 0; i < size; ++i) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->intValue = va_arg(argList, int);*(list + i) = pack;}va_end(argList);return list;
}PackagingType *boolValueOf(bool value) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->boolValue = value;return pack;
}PackagingTypeList boolBatchValueOf(int size, ...) {PackagingTypeList list = calloc(size, sizeof(PackagingType *));va_list argList;va_start(argList, size);for (int i = 0; i < size; ++i) {union PackagingType *pack = malloc(sizeof(PackagingType));pack->intValue = va_arg(argList, int);*(list + i) = pack;}va_end(argList);return list;
}
比较函数
通过创建多个数据结构发现,在数据结构内部用到的往往是比较函数,因此,我把常用的比较函数都定义了一下:
//Comparable.h#ifndef DSA_COMPARABLE_H
#define DSA_COMPARABLE_H#include "../packaging-type/PackagingType.h"extern int (*intCompare)(void *, void *);extern int (*intPackCompare)(void *, void *);extern int (*floatCompare)(void *, void *);extern int (*floatPackCompare)(void *, void *);extern int (*doubleCompare)(void *, void *);extern int (*doublePackCompare)(void *, void *);extern int (*charCompare)(void *, void *);extern int (*charPackCompare)(void *, void *);#endif //DSA_COMPARABLE_H
//Comparable.c#include "Comparable.h"int intComp(void *a, void *b) {return *((int *) a) - *((int *) b) > 0;
}int intPackComp(void *a, void *b) {return getIntValue(a) - getIntValue(b) > 0;
}int floatComp(void *a, void *b) {return *((float *) a) - *((float *) b) > 0;
}int floatPackComp(void *a, void *b) {return getFloatValue(a) - getFloatValue(b) > 0;
}int doubleComp(void *a, void *b) {return *((double *) a) - *((double *) b) > 0;
}int doublePackComp(void *a, void *b) {return getDoubleValue(a) - getDoubleValue(b) > 0;
}int charComp(void *a, void *b) {return *((char *) a) - *((char *) b) > 0;
}int charPackComp(void *a, void *b) {return getCharValue(a) - getCharValue(b) > 0;
}int (*intCompare)(void *, void *) =intComp;int (*intPackCompare)(void *, void *) =intPackComp;int (*floatCompare)(void *, void *) =floatComp;int (*floatPackCompare)(void *, void *) =floatPackComp;int (*doubleCompare)(void *, void *) =doubleComp;int (*doublePackCompare)(void *, void *) =doublePackComp;int (*charCompare)(void *, void *) =charComp;int (*charPackCompare)(void *, void *) =charPackComp;
演示
首先看一个基本类型的例子:
#include "util/Util.h"
#include "linear-structure/queue/priority-queue/PriorityQueue.h"int main() {PackagingTypeList list = intBatchValueOf(4, 1, 2, 3, 4);PriorityQueue queue = priorityQueueConstructor(intPackCompare);for (int i = 0; i < 4; ++i) {priorityQueueEnQueue(queue, *(list + i));}while (!priorityQueueIsEmpty(queue)) {printf("%d", getIntValue(priorityQueueDeQueue(queue)));}return 0;
}
再看一个结构类型的例子:
#include "util/Util.h"
#include "linear-structure/queue/priority-queue/PriorityQueue.h"struct Student {int age;char *name;
};int studentCompare(void *a, void *b) {return ((struct Student *) a)->age - ((struct Student *) b)->age > 0;
}int main() {struct Student a = {.age=18, .name="张三"}, b = {.age=28, .name="李四"};PriorityQueue queue = priorityQueueConstructor(studentCompare);priorityQueueEnQueue(queue, &a);priorityQueueEnQueue(queue, &b);while (!priorityQueueIsEmpty(queue)) {printf("%s,", ((struct Student *) priorityQueueDeQueue(queue))->name);}return 0;
}
最后看一个抛异常的例子:
#include "util/Util.h"
#include "linear-structure/queue/priority-queue/PriorityQueue.h"struct Student {int age;char *name;
};int studentCompare(void *a, void *b) {return ((struct Student *) a)->age - ((struct Student *) b)->age > 0;
}int main() {struct Student a = {.age=18, .name="张三"}, b = {.age=28, .name="李四"};PriorityQueue queue = priorityQueueConstructor(studentCompare);priorityQueueEnQueue(queue, &a);priorityQueueEnQueue(queue, &b);try {while (!priorityQueueIsEmpty(queue)) {printf("%s,", ((struct Student *) priorityQueueDeQueue(NULL))->name);//change to NULL}} catch(NULL_POINTER_EXCEPTION) {stdErr();}return 0;
}
总结
- 第一种方法类似于C++的方式
- 第二种方法类似于Java的方式
相关文章:

使用纯C语言定义通用型数据结构的方法和示例
文章目录 前言以实现优先队列来描述实现思想基本类型的包装类型比较函数演示总结 前言 最近一段时间在复习数据结构和算法,用的C语言,不得不说,不学个高级语言再回头看C语言根本不知道C语言的强大和完美,不过相比之下也有许多不便…...

数据结构基础8:二叉树oj+层序遍历。
二叉树oj层序遍历 题目一:二叉树的销毁:方法一:前序遍历:方法二:后序遍历: 题目二:二叉树查找值为x的节点方法一:方法二:方法三: 题目三:层序遍历…...

Spring注解家族介绍:@RestController
前言: Spring Boot可以说是当前JAVA最为重要的一个框架,而Spring Boot的基石Spring中有着丰富的注解,因此我们会利用几篇文章来讲解我目前学到的各种注解,因此本类型文章的篇幅会比较短,主要着重于介绍各个注解。 目录…...

rocketmq
🍓代码仓库 https://gitee.com/xuhx615/rocket-mqdemo.git 🍓基本概念 ⭐生产者(Producer):消息发布者⭐主题(Topic):topic用于标识同一类业务类型的消息⭐消息队列(MessageQueue)…...

JAVA成员变量首字母小写,第二个字母大写报错问题(原因:Lombok与Spring冲突)
1、问题现象: JAVA类里定义成员变量使用首字母小写,第二个字母大写 Getter Setter public class BrandQueryObject extends QueryObject{private String pName; }结果页面报错,无法找到类型为 cn.wolfcode.ssm.query.BrandQueryObject 的对象…...

Python入门教程 |Python 错误和异常
Python3 错误和异常 作为 Python 初学者,在刚学习 Python 编程时,经常会看到一些报错信息,在前面我们没有提及,这章节我们会专门介绍。 Python 有两种错误很容易辨认:语法错误和异常。 Python assert(断…...
API商品接口对接使用:从理论到实践
随着电子商务的飞速发展,API商品接口已成为现代电子商务应用程序不可或缺的一部分。通过API商品接口,开发者可以轻松地从其他应用程序或服务中获取商品信息,实现快速、高效的电子商务功能。本文将探讨API商品接口的概念、对接使用的方法以及一…...

解决stable diffusion webui1.6 wd1.4 tagger加载失败的问题
由于webui源码的变化,需要修改两个地方的import 1.tagger/ui.py # 第十行 # from webui import wrap_gradio_gpu_call # 原代码 from modules.call_queue import wrap_gradio_gpu_call1.preload.py # 第4行开始 # from modules.shared import models_path # 原…...
Python学习-实现简单的http服务
基于Python实现一个简单的HttpServer,当用户在浏览器中输入IP地址:8000时,则会返回index.html页面内容,访问其它信息,则会返回错误信息(404) """ httpserver v1.0 1.获取来自浏览器的请求, 2.判断如果请求内容是 …...
#循循渐进学51单片机#变量进阶与点阵LED#not.6
1、掌握变量的作用域及存储类别。 局部变量 函数内部声明的变量,只在函数内部有效,在本函数以外是不能使用的,叫局部变量。 全局变量 在函数外部声明的变量就是全局变量,一个源程序可以包含一个或多个函数,全局变量…...

访问者模式
图片转载自 #include<iostream> using namespace std; #include<list> /*模板工厂单例化,所有的商品被注册进工厂中*/ /*访问者模式(行为型模式) 访问者,被访问者 visit accept 让访问变成一种操作,不同…...

epoll 的实现
epoll 这么好,为什么迟至 2.6 版本的 kernel 才支持(epoll manual: The epoll API was introduced in Linux kernel 2.5.44.)?2.4 版本的 kernel 不支持 epoll? 原因很简单,epoll 没什么神奇的。在早期没有太多的并发连接要处理&…...

怎么用excel管理固定资产
在当今的数字时代,我们已经习惯了使用各种电子工具来提高我们的生产力。其中,Excel无疑是一个强大的工具,它不仅可以帮助我们处理数据,还可以用来进行复杂的计算和分析。然而,你可能不知道的是,Excel也可以…...

记录crack某IDE插件过程
声明:本文仅记录学习过程,已对关键位置脱敏处理,未提供任何工具,请支持正版。 反编译jar包 使用cfr进行对插件核心jar包MyBxxxxxx-obfuss.jar进行反编译,在本地生成a.txt。 java -jar cfr-0.152.jar MyBxxxx-obfuss.…...
Android DEX相关,ART加载OAT文件
android .dex文件,对于Android DEX文件详细说明 Android dex、odex、oat、vdex、art区别 Android下的DEX文件和SO文件梳理总结 Android[art]-Android dex,odex,oat,vdex,art文件结构学习总结 第四章 常见的 Android 文件格式&…...

laravel框架 - 安装初步使用学习 composer安装
一、什么是laravel框架 Laravel框架可以开发各种不同类型的项目,内容管理系统(Content Management System,CMS)是一种比较典型的项目,常见的网站类型(如门户、新闻、博客、文章等)都可以利用CM…...

API实战教程:使用身份证OCR识别API构建一个应用
1. 引言 你是否曾经想过,只需拍一张身份证的照片,就能自动读取上面的所有信息?今天,我们要介绍的就是这样一个神奇的工具:身份证OCR识别API。不管你是技术小白还是初学者,跟着我们的步骤,你都可…...

前端-layui动态渲染表格行列与复杂表头合并
说在前面: 最近一直在用layui处理表格 写的有些代码感觉还挺有用的,顺便记录下来方便以后查看使用; HTML处代码 拿到id 渲染位置表格 <div class"layui-table-body salaryTable"><table class"layui-table" i…...

IDM(Internet Download Manager)下载器2024最新版本如何下载?
IDM(Internet Download Manager)下载器能够兼容支持多种浏览器进行文件下载,很多时候只要复制一个地址IDM的下载弹窗就自动弹出来,有时候不需要下载的时候也会弹,时间久了就会感觉很烦,不过这个问题其实可以…...

前端综合练手小项目
导读 本篇文章主要以小项目的方式展开,其中给出的代码中均包含详细地注释,大家可以参照理解。下面4个小项目中均包含有 HTML、CSS、JavaScript 等相关知识,可以拿来练手,系统提升一下自己的前端开发能力。 废话少说,…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

基于Java+VUE+MariaDB实现(Web)仿小米商城
仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意:运行前…...