【从0学习Solidity】34. ERC721

news2024/12/28 5:58:15

【从0学习Solidity】34. ERC721

在这里插入图片描述

  • 博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。
  • 关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和创造!
  • 本文收录于 不写代码没饭吃 的学习汇报系列,大家有兴趣的可以看一看。
  • 欢迎访问我们的微信公众号:不写代码没饭吃,获取更多精彩内容、实用技巧、行业资讯等。您关注的是我们前进的动力!

BTCETH这类代币都属于同质化代币,矿工挖出的第1BTC与第10000BTC并没有不同,是等价的。但世界中很多物品是不同质的,其中包括房产、古董、虚拟艺术品等等,这类物品无法用同质化代币抽象。因此,以太坊EIP721提出了ERC721标准,来抽象非同质化的物品。这一讲,我们将介绍ERC721标准,并基于它发行一款NFT

EIP与ERC

这里有一个点需要理解,本节标题是ERC721,这里又提到了EIP721,这两个是什么关系呢?

EIP全称 Ethereum Improvement Proposals(以太坊改进建议), 是以太坊开发者社区提出的改进建议, 是一系列以编号排定的文件, 类似互联网上IETF的RFC。

EIP可以是 Ethereum 生态中任意领域的改进, 比如新特性、ERC、协议改进、编程工具等等。

ERC全称 Ethereum Request For Comment (以太坊意见征求稿), 用以记录以太坊上应用级的各种开发标准和协议。如典型的Token标准(ERC20, ERC721)、名字注册(ERC26, ERC13), URI范式(ERC67), Library/Package格式(EIP82), 钱包格式(EIP75,EIP85)。

ERC协议标准是影响以太坊发展的重要因素, 像ERC20, ERC223, ERC721, ERC777等, 都是对以太坊生态产生了很大影响。

所以最终结论:EIP包含ERC

在这一节学习完成后,才能明白为什么上来讲ERC165而不是ERC721,想要看结论可直接移动到最下面

ERC165

通过ERC165标准,智能合约可以声明它支持的接口,供其他合约检查。简单的说,ERC165就是检查一个智能合约是不是支持了ERC721ERC1155的接口。

IERC165接口合约只声明了一个supportsInterface函数,输入要查询的interfaceId接口id,若合约实现了该接口id,则返回true

interface IERC165 {
    /**
     * @dev 如果合约实现了查询的`interfaceId`,则返回true
     * 规则详见:https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     *
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

我们可以看下ERC721是如何实现supportsInterface()函数的:

    function supportsInterface(bytes4 interfaceId) external pure override returns (bool)
    {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC165).interfaceId;
    }

当查询的是IERC721IERC165的接口id时,返回true;反之返回false

IERC721

IERC721ERC721标准的接口合约,规定了ERC721要实现的基本函数。它利用tokenId来表示特定的非同质化代币,授权或转账都要明确tokenId;而ERC20只需要明确转账的数额即可。

/**
 * @dev ERC721标准接口.
 */
interface IERC721 is IERC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function balanceOf(address owner) external view returns (uint256 balance);

    function ownerOf(uint256 tokenId) external view returns (address owner);

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    function approve(address to, uint256 tokenId) external;

    function setApprovalForAll(address operator, bool _approved) external;

    function getApproved(uint256 tokenId) external view returns (address operator);

    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

IERC721事件

IERC721包含3个事件,其中TransferApproval事件在ERC20中也有。

  • Transfer事件:在转账时被释放,记录代币的发出地址from,接收地址totokenid
  • Approval事件:在授权时释放,记录授权地址owner,被授权地址approvedtokenid
  • ApprovalForAll事件:在批量授权时释放,记录批量授权的发出地址owner,被授权地址operator和授权与否的approved

IERC721函数

  • balanceOf:返回某地址的NFT持有量balance
  • ownerOf:返回某tokenId的主人owner
  • transferFrom:普通转账,参数为转出地址from,接收地址totokenId
  • safeTransferFrom:安全转账(如果接收方是合约地址,会要求实现ERC721Receiver接口)。参数为转出地址from,接收地址totokenId
  • approve:授权另一个地址使用你的NFT。参数为被授权地址approvetokenId
  • getApproved:查询tokenId被批准给了哪个地址。
  • setApprovalForAll:将自己持有的该系列NFT批量授权给某个地址operator
  • isApprovedForAll:查询某地址的NFT是否批量授权给了另一个operator地址。
  • safeTransferFrom:安全转账的重载函数,参数里面包含了data

IERC721Receiver

如果一个合约没有实现ERC721的相关函数,转入的NFT就进了黑洞,永远转不出来了。为了防止误转账,ERC721实现了safeTransferFrom()安全转账函数,目标合约必须实现了IERC721Receiver接口才能接收ERC721代币,不然会revertIERC721Receiver接口只包含一个onERC721Received()函数。

// ERC721接收者接口:合约必须实现这个接口来通过安全转账接收ERC721
interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

我们看下ERC721利用_checkOnERC721Received来确保目标合约实现了onERC721Received()函数(返回onERC721Receivedselector):

    function _checkOnERC721Received(
        address from,
        address to,
        uint tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            return
                IERC721Receiver(to).onERC721Received(
                    msg.sender,
                    from,
                    tokenId,
                    _data
                ) == IERC721Receiver.onERC721Received.selector;
        } else {
            return true;
        }
    }

IERC721Metadata

IERC721MetadataERC721的拓展接口,实现了3个查询metadata元数据的常用函数:

  • name():返回代币名称。
  • symbol():返回代币代号。
  • tokenURI():通过tokenId查询metadata的链接urlERC721特有的函数。
interface IERC721Metadata is IERC721 {
    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function tokenURI(uint256 tokenId) external view returns (string memory);
}

ERC721主合约

ERC721主合约实现了IERC721IERC165IERC721Metadata定义的所有功能,包含4个状态变量和17个函数。实现都比较简单,每个函数的功能见代码注释:

// SPDX-License-Identifier: MIT
// by 0xAA
pragma solidity ^0.8.4;

import "./IERC165.sol";
import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./IERC721Metadata.sol";
import "./Address.sol";
import "./String.sol";

contract ERC721 is IERC721, IERC721Metadata{
    using Address for address; // 使用Address库,用isContract来判断地址是否为合约
    using Strings for uint256; // 使用String库,

    // Token名称
    string public override name;
    // Token代号
    string public override symbol;
    // tokenId 到 owner address 的持有人映射
    mapping(uint => address) private _owners;
    // address 到 持仓数量 的持仓量映射
    mapping(address => uint) private _balances;
    // tokenID 到 授权地址 的授权映射
    mapping(uint => address) private _tokenApprovals;
    //  owner地址。到operator地址 的批量授权映射
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    /**
     * 构造函数,初始化`name` 和`symbol` .
     */
    constructor(string memory name_, string memory symbol_) {
        name = name_;
        symbol = symbol_;
    }

    // 实现IERC165接口supportsInterface
    function supportsInterface(bytes4 interfaceId)
        external
        pure
        override
        returns (bool)
    {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC165).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId;
    }

    // 实现IERC721的balanceOf,利用_balances变量查询owner地址的balance。
    function balanceOf(address owner) external view override returns (uint) {
        require(owner != address(0), "owner = zero address");
        return _balances[owner];
    }

    // 实现IERC721的ownerOf,利用_owners变量查询tokenId的owner。
    function ownerOf(uint tokenId) public view override returns (address owner) {
        owner = _owners[tokenId];
        require(owner != address(0), "token doesn't exist");
    }

    // 实现IERC721的isApprovedForAll,利用_operatorApprovals变量查询owner地址是否将所持NFT批量授权给了operator地址。
    function isApprovedForAll(address owner, address operator)
        external
        view
        override
        returns (bool)
    {
        return _operatorApprovals[owner][operator];
    }

    // 实现IERC721的setApprovalForAll,将持有代币全部授权给operator地址。调用_setApprovalForAll函数。
    function setApprovalForAll(address operator, bool approved) external override {
        _operatorApprovals[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    // 实现IERC721的getApproved,利用_tokenApprovals变量查询tokenId的授权地址。
    function getApproved(uint tokenId) external view override returns (address) {
        require(_owners[tokenId] != address(0), "token doesn't exist");
        return _tokenApprovals[tokenId];
    }
     
    // 授权函数。通过调整_tokenApprovals来,授权 to 地址操作 tokenId,同时释放Approval事件。
    function _approve(
        address owner,
        address to,
        uint tokenId
    ) private {
        _tokenApprovals[tokenId] = to;
        emit Approval(owner, to, tokenId);
    }

    // 实现IERC721的approve,将tokenId授权给 to 地址。条件:to不是owner,且msg.sender是owner或授权地址。调用_approve函数。
    function approve(address to, uint tokenId) external override {
        address owner = _owners[tokenId];
        require(
            msg.sender == owner || _operatorApprovals[owner][msg.sender],
            "not owner nor approved for all"
        );
        _approve(owner, to, tokenId);
    }

    // 查询 spender地址是否可以使用tokenId(他是owner或被授权地址)。
    function _isApprovedOrOwner(
        address owner,
        address spender,
        uint tokenId
    ) private view returns (bool) {
        return (spender == owner ||
            _tokenApprovals[tokenId] == spender ||
            _operatorApprovals[owner][spender]);
    }

    /*
     * 转账函数。通过调整_balances和_owner变量将 tokenId 从 from 转账给 to,同时释放Transfer事件。
     * 条件:
     * 1. tokenId 被 from 拥有
     * 2. to 不是0地址
     */
    function _transfer(
        address owner,
        address from,
        address to,
        uint tokenId
    ) private {
        require(from == owner, "not owner");
        require(to != address(0), "transfer to the zero address");

        _approve(owner, address(0), tokenId);

        _balances[from] -= 1;
        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);
    }
    
    // 实现IERC721的transferFrom,非安全转账,不建议使用。调用_transfer函数
    function transferFrom(
        address from,
        address to,
        uint tokenId
    ) external override {
        address owner = ownerOf(tokenId);
        require(
            _isApprovedOrOwner(owner, msg.sender, tokenId),
            "not owner nor approved"
        );
        _transfer(owner, from, to, tokenId);
    }

    /**
     * 安全转账,安全地将 tokenId 代币从 from 转移到 to,会检查合约接收者是否了解 ERC721 协议,以防止代币被永久锁定。调用了_transfer函数和_checkOnERC721Received函数。条件:
     * from 不能是0地址.
     * to 不能是0地址.
     * tokenId 代币必须存在,并且被 from拥有.
     * 如果 to 是智能合约, 他必须支持 IERC721Receiver-onERC721Received.
     */
    function _safeTransfer(
        address owner,
        address from,
        address to,
        uint tokenId,
        bytes memory _data
    ) private {
        _transfer(owner, from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, _data), "not ERC721Receiver");
    }

    /**
     * 实现IERC721的safeTransferFrom,安全转账,调用了_safeTransfer函数。
     */
    function safeTransferFrom(
        address from,
        address to,
        uint tokenId,
        bytes memory _data
    ) public override {
        address owner = ownerOf(tokenId);
        require(
            _isApprovedOrOwner(owner, msg.sender, tokenId),
            "not owner nor approved"
        );
        _safeTransfer(owner, from, to, tokenId, _data);
    }

    // safeTransferFrom重载函数
    function safeTransferFrom(
        address from,
        address to,
        uint tokenId
    ) external override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /** 
     * 铸造函数。通过调整_balances和_owners变量来铸造tokenId并转账给 to,同时释放Transfer事件。铸造函数。通过调整_balances和_owners变量来铸造tokenId并转账给 to,同时释放Transfer事件。
     * 这个mint函数所有人都能调用,实际使用需要开发人员重写,加上一些条件。
     * 条件:
     * 1. tokenId尚不存在。
     * 2. to不是0地址.
     */
    function _mint(address to, uint tokenId) internal virtual {
        require(to != address(0), "mint to zero address");
        require(_owners[tokenId] == address(0), "token already minted");

        _balances[to] += 1;
        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);
    }

    // 销毁函数,通过调整_balances和_owners变量来销毁tokenId,同时释放Transfer事件。条件:tokenId存在。
    function _burn(uint tokenId) internal virtual {
        address owner = ownerOf(tokenId);
        require(msg.sender == owner, "not owner of token");

        _approve(owner, address(0), tokenId);

        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);
    }

    // _checkOnERC721Received:函数,用于在 to 为合约的时候调用IERC721Receiver-onERC721Received, 以防 tokenId 被不小心转入黑洞。
    function _checkOnERC721Received(
        address from,
        address to,
        uint tokenId,
        bytes memory _data
    ) private returns (bool) {
        if (to.isContract()) {
            return
                IERC721Receiver(to).onERC721Received(
                    msg.sender,
                    from,
                    tokenId,
                    _data
                ) == IERC721Receiver.onERC721Received.selector;
        } else {
            return true;
        }
    }

    /**
     * 实现IERC721Metadata的tokenURI函数,查询metadata。
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_owners[tokenId] != address(0), "Token Not Exist");

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

    /**
     * 计算{tokenURI}的BaseURI,tokenURI就是把baseURI和tokenId拼接在一起,需要开发重写。
     * BAYC的baseURI为ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ 
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }
}

写一个免费铸造的APE

我们来利用ERC721来写一个免费铸造的WTF APE,总量设置为10000,只需要重写一下mint()baseURI()函数即可。由于baseURI()设置的和BAYC一样,元数据会直接获取无聊猿的,类似RRBAYC:

// SPDX-License-Identifier: MIT
// by 0xAA
pragma solidity ^0.8.4;

import "./ERC721.sol";

contract WTFApe is ERC721{
    uint public MAX_APES = 10000; // 总量

    // 构造函数
    constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){
    }

    //BAYC的baseURI为ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/ 
    function _baseURI() internal pure override returns (string memory) {
        return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
    }
    
    // 铸造函数
    function mint(address to, uint tokenId) external {
        require(tokenId >= 0 && tokenId < MAX_APES, "tokenId out of range");
        _mint(to, tokenId);
    }
}

发行ERC721NFT

有了ERC721标准后,在ETH链上发行NFT变得非常简单。现在,我们发行属于我们的NFT。

Remix上编译好ERC721合约和WTFApe合约(按照顺序),在部署栏点击下按钮,输入构造函数的参数,name_symbol_都设为WTF,然后点击transact键进行部署。

34-1.png
34-2.png

这样,我们就创建好了WTFNFT。我们需要运行mint()函数来给自己铸造一些代币。在mint函数那一栏点开右侧的下按钮输入账户地址,和tokenid,并点击mint按钮,为自己铸造0WTFNFT。

可以点开右侧的Debug按钮,具体查看下面的logs。

里面包含四个关键信息:

  • 事件Transfer
  • 铸造地址0x0000000000000000000000000000000000000000
  • 接收地址0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
  • tokenid0

34-3.png

我们利用balanceOf()函数来查询账户余额。输入我们当前的账户,可以看到有一个NFT,铸造成功。

账户信息如图左侧,右侧标注为函数执行的具体信息。

34-4.png

我们也可以利用ownerOf()函数来查询NFT属于哪个账户。输入tokenid,可以我们的地址,查询无误。

34-5.png

ERC165与ERC721详解

上面说到,为了防止NFT被转到一个没有能力操作NFT的合约中去,目标必须正确实现ERC721TokenReceiver接口:

interface ERC721TokenReceiver {
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
}

拓展到编程语言的世界中去,无论是Java的interface,还是Rust的Trait(当然solidity中和trait更像的是library),只要是和接口沾边的,都在透露着一种这样的意味:接口是某些行为的集合(在solidity中更甚,接口完全等价于函数选择器的集合),某个类型只要实现了某个接口,就表明该类型拥有这样的一种功能。因此,只要某个contract类型实现了上述的ERC721TokenReceiver接口(更具体而言就是实现了onERC721Received这个函数),该contract类型就对外表明了自己拥有管理NFT的能力。当然操作NFT的逻辑被实现在该合约其他的函数中。
ERC721标准在执行safeTransferFrom的时候会检查目标合约是否实现了onERC721Received函数,这是一种利用ERC165思想进行的操作。
那究竟什么是ERC165呢?
ERC165是一种对外表明自己实现了哪些接口的技术标准。就像上面所说的,实现了一个接口就表明合约拥有种特殊能力。有一些合约与其他合约交互时,期望目标合约拥有某些功能,那么合约之间就能够通过ERC165标准对对方进行查询以检查对方是否拥有相应的能力。
以ERC721合约为例,当外部对某个合约进行检查其是否是ERC721时,怎么做? 。按照这个说法,检查步骤应该是首先检查该合约是否实现了ERC165, 再检查该合约实现的其他特定接口。此时该特定接口是IERC721. IERC721的是ERC721的基本接口(为什么说基本,是因为还有其他的诸如ERC721Metadata ERC721Enumerable 这样的拓展):

/// 注意这个**0x80ac58cd**
///  **⚠⚠⚠ Note: the ERC-165 identifier for this interface is 0x80ac58cd. ⚠⚠⚠**
interface ERC721 /* is ERC165 */ {
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    function balanceOf(address _owner) external view returns (uint256);

    function ownerOf(uint256 _tokenId) external view returns (address);

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    function approve(address _approved, uint256 _tokenId) external payable;

    function setApprovalForAll(address _operator, bool _approved) external;

    function getApproved(uint256 _tokenId) external view returns (address);

    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

0x80ac58cd=
bytes4(keccak256(ERC721.Transfer.selector) ^ keccak256(ERC721.Approval.selector) ^ ··· ^keccak256(ERC721.isApprovedForAll.selector)),这是ERC165规定的计算方式。

那么,类似的,能够计算出ERC165本身的接口(它的接口里只有一个
function supportsInterface(bytes4 interfaceID) external view returns (bool); 函数,对其进行bytes4(keccak256(supportsInterface.selector)) 得到0x01ffc9a7。此外,ERC721还定义了一些拓展接口,比如ERC721Metadata ,长这样:

///  Note: the ERC-165 identifier for this interface is 0x5b5e139f.
interface ERC721Metadata /* is ERC721 */ {
    function name() external view returns (string _name);
    function symbol() external view returns (string _symbol);
    function tokenURI(uint256 _tokenId) external view returns (string); // 这个很重要,前端展示的小图片的链接都是这个函数返回的
}

这个0x5b5e139f 的计算就是:

IERC721Metadata.name.selector ^ IERC721Metadata.symbol.selector ^ IERC721Metadata.tokenURI.selector

solamte实现的ERC721.sol是怎么完成这些ERC165要求的特性的呢?

function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
}

没错就这么简单。当外界按照link1 的步骤去做检查的时候,如果外界想检查这个合约是否实现了165,好说,就是supportsInterface函数在入参是0x01ffc9a7时必须返回true,在入参是0xffffffff时,返回值必须是false。上述实现完美达成要求。

当外界想检查这个合约是否是ERC721的时候,好说,入参是0x80ac58cd 的时候表明外界想做这个检查。返回true。

当外界想检查这个合约是否实现ERC721的拓展ERC721Metadata接口时,入参是0x5b5e139f。好说,返回了true。

并且由于该函数是virtual的。因此该合约的使用者可以继承该合约,然后继续实现ERC721Enumerable 接口。实现完里面的什么totalSupply 啊之类的函数之后,把继承的supportsInterface重实现为

function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f || // ERC165 Interface ID for ERC721Metadata
            interfaceId == 0x780e9d63;   // ERC165 Interface ID for ERC721Enumerable
}

优雅,简洁,可拓展性拉满。

总结

这一讲,我介绍了ERC721标准、接口及其实现,并在合约代码进行了中文注释。并且我们利用ERC721做了一个免费铸造的WTF APE NFT,元数据直接调用于BAYCERC721标准仍在不断发展中,目前比较流行的版本为ERC721Enumerable(提高NFT可访问性)和ERC721A(节约铸造gas)。

在这里插入图片描述

如果这份博客对大家有帮助,希望各位给作者一个免费的点赞👍作为鼓励,并评论收藏一下⭐,谢谢大家!!!
制作不易,如果大家有什么疑问或给作者的意见,欢迎评论区留言。

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

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

相关文章

详解MySQL存储引擎

前言: 📕作者简介:热爱编程的小七,致力于C、Java、Python等多编程语言,热爱编程和长板的运动少年! 📘相关专栏Java基础语法,JavaEE初阶,数据库,数据结构和算法系列等,大家有兴趣的可以看一看。 😇😇😇有兴趣的话关注博主一起学习,一起进步吧! 一、MySQL存…

Redis学习笔记-003

Redis企业实战—基于Redis短信验证功能 文章目录 Redis企业实战---基于Redis短信验证功能一、短信登录实现1.1、导入[黑马点评项目](https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11)1.2、基于Session实现1.3、集群的session的共享问题1.4、基于Redis实现共享sessio…

【算法】排序——插入排序及希尔排序

目录 前言 一、排序的概念及其应用 1.1排序的概念 1.2排序的应用 1.3常见的排序算法 二、插入排序的实现 基于插入排序的优化——希尔排序&#xff08;缩小增量排序 个人主页 代码仓库 C语言专栏 初阶数据结构专栏 Linux专栏 LeetCode刷题 算法专栏 前言 这…

Tensorflow1架构内核和学习方法论

目录 概念简介 总体介绍 名词解释 疑问辨析 工程构建 代码生成 技术栈 模型类型 系统架构 分层架构 图控制 运行机制 会话机制 队列 运行模型 本地模式 分布式模式 技能方法论 发现领域模型 挖掘系统架构 细节是魔鬼 适可而止 发现她的美 形式化 独…

代码随想录算法训练营 60天总结

emmmm,总结怎么写呢。 暑假和高中一个同学聊天&#xff0c;因为都参加了蓝桥杯&#xff0c;我连省三也没有&#xff0c;同学竟然省一然后去北京参加国赛获得国三&#xff0c;就问问他学习编程的方法。他推荐我加入了知识星球里的代码随想录&#xff0c;里面有学习路线还有好多讨…

LVS: ambighouse pin count in file “xx“ but none has xx pins问题

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 一些foundry的standard cell的cdl没有衬底pin&#xff08;例如VNW VPW&#xff09;&#xff0c;如果pr过程中globalNetConnect 或者connect_pg_net把衬底pin接到power/ground n…

Qt5开发及实例V2.0-第二十二章-Qt.Quick Controls 2新颖界面开发

Qt5开发及实例V2.0-第二十二章-Qt.Quick Controls 2新颖界面开发 第22章 Qt Quick Controls 2新颖界面开发22.1 Qt Quick Controls 2简介22.1.1 第一个Qt Quick Controls 2程序22.1.2 Qt Quick Controls 2程序的构成 22.2 Qt Quick Controls 2与1的比较22.2.1 ApplicationWindo…

ElementUI实现登录注册+axios全局配置+CORS跨域

一、搭建项目 1.1 安装 Element-UI 先确保是否安装了vue-cli脚手架工具 !!! 安装vue脚手架可以看看我的上一篇博客 构建好项目后通过npm安装element-ui cd 项目根路径 #进入新建项目的根目录 npm install element-ui -S #安装…

MySQL简介以及安装和部署(Linux)

MySQL简介 MySQL是一个小型关系数据库管理系统&#xff0c;开发者为瑞典MySQL AB公司。在2008年1月16号被sun公司10亿美金收购。2009年&#xff0c;SUN又被Oracle以74亿美金收购。 目前MySQL被广泛地应用在Internet上的中小型网站中。由于体积小、速度快、总体拥有成本低&…

C#__简单使用TCP/UDP发送消息

Socket(套接字、插口) TCP和UCP的区别&#xff1a; 1、基于连接和无连接 2、对系统资源的要求&#xff08;TCP较多&#xff0c;UCP少&#xff09; 3、UDP程序结构简单 4、流模式和数据报模式 5、TCP保证数据正确性和数据先后顺…

【腾讯云国际站】CDN内容分发网络特性介绍

为什么使用腾讯云国际站 CDN 内容分发网络&#xff1f; 当用户直接访问源站中的静态内容时&#xff0c;可能面临的体验问题&#xff1a; 客户离服务器越远&#xff0c;访问速度越慢。客户数量越多&#xff0c;网络带宽费用越高。跨境用户访问体验较差。 腾讯云国际站CDN 如何改…

yum和vim工具的使用

目录 yum工具的使用 yum下载原理 软件的查找&下载&删除操作 查找lrzsz软件&#xff08;文件上传或者下载软件&#xff09; 下载lrzsz软件 删除lrzsz软件 vim工具的使用 vim命令模式 命令模式与光标相关的快捷键&#xff1a; 插入模式 底行模式 在本次的博客当中我们主要…

C#/Unity3D 单例模板(单例属性模板)

C# 单例单例属性 不做过多解释&#xff0c;非面向大众 using System; namespace EasyAVG {public static class SingletonProperty<T> where T : class{private static readonly object locker new object();private volatile static T instance null;public static…

一篇文章成为递归大神:MySQL递归查询(with recursive)

理论原理 1、MySQL with Recursive是什么&#xff1f; MySQL with Recursive是一种基于递归思想的MySQL查询方式&#xff0c;可以实现对数据的递归查询和处理&#xff0c;返回符合条件的数据。在MySQL 8.0版本中&#xff0c;该功能被正式引入。 2、MySQL with Recursive有什么…

【100天精通Python】Day65:Python可视化_Matplotlib3D绘图mplot3d,绘制3D散点图、3D线图和3D条形图,示例+代码

1 mpl_toolkits.mplot3d 功能介绍 mpl_toolkits.mplot3d 是 Matplotlib 库中的一个子模块&#xff0c;用于绘制和可视化三维图形&#xff0c;包括三维散点图、曲面图、线图等。它提供了丰富的功能来创建和定制三维图形。以下是 mpl_toolkits.mplot3d 的主要功能和功能简介&am…

xxe攻击(XML外部实体)

1.定义 XML用于标记电子文件使其具有结构性的标记语言&#xff0c;可以用来标记数据、定义数据类型&#xff0c;是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义&#xff08;可选&#xff09;、文档元素。 http://www.w3school.com.…

Spring学习笔记9 SpringIOC注解式开发

Spring学习笔记8 Bean的循环依赖问题_biubiubiu0706的博客-CSDN博客 注解的存在主要是为了简化XML的配置.Spring6倡导全注解式开发 回顾下 注解怎么定义,注解中的属性怎么定义 注解怎么使用 通过反射机制怎么读取注解 注解的自定义 注解的使用 通过反射机制怎么读取注解 I…

顺序表的实现和练习

杂谈&#xff1a; 有些数据结构&#xff08;C语言实现&#xff09;的教材/教程中会使用C中引用的语法&#xff0c;引用确实在形式上比指针简洁&#xff0c;这样做无非是为了避免后续对二级指针的使用。 我认为既然使用C语言实现数据结构&#xff0c;那么指针就不应该是门槛。…

【动手学深度学习-Pytorch版】序列到序列的学习(包含NLP常用的Mask技巧)

序言 这一节是对于“编码器-解码器”模型的实际应用&#xff0c;编码器和解码器架构可以使用长度可变的序列作为输入&#xff0c;并将其转换为固定形状的隐状态&#xff08;编码器实现&#xff09;。本小节将使用“fra-eng”数据集&#xff08;这也是《动手学习深度学习-Pytor…

[论文分享] How to Better Utilize Code Graphs in Semantic Code Search?

How to Better Utilize Code Graphs in Semantic Code Search? [ESEC/FSE 2022] 语义代码搜索极大地促进了软件的重用&#xff0c;使用户能够找到与用户指定的自然语言查询高度匹配的代码片段。由于代码图(如控制流图和程序依赖图)丰富的表达能力&#xff0c;两种主流的研究工…