【AI大模型应用开发】3. RAG初探 - 动手实现一个最简单的RAG应用

0. 什么是RAG

大模型也不是万能的,也有局限性。

  • LLM 的知识不是实时的
  • LLM 可能不知道你私有的领域/业务知识

RAG(Retrieval Augmented Generation)顾名思义:通过检索的方法来增强生成模型的能力。你可以把这个过程想象成开卷考试。让 LLM 先翻书,再回答问题。

1. RAG基本流程

在这里插入图片描述 看图就很容易理解RAG的流程了:
😝有需要的小伙伴,可以V扫描下方二维码免费领取🆓

(1)私有知识通过切分、向量化保存到向量数据库中,供后续使用

(2)用户提问时,将用户提问用同样的方式向量化,然后去向量数据库中检索

(3)检索出相似度最高的k个切分段落

(4)将检索结果和用户的提问放到Prompt模板中,组装成一个完整的Prompt

(5)组装好的Prompt给大模型,让大模型生成回答

理想状态下,大模型是完全依赖检索出的文档片段进行组织答案的

简化一下,可以看出RAG有两大过程:

  1. 加载文档,生成向量数据库
  2. 查询向量数据库,询问大模型得到答案

下面我们一步步拆解,深入了解下RAG的流程和实现RAG所需的基本模块。

2. 向量数据库的生成

2.1 文档加载与分块

首先加载我们私有的知识库。这里以加载PDF文件为例。Python提供了加载PDF的一些库,这里用 pdfminer。

  • 安装 pdfminer:
bash复制代码pip install pdfminer.six
  • 先看代码:
python复制代码from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainerclass PDFFileLoader():def __init__(self, file) -> None:self.paragraphs = self.extract_text_from_pdf(file, page_numbers=[0,3])i = 1for para in self.paragraphs[:3]:print(f"========= 第{i}段 ==========")print(para+"\n")i += 1def getParagraphs(self):return self.paragraphs################################# 文档的加载与切割 ############################def extract_text_from_pdf(self, filename, page_numbers=None):'''从 PDF 文件中(按指定页码)提取文字'''paragraphs = []buffer = ''full_text = ''# 提取全部文本for i, page_layout in enumerate(extract_pages(filename)):# 如果指定了页码范围,跳过范围外的页if page_numbers is not None and i not in page_numbers:continuefor element in page_layout:if isinstance(element, LTTextContainer):full_text += element.get_text() + '\n'# 段落分割lines = full_text.split('。\n')for text in lines:buffer = text.replace('\n', ' ')if buffer:paragraphs.append(buffer)buffer = ''row_count = 0if buffer:paragraphs.append(buffer)return paragraphsPDFFileLoader("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")
  • 代码解释

(1)我们首先定义了一个 PDFFileLoader 的类,接收一个PDF文件路径。然后类内部调用extract_text_from_pdf去解析PDF文件并分段。

(2)extract_text_from_pdf中前半部分代码是利用 extract_pages 按页提取出PDF文件中的文字,然后组装成 full_text

(3)extract_text_from_pdf中后半部分代码是将 full_text 进行段落划分。

说明:因为每个PDF提取出来的文字格式可能不同,有的每一行后面都带有"\n\n",有的不带有"\n\n",有的每一行中的单词都粘在一起…,各种各样,所以PDF文字划分和段落分割的算法都无法做到完美适应所有PDF。本文重点不再这,所以粗暴地根据"。\n"划分了段落。实际应用中这里你应该按照你的PDF文件去进行调试和分割,段落划分这几行代码不能直接用。

可以简单看下我为什么能如此粗暴的划分段落:通过extract_pages提取出来的文本如下:

'如何向 ChatGPT 提问以获得高质量答案:提示\n技巧工程完全指南\n\n介绍\n\n我很高兴欢迎您阅读我的最新书籍《The Art of Asking ChatGPT for High-Quality Answers: A complete \n\nGuide to Prompt Engineering Techniques》。本书是一本全面指南,介绍了各种提示技术,用于从\n\nChatGPT中生成高质量的答案。\n\n我们将探讨如何使用不同的提示工程技术来实现不同的目标。ChatGPT是一款最先进的语言模型,能够生成\n\n类似人类的文本。然而,理解如何正确地向ChatGPT提问以获得我们所需的高质量输出非常重要。而这正是\n本书的目的。\n\n无论您是普通人、研究人员、开发人员,还是只是想在自己的领域中将ChatGPT作为个人助手的人,本书都\n是为您编写的。我使用简单易懂的语言,提供实用的解释,并在每个提示技术中提供了示例和提示公式。通\n\n过本书,您将学习如何使用提示工程技术来控制ChatGPT的输出,并生成符合您特定需求的文本。\n\n在整本书中,我们还提供了如何结合不同的提示技术以实现更具体结果的示例。我希望您能像我写作时一\n\n样,享受阅读本书并从中获得知识。\n\n \n\n

与原文对比,大体上按"。\n"来分割能与实际段落比较接近,所以本例我就先这样干了。这实际是不能用于实际项目的:

在这里插入图片描述

  • 分割结果(打印前三段):

在这里插入图片描述

2.2 创建向量数据库

本文以 chromadb 向量数据库为例进行实操。

  • 安装向量数据库chromadb
python复制代码pip install chromadb
2.2.1 创建过程

(1)创建一个向量数据库类。该类add_documents函数用来添加数据,它需要三个参数:

  • 文档的向量
  • 文档的原文
  • 文档的id
python复制代码import chromadb
from chromadb.config import Settingsclass MyVectorDBConnector:def __init__(self, collection_name, embedding_fn):chroma_client = chromadb.Client(Settings(allow_reset=True))# 为了演示,实际不需要每次 reset()chroma_client.reset()# 创建一个 collectionself.collection = chroma_client.get_or_create_collection(name=collection_name)self.embedding_fn = embedding_fndef add_documents(self, documents):'''向 collection 中添加文档与向量'''self.collection.add(embeddings=self.embedding_fn(documents),  # 每个文档的向量documents=documents,  # 文档的原文ids=[f"id{i}" for i in range(len(documents))]  # 每个文档的 id)def search(self, query, top_n):'''检索向量数据库'''results = self.collection.query(query_embeddings=self.embedding_fn([query]),n_results=top_n)return results

(2)文档的向量怎么来?可以通过OpenAI的embeddings接口计算得到:

python复制代码from openai import OpenAI
import os
# 加载环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())  # 读取本地 .env 文件,里面定义了 OPENAI_API_KEYclient = OpenAI()def get_embeddings(texts, model="text-embedding-3-small"):'''封装 OpenAI 的 Embedding 模型接口'''print(texts)print(model)data = client.embeddings.create(input=texts, model=model).dataprint(data)return [x.embedding for x in data]

(3)调用接口,创建向量数据库

python复制代码# 创建一个向量数据库对象
vector_db = MyVectorDBConnector("demo", get_embeddings)
# 向向量数据库中添加文档
vector_db.add_documents(pdf_loader.getParagraphs())

(4)测试查询

python复制代码user_query = "什么是角色提示?"
results = vector_db.search(user_query, 3) # 3是指查询出最相近的3块文本
for para in results['documents'][0]:print(para+"\n\n")
2.2.2 运行结果

(1)通过OpenAI的embeddings接口计算得到的文本向量

在这里插入图片描述

(2)查询结果,查找出最相近的3块文本

在这里插入图片描述

2.2.3 踩坑
2.2.3.1 坑一:NoneType object is not iterable

在这里插入图片描述

  • 原因:传入的分块有空字符的情况。

不知道这种情况为什么会导致NoneType的错误,可能是OpenAI向量化时对特殊字符进行了去除?

在这里插入图片描述

  • 解决方法:保证分块中没有全是特殊字符的分块即可。
2.2.3.2 坑二:Number of embeddings 9 must match number of ids 10

在这里插入图片描述

  • 原因:可以看下下面的代码,上面的错误指的是embeddings是9个值,而ids有10个值。这是因为在解决坑一时,将里面最后那个空的文档分块去掉了,没去生成embeddings。
python复制代码self.collection.add(embeddings=self.embedding_fn(documents),  # 每个文档的向量documents=documents,  # 文档的原文ids=[f"id{i}" for i in range(len(documents))]  # 每个文档的 id
)
  • 解决方法:保证documents和embeddings的数组大小长度一致。

以上两个坑总体的解决方案代码,看下里面修改的部分(注释部分),在段落分割部分就把异常的分块去掉,从源头上保证documents的正常以及后面documents和embeddings数组大小一致:

python复制代码# 段落分割
lines = full_text.split('。\n')
for text in lines:buffer = text.strip(' ').replace('\n', ' ').replace('[', '').replace(']', '') ## 1. 去掉特殊字符if len(buffer) < 10: ## 2. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用continueif buffer:paragraphs.append(buffer)buffer = ''row_count = 0if buffer and len(buffer) > 10: ## 3. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用paragraphs.append(buffer)
return paragraphs

注意:文档分块不一定是按段落分。

3. Prompt模板

上面我们已经拿到了检索回来的相关文档。下面我们写一个Prompt模板用来组装这些文档以及用户的提问。

python复制代码def build_prompt(prompt_template, **kwargs):'''将 Prompt 模板赋值'''prompt = prompt_templatefor k, v in kwargs.items(): if isinstance(v,str):val = velif isinstance(v, list) and all(isinstance(elem, str) for elem in v):val = '\n'.join(v)else:val = str(v)prompt = prompt.replace(f"__{k.upper()}__",val)return promptprompt_template = """
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。
确保你的回复完全依据下述已知信息。不要编造答案。
如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。已知信息:
__INFO__用户问:
__QUERY__请用中文回答用户问题。
"""

注意以上最重要的提示词,要求大模型完全按照给定的文本回答问题:

你的任务是根据下述给定的已知信息回答用户问题。 确保你的回复完全依据下述已知信息。不要编造答案。 如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

4. 使用大模型得到答案

4.1 封装OpenAI接口

python复制代码def get_completion(prompt, model="gpt-3.5-turbo-1106"):'''封装 openai 接口'''messages = [{"role": "user", "content": prompt}]response = client.chat.completions.create(model=model,messages=messages,temperature=0,  # 模型输出的随机性,0 表示随机性最小)return response.choices[0].message.content

4.2 组装Prompt

python复制代码prompt = build_prompt(prompt_template, info=results['documents'][0], query=user_query)
print(prompt)
  • 运行结果

在这里插入图片描述

4.3 使用大模型得到答案

python复制代码response = get_completion(prompt)
print(response)
  • 运行结果

在这里插入图片描述

5. 总结

至此,我们已经实现了RAG的基本流程。总结下流程:

  • 离线部分,可提前生成好

    (1)文档加载与分块

    (2)分块数据灌入向量数据库

  • 在线部分

    (3)解析用户提问,用户提问向量化

    (4)查询向量数据库,得到最相似的k个文本块

    (5)使用得到的k个文本块和用户提问组装Prompt模板

    (6)询问大模型得到最终答案

5.1 封装RAG

我们将RAG流程封装一下,createVectorDB完成离线部分,创建出向量数据库和灌入数据。chat完成在线部分。

python复制代码class RAG_Bot:def __init__(self, n_results=2):self.llm_api = get_completionself.n_results = n_resultsdef createVectorDB(self, file):print(file)pdf_loader = PDFFileLoader(file)# 创建一个向量数据库对象self.vector_db = MyVectorDBConnector("demo", get_embeddings)# 向向量数据库中添加文档,灌入数据self.vector_db.add_documents(pdf_loader.getParagraphs())def chat(self, user_query):# 1. 检索search_results = self.vector_db.search(user_query,self.n_results)# 2. 构建 Promptprompt = build_prompt(prompt_template, info=search_results['documents'][0], query=user_query)# 3. 调用 LLMresponse = self.llm_api(prompt)return response
  • 使用
python复制代码rag_bot = RAG_Bot()
rag_bot.createVectorDB("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")
response = rag_bot.chat("什么是角色提示?")
print("response=====================>")
print(response)

5.2 完整代码

python复制代码from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainerclass PDFFileLoader():def __init__(self, file) -> None:self.paragraphs = self.extract_text_from_pdf(file, page_numbers=[0,3])i = 1for para in self.paragraphs:print(f"========= 第{i}段 ==========")print(para+"\n")i += 1def getParagraphs(self):return self.paragraphs################################# 文档的加载与切割 ############################def extract_text_from_pdf(self, filename, page_numbers=None):'''从 PDF 文件中(按指定页码)提取文字'''paragraphs = []buffer = ''full_text = ''# 提取全部文本for i, page_layout in enumerate(extract_pages(filename)):# 如果指定了页码范围,跳过范围外的页if page_numbers is not None and i not in page_numbers:continuefor element in page_layout:if isinstance(element, LTTextContainer):full_text += element.get_text() + '\n'# 段落分割lines = full_text.split('。\n')for text in lines:buffer = text.strip(' ').replace('\n', ' ').replace('[', '').replace(']', '') ## 1. 去掉特殊字符if len(buffer) < 10: ## 2. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用continueif buffer:paragraphs.append(buffer)buffer = ''row_count = 0if buffer and len(buffer) > 10: ## 3. 过滤掉长度小于 10 的段落,这可能会导致一些信息丢失,慎重使用,实际生产中不能用paragraphs.append(buffer)return paragraphs# pdf_loader = PDFFileLoader("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")from openai import OpenAI
import os
# 加载环境变量
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())  # 读取本地 .env 文件,里面定义了 OPENAI_API_KEYclient = OpenAI()def get_embeddings(texts, model="text-embedding-3-small"):'''封装 OpenAI 的 Embedding 模型接口'''data = client.embeddings.create(input=texts, model=model).datareturn [x.embedding for x in data]import chromadb
from chromadb.config import Settingsclass MyVectorDBConnector:def __init__(self, collection_name, embedding_fn):chroma_client = chromadb.Client(Settings(allow_reset=True))# 为了演示,实际不需要每次 reset()chroma_client.reset()# 创建一个 collectionself.collection = chroma_client.get_or_create_collection(name=collection_name)self.embedding_fn = embedding_fndef add_documents(self, documents):'''向 collection 中添加文档与向量'''self.collection.add(embeddings=self.embedding_fn(documents),  # 每个文档的向量documents=documents,  # 文档的原文ids=[f"id{i}" for i in range(len(documents))]  # 每个文档的 id)def search(self, query, top_n):'''检索向量数据库'''results = self.collection.query(query_embeddings=self.embedding_fn([query]),n_results=top_n)return results# # 创建一个向量数据库对象
# vector_db = MyVectorDBConnector("demo", get_embeddings)
# # 向向量数据库中添加文档
# vector_db.add_documents(pdf_loader.getParagraphs())# user_query = "什么是角色提示?"
# results = vector_db.search(user_query, 3)
# for para in results['documents'][0]:
#     print(para+"\n\n")def build_prompt(prompt_template, **kwargs):'''将 Prompt 模板赋值'''prompt = prompt_templatefor k, v in kwargs.items(): if isinstance(v,str):val = velif isinstance(v, list) and all(isinstance(elem, str) for elem in v):val = '\n'.join(v)else:val = str(v)prompt = prompt.replace(f"__{k.upper()}__",val)return promptprompt_template = """
你是一个问答机器人。
你的任务是根据下述给定的已知信息回答用户问题。
确保你的回复完全依据下述已知信息。不要编造答案。
如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。已知信息:
__INFO__用户问:
__QUERY__请用中文回答用户问题。
"""########################### 大模型接口封装 #############################def get_completion(prompt, model="gpt-3.5-turbo-1106"):'''封装 openai 接口'''messages = [{"role": "user", "content": prompt}]response = client.chat.completions.create(model=model,messages=messages,temperature=0,  # 模型输出的随机性,0 表示随机性最小)return response.choices[0].message.content# prompt = build_prompt(prompt_template, info=results['documents'][0], query=user_query)
# print(prompt)# response = get_completion(prompt)
# print(response)##################################  基于向量检索的 RAG ##################
class RAG_Bot:def __init__(self, n_results=2):self.llm_api = get_completionself.n_results = n_resultsdef createVectorDB(self, file):print(file)pdf_loader = PDFFileLoader(file)# 创建一个向量数据库对象self.vector_db = MyVectorDBConnector("demo", get_embeddings)# 向向量数据库中添加文档,灌入数据self.vector_db.add_documents(pdf_loader.getParagraphs())def chat(self, user_query):# 1. 检索search_results = self.vector_db.search(user_query,self.n_results)# 2. 构建 Promptprompt = build_prompt(prompt_template, info=search_results['documents'][0], query=user_query)print("prompt===================>")print(prompt)# 3. 调用 LLMresponse = self.llm_api(prompt)return responserag_bot = RAG_Bot()
rag_bot.createVectorDB("D:\GitHub\LEARN_LLM\RAG\如何向 ChatGPT 提问以获得高质量答案:提示技巧工程完全指南.pdf")
response = rag_bot.chat("什么是角色提示?")
print("response=====================>")
print(response)

6. 思考

RAG 是一个增强大模型垂直领域能力和减少幻觉的通用方法论,所以了解其原理和流程对实现出效果较好的大模型应用非常有用。

但是上面也可以看到,它也限制了大模型使用其自身的知识库去回答问题,只能够用给定的文本回复问题。这就导致这个RAG应用的通用性大大降低。

另外,从RAG流程中也可以看到要想实现的效果好,也是困难重重:

(1)预处理: 首先文本分割的块要恰到好处

  • 文本分割的粒度太小,查找到的参考文本较少
  • 文本颗粒度太大,参考文本太多,消耗token,同时也会带入更多的干扰信息,导致大模型出现幻觉的概率增加

(2)有些问题的回答是需要依赖上下文的,怎样将上下文所在的文本块都找出来也不容易

(3)召回正确性:召回文档的相关性也对结果比较重要。查找出的文档虽然与用户提问的向量值比较相似,但某些时候,最相似的并不一定是与问题答案相关的

(4)大模型本身的能力对结果也比较重要

目前针对以上各个困难都有非常多的研究,还在快速发展阶段,未形成一套通用、效果好的方法论。

读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用

对于0基础小白入门:

如果你是零基础小白,想快速入门大模型是可以考虑的。

一方面是学习时间相对较短,学习内容更全面更集中。
二方面是可以根据这些资料规划好学习计划和方向。

资源分享

图片

大模型AGI学习包

图片

图片

资料目录

  1. 成长路线图&学习规划
  2. 配套视频教程
  3. 实战LLM
  4. 人工智能比赛资料
  5. AI人工智能必读书单
  6. 面试题合集

人工智能\大模型入门学习大礼包》,可以扫描下方二维码免费领取

1.成长路线图&学习规划

要学习一门新的技术,作为新手一定要先学习成长路线图方向不对,努力白费

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图&学习规划。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

图片

2.视频教程

很多朋友都不喜欢晦涩的文字,我也为大家准备了视频教程,其中一共有21个章节,每个章节都是当前板块的精华浓缩

图片

3.LLM

大家最喜欢也是最关心的LLM(大语言模型)

图片

人工智能\大模型入门学习大礼包》,可以扫描下方二维码免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/34822.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MATLAB算法实战应用案例精讲-【数模应用】线性判别分析(附MATLAB、python和R语言代码实现)

目录 前言 算法原理 什么是判别分析 线性判别分析(LDA) 数学模型 二分类 多分类LDA ​编辑 算法思想: 费歇(FISHER)判别思想 贝叶斯(BAYES)判别思想 LDA算法流程 LDA与PCA对比 SPSSPRO 1、作用 2、输入输出描述 3、案例示例 4、案例数据 5、案例操作 …

VC++开发积累——vc++6.0中删除函数的方法,右键,Delete

目录 引出插曲&#xff1a;删除函数的方法多行注释的实现代码输入的自动提示搜索出来&#xff0c;标记和取消标记跳转到上一步的位置 ctrl TAB 总结其他规范和帮助文档创建第一个Qt程序对象树概念信号signal槽slot自定义信号和槽1.自定义信号2.自定义槽3.建立连接4.进行触发 自…

DM达梦数据库字符串函数整理

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

python基础篇(5):None类型

1 None类型 Python中有一个特殊的字面量&#xff1a;None&#xff0c;其类型是&#xff1a;<class NoneType> 无返回值的函数&#xff0c;实际上就是返回了&#xff1a;None这个字面量 None表示&#xff1a;空的、无实际意义的意思 函数返回的None&#xff0c;就表示…

Avue-data数据大屏显示饼图(附Demo)

目录 前言1. Sql查询2. 颜色细节 前言 对于这部分知识&#xff0c;原先有过柱状图实战&#xff1a;Avue-data数据大屏显示柱状图&#xff08;附Demo讲解&#xff09; 以下直奔主题&#xff0c;以Sql数据库数据为主 1. Sql查询 以饼图为例&#xff0c;需要返回的形式如下&am…

Kafka基本架构

「kafka设计思想」 一个最基本的架构是生产者发布一个消息到Kafka的一个Topic &#xff0c;该Topic的消息存放于的Broker中&#xff0c;消费者订阅这个Topic&#xff0c;然后从Broker中消费消息&#xff0c;下面这个图可以更直观的描述这个场景&#xff1a; 「消息状态&#x…

嵌入式通信协议-----UART协议详解(基于智芯Z20k11X)

目录 一、简介 1.概念 2.结构 3.特点 4.优缺点 二、协议帧组成 1.起始位 2.数据位 3.奇偶校验位 4.停止位 三、UART通信过程 四、USART与UART区别 五、代码实现 1.硬件框图 2.软件实现 一、简介 1.概念 USART&#xff08;Universal Synchronous Asynchronous R…

2024年最新机动车签字授权人考试题库。

31."简易瞬态工况法"所使用的五气分析仪的温度范图:分析系统及相关部件应在&#xff08; &#xff09;。 A.0-40℃ B.0-50℃ C.0-60℃ D.-10-40℃ 答案:A 32.稀释氧传感器环境空气量程检测时的读数值位于&#xff08; &#xff09;%vol范围之外时&#xff0c;应…

成都百洲文化传媒有限公司助力电商品牌乘风破浪

在当今数字化时代&#xff0c;电商行业风起云涌&#xff0c;竞争异常激烈。成都百洲文化传媒有限公司凭借其对电商行业的深刻理解与实战经验&#xff0c;已然成为行业中的佼佼者。公司专注于电商服务领域&#xff0c;通过创新的营销模式和精准的市场策略&#xff0c;助力众多品…

北京互动阅读app开发,“身”临其境,阅读精彩

随着大数据与智能化的不断发展进步&#xff0c;线上阅读软件也越来越多&#xff0c;为了缓解对传统翻页阅读方式产生的疲劳&#xff0c;人们对线上阅读提出了新的要求。对此&#xff0c;与智能科技相结合的北京互动阅读app开发&#xff0c;以高互动、高体验感的优势&#xff0c…

类的默认成员函数——构造与析构函数

如果一个类中什么成员都没有&#xff0c;简称为空类。但是空类中真的什么都没有吗&#xff1f; 当然不是&#xff0c;任何类在什么都不写的情况下&#xff0c;编译器会自动生成6个默认成员函数 1.构造函数 1.1概念引入 对于以下这个Date类&#xff0c;可以通过Init公有方法给…

食品供应链管理商城系统的设计、实现和代码

上线食品供应链管理商城系统的设计与实现是一项复杂且重要的任务&#xff0c;它不仅涉及到技术层面的具体实现&#xff0c;还包括业务流程的优化和用户体验的提升。本文将从系统设计、功能模块、技术选型以及实现步骤等方面进行详细探讨。 ### 系统设计 在设计食品供应链管理…

从单一到多元:EasyCVR流媒体视频汇聚技术推动安防监控智能升级

随着科技的飞速发展&#xff0c;视频已成为我们日常生活和工作中的重要组成部分。尤其在远程办公、在线教育、虚拟会议等领域&#xff0c;视频的应用愈发广泛。为了满足日益增长的视频需求&#xff0c;流媒体视频汇聚融合技术应运而生&#xff0c;它不仅改变了传统视频的观看和…

第二天的课根本跟不上啊 难难难啊

编程实现三个数求最大 编程实现求解一元二次方程 传参问题 直接使用返回值 复制控制 复制控制是指在C中控制对象复制行为的机制&#xff0c; 包括拷贝构造函数&#xff08;copy constructor&#xff09;、 赋值操作符&#xff08;copy assignment operator&#xff09;、 …

mysql workbench使用schema视图导出表和列结构到excel

目的&#xff1a;导出所有表和列的名字和注释 很多时候没有正规的数据库文档&#xff0c;为了快速交流啊&#xff0c;需要一个快捷的基础。数据库建表的时候可能有注释&#xff0c;也可能没有注释。有当然好&#xff0c;查看注释就能清楚很多&#xff0c;没有的话最好一个一个补…

零基础如何制作一个GIS可视化大屏?免费无难度!

一.GIS是什么&#xff1f; GIS&#xff08;地理信息系统&#xff09;开发是一种利用计算机技术对地理信息进行处理、分析、存储和展示的技术。GIS开发可以应用在很多领域&#xff0c;如城市规划、环境保护、交通管理、农业生产等。 二.如何将GIS与数字孪生结合起来&#xff1f…

Verilog的逻辑系统及数据类型(二):参数和参数重载

目录 3.参数&#xff08;parameters)3.1 参数重载&#xff08;overriding)3.2 参数重载举例 微信公众号获取更多FPGA相关源码&#xff1a; 3.参数&#xff08;parameters) 用参数声明一个可变常量&#xff0c;常用于定义延时及宽度变量。参数定义的语法&#xff1a;paramete…

安卓应用开发学习:获取经纬度及地理位置描述信息

前段时间&#xff0c;我在学习鸿蒙应用开发的过程中&#xff0c;在鸿蒙系统的手机上实现了获取经纬度及地理位置描述信息&#xff08;鸿蒙应用开发学习&#xff1a;手机位置信息进阶&#xff0c;从经纬度数据获取地理位置描述信息&#xff09;。反而学习时间更长的安卓应用开发…

【WEB】关于react的WEB应用中使用React Developer Tools便捷快速查看元素数据

1、往扩展工具中添加React Developer Tools的扩展包 2、检查是否生效&#xff0c;如下图&#xff1a; 可以看到右上角多出来一个Components的tab选项&#xff0c;就是成功了

WebStorm 环境配置带@符号的相对路径穿透

在使用WebStorm 环境开发web页面项目时有时想快速查看页面的引用代码&#xff0c;只能手工找到引入文件路径&#xff0c;这很不方便&#xff0c;只需通过配置webStorm单击打开。 1 使用符号相对路径&#xff0c;在默认情况下没有配置环境是无法打开&#xff0c;如下图&#xf…