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

【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.dtcms.com/a/301529.html

相关文章:

  • 中级全栈工程师笔试题
  • DP之背包基础
  • 详解力扣高频SQL50题之180. 连续出现的数字【困难】
  • CPA会计-5- 投资性房地产
  • 逆向入门(42)程序逆向篇-riijj_cm_20041121
  • 生态环境大数据技术专业的深度解析
  • 物理实验仿真平台设计与实现(抛体运动与电磁场交互)
  • 构建可扩展的状态系统:基于 ArkTS 的模块化状态管理设计与实现
  • MPI环形AllReduce算法实现与深度解析
  • lombok插件@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor的区别
  • RS485 半双工系统中 DE 控制端默认 0 的技术原理与工程实践
  • (实用教程)Linux操作系统(二)
  • 零基础 “入坑” Java--- 十五、字符串String
  • 【I】题目解析
  • Spring MVC设计精粹:源码级架构解析与实践指南
  • 发布 VS Code 扩展的流程:以颜色主题为例
  • Python学习-----1.认识Python
  • 墨者:X-Forwarded-For注入漏洞实战
  • 解决ubantu系统下matplotlib中文乱码问题
  • MySQL进阶学习与初阶复习第四天
  • 数据库连接操作详解:左连接、右连接、全连接与内连接
  • ABP VNext + Elastic APM:微服务性能监控
  • 【优选算法】BFS解决最短路问题(单源)
  • 初始Redis:概念、特性、使用场景、安装教程
  • 六、搭建springCloudAlibaba2021.1版本分布式微服务-admin监控中心
  • IPv6的多级地址层次的理解
  • 设计模式(五)创建型:原型模式详解
  • 【ELasticsearch】节点角色分离最佳实践
  • 【LeetCode 热题 100】35. 搜索插入位置——二分查找(左闭右开)
  • 剑指offer第2版:双指针+排序+分治+滑动窗口