先看核心結論:兩段代碼的本質區別
如果用一句話總結兩段代碼的差異:前者是 “帶中文支持的問答系統”,后者是 “真正適配中文的問答系統”。
具體來說,兩段代碼的核心功能都是 “加載問答數據→接收用戶輸入→匹配答案”,但在最關鍵的 “中文處理” 和 “系統擴展性” 上,優化版做了顛覆性改進。接下來我們從 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 個可配置入口,無需改代碼
自定義詞典(user_dict.txt):添加領域詞匯
// 新版支持加載外部詞典 bool loadUserDict(const string& file_path) {ifstream fin(file_path.c_str());// 讀取文件內容并添加到詞典 }
停用詞表(stop_words.txt):過濾無意義詞匯
內置默認停用詞(“的”“了” 等),還能通過文件添加,比如加 “請問”“您好” 等對話常用詞。跨平臺兼容:自動適配 Windows 和 Linux
// 跨平臺文件狀態獲取 #ifdef _WIN32 #include <sys/stat.h> #define stat _stat // Windows下兼容stat函數 #else #include <sys/stat.h> #endif
三、細節打磨:從 “能跑” 到 “穩定”
除了核心功能,新版在細節上做了很多工程化優化,這些正是 “玩具級” 和 “實用級” 的區別:
更嚴謹的 UTF-8 處理:
舊版對 UTF-8 的解碼邏輯有漏洞(比如中文標點判斷可能誤判),新版實現了完整的 UTF-8 編解碼函數,支持各種中文符號。日志系統更完善:
新增了日志輪轉(超過 1MB 自動備份)、更詳細的錯誤日志(比如 “加載詞典失敗” 會明確提示原因)。邊界處理更健壯:
對異常輸入(比如非 UTF-8 字符、空字符串)做了容錯,避免程序崩潰。
為什么這些優化很重要?
我們容易沉迷于 “高大上” 的算法(比如 TF-IDF、相似度計算),卻忽略中文處理這個基礎。但實際上:
- 中文分詞精度不夠,再完美的 TF-IDF 也會 “認錯詞”
- 沒有可配置入口,系統無法適應新場景(比如從 “通用問答” 改成 “醫療問答”)
- 細節處理不到位,上線后可能因為一個特殊字符就崩潰
新版代碼只多了 200 多行,但通過優化中文處理和擴展性,直接從 “演示用” 提升到了 “可實際部署” 的級別。
最后3 個實用建議
- 做中文項目,先搞定分詞和編碼:推薦先掌握 Jieba 等工具的基本用法,再深入算法
- 預留配置入口:把可能變的東西(詞典、規則、參數)放到配置文件,而不是硬編碼
- 重視異常處理:用戶輸入永遠比你想的更 “離譜”,多考慮空輸入、特殊字符、大文件等場景
如果這篇文章對你有幫助,別忘了點贊收藏 —— 你的支持是我更新的動力!
附上代碼 :
#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;
}
?注:本文使用豆包輔助寫作