学生成绩预测实战:从特征工程到模型调优的完整指南
发布时间:2026/9/24 18:10:46 作者:尧图编辑部 阅读量:1,286

简介这是一套基于 Python 的学生成绩预测机器学习入门项目面向数据科学初学者、教育统计爱好者及需要完成课设作业的高校学生。项目围绕影响学生成绩的多项因素展开利用 CSV 中的国籍、年级、举手次数、出勤人数、学习时数等字段使用决策树、支持向量机、随机森林等分类器进行预测与对比分析。压缩包共包含 3 个文件Python 脚本、CSV 数据集和 Markdown 说明文档整体大小仅 8KB结构精简便于直接运行与二次修改。这一项目目前已吸引 966 人次学习下载适合在掌握 Python 基础后快速实践。项目完整覆盖数据清洗、特征选择、特征缩放、交叉验证与网格搜索调参等步骤并生成混淆矩阵及可视化图表辅助结果解析同时包含探索性数据分析与多种模型对比的代码框架可帮助学习者系统理解从数据预处理到模型评估的全流程也便于迁移到高校成绩预警、课程质量分析等相似预测场景。1. 学生成绩预测机器学习入门最不该绕开的落地场景很多人入门机器学习第一个练手项目是房价预测或鸢尾花分类但真正常见、数据好拿、业务价值明显的其实是学生成绩预测。这个场景几乎是教科书级别的监督学习问题数据来自学校教务系统每一行是一个学生列里是出勤、作业、测验、家庭背景这些属性目标是把期末成绩或及格与否预测出来。用 Python 做这件事既能走一遍pandas数据清洗、matplotlib可视化、scikit-learn建模的完整流程又能在学期初就对高风险学生做预警属于直接能落地到教学管理里的项目。这个标题里藏着一个典型任务回归还是分类预测具体分数是回归预测“会不会挂科”是分类。两条路都要走通才算真正吃透这个项目。这篇文章我按自己做成绩预警系统的思路来讲从数据拿到手开始特征怎么处理、模型怎么选、参数怎么调、哪些坑最容易翻车一条线走完。适合刚学完 Python 基础语法、想找一个完整案例练手的人也适合学校教务或辅导员想低成本做学情分析的情况。2. 先搞清楚数据和目标预测什么、用什么预测2.1 预测目标的选择G3 期末分数与“是否挂科”我做这个项目第一次就翻车了原因很蠢没想清楚要预测的是什么。学生成绩数据里常见三个字段G1 是第一次测验G2 是第二次测验G3 是期末成绩。如果目标是预测 G3那么 G1 和 G2 要不要进特征这是一个分岔路。如果你做的是“学期末复盘”G1、G2、G3 全都在手那 G1 和 G2 就是强特征模型很容易拿到很高的 R²。但如果你做的是“学期初预警”那 G1 和 G2 根本不存在你得用学生的历史成绩、出勤、作业完成率、家庭经济状况、缺课次数这些数据去预测未来的 G3。两种场景数据结构完全不同混着用会做出一个漂亮但没用的模型。我一般建议把问题拆成两个回归任务预测 G3 的数值分数范围 0 到 20葡萄牙教育体系或 0 到 100。评估指标用 RMSE、MAE。分类任务设定一个及格线比如 10 分制下的 6 分把样本切成“及格”和“不及格”两类评估指标用准确率、召回率、F1。如果你想做一个能用于学期中的预警系统我建议用“最近一次月考成绩 出勤数据 作业提交率”来预测下一次考试。这个设定最贴近真实场景也最容易和学校沟通落地。2.2 公开数据集与自采数据从哪里找、要注意什么教育数据挖掘领域最常被引用的公开数据集是 UCI 的 Student Performance 数据集包含葡萄牙两所中学的学生成绩与家庭背景信息约 395 条样本字段包括性别、年龄、父母教育水平、每周学习时间、缺课次数、既往成绩等。数据量不大但特征类型齐全连续值和类别值都有非常适合做特征工程的练手。不过这个数据集有它的问题样本量太小395 条数据训练出来的模型泛化能力有限数据来自葡萄牙的特定学校文化背景和课程设置与国内差异很大直接拿去做国内学校的预测会失真。所以我建议把它当成练习集真正要落地到具体学校时还是用自己的教务数据格式按照这个数据集的风格整理即可。自采数据要注意几个点学号和姓名必须脱敏这是底线。特征是横向对齐的一行是一个学生。目标字段必须是数值型的成绩或者通过成绩映射出来的 0/1 标签。缺失值要记录清楚是“没填”还是“不存在”比如“父母职业”填“无”和缺失是两种含义。这些细节会在后面特征处理阶段产生连锁反应。数据这块没必要一开始就追求大而全先拿 500 行左右、二十来个字段的数据跑通全流程后面再加数据量。2.3 用 Python 读取和探查数据第一个代码块环境上装好pandas、numpy、matplotlib、seaborn、scikit-learn就够了都是 python 数据分析与可视化、机器学习方向的常用包。如果是刚接触 Python 环境配置建议直接用conda create -n edu python3.11建一个独立环境避免系统 Python 环境装包互相污染。启动环境后装依赖conda create -n edu python3.11 -y conda activate edu pip install pandas numpy matplotlib seaborn scikit-learn建独立环境这个动作看似多余实际上能帮你省掉后面大量的环境冲突问题。很多人在机器学习入门阶段把包装到 base 环境里装到一半发现某个库的版本把另一个库搞崩了最后只能重装 Python得不偿失。数据读进来后的第一步不是建模是看形状、看缺失、看分布import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(student-mat.csv, sep;) # 基本信息 print(shape:, df.shape) print(df.info()) # 目标字段G3的分布 df[G3].hist(bins20) plt.xlabel(G3 score) plt.ylabel(count) plt.show() # 缺失值检查 print(df.isnull().sum().sum())这段代码做完你应该得到几个关键判断数据是不是 395×33 左右的结构有没有缺失值G3 是不是集中在 10 到 14 分的区间有没有明显的长尾。如果发现 G3 的分布严重偏斜后面建模时就需要考虑目标变换。大多数公开教育数据集缺失值都很少但不要因此跳过这一步后面自采数据时你会感谢自己写了这段检查代码。3. 特征工程决定成绩预测模型上限的关键一步3.1 数值特征与类别特征的区分处理学生成绩预测里特征大概分三类。第一类是数值连续型比如缺课次数、每周学习时间、年龄。第二类是类别离散型比如性别、父母职业、是否参加课外活动、是否有学习辅导。第三类是顺序型比如父母教育水平它有点像类别但又有高低之分。处理方式不能一刀切。性别这种无序类别直接做LabelEncoder给一个 0/1 编码没问题但父母教育水平如果用 0 到 4 的数字表示那它的数字大小本身有意义可以直接当作数值特征用。缺课次数如果范围很大、有极端值考虑归一化或者分桶。pandas里有一句astype(category)可以快速标记类别列后面建模时方便统一处理。常用的顺序是先复制一份数据框把明显是 ID 的列比如学号直接 drop 掉然后分别处理类别列和数值列from sklearn.preprocessing import LabelEncoder df_clean df.copy() # 删除学号之类的ID列避免被当成数值特征 if student_id in df_clean.columns: df_clean df_clean.drop(columns[student_id]) # 类别列统一编码 cat_cols df_clean.select_dtypes(include[object]).columns.tolist() for col in cat_cols: le LabelEncoder() df_clean[col] le.fit_transform(df_clean[col].astype(str)) print(df_clean.head())LabelEncoder 的作用是给每个类别一个整数编号比如“是/否”变成 1/0“有/没有”变成 1/0。这个编码方式对树模型没问题但对线性模型来说编码的数字大小会被解释成“量的大小”所以后面如果用到线性回归对无序类别最好改成 One-Hot 编码也就是pd.get_dummies()。这里先做 LabelEncoder 只适合快速跑通流程要做严谨模型时我会把无序类别单独拿出来做 One-Hot。3.2 构造“时间差”和“变化率”特征只拿原始字段做预测模型的表观通常中等R² 在 0.4 到 0.6 之间。想把成绩预测的准确度往上推让人能信服这个方向值得投入就得靠特征工程。教育场景里两个特征非常有价值成绩变化趋势和学习投入度。成绩变化趋势怎么构造假如你有 G1 和 G2 两次阶段性成绩G2 - G1的差值本身就是极强的新特征。上升说明学生状态在变好下降说明需要干预。这个差值比单独两个原始分数更有业务解释力。学习投入度可以用“作业提交率 × 出勤率”之类的组合来近似如果原始数据里没有作业字段那就用每周学习时间去乘一个常数再和出勤相加。# 构造成绩变化趋势和综合投入度特征 df_clean[score_change] df_clean[G2] - df_clean[G1] df_clean[effort_score] df_clean[studytime] * 2 (20 - df_clean[absences]) * 0.5 print(df_clean[[score_change, effort_score]].describe())score_change如果经常取 0说明大部分学生在两次考试间成绩稳定这时候要检查是不是数据本身变异不够。effort_score是我临时编的加权公式studytime的权重是 2表示每周学习时间每增加一档对综合投入度的贡献是 2 分缺课次数每多一天扣 0.5 分。这种特征叫衍生特征它把原始字段里的信息压缩成一个更有业务含义的值。权重怎么定没有标准答案核心思路是能解释业务行为的特征才是好特征不是特征越多越好。3.3 相关性与特征筛选避免把噪声喂给模型特征工程做完下一步是看特征和目标之间的关系。画相关性热力图是机器学习项目里最值得花时间的一步它能帮你一眼看出哪些特征和目标真的有关哪些是废的。import seaborn as sns corr df_clean.corr() top_cols corr[G3].abs().sort_values(ascendingFalse).head(10).index sns.heatmap(df_clean[top_cols].corr(), annotTrue, fmt.2f) # 打印与目标最相关的5个特征 print(corr[G3].abs().sort_values(ascendingFalse).head(6))经验上G1、G2 与 G3 的相关系数通常会超过 0.7这是正常的。但如果特征是“缺课次数”与 G3 负相关到 -0.3 以上说明出勤对成绩影响显著。特征筛选这里有个血泪经验不要只挑相关性高的特征。有些特征单独看和目标相关系数接近 0但和其他特征组合起来对模型提升很大这叫交互效应。比如“缺课次数 × 家庭教育支持”可能比单独的缺课次数更有预测力。特征数量建议控制在 10 到 30 个之间。少于 10 个模型学不到足够模式多于 30 个又容易把噪声带进来。如果原始字段超过 50 个先用相关性过滤掉明显没用的再看模型输出的特征重要性做二次筛选。4. 建模与对比线性回归、树模型与集成模型的取舍4.1 先跑线性回归和岭回归拿到基线分数建模第一步是跑一个最简单的模型当基线。线性回归的好处是速度快、可解释性强坏处是它对特征的线性假设很强成绩预测里很多关系不是线性的。不过没关系基线就是用来被超越的。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression, Ridge from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score # 定义特征和目标 X df_clean.drop(columns[G3]) y df_clean[G3] # 划分训练集和测试集 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 线性回归 lr LinearRegression() lr.fit(X_train, y_train) y_pred lr.predict(X_test) print(LinearRegression RMSE:, mean_squared_error(y_test, y_pred, squaredFalse)) print(LinearRegression MAE:, mean_absolute_error(y_test, y_pred)) print(LinearRegression R2:, r2_score(y_test, y_pred)) # 岭回归带正则化参数alpha ridge Ridge(alpha1.0) ridge.fit(X_train, y_train) y_pred_ridge ridge.predict(X_test) print(Ridge RMSE:, mean_squared_error(y_test, y_pred_ridge, squaredFalse)) print(Ridge R2:, r2_score(y_test, y_pred_ridge))我特别想强调squaredFalse这个参数。mean_squared_error默认返回的是均方误差单位是成绩分数的平方比如 RMSE 读作“平均误差 2.8 分”但 MSE 是“7.84 平方分”听着就很别扭。加上squaredFalse直接返回 RMSE汇报结果的时候别人能听懂。这个细节看起来小但在实际汇报和写报告时很重要我第一次做项目时没注意最后给老师看结果对方看着 MSE 的数字一头雾水。岭回归和线性回归的区别是多了个 L2 正则化。alpha1.0是正则化强度值越大模型参数被压得越小越不容易过拟合。学生成绩数据只有 395 条线性回归在这么小的数据集上容易把噪声也学进去岭回归一般会比线性回归稳一点。跑完这两个模型RMSE 大约在 2.0 到 3.0 分之间、R² 在 0.7 到 0.8 之间算是一个不差的基线。4.2 随机森林与 XGBoost集成模型是否真的更好线性模型跑完接下来上随机森林和 XGBoost。这两个是当前表格数据机器学习里最常用的模型。随机森林通过多棵决策树投票来降低过拟合XGBoost 是梯度提升树的代表用加法模型逐步纠正前面树的错误。成绩预测这种特征数量和样本量都不算大的任务树模型通常比线性回归表现更好。from sklearn.ensemble import RandomForestRegressor from xgboost import XGBRegressor # 随机森林 rf RandomForestRegressor(n_estimators300, max_depth5, min_samples_leaf5, random_state42) rf.fit(X_train, y_train) y_pred_rf rf.predict(X_test) print(RandomForest R2:, r2_score(y_test, y_pred_rf)) # XGBoost xgb XGBRegressor(n_estimators300, max_depth3, learning_rate0.05, subsample0.8, colsample_bytree0.8, random_state42) xgb.fit(X_train, y_train) y_pred_xgb xgb.predict(X_test) print(XGBoost R2:, r2_score(y_test, y_pred_xgb))先看参数。随机森林里n_estimators300是树的数量不是越多越好到 300 棵之后收益递减。max_depth5限制每棵树的深度防止单棵树长得太复杂而记住噪声。min_samples_leaf5要求每个叶节点至少 5 个样本这是防止过拟合的第二道保险。XGBoost 的设置需要更细致。learning_rate0.05是学习率也叫步长每次迭代只学目标修正量的一小部分值越小越稳但需要的树越多。subsample0.8表示每棵树只用 80% 的样本训练colsample_bytree0.8表示每棵树只用 80% 的特征这两项是给模型注入随机性让每棵树长得不一样集成时才有效果。跑完你会发现 XGBoost 的 R² 可能比随机森林高一点点也可能差不多。不要迷信“越新的模型一定越好”在学生成绩这种小数据集上随机森林和 XGBoost 的差距通常在 0.03 以内。真正翻车的地方不是模型选谁而是参数没调好让 XGBoost 在训练集上表现完美、在测试集上烂成一坨。4.3 用交叉验证代替单次划分让分数可信train_test_split用random_state42固定了随机种子这会导致一个常见误区换一个随机种子模型分数变化很大。学生成绩数据集才 395 条划分方式对结果影响非常大。解决办法是交叉验证常见做法是 5 折或 10 折。这里我用cross_val_score对随机森林做一次 5 折交叉验证from sklearn.model_selection import cross_val_score cv_scores cross_val_score(rf, X, y, cv5, scoringr2) print(CV R2 scores:, cv_scores) print(Mean CV R2:, cv_scores.mean().round(3)) print(Std:, cv_scores.std().round(3))交叉验证的理念是把数据切成 5 份每次拿 4 份训练、1 份验证轮转 5 次最后得到 5 个分数。你得到的不是一个孤立的 R²而是一个分布。看两个指标均值是模型真实能力的估计标准差是稳定性。如果均值 0.75、标准差 0.05说明模型稳定。如果均值 0.75、标准差 0.15说明模型对数据划分很敏感要小心是不是样本量太小或某个样本是极端离群点。交叉验证还有个容易被忽略的好处它比单次划分更能让你判断模型是否值得上线。单次 test R² 有运气成分交叉验证的均值更贴近未来真实数据上的表现。机器学习项目里一个不稳定的高分模型比一个稳定但稍低的模型危险得多。5. 把回归问题变成分类问题预测及格与不及格5.1 及格标签的定义与类别不平衡陷阱回归预测分数虽然直观但实际应用中老师们更关心的是“这个学生会不会不及格”。于是需要把连续成绩映射成二分类标签。假设成绩范围 0 到 20及格线是 10 分标签规则G3 10 为 1及格G3 10 为 0不及格。df_clean[pass_label] (df_clean[G3] 10).astype(int) # 检查正负样本比例 print(df_clean[pass_label].value_counts()) print(df_clean[pass_label].mean())这里立刻会遇到一个现实问题不及格的人永远是少数。df_clean[pass_label].mean()如果输出 0.7 左右说明 70% 的人及格只有 30% 的人不及格。从机器学习的角度看这是明显的类别不平衡。如果不管这个不平衡直接训练分类器模型会偷懒——把所有样本都预测成“及格”准确率也有 70%看起来还挺高。但它一个危险学生都识别不出来等于白做。这时候必须调整评估指标准确率是最没用的那个信息点要看召回率也就是不及格学生里有多大比例被模型挑出来了。5.2 逻辑回归与随机森林分类召回率优先的参数设定分类模型我用逻辑回归和随机森林分类器做对比。逻辑回归作为线性分类器可解释性强适合第一次跑通随机森林分类器作为非线性模型通常能捕捉更复杂的模式。关键参数不是默认值而是class_weight。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix # 特征矩阵和分类目标 X_clf df_clean.drop(columns[G3, pass_label]) y_clf df_clean[pass_label] # 逻辑回归自带类别权重 lr_clf LogisticRegression(class_weightbalanced, max_iter1000) lr_clf.fit(X_train, y_train) # 随机森林分类器 rf_clf RandomForestClassifier(n_estimators300, max_depth5, min_samples_leaf5, class_weightbalanced, random_state42) rf_clf.fit(X_train, y_train) # 预测与评估 y_pred_clf rf_clf.predict(X_test) print(classification_report(y_test, y_pred_clf, target_names[fail, pass])) print(confusion_matrix(y_test, y_pred_clf))class_weightbalanced的意思是根据类别频率自动调整权重。少数类不及格在损失函数里被加大权重模型不再忽略它。max_iter1000是逻辑回归的迭代上限默认值是 100数据量大或特征多时容易没收敛就报错直接调大省心。classification_report会输出每一类的精确率、召回率、F1 分数。这时候重点看fail那一行的召回率。如果召回率低于 0.5说明一半以上的危险学生没被识别出来系统不可用。5.3 阈值调整召回率和精确率的权衡默认情况下模型输出的是一个概率判定阈值是 0.5。但在“找不及格学生”这个场景下0.5 的阈值往往太保守。我想要的是尽量不漏掉危险学生哪怕多找几个“误报”的也没关系因为误报顶多是老师多找学生谈一次话漏报的代价更高。这时候要下调判断阈值。# 获取预测概率 y_prob rf_clf.predict_proba(X_test)[:, 1] # 阈值设成0.3更倾向预测为不及格 y_pred_custom (y_prob 0.3).astype(int) print(classification_report(y_test, y_pred_custom, target_names[fail, pass]))把阈值从 0.5 降到 0.3 后召回率通常会上涨精确率会下降。用 0.3 这个值本身没有数学上的绝对正确只有业务上的取舍。如果你不确定选多少可以画一条 PR 曲线或 ROC 曲线在曲线上找拐点也可以用网格搜索调一遍阈值常见做法是枚举 0.2 到 0.6 的一串候选值比较 F1 或召回率后选一个。这一节是整个成绩预测项目里最有落地价值的部分因为实际使用系统的老师不需要理解分类器原理他们只需要知道“这个名单上的人是高风险学生请重点关注”。6. 成绩预测里跑不通的那些坑现象、原因、解决办法6.1 用 G1、G2 预测 G3 的成绩虚高数据泄漏的典型表现现象模型 R² 轻松超过 0.9甚至到 0.95你觉得自己已经是机器学习天才了。但把模型拿到另一批学生上预测效果差得离谱。原因G1 和 G2 本来就是 G3 的阶段性映射它们和 G3 高度相关。你训练模型时用了 G1、G2模型学到的是“G1 高G3 就高”的映射关系。可学生学期初根本没有 G1、G2你让系统预测新生它拿什么预测这是最典型的数据泄漏。数据泄漏不是靠调参能解决的它是对业务场景不理解的产物。解决回到业务场景定义特征集合。学期初预警只保留开学时已知的信息平时成绩、出勤、家庭背景、入学测试。把 G1、G2 从特征列表里拿掉重新训练模型。你会发现 R² 掉到 0.5 左右这才是真实水平。6.2 学生 ID 被当成特征用模型记住人而不是学到规律现象训练集和测试集的 R² 都高得不正常但特征重要性排列第一的是学生 ID。你隐约觉得哪里不对看特征重要性才发现它是第一特征。原因每一行样本的 ID 都是唯一的树模型可以通过 ID 精确划分出一个叶节点里面只有一个样本自然“预测”得非常准。这是特征工程的疏忽。做模型之前没有 drop 掉 ID 列。解决在模型训练第一步就执行df.drop(columns[student_id], errorsignore)。这里我踩过的坑是errorsignore这个参数如果列不存在不报错继续运行省得每次跑都看一遍红色报错。6.3 类别不平衡导致准确率虚高模型预测所有人及格现象分类报告里准确率 90%你以为模型非常好。打开混淆矩阵一看真正需要关注的“不及格”那一类一个都没预测出来预测全落在“及格”上。原因样本中不及格占比太低模型只要学“都及格”这个模式准确率就已经很好了。准确率指标在类别不平衡时完全失真。解决换评估指标看召回率和 F1。模型层面加class_weightbalanced或者用 SMOTE 做上采样让少数类样本多一点。但注意SMOTE 是在训练集上做不能对测试集做否则又是数据泄漏的另一种形式。6.4 G3 的离散整数分布让回归方差变大现象回归模型的 RMSE 看起来不大但误差图上一排一排的竖线误差在某些分数区间特别集中。原因成绩是整数分数0 到 20 一共才 21 个取值本质上是离散数据。线性回归假设目标变量是连续的模型输出像是 11.37 分这种实际不存在的东西。模型学的是一个连续函数但真实标签是离散的阶梯拟合误差天然偏大。解决如果你只关心“能不能及格”尽量用分类模型绕开连续假设的尴尬。如果一定要回归可以试试把预测结果四舍五入到整数再算误差或者用有序回归。但完全消除离散性的影响是做不到的接受它或者换分类模型二选一。6.5 单次划分结果不稳定随机种子一换分数就变现象同一份代码random_state42跑出来 R² 是 0.78把随机种子改成 0R² 变成 0.65。你没有改任何参数只是换了划分方式。原因395 条样本对训练集和测试集的划分异常敏感。某次划分可能把几个极端高分学生全分到测试集模型直接从没见过高分样本预测自然翻车。解决使用交叉验证报告均值和标准差。也可以多跑几个随机种子把多次训练的结果平均起来当成最终模型。固定random_state是科学实验的好习惯但它不代表模型稳定。7. 从模型到工具把预测结果做成可用的成绩预警清单学术上的模型对比做完真正让这个项目产生价值的是把它变成老师能用的清单。一个教务老师不需要看 RMSE 和 R²他需要知道的就是“班上这些学生是高风险这些是临界高风险”。输出一个带预测概率的名单比任何模型指标都有说服力。# 把概率和对应信息导出成表格 X_test_with_prob X_test.copy() X_test_with_prob[pred_fail_prob] rf_clf.predict_proba(X_test)[:, 1] X_test_with_prob[actual_G3] y_test X_test_with_prob[actual_label] (y_test 10).astype(int) # 按预测风险从高到低排序 risk_rank X_test_with_prob.sort_values(pred_fail_prob, ascendingFalse) print(risk_rank[[pred_fail_prob, actual_G3, actual_label]].head(10)) # 导出Excel给教务老师用 risk_rank.to_excel(risk_students.xlsx, indexFalse)to_excel需要先安装openpyxl库命令是pip install openpyxl不然会报ModuleNotFoundError。导出 Excel 是一个容易被忽略的小操作但往往是让项目好用的关键一步因为老师最常用的就是 Excel不是 Jupyter Notebook。第一人称收尾我首次做这个项目时只盯着模型分数想着把 R² 刷得越高越好等模型上线实测一部分老师反馈名单没什么用这才明白衡量一个预警系统好不好的核心标准是召回率、是名单里是否真的出现了后来挂科的学生。从那以后我做任何成绩预测项目第一步永远先问用户一句话“你打算拿预测结果做什么”——是提前干预是评优参考还是复盘总结三个答案对应的数据划分和模型选择完全不同。这个教训后来帮我避开了很多无用功。如果你正准备用机器学习做学生成绩预测建议也先问自己这个问题想清楚后再动手写代码。希望帮到你。本文还有配套的精品资源点击获取