基于QT和C++实现的中国象棋

一,源码

board.h

#ifndef BOARD_H
#define BOARD_H

#include <QWidget>
#include "Stone.h"

class Board : public QWidget
{
    Q_OBJECT
public:


    explicit Board(QWidget *parent = 0);

    bool _bRedTurn; // 红方先走
    int _currentPlayer; // 当前玩家,1为红方,-1为黑方

    Stone _s[32];
    int _r; /* 棋子的半径 */
    int _selectid;


    /* 返回象棋棋盘行列对应的像素坐标 */
    QPoint center(int row, int col);
    QPoint center(int id);
    bool getRowCol(QPoint pt, int& row, int& col);

    void drawStone(QPainter& painter, int id);

    void paintEvent(QPaintEvent *);
    void mouseReleaseEvent(QMouseEvent *);

    bool canMove(int moveid, int row, int col, int killid);
    bool canMoveJIANG(int moveid, int row, int col, int killid);
    bool canMoveSHI(int moveid, int row, int col, int killid);
    bool canMoveXIANG(int moveid, int row, int col, int killid);
    bool canMoveCHE(int moveid, int row, int col, int killid);
    bool canMoveMA(int moveid, int row, int col, int killid);
    bool canMovePAO(int moveid, int row, int col, int killid);
    bool canMoveBING(int moveid, int row, int col, int killid);
    bool isStoneAt(int row, int col);

    void saveGameState();
signals:

public slots:

};

#endif // BOARD_H

Stone.h

#ifndef STONE_H
#define STONE_H

#include <QString>

class Stone
{
public:
    Stone();
    ~Stone();
    int getRow()const
    {
        return _row;
    }
    int getCol()const
    {
        return _col;
    }

    enum TYPE{JIANG, CHE, PAO, MA, BING, SHI, XIANG};

    int _row;
    int _col;
    TYPE _type;


    int _id;
    bool _dead;
    bool _red;


    void init(int id)
    {
        struct {
                int row, col;
                Stone::TYPE type;
            } pos[16] = {
            {0, 0, Stone::CHE},
            {0, 1, Stone::MA},
            {0, 2, Stone::XIANG},
            {0, 3, Stone::SHI},
            {0, 4, Stone::JIANG},
            {0, 5, Stone::SHI},
            {0, 6, Stone::XIANG},
            {0, 7, Stone::MA},
            {0, 8, Stone::CHE},

            {2, 1, Stone::PAO},
            {2, 7, Stone::PAO},
            {3, 0, Stone::BING},
            {3, 2, Stone::BING},
            {3, 4, Stone::BING},
            {3, 6, Stone::BING},
            {3, 8, Stone::BING},
            };

        _id = id;
        _dead = false;
        _red = id<16;

        if(id < 16)
        {
            _row = pos[id].row;
            _col = pos[id].col;
            _type = pos[id].type;
        }
        else
        {
            _row = 9-pos[id-16].row;
            _col = 8-pos[id-16].col;
            _type = pos[id-16].type;
        }

    }

    QString getText()
    {
        switch(this->_type)
            {
            case CHE:
                return "车";
            case MA:
                return "马";
            case PAO:
                return "炮";
            case BING:
                return "兵";
            case JIANG:
                return "将";
            case SHI:
                return "士";
            case XIANG:
                return "相";
            }
            return "错误";
    }
};

#endif // STONE_H

board.cpp

#include "Board.h"
#include <QPainter>
#include <QMouseEvent>
#include <QMessageBox>
#include<QTextStream>
Board::Board(QWidget *parent) :
    QWidget(parent)

{

    for(int i=0; i<32; ++i)
    {
        _s[i].init(i);
    }
    _selectid = -1;
    _bRedTurn = true;
}


void Board::paintEvent(QPaintEvent*)
{
    QPainter painter(this);
    int d = 40;
    _r = d / 2;
    // 画10横线
    for (int i = 1; i <= 10; ++i)
    {
        painter.drawLine(QPoint(d, i * d), QPoint(9 * d, i * d));
    }
    // 画9竖线
    for (int i = 1; i <= 9; ++i)
    {
        if (i == 1 || i == 9)
            painter.drawLine(QPoint(i * d, d), QPoint(i * d, 10 * d));
        else
        {
            painter.drawLine(QPoint(i * d, d), QPoint(i * d, 5 * d));
            painter.drawLine(QPoint(i * d, 6 * d), QPoint(i * d, 10 * d));
        }
    }

    // 九宫格
    painter.drawLine(QPoint(4 * d, 1 * d), QPoint(6 * d, 3 * d));
    painter.drawLine(QPoint(6 * d, 1 * d), QPoint(4 * d, 3 * d));

    painter.drawLine(QPoint(4 * d, 8 * d), QPoint(6 * d, 10 * d));
    painter.drawLine(QPoint(6 * d, 8 * d), QPoint(4 * d, 10 * d));

    // 绘制32个棋子
    for (int i = 0; i < 32; ++i)
    {
        drawStone(painter, i);
    }
}

QPoint Board::center(int row, int col)
{
    QPoint ret;
    ret.rx() = (col + 1) * _r * 2;
    ret.ry() = (row + 1) * _r * 2;
    return ret;
}

QPoint Board::center(int id)
{
    return center(_s[id]._row, _s[id]._col);
}

void Board::drawStone(QPainter& painter, int id)
{
    if (_s[id]._dead)
        return;

    QPoint c = center(id);
    QRect rect = QRect(c.x() - _r, c.y() - _r, _r * 2, _r * 2);

    if (id == _selectid)
        painter.setBrush(QBrush(Qt::gray));
    else
        painter.setBrush(QBrush(Qt::yellow));

    painter.setPen(Qt::black);

    painter.drawEllipse(center(id), _r, _r);

    if (_s[id]._red)
        painter.setPen(Qt::red);
    painter.setFont(QFont("system", _r, 700));

    painter.drawText(rect, _s[id].getText(), QTextOption(Qt::AlignCenter));
}

bool Board::getRowCol(QPoint pt, int& row, int& col)
{
    for (row = 0; row <= 9; row++)
    {
        for (col = 0; col <= 8; col++)
        {
            QPoint c = center(row, col);
            int dx = c.x() - pt.x();
            int dy = c.y() - pt.y();
            int dist = dx * dx + dy * dy;
            if (dist < _r * _r)
                return true;
        }
    }
    return false;
}

bool Board::isStoneAt(int row, int col)
{
    for (int i = 0; i < 32; ++i)
    {
        if (_s[i]._row == row && _s[i]._col == col && !_s[i]._dead)
        {
            return true;
        }
    }
    return false;
}

bool Board::canMoveXIANG(int moveid, int row, int col, int killid)
{

    int fromRow = _s[moveid]._row;
    int fromCol = _s[moveid]._col;


    int toRow = row;
    int toCol = col;


    if (_s[moveid]._red && toRow > 4) return false; // 红方象不能过河
    if (!_s[moveid]._red && toRow < 5) return false; // 黑方象不能过河


    int rowDir = toRow - fromRow;
    int colDir = toCol - fromCol;


    if (abs(rowDir) != 2 || abs(colDir) != 2) return false;


    int checkRow = fromRow + rowDir / 2;
    int checkCol = fromCol + colDir / 2;
    if (isStoneAt(checkRow, checkCol)) return false; // 路径上有棋子


    if (killid != -1 && _s[killid]._red == _s[moveid]._red) return false;


    return true;
}





bool Board::canMoveCHE(int moveid, int row, int col, int killid)
{

      int fromRow = _s[moveid]._row;
      int fromCol = _s[moveid]._col;


      int toRow = row;
      int toCol = col;

      if (fromRow != toRow && fromCol != toCol) return false;

      // 计算移动的方向
      int rowDir = fromRow < toRow ? 1 : (fromRow > toRow ? -1 : 0);
      int colDir = fromCol < toCol ? 1 : (fromCol > toCol ? -1 : 0);


      int checkRow = fromRow + rowDir;
      int checkCol = fromCol + colDir;
      while (checkRow != toRow || checkCol != toCol)
      {
          if (isStoneAt(checkRow, checkCol)) return false; // 路径上有棋子
          checkRow += rowDir;
          checkCol += colDir;
      }

      // 如果有棋子被吃,检查是否是对方的棋子
      if (killid != -1 && _s[killid]._red == _s[moveid]._red) return false;

      return true;
}
bool Board::canMoveMA(int moveid, int row, int col, int killid)
{

    int fromRow = _s[moveid]._row;
    int fromCol = _s[moveid]._col;


    int toRow = row;
    int toCol = col;


    int rowDiff = toRow - fromRow;
    int colDiff = toCol - fromCol;

    // 检查移动是否是“日”字形状
    if ((abs(rowDiff) == 2 && abs(colDiff) == 1) || (abs(rowDiff) == 1 && abs(colDiff) == 2))
    {
        // 检查是否有棋子蹩马腿
       int legRow = fromRow + (rowDiff > 0 ? (rowDiff / 2) : -(abs(rowDiff) / 2));
       int legCol = fromCol + (colDiff > 0 ? (colDiff / 2) : -(abs(colDiff) / 2));
       if (!isStoneAt(legRow, legCol)) // 如果没有棋子蹩马腿
       {
            // 如果有棋子被吃,检查是否是对方的棋子
            if (killid != -1 && _s[killid]._red != _s[moveid]._red)
            {
                return true; // 可以吃掉对方的棋子
            }
            else if (killid == -1)
            {
                return true;
            }
       }
    }

    return false; // 不满足移动条件
}

bool Board::canMovePAO(int moveid, int row, int col, int killid)
{

    int fromRow = _s[moveid]._row;
    int fromCol = _s[moveid]._col;


    int toRow = row;
    int toCol = col;


    if (fromRow != toRow && fromCol != toCol) return false;


    int rowDir = fromRow < toRow ? 1 : (fromRow > toRow ? -1 : 0);
    int colDir = fromCol < toCol ? 1 : (fromCol > toCol ? -1 : 0);

    // 检查移动路径上是否有其他棋子
    int checkRow = fromRow + rowDir;
    int checkCol = fromCol + colDir;
    int jumpCount = 0;
    while (checkRow != toRow || checkCol != toCol)
    {
        if (isStoneAt(checkRow, checkCol)) jumpCount++;
        checkRow += rowDir;
        checkCol += colDir;
    }


    if (killid != -1)
    {
        if (_s[killid]._red == _s[moveid]._red) return false;
        if (jumpCount != 1) return false; // 必须跳过一个棋子
    }
    else
    {
        if (jumpCount != 0) return false; // 如果没有吃子,不能跳过棋子
    }

    return true;
}

bool Board::canMoveBING(int moveid, int row, int col, int killid)
{

        int fromRow = _s[moveid]._row;
        int fromCol = _s[moveid]._col;


        int toRow = row;
        int toCol = col;


        if (fromRow != toRow && fromCol != toCol) return false;


        int rowDiff = toRow - fromRow;
        int colDiff = toCol - fromCol;

        // 检查是否过河
        if (_s[moveid]._red && toRow >= 5) // 红方兵过河
        {
            if (abs(rowDiff) != 1) return false; // 过河后只能直走一格
        }
        else if (!_s[moveid]._red && toRow <= 4) // 黑方兵过河
        {
            if (abs(rowDiff) != 1) return false; // 过河后只能直走一格
        }
        else // 未过河
        {
            if (abs(rowDiff) != 1) return false; // 未过河只能直走一格
        }


        if (killid != -1 && _s[killid]._red == _s[moveid]._red) return false;


}



bool Board::canMoveJIANG(int moveid, int row, int col, int killid)
{

    if (_s[moveid]._red)
    {
        if (row > 2)return false;
    }
    else
    {
        if (row < 7)return false;
    }

    if (col < 3) return false;
    if (col > 5) return false;

    int rowDir = _s[moveid]._row - row;
    int colDir = _s[moveid]._col - col;
    int d = abs(rowDir) * 10 + abs(colDir);
    if (d == 1 || d == 10)
        return true;

    return false;
}

bool Board::canMoveSHI(int moveid, int row, int col, int killid)
{
    if (_s[moveid]._red)
    {
        if (row > 2)return false;
    }
    else
    {
        if (row < 7)return false;
    }

    if (col < 3) return false;
    if (col > 5) return false;

    int rowDir = _s[moveid]._row - row;
    int colDir = _s[moveid]._col - col;
    int d = abs(rowDir) * 10 + abs(colDir);
    if (d == 11)
        return true;

    return false;

}

bool Board::canMove(int moveid, int row, int col, int killid)
{
    // 如果移动的棋子和被吃的棋子颜色相同,则切换选择
    if (_s[moveid]._red == _s[killid]._red && killid != -1)
    {
        _selectid = killid;
        update();
        return false;
    }

    // 判断是否是主将被吃
    if (_s[killid]._type == Stone::JIANG && _s[killid]._red != _s[moveid]._red)
    {
        QString winner = _s[moveid]._red ? "红方" : "黑方";
        QMessageBox::information(this, "胜利", "恭喜," + winner + "赢了!");
        return false; // 停止游戏
    }

    // 根据棋子类型调用相应的移动规则函数
    switch (_s[moveid]._type)
    {
    case Stone::JIANG:
        return canMoveJIANG(moveid, row, col, killid);
        break;
    case Stone::SHI:
        return canMoveSHI(moveid, row, col, killid);
        break;
    case Stone::XIANG:
        return canMoveXIANG(moveid, row, col, killid);
        break;
    case Stone::CHE:
        return canMoveCHE(moveid, row, col, killid);
        break;
    case Stone::MA:
        return canMoveMA(moveid, row, col, killid);
        break;
    case Stone::PAO:
        return canMovePAO(moveid, row, col, killid);
        break;
    case Stone::BING:
        return canMoveBING(moveid, row, col, killid);
        break;
    default:
        return false;
    }

    return true;
}

void Board::saveGameState() {
    // 假设你有一个数组来存储棋盘的状态,比如棋子的类型和位置
    QString fileName = "game_state.txt";
    QFile file("game.txt");
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::critical(this, "保存棋局状态", "无法打开文件进行保存!");
        return;
    }

    QTextStream out(&file);
    for (int i = 0; i < 32; ++i) {
        out << _s[i].getText() << "," << _s[i].getRow() << "," << _s[i].getCol() << "\n";
    }

    file.close();
    QMessageBox::information(this, "保存棋局状态", "棋局状态已成功保存!");
}

void Board::mouseReleaseEvent(QMouseEvent *ev)
{
    QPoint pt = ev->pos();
    // 将pt转化成象棋的行列值
    // 判断这个行列值上面有没有棋子
    int row, col;
    bool bRet = getRowCol(pt, row, col);
    if(bRet == false) // 点到棋盘外
        return;

    int i;
    int clickid = -1;
    for(i=0;i<32;++i)
    {
        if(_s[i]._row == row && _s[i]._col == col && _s[i]._dead== false)
        {
            clickid = i;
            break;
        }
    }

    if(_selectid == -1)
    {
        if(clickid != -1)
        {
            if(_bRedTurn == _s[clickid]._red)
            {
                _selectid = clickid;
                update();
            }
        }
    }
    else
    {
        if(canMove(_selectid, row, col, clickid))
        {
            /*走棋*/
            _s[_selectid]._row = row;
            _s[_selectid]._col = col;
            if(clickid != -1)
            {
                _s[clickid]._dead = true;
            }
            _selectid = -1;
            _bRedTurn = !_bRedTurn;
            update();
        }
        else
        {
            // 不能移动棋子,给出提示
            QMessageBox::information(this, "提示", "该棋子不能移动到指定的位置!");
            // 如果点击的是另一个相同颜色的棋子,则切换选择
            if(clickid != -1 && _s[clickid]._red == _s[_selectid]._red)
            {
                _selectid = clickid;
                update();
            }
            else
            {
                _selectid = -1; // 取消选择
                update();
            }
        }
    }

}


main.cpp


#include <QApplication>
#include "Board.h"

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);

    Board board;
    board.show();
    return app.exec();
}

Stone.cpp

#include "Stone.h"

Stone::Stone()
{

}

Stone::~Stone()
{

}

二,具体分析

棋盘的绘制,棋子的绘制,以及选中棋子进行一系列操作都使用QT的库函数,所以在创建

board类时我们需要选择基于Qwidge作为父类

board.cpp是实现绘制棋盘,棋子,以及棋子运行逻辑的文件

Stone.h包含了棋子类

三,运行展示

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

破局消费供应链,企业费用管理如何应对变与不变?

供应链管理在过去一直被局限在生产与产品供应领域&#xff0c;更多被理解为生产及流通过程中&#xff0c;涉及将产品或服务提供给最终用户活动的上游与下游企业所形成的网链结构&#xff0c;即将产品从商家送到消费者手中整个链条。因为直接对企业利润产生重大影响&#xff0c;…

大模型精调:实现高效迁移学习的艺术

在人工智能领域&#xff0c;大型预训练模型&#xff08;以下简称“大模型”&#xff09;已经取得了令人瞩目的成果。这些模型通过在海量数据上进行预训练&#xff0c;能够捕捉到丰富的特征信息&#xff0c;为各种下游任务提供强大的支持。然而&#xff0c;如何将这些大模型应用…

2024 新项目还用java8的人到底是怎么想的,你又怎么看待这些人?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「java的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 对于2024年新项目仍选择使…

近万条英文智力问答题库ACCESS\EXCEL数据库

今天弄到了一份很不错的英文版智力问答题库&#xff0c;属于那种我很满意的数据库&#xff0c;原因有&#xff1a;1.记录数将近1万条达到库的基础&#xff1b;2.分类表信息包含大小分类非常详细&#xff1b;3.题目内容包含六七百条含有图片的题&#xff1b;4.题库除了选择题外还…

Web应用安全测试-综合利用(三)

Web应用安全测试-综合利用&#xff08;三&#xff09; XML注入 漏洞描述 可扩展标记语言 (Extensible Markup Language, XML) &#xff0c;用于标记电子文件使其具有结构性的标记语言&#xff0c;可以用来标记数据、定义数据类型&#xff0c;是一种允许用户对自己的标记语言进…

计算机专业毕设-校园二手交易平台

1 项目介绍 基于SpringBoot的校园二手交易平台&#xff1a;前端Freemarker&#xff0c;后端 SpringBoot、Jpa&#xff0c;系统用户分为两类&#xff0c;管理员、学生&#xff0c;具体功能如下&#xff1a; 管理员&#xff1a; 基本功能&#xff1a;登录、修改个人信息、修改…

2024年: 您准备好进行持续绩效管理了吗?

在过去几年中&#xff0c;”人力资源 “这个既最重要又最讨厌的过程受到了关注。每个人都在跃跃欲试&#xff0c;从静态的、以快照为基础的年度回顾转向频繁的双向对话。这是一个正确的时机&#xff1b;在当今复杂的业务和人际关系中&#xff0c;我们需要进行上下左右的沟通&am…

安装ps提示vcruntime140.dll丢失的多种有效的解决方法分享

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到vcruntime140.dll”。这个错误通常出现在运行某些程序时&#xff0c;特别是ps这样的图像处理软件。那么&#xff0c;如何解决这个错误呢&#xff1f;小编将为您详细介绍打开提示ps找…

Django期末重点

思维导图 一、Djanog框架基础 MVT设计模式&#xff08;model模型【操作数据库】、template模板【页面展示】、view视图【处理请求和调用模型模板】&#xff09; 二、Django项目框架搭建 创建项目骨架 django-admin startproject 项目名启动服务 &#xff08;1&#xff09;p…

视频汇聚安防综合管理平台EasyCVR支持GA/T 1400视图库标准及设备接入配置

一、概述 视频汇聚安防综合管理平台EasyCVR视频监控系统已经与公安部GA/T 1400视图库标准协议实现了对接&#xff0c;即《公安视频图像信息应用系统》。 安防监控系统EasyCVR支持采用GA/T 1400进行对接&#xff0c;可实现人脸数据使用的标准化、合规化。其采用统一接口对接雪…

多模态融合算法分析

多模态融合算法分析 多模态论文多模态融合早期融合晚期融合混合融合模型级融合 对比分析早期融合&#xff08;Feature-level Fusion&#xff09;晚期融合&#xff08;Decision-level Fusion&#xff09;混合融合&#xff08;Hybrid Fusion&#xff09;ML-LSTM&#xff08;Multi…

【小白专用 已验证24.6.18】C# SqlSugar操作MySQL数据库实现增删改查

【小白专用24.6.18】C# SqlSugar&#xff1a;连接数据库实现简单的&#xff0c;增、删、改、查-CSDN博客 SqlSugar .Net ORM 5.X 官网 、文档、教程 - SqlSugar 5x - .NET果糖网 SqlSugar项目创建 通过NuGet包管理器搜索SqlSugar&#xff08;MySql还要安装MySql.Data、Newton…

C语言入门系列:数据类型之字符

文章目录 字符类型声明与初始化字符与单引号字符的内部表示字符类型的范围整数与字符的互换性字符变量的数学运算转义字符八进制与十六进制表示字符 字符类型声明与初始化 在C语言中&#xff0c;使用char关键字来声明字符类型变量。例如&#xff1a; char c B; // 声明并初始…

MongoDB和AI 赋能行业应用:零售

欢迎阅读“MongoDB 和 AI 赋能行业应用”系列的第三篇。 本系列重点介绍 AI 应用于不同行业的关键用例&#xff0c;涵盖制造业和汽车行业、金融服务、零售、电信和媒体、保险以及医疗保健行业。 利用生成式 AI 技术&#xff08;Gen AI&#xff09;&#xff0c;零售商可以创造…

【一】【网络使用小知识】使用aria2软件结合Windows PowerShell命令行快速下载文件

下载aria2软件 点击进入网址,aria2下载网址. 下载windows版本. 通过Windows PowerShell命令行使用aria2软件下载文件 通用下载文件命令行代码 aria2软件完整路径 -x 16 -s 32 -d 下载目录(文件夹) -o 文件名 下载链接路径示例,用aria2下载qq 找到aria2应用的直接地址,结合…

Python调用外部系统命令

利用Python调用外部系统命令的方法可以提高编码效率。调用外部系统命令完成后可以通过获取命令执行返回结果码、命令执行的输出结果进行进一步的处理。本文主要描述Python常见的调用外部系统命令的方法&#xff0c;包括os.system()、os.popen()、subprocess.Popen()等。 本文分…

EdgeOne 边缘函数—如何动态改写 M3U8 媒体文件

目前&#xff0c;各大主流厂商都推出了自己的边缘 Serverless 服务&#xff0c;如 CloudFlare Workers、 Vercel EdgeRuntime 等&#xff1b;腾讯云 EdgeOne 边缘函数提供了部署在边缘节点的 Serverless 代码执行环境&#xff0c;只需编写业务函数代码并设置触发规则&#xff0…

网上书店商城项目采用SpringBoot+Vue前后端分离技术(商家端、移动端、PC端)

项目简介&#xff1a; 本项目基于SpringBootVue2技术设计并实现了一个网上书店商城系统。系统的数据采用MYSQL数据库进行存储&#xff0c;开发工具选择为IDEA或VSCode工具。本商城系统具有前台购物功能和后台相应的信息管理。前台用户登陆注册后可以进行商品浏览、添加购物车、…

【代码随想录】【算法训练营】【第42天】 [1049]最后一块石头的重量II [494]目标和 [474]一和零

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 42&#xff0c;周二&#xff0c;坚持一下~ 题目详情 [1049] 最后一块石头的重量II 题目描述 1049 最后一块石头的重量II 解题思路 前提&#xff1a;最多只会剩下一块 石头&#xff0c;求此…

SQL Server入门-SSMS简单使用(2008R2版)-2

环境&#xff1a; win10&#xff0c;SQL Server 2008 R2 参考&#xff1a; SQL Server 管理套件&#xff08;SSMS&#xff09;_w3cschool https://www.w3cschool.cn/sqlserver/sqlserver-oe8928ks.html SQL Server存储过程_w3cschool https://www.w3cschool.cn/sqlserver/sql…