【c++】从 “勉强能用” 到 “真正好用”:中文问答系统的 200 行关键优化——关于我用AI编写了一个聊天机器人……(16)

先看核心结论:两段代码的本质区别

如果用一句话总结两段代码的差异:前者是 “带中文支持的问答系统”,后者是 “真正适配中文的问答系统”

具体来说,两段代码的核心功能都是 “加载问答数据→接收用户输入→匹配答案”,但在最关键的 “中文处理” 和 “系统扩展性” 上,优化版做了颠覆性改进。接下来我们从 3 个核心维度展开对比。

一、中文分词:从 “粗暴切割” 到 “智能识别”

中文和英文最大的区别是:英文天然以空格分隔单词,而中文需要 “分词”—— 把 “我爱机器学习” 拆成 “我 / 爱 / 机器学习”,这一步直接决定后续匹配精度。

原来的分词逻辑:双字切割(勉强能用)

ChineseProcessor::segment函数用了最简易的处理方式:

显然,新版能正确识别核心词汇,后续的 TF-IDF 匹配自然更准确。

效果对比:分词精度决定问答质量

用户输入旧版分词结果(双字切割)新版分词结果(词典匹配)
机器学习怎么入门机器 / 学习 / 怎么 / 入门机器学习 / 怎么 / 入门
自然语言处理教程自然 / 语言 / 处理 / 教程自然语言处理 / 教程

二、系统扩展性:从 “固定死” 到 “可配置”

  • 对英文:直接拼接字母,遇到非字母就截断
  • 对中文:硬拆成连续两个字(比如 “人工智能” 拆成 “人工”+“智能”)
  • 问题:完全不理解语义,比如 “机器学习” 会被拆成 “机器”+“学习”,如果训练数据里是 “机器学习” 这个词,就无法匹配
    // 旧版分词核心代码(简易双字切割)
    if (i + 2 < text.size() && isChineseChar(c1, text[i+1], text[i+2])) {// 强行提取双字,不考虑语义if (i + 5 < text.size() && isChineseChar(text[i+3], text[i+4], text[i+5])) {string twoChars = text.substr(i, 6);  // 固定取6字节(2个汉字)tokens.push_back(twoChars);i += 6;} else {string oneChar = text.substr(i, 3);  // 单个汉字tokens.push_back(oneChar);i += 3;}
    }

    新版的分词逻辑:基于词典的最大匹配(真正可用)

    新版直接实现了一个简化版的 Jieba 分词(中文分词领域的经典工具),核心改进有 3 点:

  • 内置基础词典:提前定义 “人工智能”“机器学习” 等常见词,避免被拆错

    // 新版内置词典(部分)
    string dict_content = "人工智能\n""机器学习\n""深度学习\n""自然语言处理\n";

  • 最大匹配算法:从左到右尝试匹配最长的词(比如 “深度学习入门” 会优先匹配 “深度学习”,而不是 “深度”+“学习”)

  • 支持自定义词典:可以通过user_dict.txt添加领域词汇(比如专业术语 “Transformer”“BERT”)

一个实用的问答系统,必须能根据场景调整 —— 比如不同领域需要不同的专业词汇,不同用户可能有不同的停用词(比如 “的”“了” 这类无意义词需要过滤)。

旧版的局限:写死在代码里,改一点就得重编译

旧版的中文处理逻辑完全硬编码:

  • 没有停用词过滤(“的”“了” 这类词会干扰匹配)
  • 分词规则固定,无法添加新词汇(比如要加 “大语言模型”,必须改代码重新编译)
  • 跨平台支持缺失(Windows 下可能因文件操作 API 报错)

新版的优化:3 个可配置入口,无需改代码

  1. 自定义词典(user_dict.txt):添加领域词汇

    // 新版支持加载外部词典
    bool loadUserDict(const string& file_path) {ifstream fin(file_path.c_str());// 读取文件内容并添加到词典
    }
  2. 停用词表(stop_words.txt):过滤无意义词汇
    内置默认停用词(“的”“了” 等),还能通过文件添加,比如加 “请问”“您好” 等对话常用词。

  3. 跨平台兼容:自动适配 Windows 和 Linux

    // 跨平台文件状态获取
    #ifdef _WIN32
    #include <sys/stat.h>
    #define stat _stat  // Windows下兼容stat函数
    #else
    #include <sys/stat.h>
    #endif

三、细节打磨:从 “能跑” 到 “稳定”

除了核心功能,新版在细节上做了很多工程化优化,这些正是 “玩具级” 和 “实用级” 的区别:

  1. 更严谨的 UTF-8 处理
    旧版对 UTF-8 的解码逻辑有漏洞(比如中文标点判断可能误判),新版实现了完整的 UTF-8 编解码函数,支持各种中文符号。

  2. 日志系统更完善
    新增了日志轮转(超过 1MB 自动备份)、更详细的错误日志(比如 “加载词典失败” 会明确提示原因)。

  3. 边界处理更健壮
    对异常输入(比如非 UTF-8 字符、空字符串)做了容错,避免程序崩溃。

为什么这些优化很重要?

我们容易沉迷于 “高大上” 的算法(比如 TF-IDF、相似度计算),却忽略中文处理这个基础。但实际上:

  • 中文分词精度不够,再完美的 TF-IDF 也会 “认错词”
  • 没有可配置入口,系统无法适应新场景(比如从 “通用问答” 改成 “医疗问答”)
  • 细节处理不到位,上线后可能因为一个特殊字符就崩溃

新版代码只多了 200 多行,但通过优化中文处理和扩展性,直接从 “演示用” 提升到了 “可实际部署” 的级别。

最后3 个实用建议

  1. 做中文项目,先搞定分词和编码:推荐先掌握 Jieba 等工具的基本用法,再深入算法
  2. 预留配置入口:把可能变的东西(词典、规则、参数)放到配置文件,而不是硬编码
  3. 重视异常处理:用户输入永远比你想的更 “离谱”,多考虑空输入、特殊字符、大文件等场景

如果这篇文章对你有帮助,别忘了点赞收藏 —— 你的支持是我更新的动力!

附上代码 :

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <set>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <iterator>
#include <limits>// 跨平台文件状态获取
#ifdef _WIN32
#include <sys/stat.h>
#define stat _stat
#else
#include <sys/stat.h>
#endifusing namespace std;// JiebaCpp 分词库实现 (简化版,适合单文件集成)
namespace jieba {// 字典节点结构struct DictNode {bool is_end;map<unsigned short, DictNode*> children;DictNode() : is_end(false) {}~DictNode() {for (map<unsigned short, DictNode*>::iterator it = children.begin(); it != children.end(); ++it) {delete it->second;}}};// 分词工具类class Jieba {private:DictNode* root;set<string> stop_words;const static int MAX_WORD_LENGTH = 16; // 最大词长// UTF-8字符解码bool decodeUTF8(const string& str, size_t& pos, unsigned short& code) {if (pos >= str.size()) return false;unsigned char c = static_cast<unsigned char>(str[pos]);if (c < 0x80) { // 单字节code = c;pos++;return true;} else if (c < 0xE0) { // 双字节if (pos + 1 >= str.size()) return false;unsigned char c2 = static_cast<unsigned char>(str[pos + 1]);if ((c2 & 0xC0) != 0x80) return false;code = ((c & 0x1F) << 6) | (c2 & 0x3F);pos += 2;return true;} else if (c < 0xF0) { // 三字节if (pos + 2 >= str.size()) return false;unsigned char c2 = static_cast<unsigned char>(str[pos + 1]);unsigned char c3 = static_cast<unsigned char>(str[pos + 2]);if ((c2 & 0xC0) != 0x80 || (c3 & 0xC0) != 0x80) return false;code = ((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);pos += 3;return true;}return false; // 不支持四字节及以上字符}// UTF-8字符编码string encodeUTF8(unsigned short code) {string res;if (code < 0x80) {res += static_cast<char>(code);} else if (code < 0x800) {res += static_cast<char>(0xC0 | (code >> 6));res += static_cast<char>(0x80 | (code & 0x3F));} else {res += static_cast<char>(0xE0 | (code >> 12));res += static_cast<char>(0x80 | ((code >> 6) & 0x3F));res += static_cast<char>(0x80 | (code & 0x3F));}return res;}// 向字典添加词void addWordToDict(const vector<unsigned short>& codes) {DictNode* node = root;for (size_t i = 0; i < codes.size(); ++i) {unsigned short code = codes[i];if (node->children.find(code) == node->children.end()) {node->children[code] = new DictNode();}node = node->children[code];}node->is_end = true;}// 从字符串加载词典void loadDictFromContent(const string& content) {vector<unsigned short> codes;size_t pos = 0;unsigned short code;while (decodeUTF8(content, pos, code)) {if (code == '\n' || code == '\r') {if (!codes.empty()) {addWordToDict(codes);codes.clear();}} else if (code != ' ' && code != '\t') {codes.push_back(code);}}// 添加最后一个词if (!codes.empty()) {addWordToDict(codes);}}// 从字符串加载停用词void loadStopWordsFromContent(const string& content) {string word;size_t pos = 0;unsigned short code;while (decodeUTF8(content, pos, code)) {if (code == '\n' || code == '\r') {if (!word.empty()) {stop_words.insert(word);word.clear();}} else if (code != ' ' && code != '\t') {word += encodeUTF8(code);}}// 添加最后一个停用词if (!word.empty()) {stop_words.insert(word);}}public:Jieba() {root = new DictNode();// 内置基础词典 (简化版)string dict_content = "人工智能\n""机器学习\n""深度学习\n""自然语言处理\n""计算机\n""电脑\n""程序\n""编程\n""C++\n""Python\n""Java\n""语言\n""学习\n""教程\n""入门\n""进阶\n""问题\n""答案\n""你好\n""再见\n""谢谢\n";// 内置停用词表string stop_words_content = "的\n""了\n""在\n""是\n""我\n""有\n""和\n""就\n""不\n""人\n""都\n""一\n""一个\n""上\n""也\n""很\n""到\n""说\n""要\n""去\n""你\n""会\n""着\n""没有\n""看\n""好\n""自己\n""这\n""啊\n""呢\n""吗\n""吧\n";loadDictFromContent(dict_content);loadStopWordsFromContent(stop_words_content);}~Jieba() {delete root;}// 加载外部词典bool loadUserDict(const string& file_path) {ifstream fin(file_path.c_str());if (!fin.is_open()) return false;string content((istreambuf_iterator<char>(fin)), istreambuf_iterator<char>());loadDictFromContent(content);return true;}// 加载外部停用词表bool loadStopWords(const string& file_path) {ifstream fin(file_path.c_str());if (!fin.is_open()) return false;string content((istreambuf_iterator<char>(fin)), istreambuf_iterator<char>());loadStopWordsFromContent(content);return true;}// 分词主函数vector<string> cut(const string& text, bool use_hmm = true) {vector<string> result;size_t pos = 0;size_t text_len = text.size();while (pos < text_len) {// 尝试读取一个UTF8字符unsigned short first_code;if (!decodeUTF8(text, pos, first_code)) {pos++;continue;}pos -= (first_code < 0x80) ? 1 : (first_code < 0x800) ? 2 : 3;// 最大匹配int max_len = 0;string best_word;DictNode* node = root;size_t current_pos = pos;unsigned short code;for (int i = 0; i < MAX_WORD_LENGTH; ++i) {if (!decodeUTF8(text, current_pos, code)) break;if (node->children.find(code) == node->children.end()) break;node = node->children[code];size_t word_len = current_pos - pos;if (node->is_end) {max_len = word_len;best_word = text.substr(pos, max_len);}}// 如果没有找到匹配的词,取单个字符if (max_len == 0) {decodeUTF8(text, pos, code);best_word = encodeUTF8(code);max_len = best_word.size();}// 过滤停用词if (stop_words.find(best_word) == stop_words.end()) {result.push_back(best_word);}pos += max_len;}return result;}};
}// 全局常量定义
const string LOG_FILE = "chat_log.txt";
const string TRAINING_FILE = "training_data.txt";
const string USER_DICT_FILE = "user_dict.txt";       // 用户自定义词典
const string STOP_WORDS_FILE = "stop_words.txt";   // 自定义停用词表
const int LOG_MAX_SIZE = 1024 * 1024;  // 日志最大1MB
const int CONTEXT_WINDOW = 3;          // 上下文窗口大小(保留3轮对话)
const double SIMILARITY_THRESHOLD = 0.15;  // 匹配阈值// 工具类:日志管理器(支持日志轮转)
class LogManager {
public:static void writeLog(const string& type, const string& content) {// 检查日志大小,超过阈值则轮转rotateLogIfNeeded();ofstream logFile(LOG_FILE.c_str(), ios::app);if (logFile.is_open()) {string timeStr = getCurrentTime();logFile << "[" << timeStr << "] [" << type << "] " << content << endl;logFile.close();} else {cerr << "警告: 无法打开日志文件 " << LOG_FILE << endl;}}private:static string getCurrentTime() {time_t now = time(NULL);struct tm* localTime = localtime(&now);char timeStr[20];sprintf(timeStr, "%04d-%02d-%02d %02d:%02d:%02d",localTime->tm_year + 1900,localTime->tm_mon + 1,localTime->tm_mday,localTime->tm_hour,localTime->tm_min,localTime->tm_sec);return string(timeStr);}static void rotateLogIfNeeded() {struct stat fileInfo;if (stat(LOG_FILE.c_str(), &fileInfo) == 0) {  // 获取文件信息if (fileInfo.st_size >= LOG_MAX_SIZE) {// 生成带时间戳的旧日志文件名string oldLog = LOG_FILE + "." + getCurrentTime();// 替换时间中的冒号(避免文件系统不支持)replace(oldLog.begin(), oldLog.end(), ':', '-');rename(LOG_FILE.c_str(), oldLog.c_str());  // 重命名旧日志}}}
};// 工具类:中文处理(基于Jieba分词)
class ChineseProcessor {
private:static jieba::Jieba jieba;static bool initialized;// 初始化Jieba分词器static void initialize() {if (!initialized) {// 尝试加载用户自定义词典ifstream userDict(USER_DICT_FILE.c_str());if (userDict.is_open()) {jieba.loadUserDict(USER_DICT_FILE);LogManager::writeLog("系统", "加载用户词典: " + USER_DICT_FILE);userDict.close();}// 尝试加载自定义停用词表ifstream stopWords(STOP_WORDS_FILE.c_str());if (stopWords.is_open()) {jieba.loadStopWords(STOP_WORDS_FILE);LogManager::writeLog("系统", "加载停用词表: " + STOP_WORDS_FILE);stopWords.close();}initialized = true;}}// 转换为小写(C++98无to_string,手动实现)static string toLower(const string& str) {string res;for (size_t i = 0; i < str.size(); ++i) {res += tolower(static_cast<unsigned char>(str[i]));}return res;}public:// 判断是否为UTF-8汉字(3字节)static bool isChineseChar(unsigned char c1, unsigned char c2, unsigned char c3) {return (c1 >= 0xE0 && c1 <= 0xEF) &&  // 3字节UTF-8首字节范围(c2 >= 0x80 && c2 <= 0xBF) && (c3 >= 0x80 && c3 <= 0xBF);}// 判断是否为UTF-8标点static bool isChinesePunctuation(const string& ch) {if (ch.empty()) return false;unsigned char c1 = static_cast<unsigned char>(ch[0]);// ASCII标点if (c1 <= 0x7F) {return !isalnum(c1);}// 中文标点的Unicode范围unsigned short code = 0;size_t pos = 0;// 解码第一个字符if (c1 >= 0xE0 && c1 <= 0xEF && ch.size() >= 3) {  // 3字节unsigned char c2 = static_cast<unsigned char>(ch[1]);unsigned char c3 = static_cast<unsigned char>(ch[2]);code = ((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);} else if (c1 >= 0xC0 && c1 <= 0xDF && ch.size() >= 2) {  // 2字节unsigned char c2 = static_cast<unsigned char>(ch[1]);code = ((c1 & 0x1F) << 6) | (c2 & 0x3F);}// 中文标点的Unicode范围return (code >= 0x3000 && code <= 0x303F) ||  // 标点、符号(code >= 0xFF00 && code <= 0xFFEF);    // 全角ASCII、全角标点}// 使用Jieba进行中文分词static vector<string> segment(const string& text) {initialize();  // 确保分词器已初始化vector<string> tokens = jieba.cut(text);vector<string> filteredTokens;// 过滤标点和空字符串,并处理英文小写for (size_t i = 0; i < tokens.size(); ++i) {if (tokens[i].empty()) continue;// 检查是否为标点if (isChinesePunctuation(tokens[i])) continue;// 处理英文:转为小写bool isAllAscii = true;for (size_t j = 0; j < tokens[i].size(); ++j) {if (static_cast<unsigned char>(tokens[i][j]) > 0x7F) {isAllAscii = false;break;}}if (isAllAscii) {filteredTokens.push_back(toLower(tokens[i]));} else {filteredTokens.push_back(tokens[i]);}}return filteredTokens;}
};// 静态成员初始化
jieba::Jieba ChineseProcessor::jieba;
bool ChineseProcessor::initialized = false;// 核心类:问答引擎
class QAEngine {
private:map<string, string> exactAnswers;  // 精确匹配答案map<string, vector<string> > qas;   // 问题-答案列表map<string, vector<string> > questionTokens;  // 问题-分词结果map<string, map<string, double> > precomputedTFIDF;  // 预计算的TF-IDF向量map<string, int> docFreq;          // 文档频率int totalDocs;                     // 总文档数vector<pair<string, string> > context;  // 对话上下文(问题-答案)public:QAEngine() : totalDocs(0) {}// 加载训练数据bool loadTrainingData() {ifstream fin(TRAINING_FILE.c_str());if (!fin.is_open()) {LogManager::writeLog("错误", "训练文件打开失败: " + TRAINING_FILE);return false;}string line, question, answer;bool readingAnswer = false;int lineNum = 0;while (getline(fin, line)) {lineNum++;if (line.empty()) {if (!question.empty() && !answer.empty()) {addQA(question, answer);  // 保存一组问答}question.clear();answer.clear();readingAnswer = false;continue;}if (line.size() >= 2 && line.substr(0, 2) == "Q:") {if (!question.empty() && !answer.empty()) {addQA(question, answer);  // 保存上一组问答}question = line.substr(2);answer.clear();readingAnswer = false;} else if (line.size() >= 2 && line.substr(0, 2) == "A:") {if (question.empty()) {LogManager::writeLog("警告", "行" + intToString(lineNum) + ":A:前无Q:");continue;}answer = line.substr(2);readingAnswer = true;} else if (readingAnswer) {answer += "\n" + line;  // 多行答案拼接}}// 处理最后一组问答if (!question.empty() && !answer.empty()) {addQA(question, answer);}// 预计算所有问题的TF-IDFprecomputeTFIDF();LogManager::writeLog("系统", "加载训练数据完成,共" + intToString(totalDocs) + "条");return true;}// 获取回答string getAnswer(const string& input) {// 检查命令if (input == "exit" || input == "quit") return "__EXIT__";if (input == "help") return showHelp();if (input == "topics") return showTopics();if (input == "history") return showHistory();// 更新上下文if (context.size() >= CONTEXT_WINDOW) {context.erase(context.begin());  // 超出窗口则移除最早记录}// 精确匹配string exactAns = exactMatch(input);if (!exactAns.empty()) {context.push_back(make_pair(input, exactAns));return exactAns;}// 模糊匹配(TF-IDF)vector<string> inputTokens = ChineseProcessor::segment(input);string bestAns = tfidfMatch(inputTokens);if (!bestAns.empty()) {context.push_back(make_pair(input, bestAns));return bestAns;}// 无匹配时推荐相似问题string noAns = "抱歉,我无法理解这个问题。\n可能相关的问题:\n" + recommendSimilar(input, 3);context.push_back(make_pair(input, noAns));return noAns;}private:// 添加问答对void addQA(const string& q, const string& a) {exactAnswers[q] = a;qas[q].push_back(a);vector<string> tokens = ChineseProcessor::segment(q);questionTokens[q] = tokens;// 更新文档频率set<string> uniqueTokens(tokens.begin(), tokens.end());for (set<string>::iterator it = uniqueTokens.begin(); it != uniqueTokens.end(); ++it) {docFreq[*it]++;}totalDocs++;}// 预计算TF-IDF向量void precomputeTFIDF() {for (map<string, vector<string> >::iterator it = questionTokens.begin(); it != questionTokens.end(); ++it) {const string& q = it->first;const vector<string>& tokens = it->second;map<string, double> tfidf;// 计算TFmap<string, int> tf;for (vector<string>::const_iterator t = tokens.begin(); t != tokens.end(); ++t) {tf[*t]++;}// 计算TF-IDFfor (map<string, int>::iterator t = tf.begin(); t != tf.end(); ++t) {double tfVal = static_cast<double>(t->second) / tokens.size();double idfVal = log(static_cast<double>(totalDocs + 1) / (docFreq[t->first] + 1)) + 1;tfidf[t->first] = tfVal * idfVal;}precomputedTFIDF[q] = tfidf;}}// 精确匹配string exactMatch(const string& input) {map<string, string>::iterator it = exactAnswers.find(input);if (it != exactAnswers.end()) {return it->second;}return "";}// TF-IDF匹配string tfidfMatch(const vector<string>& inputTokens) {if (inputTokens.empty()) return "";// 计算输入的TF-IDFmap<string, double> inputTFIDF;map<string, int> inputTF;for (vector<string>::const_iterator t = inputTokens.begin(); t != inputTokens.end(); ++t) {inputTF[*t]++;}for (map<string, int>::iterator t = inputTF.begin(); t != inputTF.end(); ++t) {double tfVal = static_cast<double>(t->second) / inputTokens.size();double idfVal = log(static_cast<double>(totalDocs + 1) / (docFreq[t->first] + 1)) + 1;inputTFIDF[t->first] = tfVal * idfVal;}// 计算与所有问题的相似度map<string, double> scores;for (map<string, map<string, double> >::iterator it = precomputedTFIDF.begin();it != precomputedTFIDF.end(); ++it) {const string& q = it->first;const map<string, double>& qTFIDF = it->second;double dot = 0.0, normQ = 0.0, normInput = 0.0;for (map<string, double>::iterator t = inputTFIDF.begin(); t != inputTFIDF.end(); ++t) {map<string, double>::const_iterator qIt = qTFIDF.find(t->first);if (qIt != qTFIDF.end()) {dot += t->second * qIt->second;}normInput += t->second * t->second;}for (map<string, double>::const_iterator qIt = qTFIDF.begin(); qIt != qTFIDF.end(); ++qIt) {normQ += qIt->second * qIt->second;}if (normQ == 0 || normInput == 0) continue;double sim = dot / (sqrt(normQ) * sqrt(normInput));scores[q] = sim;}// 找最高相似度string bestQ;double maxSim = 0.0;for (map<string, double>::iterator it = scores.begin(); it != scores.end(); ++it) {if (it->second > maxSim && it->second >= SIMILARITY_THRESHOLD) {maxSim = it->second;bestQ = it->first;}}if (!bestQ.empty()) {return qas[bestQ][0];}return "";}// 推荐相似问题string recommendSimilar(const string& input, int count) {vector<string> inputTokens = ChineseProcessor::segment(input);map<string, double> scores;// 计算所有问题与输入的相似度for (map<string, vector<string> >::iterator it = questionTokens.begin();it != questionTokens.end(); ++it) {vector<string> qTokens = it->second;set<string> common;set_intersection(inputTokens.begin(), inputTokens.end(),qTokens.begin(), qTokens.end(),inserter(common, common.begin()));double sim = static_cast<double>(common.size()) / (inputTokens.size() + qTokens.size());scores[it->first] = sim;}// 取前N个vector<pair<double, string> > sortedScores;for (map<string, double>::iterator it = scores.begin(); it != scores.end(); ++it) {sortedScores.push_back(make_pair(it->second, it->first));}sort(sortedScores.rbegin(), sortedScores.rend());string rec;for (int i = 0; i < sortedScores.size() && i < count; ++i) {string q = sortedScores[i].second;if (q.size() > 20) q = q.substr(0, 20) + "...";rec += "- " + q + "\n";}return rec;}// 辅助函数:整数转字符串string intToString(int n) {char buf[20];sprintf(buf, "%d", n);return string(buf);}// 显示帮助string showHelp() {return "使用帮助:\n""1. 直接输入问题获取答案\n""2. 输入exit/quit退出\n""3. 输入help查看帮助\n""4. 输入topics查看可回答的话题\n""5. 输入history查看对话历史";}// 显示话题string showTopics() {string topics = "可回答的话题(示例):\n";int cnt = 0;for (map<string, string>::iterator it = exactAnswers.begin(); it != exactAnswers.end() && cnt < 5; ++it, cnt++) {string t = it->first;if (t.size() > 30) t = t.substr(0, 30) + "...";topics += "- " + t + "\n";}if (exactAnswers.size() > 5) {topics += "... 共" + intToString(exactAnswers.size()) + "个话题";}return topics;}// 显示历史string showHistory() {if (context.empty()) return "无对话历史";string hist = "对话历史:\n";for (size_t i = 0; i < context.size(); ++i) {hist += "Q: " + context[i].first + "\nA: " + context[i].second + "\n\n";}return hist;}
};int main() {LogManager::writeLog("系统", "程序启动");QAEngine qa;if (!qa.loadTrainingData()) {cerr << "加载训练数据失败,请检查training_data.txt" << endl;return 1;}cout << "欢迎使用问答系统!输入help查看帮助,exit退出。" << endl;string input;while (true) {cout << "\n请输入问题: ";getline(cin, input);string response = qa.getAnswer(input);if (response == "__EXIT__") {cout << "再见!" << endl;LogManager::writeLog("系统", "用户退出");break;}cout << "机器人: " << response << endl;LogManager::writeLog("交互", "用户问:" + input + ";回答:" + response.substr(0, 30) + "...");}return 0;
}

 注:本文使用豆包辅助写作

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

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

相关文章

VR 技术在污水处理领域的创新性应用探索​

在广州&#xff0c;VR 污水处理技术的应用可谓是多点开花。首先&#xff0c;在污水处理流程模拟方面&#xff0c;工程师们利用 VR 技术创建了高度逼真的污水处理厂三维模型&#xff0c;将污水处理的整个流程&#xff0c;从预处理去除大颗粒杂质和悬浮物&#xff0c;到初级处理通…

深度学习暑期科研项目(两个月发EI论文)

深度学习暑期科研项目&#xff08;8周发EI论文&#xff09; 哈尔滨工业大学博士的六大选题对本科生而言&#xff0c;越早接触系统的科研训练开始上手科研项目&#xff0c;就越能在未来的升学求职中占据很大的优势。暑假是提升个人简历、丰富科研经历的最佳时期&#xff01;哈尔…

【RH134 问答题】第 1 章 提高命令行运行效率

目录#!/bin/bash 是什么意思&#xff1f;PATH 变量有什么重要作用&#xff1f;echo 命令的作用是什么&#xff1f;解释下列正则表达式的含义简述正则表达式和 shell 模式匹配的区别&#xff0c;在 shell 命令使用正则表达式的时候需要注意什么&#xff1f;#!/bin/bash 是什么意…

OpenCV(02)图像颜色处理,灰度化,二值化,仿射变换

【OpenCV&#xff08;01&#xff09;】基本图像操作、绘制&#xff0c;读取视频 目录图像颜色加法灰度化二值化仿射变换图像颜色加法 颜色加法 import cv2 as cv import numpy as np#读图 cao cv.imread(E:\hqyj\code\opencv\images\cao.png) pig cv.imread(E:\hqyj\code\o…

嵌入式——单片机的独立按键

一、目的功能通过开发板上的独立按键k1控制d1指示灯亮灭&#xff0c;k1一次亮再按一次灭。二、硬件原理图三、消抖理解&#xff08;一&#xff09;核心原理&#xff1a;当事件被重复触发时&#xff0c;设置一个延迟&#xff0c;只有在该时间内没有新的事件被触发&#xff0c;才…

机器学习的工作流程

&#x1f31f; 欢迎来到AI奇妙世界&#xff01; &#x1f31f; 亲爱的开发者朋友们&#xff0c;大家好&#xff01;&#x1f44b; 我是人工智能领域的探索者与分享者&#xff0c;很高兴在CSDN与你们相遇&#xff01;&#x1f389; 在这里&#xff0c;我将持续输出AI前沿技术、实…

聚类里面的一些相关概念介绍阐述

一、性能度量外部指标&#xff1a;聚类结果与某个“参考模型”进行比较&#xff1b;系数&#xff1a; &#xff0c;其中的 表示样本是否属于某类簇&#xff1b; 指数&#xff1a;&#xff0c;其中 表示样本在两个聚类结果中都是同一类簇&#xff0c; 表示在其中一个聚类结果中…

mmap机制

先看这个 MMAP 机制通俗易懂-CSDN博客 一句话 **mmap(memory map)是操作系统提供的“把文件或设备直接映射到进程虚拟地址空间”的机制,Java 里对应 `MappedByteBuffer`。** --- ### 1. 技术本质 - 系统调用:`mmap()`(POSIX)、`CreateFileMapping`(Windows)。 …

嵌入式硬件篇---驱动板

制作 ESP32 驱动板的核心是 “搭建 ESP32 与外设之间的桥梁”—— 因为 ESP32 的 GPIO 引脚输出电流很小&#xff08;最大 20mA&#xff09;&#xff0c;无法直接驱动大功率设备&#xff08;如电机、继电器、电磁阀等&#xff09;&#xff0c;驱动板的作用就是放大电流 / 功率&…

UniappDay01

1.技术架构2.创建uniapp项目 通过HBuilderX创建 官网安装创建uniapp vue3项目安装uniapp vue3的编译器在工具栏启动微信小程序开启服务端口模拟器窗口分离和置顶 通过命令行创建 3.pages.json和tabbar案例 pages.json用来配置路由&#xff0c;导航栏&#xff0c;tabbar等页面类…

子空间投影,投影矩阵,最小二乘法

一、子空间投影 1.1 投影与误差向量b 在 向量a 上的投影即 a 上离 b 最近的点&#xff1a; paTbaTaa p \frac{a^T b}{a^Ta}a paTaaTb​a 我们记 误差 e b - p&#xff0c;显然误差e 和 a 是正交的。 1.2 投影矩阵向量b 在子空间S上的投影是S中离b 最近的向量p。 我们做如下推…

基于FPGA的SPI控制FLASH读写

基于FPGA的SPI控制FLASH读写 文章目录基于FPGA的SPI控制FLASH读写一、SPI简介二、FLASH_M25P16简介信号描述功能操作注意时序三、设计思路框图设计状态机设计四、上板验证1、读ID2、读数据3、扇区擦除写数据五、总结六、代码一、SPI简介 SPI是Serial Peripheral interface的缩…

Pytest 参数化进阶:掌握 parametrize 的多种用法

概述 在自动化测试中,@pytest.mark.parametrize 不仅仅能用来为测试函数提供多组输入数据,还能配合其他功能实现更复杂的测试逻辑。本文将带你深入了解 @pytest.mark.parametrize 的多种常见用法,助你在不同场景下写出更高效、更清晰的测试代码 基础用法回顾:单个参数化 …

K8S 九 安全认证 TLS

目录第九章 安全认证访问控制概述认证管理授权管理 RBACRBACRolerules中的参数RoleBinding9.4 准入控制其他K8S的TLS是什么&#xff08;DeepSeek&#xff09;1. 加密通信2. 身份认证&#xff08;Authentication&#xff09;3. 数据完整性K8s 中 TLS 的具体应用**1. API Server …

积分兑换小程序Java

某个学校为了激励学生踊跃参加一些社会实践活动&#xff0c;会对参与者给予一些校园积分&#xff0c;学生们获得校园积分后可以使用校园积分在指定的老师那兑换一些学习用具&#xff0c;当前可兑换的物品和对应的积分数量如下&#xff1a;铅笔1分橡皮2分作业本3分文具盒5分为了…

函数指针示例

使用函数指针来调用 printf。下面是对代码的详细解释&#xff1a;&#x1f4c4; 源代码解析#include <stdio.h>int main() {int (*myshow)(const char *, ...); // 声明一个函数指针&#xff0c;指向可变参数函数printf("hello world!\n");myshow printf; /…

不坑盒子突然不见了怎么办?

如果你安装后之前一切正常&#xff0c;突然某天在Office的功能区看不到不坑盒子了&#xff0c;这种是插件被禁用了&#xff0c;重装安装插件、Office都是不能解决的&#xff0c;必须按下面的方法解决。WPS中1.随便打开一个文档&#xff0c;点击文件-选项-信任中心&#xff0c;最…

Java面试全栈通关:从微服务到AI的技术深度解析

Java面试全栈通关&#xff1a;从微服务到AI的技术深度解析 面试现场&#xff1a;大厂技术终面室 面试官&#xff1a;谢飞机同学&#xff0c;今天我们将从基础到架构&#xff0c;全面考察你的Java技术栈掌握程度。请真实展示你的技术水平。 谢飞机&#xff1a;&#xff08;挺胸抬…

《Java 程序设计》第 7 章 - 继承与多态

引言在 Java 面向对象编程中&#xff0c;继承与多态是两大核心特性&#xff0c;它们共同支撑了代码的复用性、扩展性和灵活性。本章将从继承的基本实现开始&#xff0c;逐步深入到方法覆盖、访问控制、抽象类等概念&#xff0c;最终揭示多态的本质与应用。通过大量可运行的代码…

ksql连接数据库免输入密码交互

ksql连接数据库免输入密码交互1. 使用 .pgpass 文件&#xff08;推荐&#xff09;2. 使用环境变量3. 使用连接字符串4. 修改 ksql 的别名&#xff08;简化命令&#xff09;5. 注意事项6. 密码含特殊字符转义在 Kingbase 中使用 ksql 连接数据库时&#xff0c;如果希望避免每次手…