9.spring-图书管理系统

文章目录

  • 1.开发项目流程
    • 1.1开发开发
    • 1.2数据库的设计
  • 2.MySQL数据库相关代码
  • 3.构造图书结构
    • 3.1用户登录
    • 3.2图书列表
    • 3.3图书添加
    • 3.4图书删除
      • 3.4.1批量删除
    • 3.5图书查询(翻页)
  • 4.页面展示
    • 4.1登录页面
    • 4.2列表页面
    • 4.3增加图书页面
    • 4.4修改图书信息页面
  • 5.功能展示
    • 5.1增加图书信息
    • 5.2修改图书信息
    • 5.3删除单个图书
    • 5.4批量删除图书
    • 5.5翻页功能
  • 7.拓展知识
    • 7.1SpringBoot 中的mapper,service,controller,model 分别有什么用?
    • 7.2DAO层、Service层和Controller层的区别
    • 7.3 SSM系统架构
    • 7.4MySQL(关系型数据库)和mongodb(非关系型数据库)的区别
    • 7.5redis(非关系型数据库)

大家好,我是晓星航。今天为大家带来的是 相关的讲解!😀

1.开发项目流程

1.1开发开发

1.需求确认阶段:需求分析,需求评审

2.开发

1)方案设计
2)接口定义
3)开发业务代码
4)测试(自测+联调) - 和其他团队一起联合测试

3.提测阶段 - 测试人员

4.上线(发布)阶段

1.2数据库的设计

image-20240324143734595

数据库编码:

1.安装时修改

2.建表时修改

2.MySQL数据库相关代码

1.建库

create databases book_test default character set utf8mb4;

2.建表

用户表:

DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (id INT NOT NULL AUTO_INCREMENT,user_name VARCHAR(128) NOT NULL,password VARCHAR(128) NOT NULL,delete_flag TINYINT(4) NULL DEFAULT 0,create_time DATETIME DEFAULT now(),update_time DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY(id),
UNIQUE INDEX user_name_UNIQUE (user_name ASC)) ENGINE = INNODB DEFAULT CHARACTER SET = utf8mb4 COMMENT ='用户表';

图书表:

DROP TABLE IF EXISTS book_info;
CREATE TABLE book_info (id INT(11) NOT NULL AUTO_INCREMENT,book_name VARCHAR(127) NOT NULL,author VARCHAR(127) NOT NULL,count INT(11) NOT NULL,price DECIMAL(7,2) NOT NULL,publish VARCHAR(256) NOT NULL,status TINYINT(4) DEFAULT 1 COMMENT '0-无效,1-正常,2-不允许借阅',create_time DATETIME DEFAULT noW(),update_time DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY (id)
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;

3.插入数据检验

用户表插入数据:

-- 初始化数据
INSERT INTo user_info (user_name,PASSWORD) VALUES ("admin","admin");
INSERT INTO user_info( user_name,PASSWORD) VALUES ("zhangsan","123456");

图书表插入数据:

-- 初始化图书数据
INSERT INTO book_info (book_name,author,count,price, publish) VALUES ('活着','余华',29,22.00,'北京文艺出版社');
INSERT INTO book_info (book_name,author,count,price, publish) VALUES ('平凡的世界','路遥',5,98.56,'北京十月文艺出版社');
INSERT INTO book_info (book_name,author,count,price,publish) VALUES ('三体','刘慈欣',9,102.67,'重庆出版社');
INSERT INTO book_info (book_name,author,count,price,publish) VALUES ('金字塔原理','麦肯锡',16,178.00,'民主与建设出版社');

批量化构造数据:

INSERT INTO book_info (book_name,author,count,price,publish) VALUES ('图书2','作者2',29,22.00,'出版社2'),('图书3','作者2',29,22.00,'出版社3'),('图书4','作者2',29,22.00,'出版社4'),('图书5','作者2',29,22.00,'出版社5'),('图书6','作者2',29,22.00,'出版社6'),('图书7','作者2',29,22.00,'出版社7'),('图书8','作者2',29,22.00,'出版社8'),('图书9','作者2',29,22.00,'出版社9'),('图书10','作者2',29,22.00,'出版社10'),('图书11','作者2',29,22.00,'出版社11');
INSERT INTO book_info (book_name,author,count,price,publish) VALUES ('图书12','作者2',29,22.00,'出版社12'),('图书13','作者2',29,22.00,'出版社13'),('图书14','作者2',29,22.00,'出版社14'),('图书15','作者2',29,22.00,'出版社15'),('图书16','作者2',29,22.00,'出版社16'),('图书17','作者2',29,22.00,'出版社17'),('图书18','作者2',29,22.00,'出版社18'),('图书19','作者2',29,22.00,'出版社19'),('图书20','作者2',29,22.00,'出版社20'),('图书21','作者2',29,22.00,'出版社21');

3.构造图书结构

3.1用户登录

约定前后端交互接口

[请求]
/user/login
Content-Type:application/x-www-form-urlencoded; charset-UTF-8[参数]
name=zhangsan&password=123456[响应]
true //账号密码验证正确,否则返回false

实现服务器代码

控制层

从数据库中,根据名称查询用户,如果可以查到,并且密码一致,就认为登录成功

import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.stringutils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.Restcontroller;import javax.servlet.http.HttpSession;@RequestMapping("/user")
@Restcontrnller

model层 - 存放实体类:

UserInfo.java:

package com.example.book.model;import lombok.Data;import java.util.Date;@Data
public class UserInfo {private Integer id;private String userName;private String password;private Integer deleteFlag;private Date createTime;private Date updateTime;
}

Controller层:

UserController.java:

package com.example.book.controller;import com.example.book.constant.Constants;
import com.example.book.model.UserInfo;
import com.example.book.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/login")public Boolean login(String userName, String password, HttpSession session){//校验参数if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){return false;}//验证账号密码是否正确/*** if (userName.equals("admin")){ }  这种写法, 如果userName为null, 会报空指针异常* 开发习惯, 养成*///1. 根据用户名去查找用户信息UserInfo userInfo = userService.getUserInfoByName(userName);//2. 比对密码是否正确if (userInfo==null || userInfo.getId()<=0){return false;}if (password.equals(userInfo.getPassword())){//账号密码正确//存SessionuserInfo.setPassword("");session.setAttribute(Constants.SESSION_USER_KEY,userInfo);return true;}return false;}
}

根据用户名查用户信息,比对验证密码是否正确,账号密码正确后存Session,登录成功!

service层 - 主要是针对具体的问题的操作,把一些数据层的操作进行组合,间接与数据库打交道:

UserService.java:

package com.example.book.service;import com.example.book.mapper.UserInfoMapper;
import com.example.book.model.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;public UserInfo getUserInfoByName (String name) {return userInfoMapper.selectUserByName(name);}
}

Mapper层(Dao层):

UserInfoMapper.java:

package com.example.book.mapper;import com.example.book.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface UserInfoMapper {/*** 根据用户名称查询用户信息* @param name* @return*/@Select("select * from user_info where user_name=#{name}")UserInfo selectUserByName(String name);}

3.2图书列表

可以由前端完成也可以由后端完成

前端完成:

image-20240325153145143

后端完成:

image-20240325153153208

model层 - 存放实体类:

PageResult.java:

package com.example.book.model;import lombok.Data;import java.util.List;@Data
public class PageResult<T> {/*** 当前页的记录*/private List<T> records;/*** 总记录数*/private Integer total;private PageRequest pageRequest;public PageResult(List<T> records, Integer total,PageRequest pageRequest) {this.records = records;this.total = total;this.pageRequest = pageRequest;}
}

PageRequest.java:

package com.example.book.model;import lombok.Data;@Data
public class PageRequest {/*** 当前页码*/private Integer currentPage=1;/*** 每页显示条数*/private Integer pageSize=10;private Integer offset;public Integer getOffset() {return (currentPage-1) * pageSize;}
}

controller层:

BookController.java

package com.example.book.controller;import com.example.book.model.*;
import com.example.book.service.BookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;
import java.util.List;@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {@Autowiredprivate BookService bookService;@RequestMapping("/getBookListByPage")public Result getBookListByPage(PageRequest pageRequest, HttpSession session){log.info("查询翻页信息, pageRequest:{}",pageRequest);
//        //用户登录校验
//        UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
//        if (userInfo==null|| userInfo.getId()<=0 || "".equals(userInfo.getUserName())){
//            //用户未登录
//            return Result.unlogin();
//        }//校验成功if (pageRequest.getPageSize()<0 || pageRequest.getCurrentPage()<1){return Result.fail("参数校验失败");}PageResult<BookInfo> bookInfoPageResult = null;try {bookInfoPageResult = bookService.selectBookInfoByPage(pageRequest);return Result.success(bookInfoPageResult);}catch (Exception e){log.error("查询翻页信息错误,e:{}",e);return Result.fail(e.getMessage());}}
}

service层:

BookService.java:

package com.example.book.service;import com.example.book.enums.BookStatusEnum;
import com.example.book.mapper.BookInfoMapper;
import com.example.book.model.BookInfo;
import com.example.book.model.PageRequest;
import com.example.book.model.PageResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Slf4j
@Service
public class BookService {@Autowiredprivate BookInfoMapper bookInfoMapper;public PageResult<BookInfo> selectBookInfoByPage(PageRequest pageRequest) {if (pageRequest == null) {return null;}//获取总记录数Integer count = bookInfoMapper.count();//获取当前记录List<BookInfo> bookInfos = bookInfoMapper.selectBookInfoByPage(pageRequest.getOffset(), pageRequest.getPageSize());if (bookInfos != null && bookInfos.size() > 0) {for (BookInfo bookInfo : bookInfos) {//根据status 获取状态的定义bookInfo.setStatusCN(BookStatusEnum.getNameByCode(bookInfo.getStatus()).getName());}}return new PageResult<>(bookInfos, count, pageRequest);}/*** 添加图书** @param bookInfo* @return*/public Integer addBook(BookInfo bookInfo) {Integer result = 0;try {result = bookInfoMapper.insertBook(bookInfo);} catch (Exception e) {log.error("添加图书出错, e:{}", e);}return result;}public BookInfo queryBookInfoById(Integer id) {return bookInfoMapper.queryBookInfoById(id);}/*** 更新图书* @param bookInfo* @return*/public Integer updateBook(BookInfo bookInfo) {Integer result = 0;try {result = bookInfoMapper.updateBook(bookInfo);} catch (Exception e) {log.error("更新图书失败, e:{}", e);}return result;}public Integer batchDelete(List<Integer> ids){Integer result =0;try {result = bookInfoMapper.batchDelete(ids);}catch (Exception e){log.error("批量删除图书失败, ids:{}",ids);}return result;}
}

mapper层:

BookInfoMapper.java:

package com.example.book.mapper;import com.example.book.model.BookInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface BookInfoMapper {/*** 获取当前页的信息* @param offset* @param pageSize* @return*/@Select("select * from book_info where status !=0 " +"order by id desc limit #{offset},#{pageSize}")List<BookInfo> selectBookInfoByPage(Integer offset, Integer pageSize);/*** 获取总记录数* @return*/@Select("select count(1) from book_info where status !=0")Integer count();@Insert("insert into book_info (book_name,author, count, price, publish, status) " +"values(#{bookName}, #{author}, #{count}, #{price},#{publish}, #{status})")Integer insertBook(BookInfo bookInfo);@Select("select * from book_info where id =#{id}")BookInfo queryBookInfoById(Integer id);Integer updateBook(BookInfo bookInfo);Integer batchDelete(List<Integer> ids);
}

3.3图书添加

前端代码:

image-20240326152454105

Controller层:

image-20240326152529837

service层:

image-20240326152608120

mapper层(Dao层):

image-20240326152634176

3.4图书删除

企业中很少使用delete语句
delete语句通常是在进行数据修复时才会使用

pg.测试人员进行测试,手工造一些数据,测试完成之后,这条数据就是脏数据了
这个脏数据(假数据) 没有任何价值的,需要把数据删掉,使用delete语句

逻辑删除,物理删除

image-20240326101620298

image-20240326140815832

前端代码:

image-20240326141430507 image-20240326141653479

3.4.1批量删除

image-20240326141711489

Controller层:

image-20240326141747075

service层:

image-20240326142106050

mapper层(Dao层):

image-20240326141822193

3.5图书查询(翻页)

Controller层:

image-20240326152744171

service层:

image-20240326152838700

mapper层(Dao层):

image-20240326152857339

4.页面展示

4.1登录页面

image-20240326162901803

登录失败:

image-20240326162919495

登录成功:

image-20240326162934694

4.2列表页面

image-20240326163009627

4.3增加图书页面

image-20240326163149057

image-20240326163200614

4.4修改图书信息页面

image-20240326163228825

image-20240326163238887

5.功能展示

5.1增加图书信息

image-20240326163149057

image-20240326163200614

5.2修改图书信息

image-20240326163228825
image-20240326163238887

5.3删除单个图书

image-20240326163531046

image-20240326163552634

image-20240326163612445

5.4批量删除图书

image-20240326163713219

image-20240326163726067

5.5翻页功能

image-20240326163757337

image-20240326163836758

7.拓展知识

7.1SpringBoot 中的mapper,service,controller,model 分别有什么用?

MSCM:

  1. controller - 控制层

相当于MVC的C层,controller通过service的接口来控制业务流程,也可通过接收前端传过来的参数进行业务操作。

  1. model - 数据模型层

相当于MVC的M层,存放实体类,与数据库中的属性值基本保持一致。

  1. service - 业务逻辑层

主要是针对具体的问题的操作,把一些数据层的操作进行组合,间接与数据库打交道(提供操作数据库的方法)。

要做这一层的话,要先设计接口,在实现类。

  1. mapper - 数据存储对象 (Dao)

相当于DAO层,mapper层直接与数据库打交道(执行SQL语句),接口提供给service层。

图书管理系统目录:

image-20240325144321175

7.2DAO层、Service层和Controller层的区别

image-20240324112001591

简化理解:

image-20240324112329505

图书管理系统目录:

image-20240325144321175

7.3 SSM系统架构

image-20240325143656658

7.4MySQL(关系型数据库)和mongodb(非关系型数据库)的区别

image-20240326155315647

MySQL:

image-20240326155434335

mongodb:

image-20240326155459830

7.5redis(非关系型数据库)

image-20240326162500120

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3031483.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

EPAI手绘建模APP动画、场景、手势操作

(15) 动画 图 299 动画控制器 ① 打开动画控制器。播放动画过程中&#xff0c;切换场景观察视角时&#xff0c;自动停止播放。动画编辑参见常用工具栏-更多-动画动画编辑器部分。 ② 关闭动画控制器。 ③ 设置动画参数&#xff1a;设置动画总帧数&#xff1b;这只帧率&#x…

docker安装nginx支持ssl 实现https访问(完整版)

全文目录,一步到位 1.前言简介1.1 专栏传送门1.1.1 本文简介 2. docker安装nginx支持ssl2.0 准备ssl证书(例: 阿里云)2.0.1 配置域名解析2.0.2 找到数字证书管理服务并签发ssl证书2.0.3 选择默认证书 填写域名 创建2.0.4 提交审核, 签发成功2.0.5 解压并上传到宿主机ssl路径下 …

FFmpeg常用API与示例(三)—— 音视频解码与编码

编解码层 1.解码 (1) 注册所有容器格式和 CODEC:av_register_all() (2) 打开文件:av_open_input_file() (3) 从文件中提取流信息:av_find_stream_info() (4) 穷举所有的流&#xff0c;查找其中种类为 CODEC_TYPE_VIDEO (5) 查找对应的解码器:avcodec_find_decoder() (6) …

解决电脑睡眠后,主机ping不通VMware虚拟机

文章目录 问题解决方法方法一方法二注意 问题 原因&#xff1a;电脑休眠一段时间&#xff0c;再次打开电脑就ping不通VMware虚拟机。 解决方法 方法一 重启电脑即可&#xff0c;凡是遇到电脑有毛病&#xff0c;重启能解决90%问题。但是重启电脑比较慢&#xff0c;而且重启…

Linux 磁盘分区工具 gdisk / fdisk

fdisk 是传统的 Linux 磁盘分区工具&#xff0c;磁盘容量有2T的大小限制&#xff1b;gdisk 又叫 GPT fdisk, 作为 fdisk 的升级版&#xff0c;主要使用的是GPT分区类型&#xff0c;用来划分容量大于2T的硬盘&#xff0c;本文介绍使用方法。 简介 早期的磁盘使用 fdisk 工具分区…

C——单链表

一.前言 我们在前面已经了解了链表中的双向链表&#xff0c;而我们在介绍链表分类的时候就说过常用的链表只有两种——双向带头循环链表和单向不带头不循环链表。下来我来介绍另一种常用的链表——单向不带头不循环链表也叫做单链表。不清楚链表分类的以及不了解双向链表的可以…

Prometheus-Operator:快速部署

1. 项目目标 &#xff08;1&#xff09;了解Prometheus-Operator的优势 &#xff08;2&#xff09;熟练掌握Prometheus-Operator部署 &#xff08;3&#xff09;操作Grafana对接Prometheus 2. 项目准备 2.1. 规划节点 主机名 主机IP 节点规划 Prometheus 主机IP Prom…

如何克服挑战?跨境电商平台开发中的关键挑战解析

在当今的数字化时代&#xff0c;跨境电商平台开发成为了许多企业所关注的焦点。然而&#xff0c;随之而来的是一系列挑战&#xff0c;需要克服才能确保项目成功推进。从法律合规、支付结算到物流运输&#xff0c;跨境电商平台开发中的挑战多而复杂。本文将针对跨境电商平台开发…

【Matlab】Matlab之美,抓紧来膜拜大神的创星之作(附2024Matlab教程+代码)

软件介绍 MATLAB是一款商业数学软件&#xff0c;用于算法开发、数据可视化、数据分析以及数值计算的高级技术计算语言和交互式环境&#xff0c;主要包括MATLAB和Simulink两大部分&#xff0c;可以进行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程序…

福派斯鸭肉梨狗粮,让狗狗告别泪痕困扰!值得一试吗?

亲爱的宠友们&#xff0c;你们是不是也为自家狗狗的泪痕问题头疼不已呢&#xff1f;&#x1f914;今天我就来给大家聊聊福派斯鸭肉梨去泪痕狗粮&#xff0c;看看这款狗粮究竟怎么样&#xff0c;去泪痕效果好不好&#xff0c;以及值不值得入手哦&#xff01; 首先&#xff0c;我…

【MySQL】SQL基本知识点DDL(1)

目录 1.SQL分类&#xff1a; 2.DDL-数据库操作 3.DDL-表操作-创建 4.DDL-表操作-查询 5.DDL-表操作-数据类型 6.DDL-表操作-修改 1.SQL分类&#xff1a; 2.DDL-数据库操作 3.DDL-表操作-创建 注意&#xff1a;里面的符号全部要切换为英文状态 4.DDL-表操作-查询 5.DDL…

如何内网穿透,远程访问内网设备

文章目录 0.前言1.准备工作2.内网穿透原理3.配置公网服务器的frp5.配置访问内网主机6.配置win10的远程桌面访问&#xff08;win11类似&#xff09;7.参考资料 0.前言 最近想研究一些新东西&#xff0c;公司的机器不敢乱搞&#xff0c;公司测试的服务器安装软件太多&#xff0c…

【送书福利第六期】Java开发的150多个坑,你踩过几个?(文末送书)

文章目录 做Java开发别掉坑里还不知道 程序员为什么会掉到坑里却不自知&#xff1f;第一是意识不到坑的存在。第二是有些 bug 或问题只在特定情况下暴露。第三是变化不明显的性能问题。 《Java开发坑点解析&#xff1a;从根因分析到最佳实践》Java 开发完美避坑指南结语 &#…

zblog中用户中心-邀请码注册插件的导出功能补充

自己加了一个导出未使用的邀请码功能&#xff0c;可惜我不是入驻作者&#xff0c;没有权限发布&#xff0c;之前被一条大河拒了&#xff0c;他说我抄他代码&#xff0c;不给我过审还冷嘲热讽&#xff0c;我一气之下&#xff0c;就没继续申请了&#xff0c;话说我是专业搞java开…

中国地图(2024版审图号地图)和地图变化说明

2024版shp格式审图号地图预览图&#xff1a; 新版中国地图的变化&#xff08;简述&#xff09; 国土面积的增加&#xff1a;新版中国地图显示&#xff0c;中国的国土面积从960万平方公里增加到1045万平方公里&#xff0c;增加了85万平方公里。 九段线变为十段线&#xff1a;…

RFID工业读写器在危化品储存管理的优势有哪些?

在化学品和危险品储存管理领域&#xff0c;传统的管理方式通常存在一些痛点和挑战&#xff1a;化学品和危险品的管理主要依赖于人工记录和监控&#xff0c;容易出现数据不准确、遗漏、混淆等问题&#xff0c;导致安全隐患和管理困难。化学品和危险品的存储和管理涉及到一系列安…

#04 构建您的第一个神经网络:PyTorch入门指南

文章目录 前言理论基础神经网络层的组成前向传播与反向传播 神经网络设计步骤1&#xff1a;准备数据集步骤2&#xff1a;构建模型步骤3&#xff1a;定义损失函数和优化器步骤4&#xff1a;训练模型步骤5&#xff1a;评估模型结论 前言 在过去的几天里&#xff0c;我们深入了解了…

InLine Chat功能优化对标Github Copilot,CodeGeeX带来更高效、更直观的编程体验!

VSCode中的CodeGeeX 插件上线InLine Chat功能后&#xff0c;收到不少用户的反馈&#xff0c;大家对行内交互编程这一功能非常感兴趣。近期我们针对这个功能再次进行了深度优化&#xff0c;今天详细介绍已经在VSCode插件v2.8.0版本上线的 CodeGeeX InLine Chat功能&#xff0c;以…

C++ 多态的相关问题

目录 1. 第一题 2. 第二题 3. inline 函数可以是虚函数吗 4. 静态成员函数可以是虚函数吗 5. 构造函数可以是虚函数吗 6. 析构函数可以是虚函数吗 7. 拷贝构造和赋值运算符重载可以是虚函数吗 8. 对象访问普通函数快还是访问虚函数快 9. 虚函数表是什么阶段生成的&…