简介本资源是基于Visual Studio 2019开发的C计算器项目实战包面向C初学者及Windows桌面应用入门学习者聚焦从控制台到MFC图形界面的渐进式开发实践解决语法应用、UI交互、异常处理与工程构建等典型学习痛点。压缩包共48个文件涵盖3个核心.cpp源文件、6个.h头文件、1个.sln解决方案、1个.vcxproj项目配置以及调试所需的.pdb、.obj、.ilk等中间产物另有.ico图标、.rc资源脚本和.exe可执行文件完整呈现VS2019环境下C项目的编译、链接与调试全流程整体大小为162.28MB。已有1546人下载学习。读者可直接运行可执行程序体验简单四则运算与复杂函数如括号解析、平方根、对数功能复用MFC简易计算器代码结构参考出问题解决方法文档快速排错并结合VIP课程清单规划系统学习路径切实掌握面向对象设计、IO流交互、模块化编码与VS2019工程管理等关键能力。1. 项目概述一个基于VS2019与MFC的计算器实现最近在整理旧项目时翻出了一个名为“VS2019计算器简单复杂 (1).rar”的压缩包。这让我想起了几年前为了给团队新人演示如何快速上手Windows桌面开发我用Visual Studio 2019和MFCMicrosoft Foundation Classes框架搭建的一个计算器示例。这个项目麻雀虽小五脏俱全它不仅仅是一个能进行加减乘除的简单工具更是一个涵盖了MFC对话框程序核心知识点的“教学标本”。从最基础的按钮事件响应、控件数据交换到稍复杂的表达式解析、运算逻辑封装乃至界面布局与用户体验优化这个项目都提供了清晰的实现路径。对于正在学习C桌面开发特别是希望理解MFC这套经典框架如何运作的朋友来说通过亲手复现并扩展这样一个计算器远比阅读枯燥的文档来得有效。接下来我就把这个“压箱底”的项目拆开揉碎从环境搭建到功能实现再到那些容易踩坑的细节完整地分享给大家。2. 开发环境准备与MFC项目创建2.1 Visual Studio 2019的安装与配置要点工欲善其事必先利其器。首先得确保你的开发环境是VS2019并且安装了必要的C桌面开发组件。很多新手在安装VS时会直接使用默认选项这可能导致后续找不到MFC项目模板。正确的做法是运行Visual Studio Installer在“工作负载”选项卡中务必勾选“使用C的桌面开发”。这还不够你还需要点击右侧的“安装详细信息”展开“使用C的桌面开发”项确保“用于x86和x64的Visual C MFC”这一项是被选中的。如果之前安装时漏掉了可以随时通过Installer进行修改。安装完成后首次启动VS2019可能会让你登录微软账户。对于个人学习和内部项目你可以选择“暂时跳过此项”这并不影响核心功能的正常使用。进入主界面后建议在“工具”-“选项”-“环境”-“字体和颜色”中调整一下编辑器的字体和主题选择一个让自己眼睛舒服的配置毕竟接下来要长时间面对代码。2.2 创建MFC对话框应用程序的详细步骤环境就绪后我们开始创建项目。点击“创建新项目”在搜索框输入“MFC”选择“MFC应用程序”模板点击“下一步”。给项目起个名字比如“MyCalculator”选择好项目存放的位置。接下来是关键的项目配置向导应用程序类型选择“基于对话框”。这是最常用的MFC程序类型我们的计算器主界面就是一个对话框。资源语言根据你的需要选择通常保持默认的“中文简体中国”即可。文档模板属性对于对话框程序这部分通常无需修改。用户界面功能这里需要仔细设置。主框架样式取消“粗框架”、“最小化框”、“最大化框”的勾选。因为计算器通常是一个固定大小、没有复杂菜单栏和工具栏的简洁窗口。“系统菜单”可以保留以便用户能关闭窗口。对话框标题可以在这里输入“我的计算器”这将成为窗口标题栏显示的文字。使用菜单和使用经典停靠都选择“否”。我们的计算器不需要菜单和工具栏。使用功能区选择“否”。使用状态栏选择“否”。一个简单的计算器通常不需要状态栏。高级功能保持默认设置即可不需要ActiveX控件或自动化支持。生成的类向导最后会展示即将生成的类主要是CMyCalculatorApp应用类和CMyCalculatorDlg主对话框类。我们可以把对话框类的头文件和源文件改成更贴切的名字比如CCalculatorDlg.h和CCalculatorDlg.cpp但这不是必须的。点击“完成”VS2019就会为我们生成一个完整的MFC对话框项目骨架。编译并运行F5你应该能看到一个空白的对话框窗口。至此我们的“舞台”已经搭好。注意在项目创建后建议立即在解决方案配置管理器中将活动解决方案平台从默认的“x86”调整为“x64”以匹配现在主流的64位操作系统环境。虽然对于计算器这样的小程序影响不大但养成好习惯很重要。3. 计算器界面设计与控件布局3.1 对话框资源编辑与控件拖放在“解决方案资源管理器”中找到“资源文件”下的“MyCalculator.rc”双击打开资源视图。展开“Dialog”文件夹双击“IDD_MYCALCULATOR_DIALOG”主界面设计器就会打开。我们的计算器界面主要包含两大区域显示区和按键区。显示区我们需要一个Edit Control编辑框来显示输入和结果。从工具箱拖一个编辑框到对话框上部。选中它在属性窗口按F4打开中做如下关键设置ID改为一个更有意义的ID如IDC_EDIT_DISPLAY。Align Text设置为Right右对齐这样数字看起来更规整。Read Only设置为True。我们通常不希望用户直接通过键盘在显示区乱输入所有输入都应通过按钮触发。Multiline可以设置为True并适当拉高编辑框以便能显示多行历史记录复杂计算器功能简单版可以保持单行。Number设置为True。这会限制编辑框只能输入数字但对于要显示运算符和括号的表达式计算器不要勾选此项否则无法显示非数字字符。按键区从工具箱拖放多个Button Control到对话框上。我们需要数字按钮0-9、小数点按钮、运算符按钮、-、*、/、等号按钮、清除按钮C或CE、以及可能需要的括号按钮、退格按钮←等。3.2 控件属性设置与Tab键顺序调整为每个按钮设置有意义的ID和标题Caption。例如数字按钮ID设为IDC_BUTTON_1Caption设为1。加号按钮ID设为IDC_BUTTON_ADDCaption设为。等号按钮ID设为IDC_BUTTON_EQUALCaption设为。清除按钮ID设为IDC_BUTTON_CLEARCaption设为C。布局时可以利用对话框编辑器的布局工具如“使宽度相同”、“使高度相同”、“左对齐”、“顶端对齐”等来让按钮排列整齐大小一致。一个常见的布局是模仿Windows标准计算器采用网格状排列。一个极易被忽略但至关重要的步骤是调整Tab键顺序。在对话框编辑器中点击“格式”菜单下的“Tab键顺序”或者按CtrlD对话框上的每个控件都会显示一个数字这就是用户按Tab键时焦点移动的顺序。我们必须按照按钮的视觉布局通常是从左到右从上到下来手动设置这个顺序确保用户体验符合直觉。例如第一行按钮C, (, ), /的Tab顺序可能是1,2,3,4第二行7,8,9,*是5,6,7,8以此类推。显示编辑框的Tab顺序通常设为1让它最先获得焦点虽然设为只读但Tab顺序逻辑要正确。3.3 为控件关联成员变量为了在代码中操作界面控件如获取显示框的内容、设置按钮状态我们需要为它们关联成员变量。MFC通常使用“控件变量”来实现。在对话框设计界面右键点击显示编辑框IDC_EDIT_DISPLAY选择“添加变量”。在弹出的对话框中“类别”选择Control这样添加的是控件对象变量如CEdit变量名可以设为m_editDisplay。访问权限通常选择Private即可因为一般只在对话框类内部访问它。点击“完成”。这样在CCalculatorDlg类的头文件中就会自动添加一个CEdit m_editDisplay;的成员变量声明并在DoDataExchange函数中自动生成数据交换代码DDX_Control(pDX, IDC_EDIT_DISPLAY, m_editDisplay);。对于按钮我们通常不需要添加控件变量因为大部分交互是通过处理按钮的点击事件BN_CLICKED来完成的。但如果你需要动态禁用或启用某个按钮也可以为其添加一个CButton类型的控件变量。4. 核心计算逻辑的实现4.1 简单计算器基于状态机的即时计算简单计算器的逻辑相对直接类似于老式的四则运算计算器输入第一个数按运算符输入第二个数按等号得到结果。实现这种逻辑一个清晰的方法是使用“状态机”。我们需要在对话框类中定义几个关键的成员变量来记录计算状态class CCalculatorDlg : public CDialogEx { // ... private: CString m_strDisplay; // 显示字符串 double m_dblOperand1; // 操作数1 double m_dblOperand2; // 操作数2 TCHAR m_cOperator; // 当前运算符 (, -, *, /) BOOL m_bWaitForNewOperand; // 标志是否等待输入新的操作数即是否已按过运算符 BOOL m_bIsResultDisplayed; // 标志当前显示的是否是计算结果 };在对话框的OnInitDialog函数中初始化这些变量。数字按钮的处理所有数字按钮0-9和小数点的点击事件可以映射到同一个处理函数比如OnBnClickedButtonNumber。在这个函数中我们需要判断当前状态如果m_bWaitForNewOperand为TRUE或者m_bIsResultDisplayed为TRUE说明应该开始输入一个新的数此时需要先清空显示字符串m_strDisplay并重置相关标志。然后将按钮对应的字符追加到m_strDisplay末尾并更新到编辑框控件。运算符按钮的处理当用户点击、-、*、/时触发OnBnClickedButtonAdd等函数。这里的逻辑是如果这不是第一次按运算符即m_bWaitForNewOperand已经为TRUE并且之前已经有一个操作数m_strDisplay不为空那么可以视为用户想连续计算如“345”。这时需要先把当前显示的数字当作m_dblOperand2用之前存储的m_dblOperand1和m_cOperator进行计算将结果显示出来同时这个结果成为新的m_dblOperand1。否则将当前显示的数字转换为double存入m_dblOperand1。记录当前按下的运算符到m_cOperator。设置m_bWaitForNewOperand TRUE表示接下来输入的数字将是第二个操作数。等号按钮的处理点击等号时如果m_bWaitForNewOperand为TRUE且当前显示的是第二个操作数则进行运算void CCalculatorDlg::OnBnClickedButtonEqual() { if (m_bWaitForNewOperand !m_strDisplay.IsEmpty()) { UpdateData(TRUE); // 确保从控件获取最新显示内容到m_strDisplay如果用了DDX m_dblOperand2 _tstof(m_strDisplay); // 将字符串转为浮点数 double dblResult 0.0; switch (m_cOperator) { case _T(): dblResult m_dblOperand1 m_dblOperand2; break; case _T(-): dblResult m_dblOperand1 - m_dblOperand2; break; case _T(*): dblResult m_dblOperand1 * m_dblOperand2; break; case _T(/): if (fabs(m_dblOperand2) 1e-10) // 处理除零错误 { MessageBox(_T(除数不能为零), _T(错误), MB_ICONERROR); OnBnClickedButtonClear(); // 调用清除函数 return; } dblResult m_dblOperand1 / m_dblOperand2; break; default: break; } m_strDisplay.Format(_T(%g), dblResult); // 格式化结果%g会自动选择合理格式 UpdateData(FALSE); // 更新到控件显示 m_bIsResultDisplayed TRUE; m_bWaitForNewOperand FALSE; // 等号后状态重置准备接受新的一次计算 } }清除按钮的处理点击C按钮时重置所有状态变量和显示内容。4.2 复杂计算器表达式解析与求值复杂计算器支持用户输入完整的数学表达式如“3.14 * (2 5) / sqrt(4)”然后一次性求值。这需要引入表达式解析器。对于桌面计算器一种经典且易于实现的算法是“调度场算法”Shunting-yard algorithm它可以将中缀表达式人类习惯的写法转换为后缀表达式逆波兰表示法然后再对后缀表达式求值这个过程能自动处理运算符优先级和括号。第一步定义数据结构我们需要栈Stack来辅助转换和求值。可以使用C标准库的stack。#include stack #include vector #include string #include cctype #include cmath #include map // 定义运算符的优先级 std::mapwchar_t, int g_operatorPriority { {L, 1}, {L-, 1}, {L*, 2}, {L/, 2}, {L^, 3}, // 幂运算如果有的话 };第二步中缀转后缀调度场算法std::vectorstd::wstring InfixToPostfix(const std::wstring infix) { std::vectorstd::wstring postfix; std::stackwchar_t opStack; std::wstring numberBuffer; for (size_t i 0; i infix.length(); i) { wchar_t ch infix[i]; if (iswdigit(ch) || ch L.) { // 处理数字包括小数点和可能的多位数字 numberBuffer ch; } else { // 数字缓冲区非空说明一个数字token结束输出到后缀表达式 if (!numberBuffer.empty()) { postfix.push_back(numberBuffer); numberBuffer.clear(); } if (ch L() { opStack.push(ch); } else if (ch L)) { // 弹出栈顶运算符直到遇到左括号 while (!opStack.empty() opStack.top() ! L() { postfix.push_back(std::wstring(1, opStack.top())); opStack.pop(); } if (!opStack.empty()) opStack.pop(); // 弹出左括号 } else if (g_operatorPriority.find(ch) ! g_operatorPriority.end()) { // 处理运算符弹出栈顶优先级更高或相等的运算符 while (!opStack.empty() opStack.top() ! L( g_operatorPriority[opStack.top()] g_operatorPriority[ch]) { postfix.push_back(std::wstring(1, opStack.top())); opStack.pop(); } opStack.push(ch); } // 可以在这里添加对函数名如sin, cos, sqrt的处理 } } // 处理最后一个数字 if (!numberBuffer.empty()) { postfix.push_back(numberBuffer); } // 弹出栈中所有剩余运算符 while (!opStack.empty()) { postfix.push_back(std::wstring(1, opStack.top())); opStack.pop(); } return postfix; }第三步后缀表达式求值double EvaluatePostfix(const std::vectorstd::wstring postfix) { std::stackdouble valueStack; for (const auto token : postfix) { // 如果是数字 if (iswdigit(token[0]) || (token[0] L.) || (token[0] L- token.length() 1)) { valueStack.push(std::stod(token)); } else if (token.length() 1 g_operatorPriority.find(token[0]) ! g_operatorPriority.end()) { // 是运算符 if (valueStack.size() 2) { throw std::runtime_error(无效的表达式); } double rhs valueStack.top(); valueStack.pop(); double lhs valueStack.top(); valueStack.pop(); wchar_t op token[0]; switch (op) { case L: valueStack.push(lhs rhs); break; case L-: valueStack.push(lhs - rhs); break; case L*: valueStack.push(lhs * rhs); break; case L/: if (fabs(rhs) 1e-10) throw std::runtime_error(除数不能为零); valueStack.push(lhs / rhs); break; // 可以扩展其他运算符如幂运算 default: throw std::runtime_error(未知的运算符); } } // 可以在这里添加对函数token的处理 } if (valueStack.size() ! 1) { throw std::runtime_error(表达式不完整或格式错误); } return valueStack.top(); }第四步界面整合在对话框类中当用户点击“”按钮或者专门为复杂表达式设计的“计算”按钮时我们获取编辑框中的完整字符串调用InfixToPostfix和EvaluatePostfix函数最后显示结果。务必使用try-catch块来捕获求值过程中可能抛出的异常如除零、表达式错误并给用户友好的提示。实操心得在实现表达式解析时对空白字符的处理很重要。最好在解析前先去除表达式字符串中的所有空格。另外对于负数的处理要小心比如“-5”应该被识别为一个数字token“-5”而不是运算符“-”和数字“5”。上面的简单示例没有完全处理这种情况更健壮的实现需要更精细的tokenizer词法分析器。5. 功能增强与用户体验优化5.1 高级运算功能的集成一个实用的计算器除了四则运算还应该包含一些常用函数。我们可以在界面增加一排按钮如“sqrt”平方根、“sin”、“cos”、“log”等。实现这些功能有两种思路作为表达式的一部分这是复杂计算器模式的延伸。当用户点击“sqrt”按钮时不是在当前数字后追加字符而是向表达式字符串中插入“sqrt(”并自动补上一个右括号的占位符或者等待用户输入参数后再补全。这要求我们的表达式解析器能够识别这些函数名并在求值阶段调用对应的数学库函数cmath中的sqrt,sin,cos,log10等。作为一元运算符即时计算这是简单计算器模式的扩展。例如用户输入一个数字后点击“sqrt”按钮立即显示该数字的平方根并将结果作为当前显示值可以继续参与后续运算。这需要在对话框类中为每个函数按钮单独编写事件处理函数直接对当前显示值进行数学变换。对于初学者第二种方法更简单直接。例如平方根按钮的处理函数void CCalculatorDlg::OnBnClickedButtonSqrt() { UpdateData(TRUE); double dblVal _tstof(m_strDisplay); if (dblVal 0) { MessageBox(_T(无效输入负数不能开平方根), _T(错误), MB_ICONERROR); return; } dblVal sqrt(dblVal); m_strDisplay.Format(_T(%g”), dblVal); m_bIsResultDisplayed TRUE; // 标记为结果状态 UpdateData(FALSE); }5.2 界面美化与交互优化一个友好的界面能极大提升使用体验。按钮状态反馈可以使用CButton的SetState方法或修改样式实现按钮按下时的视觉反馈。更高级的可以自定义绘制按钮实现扁平化或Material Design风格。键盘支持重写对话框的PreTranslateMessage函数拦截键盘消息。将数字键、回车键作为等号、退格键、Esc键作为清除等映射到对应的按钮点击事件。这样用户就可以用键盘快速操作。输入历史在复杂计算器模式下可以增加一个列表框List Box控件用于记录每次计算的表达式和结果。这需要在每次成功计算后将表达式和结果作为一个字符串添加到列表框中。错误处理与提示除了除零错误还要处理表达式语法错误如括号不匹配、数学域错误如对负数取对数、溢出错误等。使用try-catch捕获异常并用MessageBox或直接在显示区显示“Error”等方式告知用户。复制与粘贴为显示编辑框添加上下文菜单右键菜单支持复制CtrlC和粘贴CtrlV。对于粘贴需要验证粘贴内容的有效性是否全是合法字符。5.3 代码结构优化与可维护性随着功能增加把所有逻辑都塞在CCalculatorDlg类里会变得臃肿不堪。好的实践是将核心计算逻辑剥离出来封装成独立的类。创建计算引擎类例如创建一个CCalculatorEngine类。这个类负责所有与计算相关的状态管理和逻辑。// CalculatorEngine.h class CCalculatorEngine { public: enum class Mode { Simple, Scientific }; void SetMode(Mode mode); CString ProcessInput(const CString input); // 处理输入返回显示内容 void Clear(); // ... 其他公共接口如获取历史等 private: // 简单计算器所需的状态变量 double m_dblOperand1, m_dblOperand2; wchar_t m_cOperator; BOOL m_bWaitForNewOperand; // 复杂计算器所需的解析器状态 // ... 以及计算引擎内部实现方法 };对话框类专注于UICCalculatorDlg类中持有一个CCalculatorEngine的实例。所有按钮点击事件的处理函数都转化为对CCalculatorEngine::ProcessInput的调用并传入代表该按钮的标识符如“1”、“”、“”。引擎处理完后返回需要显示的字符串对话框类只需将其更新到编辑框控件。这样实现了UI与逻辑的分离代码更清晰也便于未来替换计算引擎或增加新功能如单位换算、汇率计算。6. 项目构建、调试与发布6.1 编译配置与常见编译错误解决在开发过程中你可能会遇到一些典型的编译或链接错误。“无法打开源文件stdafx.h”这是MFC项目使用的预编译头文件。确保在pch.cpp或stdafx.cpp中#include pch.h或#include stdafx.h是第一个包含指令。在其他.cpp文件中也必须首先包含此预编译头文件。“error LNK2001: 无法解析的外部符号”这通常是链接错误意味着函数声明了但没找到定义。对于MFC项目最常见的原因是忘记链接必要的库。确保在“项目属性”-“链接器”-“输入”-“附加依赖项”中包含了mfc140u.lib根据VS版本不同数字可能变化如mfc120u.lib等MFC库。对于使用数学函数的项目还需要添加libcmt.lib或msvcrt.lib并确保在代码中包含了cmath头文件。Unicode与多字节字符集问题MFC项目默认使用Unicode字符集。这意味着所有字符串字面量需要用_T()宏包裹或者使用L字符串的形式。如果你看到关于CString与const char*转换的错误检查字符集设置项目属性-常规-字符集和字符串的使用方式是否一致。6.2 调试技巧与内存管理使用TRACE宏输出调试信息在调试版本中可以使用TRACE(_T(“当前值%s\n”), m_strDisplay);在VS的输出窗口中打印信息这对于跟踪程序流程和变量状态非常有用且不会影响发布版本。检查控件变量绑定如果发现编辑框显示不正常首先检查DoDataExchange函数中的DDX_Text或DDX_Control调用是否正确以及UpdateData(TRUE/FALSE)是否在正确的地方被调用。UpdateData(TRUE)将控件数据更新到变量UpdateData(FALSE)将变量数据更新到控件。内存泄漏检查对于这个小项目通常不会有复杂的内存分配。但如果你在计算引擎中使用了new或malloc务必确保有对应的delete或free。可以使用VS内置的内存泄漏检测工具在程序入口点添加_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);程序退出时会在输出窗口报告未释放的内存块。6.3 生成可执行文件与打包发布当项目开发完成并通过测试后就可以生成最终的可执行文件了。切换到Release模式在VS顶部的工具栏将解决方案配置从“Debug”切换到“Release”。Release版本经过了编译器优化体积更小运行速度更快且不包含调试信息。清理并重新生成点击“生成”菜单下的“清理解决方案”然后点击“重新生成解决方案”。这能确保所有文件都是基于Release配置编译的。查找可执行文件编译成功后在项目目录下的x64\Release如果你用的是x64平台或Release文件夹中可以找到生成的.exe文件例如MyCalculator.exe。依赖项检查直接双击这个.exe文件可能会因为缺少运行时库如vcruntime140.dllmfc140u.dll而无法运行。有两种发布方式静态链接MFC在项目属性-“配置属性”-“高级”中将“MFC的使用”从“在共享DLL中使用MFC”改为“在静态库中使用MFC”。这样会把MFC库代码静态链接到你的EXE中生成的文件会变大但可以独立运行在任何兼容的Windows机器上无需额外DLL。动态链接并分发运行时库保持动态链接但需要将必要的Microsoft Visual C Redistributable包VC_redist.x64.exe和你的EXE一起打包给用户安装或者将所需的DLL如msvcp140.dll,vcruntime140.dll,mfc140u.dll复制到EXE同目录下需注意许可协议。打包你可以使用简单的压缩包就像我最初的那个.rar文件或者使用专业的安装包制作工具如Inno Setup, NSIS来创建安装程序包含快捷方式、注册文件关联等。7. 从项目延伸MFC学习路径与进阶思考通过这个计算器项目我们实际上走马观花地体验了MFC桌面应用开发的核心流程。但MFC的世界远比这广阔。如果你想继续深入可以从以下几个方向拓展这个项目同时也是学习MFC的绝佳路径文档/视图架构尝试将计算器改造成一个基于文档/视图架构的程序。这样你可以利用MFC的序列化功能将计算历史保存到文件下次打开程序时可以加载。这涉及到CDocument,CView等类的理解和使用。自定义控件不满足于标准按钮和编辑框尝试自绘控件。例如创建一个圆形的数字按钮或者一个具有渐变背景的显示区域。你需要重写OnDraw或OnPaint函数使用GDI或GDI进行绘制。多线程计算如果实现了一个非常复杂的表达式解析器比如支持变量、函数定义计算可能耗时。为了避免界面“假死”可以将计算任务放到一个工作线程中通过消息或事件通知主线程更新结果。这涉及到AfxBeginThread、线程同步等知识。插件化架构设计一个接口允许通过动态链接库DLL来扩展计算器的功能如添加新的科学函数、单位换算模块。这能让你学习MFC下的DLL创建与调用以及接口设计的思想。现代化UIMFC本身风格较老但完全可以借助第三方库如BCGControlBar, Ultimate Toolbox或直接使用Windows API如Direct2D来打造现代化的UI界面。这个“简单复杂”的计算器项目就像一颗种子。它包含了MFC应用程序的根对话框、茎消息映射、叶控件和果实功能逻辑。通过精心培育它添加更多功能重构代码结构你不仅能收获一个实用的工具更能深刻理解Windows桌面应用程序开发的内在机理。当你下次再看到任何桌面软件时或许就能一眼看穿它背后大概是如何组织代码、处理消息的了。这正是动手做项目最大的价值所在。本文还有配套的精品资源点击获取