从零实现C++表达式计算库:编译原理实践与性能优化
1. 项目概述为什么我们需要一个自己的表达式计算库在C开发中处理数学表达式是一个高频且棘手的需求。无论是开发一个科学计算器、一个数据可视化工具还是一个游戏中的技能伤害公式引擎你总会遇到需要解析并计算类似(sin(pi/4) 2^3) * max(5, a)这样的字符串。直接使用eval抱歉C标准库没有提供。手动写一个字符串分割和计算那将是一场噩梦尤其是当表达式变得复杂包含函数、变量和括号嵌套时。这就是为什么一个健壮的、自实现的数学表达式计算库会成为你工具箱里的“瑞士军刀”。它不仅仅是把字符串变成数字更是一个将用户输入或配置文件中的公式动态转化为可执行逻辑的桥梁。市面虽有成熟的库如ExprTK、muParser但自己动手实现一遍其价值远超调用一个API。你能彻底掌握从词法分析、语法解析到抽象语法树构建和求值的完整编译原理前端流程这对理解编译器、解释器乃至任何领域特定语言DSL的设计都至关重要。对于面试准备尤其是那些经典的“实现一个计算器”题目和夯实C基础涉及字符串处理、数据结构、递归、面向对象设计、内存管理等这更是一个绝佳的练手项目。接下来我将带你从零开始构建一个支持加减乘除、乘方、括号、常用数学函数如sin, cos, sqrt以及自定义变量的C表达式计算库。我们会遵循清晰的模块化设计并深入每个环节的实现细节与避坑指南。2. 核心架构设计分而治之的编译原理思想一个表达式计算库的核心工作流程可以类比一个微型编译器。它接收源代码表达式字符串经过一系列处理最终输出结果数值。这个过程通常被分解为四个清晰的阶段我们采用经典的“管道”式架构词法分析器将原始的字符串流如3.14 a * 2切割成一个个有意义的“单词”即词法单元。例如3.14数字、运算符、a标识符、*运算符、2数字。语法分析器根据预定义的语法规则如运算符优先级、括号匹配将词法单元序列组织成一棵抽象语法树。这棵树定义了计算的先后顺序。符号表与环境管理表达式中出现的变量如a。它负责在求值时将变量名映射到具体的数值。求值器遍历AST递归或迭代地计算每个节点的值最终得到整个表达式的结果。这种分离关注点的设计好处明显每个模块职责单一易于测试、调试和扩展。例如你想增加一个“取模”运算符%只需在词法分析和语法分析中增加对应的规则即可求值逻辑可以独立修改。2.1 抽象语法树AST节点设计AST是整个过程的核心数据结构。我们使用面向对象的多态来设计节点。首先定义一个基类ExprNode。// 表达式节点的抽象基类 class ExprNode { public: virtual ~ExprNode() default; // 纯虚函数用于求值。参数 env 是变量环境符号表 virtual double evaluate(const std::mapstd::string, double env) const 0; // 可选用于打印调试可视化AST结构 virtual void print(int indent 0) const 0; };然后我们派生出几种具体的节点类型数值节点代表一个常数如3.14。class NumberNode : public ExprNode { double value; public: explicit NumberNode(double val) : value(val) {} double evaluate(const std::mapstd::string, double) const override { return value; // 数值节点求值直接返回存储的值 } void print(int indent) const override { std::cout std::string(indent, ) Number( value )\n; } };变量节点代表一个变量如x。求值时需要从环境env中查找。class VariableNode : public ExprNode { std::string name; public: explicit VariableNode(std::string varName) : name(std::move(varName)) {} double evaluate(const std::mapstd::string, double env) const override { auto it env.find(name); if (it env.end()) { throw std::runtime_error(Undefined variable: name); } return it-second; } void print(int indent) const override { std::cout std::string(indent, ) Variable( name )\n; } };二元运算符节点代表,-,*,/,^等运算。它有两个子节点左操作数和右操作数。class BinaryOpNode : public ExprNode { char op; // 运算符字符 std::unique_ptrExprNode left; std::unique_ptrExprNode right; public: BinaryOpNode(char opChar, std::unique_ptrExprNode l, std::unique_ptrExprNode r) : op(opChar), left(std::move(l)), right(std::move(r)) {} double evaluate(const std::mapstd::string, double env) const override { double lval left-evaluate(env); double rval right-evaluate(env); switch (op) { case : return lval rval; case -: return lval - rval; case *: return lval * rval; case /: if (rval 0) throw std::runtime_error(Division by zero); return lval / rval; case ^: return std::pow(lval, rval); // 乘方 default: throw std::runtime_error(Unknown operator: std::string(1, op)); } } void print(int indent) const override { std::cout std::string(indent, ) BinaryOp( op )\n; left-print(indent 2); right-print(indent 2); } };函数调用节点代表sin(x),sqrt(4)等。它有一个函数名和一个参数子节点我们这里先实现单参数函数。class FunctionCallNode : public ExprNode { std::string funcName; std::unique_ptrExprNode argument; public: FunctionCallNode(std::string name, std::unique_ptrExprNode arg) : funcName(std::move(name)), argument(std::move(arg)) {} double evaluate(const std::mapstd::string, double env) const override { double argVal argument-evaluate(env); if (funcName sin) return std::sin(argVal); else if (funcName cos) return std::cos(argVal); else if (funcName tan) return std::tan(argVal); else if (funcName sqrt) { if (argVal 0) throw std::runtime_error(Square root of negative number); return std::sqrt(argVal); } else if (funcName log) { if (argVal 0) throw std::runtime_error(Log of non-positive number); return std::log(argVal); } else if (funcName exp) return std::exp(argVal); // ... 可以扩展更多函数 else throw std::runtime_error(Unknown function: funcName); } void print(int indent) const override { std::cout std::string(indent, ) FunctionCall( funcName )\n; argument-print(indent 2); } };设计心得使用std::unique_ptrExprNode来管理子节点可以自动处理内存释放避免内存泄漏这是现代C资源管理的核心实践。evaluate方法声明为const因为它不应修改节点状态这符合函数式求值的理念。3. 词法分析器实现将字符串拆解成Token词法分析器也叫扫描器它的任务是把输入字符串转换成一系列Token。每个Token包含类型和值如果是数字或标识符。3.1 定义Token类型enum class TokenType { Number, // 数字如 3.14, 42 Identifier, // 标识符如 x, y, sin, pi Operator, // 运算符如 , -, *, /, ^ LeftParen, // 左括号 ( RightParen, // 右括号 ) Comma, // 逗号 , (用于函数多参数后续扩展) End, // 输入结束 Error // 错误token }; struct Token { TokenType type; std::string value; // 对于Number/Identifier存储其字符串表示对于Operator存储运算符字符 // 可选可以添加行列号信息用于错误定位 };3.2 实现Lexer类Lexer需要逐个字符地读取输入并识别出不同的Token。class Lexer { std::string input; size_t pos; // 当前位置索引 char currentChar() const { return (pos input.size()) ? input[pos] : \0; } void advance() { pos; } void skipWhitespace() { while (std::isspace(static_castunsigned char(currentChar()))) advance(); } public: explicit Lexer(std::string expr) : input(std::move(expr)), pos(0) {} Token getNextToken() { skipWhitespace(); if (currentChar() \0) return {TokenType::End, }; // 处理数字 if (std::isdigit(static_castunsigned char(currentChar())) || currentChar() .) { std::string numberStr; bool hasDot false; while (std::isdigit(static_castunsigned char(currentChar())) || currentChar() .) { if (currentChar() .) { if (hasDot) break; // 遇到第二个小数点停止可以报错 hasDot true; } numberStr currentChar(); advance(); } // 简单验证数字不能以点结尾如 123. 或 . if (numberStr . || numberStr.back() .) { return {TokenType::Error, Invalid number format}; } return {TokenType::Number, numberStr}; } // 处理标识符和函数名 (字母或下划线开头) if (std::isalpha(static_castunsigned char(currentChar())) || currentChar() _) { std::string identStr; while (std::isalnum(static_castunsigned char(currentChar())) || currentChar() _) { identStr currentChar(); advance(); } return {TokenType::Identifier, identStr}; } // 处理运算符和括号 char ch currentChar(); switch (ch) { case : case -: case *: case /: case ^: advance(); return {TokenType::Operator, std::string(1, ch)}; case (: advance(); return {TokenType::LeftParen, (}; case ): advance(); return {TokenType::RightParen, )}; case ,: advance(); return {TokenType::Comma, ,}; default: // 未知字符 std::string errorStr Unexpected character: ; errorStr ch; advance(); return {TokenType::Error, errorStr}; } } };实操要点在skipWhitespace和字符分类函数如isdigit中将char转换为unsigned char再传入是为了避免当char为负值时在某些平台上标准库函数的行为未定义。这是一个容易被忽略但重要的可移植性细节。4. 语法分析器实现构建抽象语法树语法分析是核心中的核心它决定了运算符优先级和结合性。我们将使用递归下降分析法这是手写解析器最直观的方法。其核心思想是为每一种语法规则如表达式、项、因子编写一个函数。我们定义以下语法规则使用巴科斯范式近似表示expression-term( (|-)term)*term-factor( (*|/)factor)*factor-primary(^factor)? // 右结合如 2^3^2 是 2^(3^2)primary-Number|Identifier|(expression)|FunctionCall4.1 实现Parser类Parser持有Lexer实例并消费Token来构建AST。class Parser { Lexer lexer; Token currentToken; void eat(TokenType type) { if (currentToken.type type) { currentToken lexer.getNextToken(); } else { throw std::runtime_error(Syntax error: unexpected token currentToken.value ); } } public: explicit Parser(Lexer l) : lexer(std::move(l)) { currentToken lexer.getNextToken(); // 读取第一个token } // 解析入口 std::unique_ptrExprNode parse() { auto node parseExpression(); if (currentToken.type ! TokenType::End) { throw std::runtime_error(Syntax error: extra characters after expression); } return node; } private: // expression - term ( (|-) term )* std::unique_ptrExprNode parseExpression() { auto node parseTerm(); while (currentToken.type TokenType::Operator (currentToken.value || currentToken.value -)) { char op currentToken.value[0]; eat(TokenType::Operator); auto right parseTerm(); // 注意这里构建左结合的树。对于 a b c会构建成 ((a b) c) node std::make_uniqueBinaryOpNode(op, std::move(node), std::move(right)); } return node; } // term - factor ( (*|/) factor )* std::unique_ptrExprNode parseTerm() { auto node parseFactor(); while (currentToken.type TokenType::Operator (currentToken.value * || currentToken.value /)) { char op currentToken.value[0]; eat(TokenType::Operator); auto right parseFactor(); node std::make_uniqueBinaryOpNode(op, std::move(node), std::move(right)); } return node; } // factor - primary (^ factor)? // 右结合性 std::unique_ptrExprNode parseFactor() { auto node parsePrimary(); if (currentToken.type TokenType::Operator currentToken.value ^) { eat(TokenType::Operator); auto right parseFactor(); // 递归调用parseFactor实现右结合 node std::make_uniqueBinaryOpNode(^, std::move(node), std::move(right)); } return node; } // primary - Number | Identifier | ( expression ) | FunctionCall std::unique_ptrExprNode parsePrimary() { Token token currentToken; if (token.type TokenType::Number) { eat(TokenType::Number); double value std::stod(token.value); return std::make_uniqueNumberNode(value); } else if (token.type TokenType::Identifier) { eat(TokenType::Identifier); std::string name token.value; // 检查后面是不是左括号如果是则是函数调用 if (currentToken.type TokenType::LeftParen) { eat(TokenType::LeftParen); // 解析函数参数目前只支持单参数 auto arg parseExpression(); eat(TokenType::RightParen); return std::make_uniqueFunctionCallNode(name, std::move(arg)); } else { // 否则是普通变量 return std::make_uniqueVariableNode(name); } } else if (token.type TokenType::LeftParen) { eat(TokenType::LeftParen); auto node parseExpression(); eat(TokenType::RightParen); return node; } else { throw std::runtime_error(Syntax error: expected number, identifier, or (); } } };关键解析parseFactor函数中处理乘方^的方式体现了右结合性。当遇到^时它递归调用parseFactor()来解析右边的指数而不是parsePrimary()。这意味着对于表达式2^3^2解析过程是parseFactor(2) - 遇到^ - 递归parseFactor(3) - 遇到^ - 递归parseFactor(2) - 得到2最终构建的树是2^(3^2)符合数学惯例。这是递归下降法处理结合性的一个经典技巧。5. 求值与环境集成让表达式“活”起来有了AST求值就变得直截了当。我们已经在每个ExprNode的子类中实现了evaluate方法。现在需要创建一个顶层的Expression类将解析和求值封装起来并提供变量环境。class Expression { std::unique_ptrExprNode astRoot; std::mapstd::string, double variables; // 符号表存储变量名到值的映射 public: // 从字符串构造表达式 explicit Expression(const std::string exprStr) { Lexer lexer(exprStr); Parser parser(std::move(lexer)); astRoot parser.parse(); } // 设置/修改变量的值 void setVariable(const std::string name, double value) { variables[name] value; } // 批量设置变量 void setVariables(const std::mapstd::string, double vars) { variables.insert(vars.begin(), vars.end()); } // 求值 double evaluate() const { if (!astRoot) { throw std::runtime_error(Expression not parsed successfully); } return astRoot-evaluate(variables); } // 打印AST调试用 void printAST() const { if (astRoot) { astRoot-print(); } else { std::cout (Empty AST)\n; } } };现在我们可以像下面这样使用这个库int main() { try { Expression expr((sin(pi/2) 3^2) * x); expr.setVariable(pi, 3.141592653589793); expr.setVariable(x, 10.0); expr.printAST(); // 可视化AST结构 double result expr.evaluate(); std::cout Result: result std::endl; // 应输出 (1 9) * 10 100 } catch (const std::exception e) { std::cerr Error: e.what() std::endl; } return 0; }6. 性能优化与高级特性探讨一个基础的库已经完成但要投入实用还需要考虑更多。6.1 性能优化避免重复解析与AST编译如果同一个表达式需要被反复求值例如在循环中或用于绘制函数图像每次从字符串开始解析和构建AST是巨大的浪费。我们可以引入“编译”的概念。class CompiledExpression { std::unique_ptrExprNode astRoot; // 可以进一步优化将AST转换为字节码在虚拟机上运行速度更快。 public: CompiledExpression(std::unique_ptrExprNode root) : astRoot(std::move(root)) {} double evaluate(const std::mapstd::string, double env) const { return astRoot-evaluate(env); } }; class ExpressionCompiler { public: static CompiledExpression compile(const std::string exprStr) { Lexer lexer(exprStr); Parser parser(std::move(lexer)); return CompiledExpression(parser.parse()); } };这样用户可以先编译表达式然后传入不同的变量环境快速求值性能提升显著。6.2 支持多参数函数和常量扩展函数调用节点以支持多参数如max(5, 10, x)修改parsePrimary中函数解析部分在(和)之间循环调用parseExpression并用Commatoken分隔将多个参数节点存入std::vector。相应地FunctionCallNode需要存储一个参数节点列表并在evaluate中依次求值。添加常量支持如pi,e可以在VariableNode::evaluate中先检查名称是否在预定义的常量表中是则返回常量值否则再去环境变量中查找。6.3 错误处理与用户友好性目前的错误处理比较简单抛出std::runtime_error。一个工业级的库需要更丰富的错误类型区分词法错误、语法错误、运行时错误除零、未定义变量等。错误定位在Token和Lexer中记录行号、列号在抛出异常时附上位置信息。自定义异常类派生自std::exception携带更多上下文信息。6.4 内存管理与智能指针我们全程使用了std::unique_ptr这是正确的选择它明确了所有权且零开销。在更复杂的场景如可能需要的AST节点缓存、共享子表达式优化可以评估使用std::shared_ptr但绝大多数情况下unique_ptr足矣。7. 常见问题排查与调试技巧实录在实际实现和使用的过程中你几乎一定会遇到下面这些问题。7.1 浮点数精度问题这是所有数学计算库的“阿喀琉斯之踵”。我们的库直接使用Cdouble类型因此继承了所有浮点数精度问题。现象0.1 0.2 ! 0.3或者sin(pi)不等于0而是一个极小的数。应对策略教育用户在文档中明确说明库使用双精度浮点数。比较时使用容差在库内部或用户代码中比较两个浮点数结果时不要用而应使用std::abs(a - b) epsilon例如epsilon 1e-12。谨慎处理临界值例如在sqrt函数中对于负数判断也可以使用容差避免因计算误差导致的误报。7.2 运算符优先级与结合性错误这是语法分析中最容易出错的地方。症状表达式1 2 * 3被计算成9而不是7或者2^3^2结果错误。调试方法打印Token流在Lexer完成后打印出所有识别到的Token确保分词正确。打印AST我们实现的print方法至关重要。对于12*3正确的AST应该先有*节点其子节点是2和3然后节点的左子节点是1右子节点是那个*节点。如果结构不对立刻能发现问题出在parseTerm和parseExpression的优先级处理上。单步调试在递归下降的每个函数入口打印信息观察解析路径。7.3 未定义变量或函数名症状求值时抛出Undefined variable: xxx。处理建议提供默认值可以设计一个“安全求值”模式当变量未定义时返回0或NaN而不是抛出异常。预检查实现一个getVariables()方法遍历AST收集所有出现的标识符让用户提前知道需要提供哪些变量。区分函数和变量在词法分析阶段可以维护一个已知函数名列表如sin, cos, sqrt。当识别到一个标识符时如果它在函数列表中就产生一个Function类型的Token否则产生IdentifierToken。这可以提前发现拼写错误的函数名。7.4 内存泄漏与指针管理如果你使用了原始指针new/delete内存泄漏风险极高。务必使用智能指针我们用了std::unique_ptr。使用valgrind(Linux) 或Dr. Memory(Windows) 等工具进行内存检查确保所有节点都能被正确释放。7.5 处理复杂表达式时的递归深度限制递归下降解析器和递归求值器在解析极深嵌套的表达式如(((((...)))))时可能导致栈溢出。缓解方案将递归算法改为迭代算法如使用显式栈的调度场算法进行语法分析但这会大大增加复杂度。对于求值可以将递归遍历AST改为迭代遍历使用栈或队列。在实践中除非用户故意输入恶意表达式否则正常的数学表达式嵌套深度很难导致栈溢出。可以将其视为一个已知限制在文档中说明。8. 从玩具到实用扩展思路与项目升华至此一个核心可用的表达式计算库已经完成。但如果你想让它更强大、更专业这里有一些进阶方向支持逻辑与比较运算符增加,,,,||等使表达式能返回布尔值可用于条件判断。支持赋值与语句扩展语法支持如x 5 3这样的赋值语句甚至支持多条语句用分号分隔。这会让你的库演变成一个简单的脚本引擎。JIT编译这是性能追求的终极目标。使用LLVM或libgccjit等库将AST直接编译成本地机器码求值速度可提升几个数量级。ExprTK库就以其模板元编程实现的编译时优化而闻名。绑定到其他语言使用Python的C API或pybind11为你的库创建Python绑定让Python用户也能享受高性能的表达式计算。可视化调试工具开发一个图形界面输入表达式后能实时显示Token流、AST图形和求值步骤这对于教学和理解库的工作原理非常有帮助。实现这个库的过程是一次对C核心特性面向对象、智能指针、STL容器和计算机科学基础编译原理、数据结构的深度实践。它麻雀虽小五脏俱全。当你看到自己写的库成功计算出第一个复杂表达式时那种成就感是调用现成API无法比拟的。更重要的是这套词法分析、语法分析、构建AST的模式是处理任何结构化文本配置文件、查询语言、模板引擎的通用利器其价值远超一个计算器本身。

相关新闻

掌握rebel-readline快捷键:提升Clojure REPL效率的10个必备技巧

掌握rebel-readline快捷键:提升Clojure REPL效率的10个必备技巧

掌握rebel-readline快捷键:提升Clojure REPL效率的10个必备技巧 【免费下载链接】rebel-readline Terminal readline library for Clojure dialects 项目地址: https://gitcode.com/gh_mirrors/re/rebel-readline rebel-readline是Clojure方言的终端readline…

2026/8/25 14:50:39 阅读更多 →
Vue CLI4项目配置全解析与优化实践

Vue CLI4项目配置全解析与优化实践

1. Vue CLI4 项目配置核心解析 作为现代前端开发的标配工具,Vue CLI 的配置文件 vue.config.js 承载着项目构建的关键逻辑。这个文件就像是项目的控制中心,从资源路径到编译规则,从开发服务器到生产优化,几乎所有构建相关的配置…

2026/8/23 4:04:54 阅读更多 →
Unity跨场景引用持久化:基于GUID的解决方案与Timeline实战

Unity跨场景引用持久化:基于GUID的解决方案与Timeline实战

1. 项目概述与痛点剖析 在Unity项目开发中,尤其是涉及大型关卡、动态加载或复杂叙事流程的游戏里,跨场景的对象引用问题就像一颗定时炸弹,随时可能引爆你的工作流。想象一下这个场景:你在一个“主菜单”场景里精心编排了一段Timel…

2026/8/22 17:08:17 阅读更多 →

最新新闻

烟雾检测实战:5000张数据集三种格式详解与YOLOv8训练全流程

烟雾检测实战:5000张数据集三种格式详解与YOLOv8训练全流程

简介:目标检测是计算机视觉的核心任务,而烟雾作为一种半透明、边界模糊的目标,其检测在工厂安全、森林防火和楼宇监控等场景中具有重要意义。高质量的数据集是训练可靠模型的基础,但烟雾数据往往稀缺且标注格式混乱。本文从目标检…

2026/8/26 10:59:09 阅读更多 →
完全二叉树数组存储与双指针层序遍历

完全二叉树数组存储与双指针层序遍历

1. 这道题到底在考什么:从蓝桥杯国赛现场还原真实解题场景 “完全二叉树的权值”这道题,出现在蓝桥杯全国总决赛的编程类赛道中,不是那种靠背模板就能蒙混过关的送分题。我连续三年担任省赛阅卷组技术顾问,也带过七届蓝桥杯集训队…

2026/8/26 10:59:09 阅读更多 →
ABB机器人安全逻辑配置实战:从硬件双通道到软件SafeMove详解

ABB机器人安全逻辑配置实战:从硬件双通道到软件SafeMove详解

1. 项目概述:为什么工业机器人的安全逻辑配置是“保命”的活儿干了这么多年工业自动化,从最早的机械手到现在的六轴协作机器人,我经手的项目里,最让我后怕、也最让我觉得“值钱”的活儿,从来不是那些花里胡哨的轨迹优化…

2026/8/26 10:59:09 阅读更多 →
Windows逆向工程入门:从环境搭建到实战破解密码验证程序

Windows逆向工程入门:从环境搭建到实战破解密码验证程序

1. 逆向工程入门:从“黑盒”到“白盒”的思维转变逆向工程,听起来像是电影里黑客的专属技能,带着一层神秘的面纱。但如果你是一名开发者、安全研究员,或者只是对软件内部运作充满好奇的技术爱好者,逆向其实是一门非常实…

2026/8/26 10:59:09 阅读更多 →
B站收藏夹视频统计脚本开发指南:从API调用到油猴脚本实现

B站收藏夹视频统计脚本开发指南:从API调用到油猴脚本实现

1. 项目概述:为什么我们需要一个B站收藏夹统计脚本?作为一名重度B站用户,我的收藏夹早就变成了一个“数字黑洞”。从编程教程到美食探店,从游戏攻略到纪录片,看到感兴趣的视频就习惯性地点下那个五角星。久而久之&…

2026/8/26 10:59:09 阅读更多 →
开源招聘系统与智能中台架构实践

开源招聘系统与智能中台架构实践

1. 项目背景与核心价值去年帮一家快速扩张的科技公司搭建招聘系统时,他们的HRD给我看了一组数据:平均每个岗位要处理487份简历,但用人部门满意度却不到30%。这让我意识到,传统招聘模式已经难以支撑千人规模企业的用人需求。开源招…

2026/8/26 10:58:07 阅读更多 →

日新闻

Python random 模块常用函数详解:从入门到实战

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 0:00:40 阅读更多 →
《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》索引目录: 《Microsoft Sql server 2008 Internals》读书笔记--目录索引 在上篇文章中,主要介绍了创建数据库的基本语法和FileGroup的初步知识。需要注意的是: 关于FileGroup 如果你的系统是用Raid设备直接存…

2026/8/26 1:18:18 阅读更多 →
政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体已经从概念试点阶段,转入了政务服务的常态化落地应用;在实际使用过程中,它能自主理解办事需求、辅助完成填报申报、开展材料预审,并联动多个系统协同作业,真正嵌入到政务办理的全流程当中。但在落地推进过…

2026/8/26 1:18:18 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 3:38:12 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 3:38:18 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/25 3:38:23 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/26 3:50:20 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/25 10:31:12 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/26 1:24:05 阅读更多 →