Michael.W基于Foundry精读Openzeppelin第19期——EnumerableSet.sol

news2024/11/15 11:42:20

Michael.W基于Foundry精读Openzeppelin第19期——EnumerableSet.sol

      • 0. 版本
        • 0.1 EnumerableSet.sol
      • 1. 目标合约
      • 2. 代码精读
        • 2.1 结构体Set
          • 2.1.1 _contains(Set storage set, bytes32 value) && _length(Set storage set) && _at(Set storage set, uint256 index) &&
          • 2.1.2 _add(Set storage set, bytes32 value)
          • 2.1.3 _remove(Set storage set, bytes32 value)
        • 2.2 Bytes32Set体系
          • 2.2.1 add(Bytes32Set storage set, bytes32 value) && remove(Bytes32Set storage set, bytes32 value)
          • 2.2.2 contains(Bytes32Set storage set, bytes32 value) && length(Bytes32Set storage set) && at(Bytes32Set storage set, uint256 index) && values(Bytes32Set storage set)
          • 2.2.3 foundry代码验证
        • 2.3 AddressSet体系
          • 2.3.1 add(AddressSet storage set, address value) && remove(AddressSet storage set, address value)
          • 2.3.2 contains(AddressSet storage set, address value) && length(AddressSet storage set) && at(AddressSet storage set, uint256 index) && values(AddressSet storage set)
          • 2.3.3 foundry代码验证
        • 2.4 UintSet体系
          • 2.4.1 add(UintSet storage set, uint256 value) && remove(UintSet storage set, uint256 value)
          • 2.4.2 contains(UintSet storage set, uint256 value) && length(UintSet storage set) && at(UintSet storage set, uint256 index) && values(UintSet storage set)
          • 2.4.3 foundry代码验证

0. 版本

[openzeppelin]:v4.8.3,[forge-std]:v1.5.6

0.1 EnumerableSet.sol

Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/utils/structs/EnumerableSet.sol

EnumerableSet库提供了Bytes32Set、AddressSet和UintSet三种类型的set,分别用于bytes32、address和uint256类型的元素。 每种set都提供了对应的增添元素、删除元素、检查目标元素是否处于set中、查询当前set中元素个数等操作。几乎所有操作的时间复杂度均为O(1)。

1. 目标合约

封装EnumerableSet library成为一个可调用合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/utils/structs/MockEnumerableSet.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";

contract MockBytes32Set {
    using EnumerableSet for EnumerableSet.Bytes32Set;

    EnumerableSet.Bytes32Set _bytes32Set;

    function add(bytes32 value) external returns (bool) {
        return _bytes32Set.add(value);
    }

    function remove(bytes32 value) external returns (bool){
        return _bytes32Set.remove(value);
    }

    function contains(bytes32 value) external view returns (bool) {
        return _bytes32Set.contains(value);
    }

    function length() external view returns (uint) {
        return _bytes32Set.length();
    }

    function at(uint index) external view returns (bytes32){
        return _bytes32Set.at(index);
    }

    function values() external view returns (bytes32[] memory){
        return _bytes32Set.values();
    }
}

contract MockAddressSet {
    using EnumerableSet for EnumerableSet.AddressSet;

    EnumerableSet.AddressSet _addressSet;

    function add(address value) external returns (bool) {
        return _addressSet.add(value);
    }

    function remove(address value) external returns (bool){
        return _addressSet.remove(value);
    }

    function contains(address value) external view returns (bool) {
        return _addressSet.contains(value);
    }

    function length() external view returns (uint) {
        return _addressSet.length();
    }

    function at(uint index) external view returns (address){
        return _addressSet.at(index);
    }

    function values() external view returns (address[] memory){
        return _addressSet.values();
    }
}

contract MockUintSet {
    using EnumerableSet for EnumerableSet.UintSet;

    EnumerableSet.UintSet _uintSet;

    function add(uint value) external returns (bool) {
        return _uintSet.add(value);
    }

    function remove(uint value) external returns (bool){
        return _uintSet.remove(value);
    }

    function contains(uint value) external view returns (bool) {
        return _uintSet.contains(value);
    }

    function length() external view returns (uint) {
        return _uintSet.length();
    }

    function at(uint index) external view returns (uint){
        return _uintSet.at(index);
    }

    function values() external view returns (uint[] memory){
        return _uintSet.values();
    }
}

全部foundry测试合约:

Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/utils/structs/EnumerableSet.t.sol

2. 代码精读

2.1 结构体Set

结构体Set的由一个存储set中元素值的bytes32数组和一个用于记录元素值在元素数组中的index的mapping构成:

    struct Set {
        // 用于存放set内元素值的数组。存储类型为bytes32,根据需求可以对此进行适当修改
        bytes32[] _values;
        
        // 用于记录元素值在_values数组中的index的mapping。如果一个元素值的index记录值为0,表示该元素不存在于set中
        mapping(bytes32 => uint256) _indexes;
    }

结构体Set和它对应的方法都不对外开放。

2.1.1 _contains(Set storage set, bytes32 value) && _length(Set storage set) && _at(Set storage set, uint256 index) &&
  • _contains(Set storage set, bytes32 value):查看元素value是否存在于set中。如果存在,返回true。时间复杂度为O(1);
  • _length(Set storage set):返回当前set中的元素个数。时间复杂度为O(1);
  • _at(Set storage set, uint256 index):返回当前set中对应index位置上的元素值。时间复杂度为O(1)。注意:方法内无索引越界检查,所以使用时需要保证传入的index < set中的元素总个数;
  • _values(Set storage set):返回当前set中全部的元素值(无序)。注意:该方法内部会将storage数组中的全部元素复制到memory中,这将消耗大量gas。所以请不要在非view方法中调用该方法。
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        // 如果记录的value元素对应index为0表示不存在,不为0表示存在
        return set._indexes[value] != 0;
    }

    function _length(Set storage set) private view returns (uint256) {
    	// 返回Set._values的长度
        return set._values.length;
    }

    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        // 直接从Set._values中用index取值
        return set._values[index];
    }

    function _values(Set storage set) private view returns (bytes32[] memory) {
        // 直接将整个bytes32[] storage复制到memory中返回
        return set._values;
    }
2.1.2 _add(Set storage set, bytes32 value)

向set中增添元素。如果该元素为非set元素返回true,否则返回false。时间复杂度为O(1)

    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
        	// 如果元素value不存在于当前set中,向Set._values中添加该元素
            set._values.push(value);
            // 在Set._indexes中记录该元素value位于Set._values数组中的index——即当前Set._values数组的长度。
            // 注:按照传统编程思想该元素位于Set._values数组最后,其index应该为总长度-1。这里对所有的元素的index记录值都+1,其目的是为了将index 0作为非set元素的flag。如果不这么设计,第一个添加的元素的index就是0,这将导致查询该元素是否处于set中的结果不符合预期
            set._indexes[value] = set._values.length;
            // 增添了新元素返回true
            return true;
        } else {
        	// 如果元素value已存在于set中,不进行任何添加操作并返回false
            return false;
        }
    }
2.1.3 _remove(Set storage set, bytes32 value)

从set中移除元素。如果该元素为当前set元素返回true,否则返回false。时间复杂度为O(1)

    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // 获取元素value位于set中的index
        uint256 valueIndex = set._indexes[value];

        if (valueIndex != 0) {
			// 如果valueIndex不为0,表示该元素处于当前set中
			// 从一个数组删除某给位置的元素的思路:将数组最后一个元素复制到待删除元素的位置上,然后将最后一个元素pop。该操作的时间复杂度为O(1)
			// valueIndex-1为待删除元素在Set._values数组中的真实index
            uint256 toDeleteIndex = valueIndex - 1;
            // lastIndex为当前数组最后一个元素的真实index
            uint256 lastIndex = set._values.length - 1;

            if (lastIndex != toDeleteIndex) {
            	// 如果待删除元素非数组内最后一个元素,取出数组最后一个元素的值
                bytes32 lastValue = set._values[lastIndex];
                // 数组待删除元素位置上的值替换为当前数组最后一个元素。其实此时已经实现了目标元素真正意义上的删除
                set._values[toDeleteIndex] = lastValue;
                // 由于数组最后一个元素已经换了位置,更新Set._indexes中最后一个元素的index为valueIndex,即待删除元素在Set._indexes中记录的index值
                set._indexes[lastValue] = valueIndex; 
            }

            // 直接pop掉Set._values中的最后一个元素
            set._values.pop();
            // 删除Set._indexes中关于待删除元素的记录
            delete set._indexes[value];
						// 返回true
            return true;
        } else {
        		// 如果待删除元素value非set中的元素,直接返回false
            return false;
        }
    }

2.2 Bytes32Set体系

如果要存储的元素类型为bytes32,可以采用该体系

    struct Bytes32Set {
    	// 封装了一个Set
        Set _inner;
    }
2.2.1 add(Bytes32Set storage set, bytes32 value) && remove(Bytes32Set storage set, bytes32 value)
  • add(Bytes32Set storage set, bytes32 value):向Bytes32Set中增添元素。如果该元素为非set元素返回true,否则返回false。时间复杂度为O(1);
  • remove(Bytes32Set storage set, bytes32 value):从Bytes32Set中移除元素。如果该元素为当前set元素返回true,否则返回false。时间复杂度为O(1)。
 		function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        // 直接调用Set._add()方法
        return _add(set._inner, value);
    }

    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        // 直接调用Set._remove()方法
        return _remove(set._inner, value);
    }
2.2.2 contains(Bytes32Set storage set, bytes32 value) && length(Bytes32Set storage set) && at(Bytes32Set storage set, uint256 index) && values(Bytes32Set storage set)
  • contains(Bytes32Set storage set, bytes32 value):查看元素value是否存在于Bytes32Set中。如果存在,返回true。时间复杂度为O(1);
  • length(Bytes32Set storage set):返回当前Bytes32Set中的元素个数。时间复杂度为O(1);
  • at(Bytes32Set storage set, uint256 index):返回当前Bytes32Set中对应index位置上的元素值。时间复杂度为O(1)。注意:方法内无索引越界检查,所以使用时需要保证传入的index < Bytes32Set中的元素总个数;
  • values(Bytes32Set storage set):返回当前Bytes32Set中全部的元素值(无序)。注意:该方法内部会将storage数组中的全部元素复制到memory中,这将消耗大量gas。所以请不要在非view方法中调用该方法。
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        // 直接调用Set._contains()方法
        return _contains(set._inner, value);
    }

    function length(Bytes32Set storage set) internal view returns (uint256) {
        // 直接调用Set._length()方法
        return _length(set._inner);
    }

    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        // 直接调用Set._at()方法
        return _at(set._inner, index);
    }

    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        // 直接调用Set._values()方法得到底层set中存储的元素总集(是一个bytes32[])
        bytes32[] memory store = _values(set._inner);
        // 将store转换成Bytes32Set的外层封装类型bytes32[]
        // 注:个人认为对于Bytes32Set的values()方法可以直接返回store,不需要再做类型转换。由于本库的代码是由js代码生成的,所以此处没与后面的uint[]和address[]做差别处理
        bytes32[] memory result;
        /// @solidity memory-safe-assembly
        assembly {
        	// 内联汇编中,直接在memory中进行bytes32[]->bytes32[]的类型转换
            result := store
        }
		// 返回类型转换后的bytes32[]
        return result;
    }
2.2.3 foundry代码验证
contract EnumerableSetTest is Test {
    MockBytes32Set mbs = new MockBytes32Set();

    function test_Bytes32Set_Operations() external {
        // empty
        assertEq(mbs.length(), 0);
        assertEq(mbs.values().length, 0);
        assertFalse(mbs.contains('a'));

        // add
        assertTrue(mbs.add('a'));
        assertTrue(mbs.contains('a'));
        assertEq(mbs.length(), 1);
        assertTrue(mbs.add('b'));
        assertEq(mbs.length(), 2);
        // add 'a' again
        assertFalse(mbs.add('a'));
        assertEq(mbs.length(), 2);
        bytes32[] memory values = mbs.values();
        assertEq('a', values[0]);
        assertEq('b', values[1]);

        assertTrue(mbs.add('c'));
        assertTrue(mbs.add('d'));
        assertEq(mbs.length(), 4);

        // remove
        // inner array: ['a','b','c','d']
        assertTrue(mbs.contains('b'));
        assertTrue(mbs.remove('b'));
        assertFalse(mbs.contains('b'));
        assertEq(mbs.length(), 3);
        // remove 'b' again
        assertFalse(mbs.remove('b'));
        assertEq(mbs.length(), 3);
        // inner array after remove: ['a','d','c']
        assertEq(mbs.at(0), 'a');
        assertEq(mbs.at(1), 'd');
        assertEq(mbs.at(2), 'c');
        // check values()
        values = mbs.values();
        assertEq('a', values[0]);
        assertEq('d', values[1]);
        assertEq('c', values[2]);

        // revert if out of bounds
        vm.expectRevert();
        mbs.at(1024);
    }
}

2.3 AddressSet体系

如果要存储的元素类型为address,可以采用该体系

    struct AddressSet {
        // 封装了一个Set
        Set _inner;
    }
2.3.1 add(AddressSet storage set, address value) && remove(AddressSet storage set, address value)
  • add(AddressSet storage set, address value):向AddressSet中增添元素。如果该元素为非set元素返回true,否则返回false。时间复杂度为O(1);
  • remove(AddressSet storage set, address value): 从AddressSet中移除元素。如果该元素为当前set元素返回true,否则返回false。时间复杂度为O(1)。
    function add(AddressSet storage set, address value) internal returns (bool) {
        // 直接调用Set._add()方法,参数value做了address->bytes32的类型转换
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    function remove(AddressSet storage set, address value) internal returns (bool) {
        // 直接调用Set._remove()方法,参数value做了address->bytes32的类型转换
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }
2.3.2 contains(AddressSet storage set, address value) && length(AddressSet storage set) && at(AddressSet storage set, uint256 index) && values(AddressSet storage set)
  • contains(AddressSet storage set, address value):查看元素value是否存在于AddressSet中。如果存在,返回true。时间复杂度为O(1);
  • length(AddressSet storage set):返回当前AddressSet中的元素个数。时间复杂度为O(1);
  • at(AddressSet storage set, uint256 index):返回当前AddressSet中对应index位置上的元素值。时间复杂度为O(1)。注意:方法内无索引越界检查,所以使用时需要保证传入的index < AddressSet中的元素总个数;
  • values(AddressSet storage set):返回当前AddressSet中全部的元素值(无序)。注意:该方法内部会将storage数组中的全部元素复制到memory中,这将消耗大量gas。所以请不要在非view方法中调用该方法。
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        // 直接调用Set._contains()方法,参数value做了address->bytes32的类型转换
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    function length(AddressSet storage set) internal view returns (uint256) {
        // 直接调用Set._length()方法
        return _length(set._inner);
    }

    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        // 直接调用Set._at()方法,并将bytes32类型的返回值转换为address类型返回
        return address(uint160(uint256(_at(set._inner, index))));
    }

    function values(AddressSet storage set) internal view returns (address[] memory) {
        // 直接调用Set._values()方法得到底层set中存储的元素总集(是一个bytes32[])
        bytes32[] memory store = _values(set._inner);
        // 将store转换成AddressSet的外层封装类型address[]
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            // 内联汇编中,直接在memory中进行bytes32[]->address[]的类型转换
            result := store
        }
		// 返回类型转换后的address[]
        return result;
    }
2.3.3 foundry代码验证
contract EnumerableSetTest is Test {
    MockAddressSet mas = new MockAddressSet();

    function test_AddressSet_Operations() external {
        // empty
        assertEq(mas.length(), 0);
        assertEq(mas.values().length, 0);
        assertFalse(mas.contains(address(1)));

        // add
        assertTrue(mas.add(address(1)));
        assertTrue(mas.contains(address(1)));
        assertEq(mas.length(), 1);
        assertTrue(mas.add(address(2)));
        assertEq(mas.length(), 2);
        // add address(1) again
        assertFalse(mas.add(address(1)));
        assertEq(mas.length(), 2);
        address[] memory values = mas.values();
        assertEq(address(1), values[0]);
        assertEq(address(2), values[1]);

        assertTrue(mas.add(address(4)));
        assertTrue(mas.add(address(8)));
        assertEq(mas.length(), 4);

        // remove
        // inner array: [address(1),address(2),address(4),address(8)]
        assertTrue(mas.contains(address(2)));
        assertTrue(mas.remove(address(2)));
        assertFalse(mas.contains(address(2)));
        assertEq(mas.length(), 3);
        // remove address(2) again
        assertFalse(mas.remove(address(2)));
        assertEq(mas.length(), 3);
        // inner array after remove: [address(1),address(8),address(4)]
        assertEq(mas.at(0), address(1));
        assertEq(mas.at(1), address(8));
        assertEq(mas.at(2), address(4));
        // check values()
        values = mas.values();
        assertEq(address(1), values[0]);
        assertEq(address(8), values[1]);
        assertEq(address(4), values[2]);

        // revert if out of bounds
        vm.expectRevert();
        mas.at(1024);
    }
}

2.4 UintSet体系

如果要存储的元素类型为uint256,可以采用该体系

    struct UintSet {
        // 封装了一个Set
        Set _inner;
    }
2.4.1 add(UintSet storage set, uint256 value) && remove(UintSet storage set, uint256 value)
  • add(UintSet storage set, uint256 value):向UintSet中增添元素。如果该元素为非set元素返回true,否则返回false。时间复杂度为O(1);
  • remove(UintSet storage set, uint256 value):从UintSet中移除元素。如果该元素为当前set元素返回true,否则返回false。时间复杂度为O(1)。
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        // 直接调用Set._add()方法,参数value做了uint256->bytes32的类型转换
        return _add(set._inner, bytes32(value));
    }

    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        // 直接调用Set._remove()方法,参数value做了uint256->bytes32的类型转换
        return _remove(set._inner, bytes32(value));
    }
2.4.2 contains(UintSet storage set, uint256 value) && length(UintSet storage set) && at(UintSet storage set, uint256 index) && values(UintSet storage set)
  • contains(UintSet storage set, uint256 value):查看元素value是否存在于UintSet中。如果存在,返回true。时间复杂度为O(1);
  • length(UintSet storage set):返回当前UintSet中的元素个数。时间复杂度为O(1);
  • at(UintSet storage set, uint256 index):返回当前UintSet中对应index位置上的元素值。时间复杂度为O(1)。注意:方法内无索引越界检查,所以使用时需要保证传入的index < UintSet中的元素总个数;
  • values(UintSet storage set):返回当前UintSet中全部的元素值(无序)。注意:该方法内部会将storage数组中的全部元素复制到memory中,这将消耗大量gas。所以请不要在非view方法中调用该方法。
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        // 直接调用Set._contains()方法,参数value做了uint256->bytes32的类型转换
        return _contains(set._inner, bytes32(value));
    }

    function length(UintSet storage set) internal view returns (uint256) {
        // 直接调用Set._length()方法
        return _length(set._inner);
    }

    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        // 直接调用Set._at()方法,并将bytes32类型的返回值转换为uint256类型返回
        return uint256(_at(set._inner, index));
    }

    function values(UintSet storage set) internal view returns (uint256[] memory) {
        // 直接调用Set._values()方法得到底层set中存储的元素总集(是一个bytes32[])
        bytes32[] memory store = _values(set._inner);
        // 将store转换成UintSet的外层封装类型uint256[]
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            // 内联汇编中,直接在memory中进行bytes32[]->uint256[]的类型转换
            result := store
        }
		// 返回类型转换后的uint256[]
        return result;
    }
2.4.3 foundry代码验证
contract EnumerableSetTest is Test {
    MockUintSet mus = new MockUintSet();

    function test_UintSet_Operations() external {
        // empty
        assertEq(mus.length(), 0);
        assertEq(mus.values().length, 0);
        assertFalse(mus.contains(1));

        // add
        assertTrue(mus.add(1));
        assertTrue(mus.contains(1));
        assertEq(mus.length(), 1);
        assertTrue(mus.add(2));
        assertEq(mus.length(), 2);
        // add 1 again
        assertFalse(mus.add(1));
        assertEq(mus.length(), 2);
        uint[] memory values = mus.values();
        assertEq(1, values[0]);
        assertEq(2, values[1]);

        assertTrue(mus.add(4));
        assertTrue(mus.add(8));
        assertEq(mus.length(), 4);

        // remove
        // inner array: [1,2,4,8]
        assertTrue(mus.contains(2));
        assertTrue(mus.remove(2));
        assertFalse(mus.contains(2));
        assertEq(mus.length(), 3);
        // remove 2 again
        assertFalse(mus.remove(2));
        assertEq(mus.length(), 3);
        // inner array after remove: [1,8,4]
        assertEq(mus.at(0), 1);
        assertEq(mus.at(1), 8);
        assertEq(mus.at(2), 4);
        // check values()
        values = mus.values();
        assertEq(1, values[0]);
        assertEq(8, values[1]);
        assertEq(4, values[2]);

        // revert if out of bounds
        vm.expectRevert();
        mus.at(1024);
    }
}

ps:
本人热爱图灵,热爱中本聪,热爱V神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!

在这里插入图片描述

公众号名称:后现代泼痞浪漫主义奠基人

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

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

相关文章

Linux的基本指令(2)

指令1&#xff1a;man 作用&#xff1a;可以查询linux指令语法内容。 格式&#xff1a; man 指令 安装man指令&#xff1a; yum install -y man-pages 例如&#xff1a; 查询 指令 ls 的语法内容。 man ls 查询 fork 指令的语法内容。 man fork 在man中存在9个手册&…

2023年08月编程语言流行度排名

点击查看最新编程语言流行度排名&#xff08;每月更新&#xff09; 2023年08月编程语言流行度排名 编程语言流行度排名是通过分析在谷歌上搜索语言教程的频率而创建的 一门语言教程被搜索的次数越多&#xff0c;大家就会认为该语言越受欢迎。这是一个领先指标。原始数据来自…

word转pdf怎么转?几种常用方法分享

word转pdf怎么转&#xff1f;在日常工作和学习中&#xff0c;将Word文档转换为PDF格式是一项必要的任务。不仅可以保证文档的格式不变&#xff0c;还可以防止文档被他人篡改。但是&#xff0c;Word文档并不是所有人都能够轻松打开和编辑的&#xff0c;而PDF文件则可以在各种设备…

Python---Matplotlib

文章目录 1.什么是matplotlib?2.实现一个简单的matplotlib绘图3.matplotlib图像结构4.matplotlib基础绘图多个坐标系显示— plt.subplots(面向对象的画图方法) 5.常见图形种类 1.什么是matplotlib? Matplotlib是一个用于绘制数据可视化图形的Python库。它是一个广泛使用的数…

SystemVerilog scheduler

文章目录 简介调度器simulation regionPreponed regionActive regionInactive regionNBA(Non-blocking Assignment Events region)Observed regionReactive regionRe-Inactive Events regionRe-NBA RegionPostponed Region PLI region:Pre-active regionPre-NBA regionPost-NBA…

锁相环是锁住正弦电压波形的0度位置还是90度位置,欢迎大家参与讨论?

文章目录 最近做三相PFC控制电源开发&#xff0c;里面使用到锁相环&#xff0c;跟大家一起讨论一下&#xff0c;锁相环锁的是A,B,C哪一相&#xff0c;是0度位置还是90度位置&#xff1f;讨论区&#xff1a;大家的观点是什么&#xff0c;请投票选择&#xff0c;后续会一起讨论正…

2023年华数杯赛题浅析

2023年华数杯作为与国赛同频的比赛&#xff08;周四6点发题&#xff0c;周日晚8点交卷&#xff09;&#xff0c;也是暑期唯一一个正式比赛。今年的报名队伍已经高达​6000多对。基于这么多的人数进行国赛前队伍的练习&#xff0c;以及​其他用途。为了方便大家跟更好的选题&…

SQL 语句中 left join 后用 on 还是 where,区别大了!

目录 情况 小结 举例 情况 前天写SQL时本想通过 A left B join on and 后面的条件来使查出的两条记录变成一条&#xff0c;奈何发现还是有两条。 后来发现 join on and 不会过滤结果记录条数&#xff0c;只会根据and后的条件是否显示 B表的记录&#xff0c;A表的记录一定会显…

响应号召!加强基础研究 | GBASE南大通用坚决打好基础软件国产化攻坚战

数据库上托应用&#xff0c;下连基础设施&#xff0c;是IT系统中承上启下最关键的一环&#xff0c;被誉为“基础软件皇冠上的明珠”。加强数据库基础研究&#xff0c;从源头和底层把握关键技术&#xff0c;打造自主可信的大国重器&#xff0c;是打好基础软件国产化攻坚战的必然…

免费!功能强大的PS在线网页版推荐!

PS功能强大&#xff0c;但是对于设计师尤其是 UI 设计师来说获取稍许庞大&#xff0c;其版本更新频繁且不稳定&#xff0c;运行对电脑配置要求高&#xff0c;对于初学者来说是一种“负担”&#xff0c;更轻型却强大的设计工具出现&#xff0c;也就是本文为大家带来的 PS 在线网…

Restful开发规范以及开发流程

目录 一、RestFul开发规范 二、开发流程 一、RestFul开发规范 RESTful&#xff08;Representational State Transfer&#xff09;是一种用于设计和开发网络应用程序的架构风格&#xff0c;它强调使用标准HTTP方法和状态码来进行资源的管理和交互。以下是一些常见的RESTful开发…

NGZORRO:动态表单/模型驱动 的相关问题

官网的demo的[nzFor]"control.controlInstance"&#xff0c;似乎是靠[formControlName]"control.controlInstance"来关联的。 <form nz-form [formGroup]"validateForm" (ngSubmit)"submitForm()"><nz-form-item *ngFor&quo…

利用ChatGPT制作行业应用:哪些行业最受益

引言 随着人工智能技术的快速发展&#xff0c;ChatGPT&#xff08;Chat Generative Pre-trained Transformer&#xff09;成为了一种引人注目的工具&#xff0c;它能够生成自然流畅的对话内容。这种技术不仅在娱乐领域有着广泛的应用&#xff0c;还可以在各个行业中发挥重要作…

为了应付枯燥的工作需求,我造了一个“轮子”

项目代码已上传至Github&#xff0c;已开源&#xff0c;欢迎Star。 项目地址&#xff1a;JSON_EXTRACT_SQL 正如大家标题所见的&#xff0c;我造了一个“轮子”!!! 至于是什么需求呢&#xff1f;下面我贴出一段json&#xff1a; {"type": "test","pro…

小程序商品如何开启秒杀?

在小程序中&#xff0c;开启秒杀活动可以有效地吸引用户的注意力&#xff0c;提升销售额。下面就让我们来看看小程序商品怎么开启秒杀功能吧。 首先&#xff0c;确定秒杀活动的商品。一般来说&#xff0c;我们可以选择一些库存较多的商品或者是需要清理库存的商品作为秒杀商品…

第四次作业 运维高级 安装tomcat8和部署jpress应用

1. 简述静态网页和动态网页的区别。 静态网页 静态网页是指存放在服务器文件系统中实实在在的HTML文件。当用户在浏览器中输入页面的URL&#xff0c;然后回车&#xff0c;浏览器就会将对应的html文件下载、渲染并呈现在窗口中。早期的网站通常都是由静态页面制作的。 静态网页…

Vue2 第十八节 插槽

1.默认插槽 2.具名插槽 3.作用域插槽 插槽 ① 作用&#xff1a;让父组件可以向子组件指定位置插入html结构&#xff0c;也是一种组件间通信的方式&#xff0c;适用于父组件和子组件间通信 ② 分类&#xff1a;默认插槽&#xff0c;具名插槽&#xff0c;作用域插槽 一.默认…

面试必问,敲重点!讲一下 Android Application 启动流程及其源码?

一、写在前面 在开始之前&#xff0c;你需要知道下面几点&#xff1a; 有一份编译好的 Android 源码&#xff0c;现在的 AS 基本能满足&#xff0c;动手跟着步骤走&#xff0c;理解更深刻对 Binder 机制有一定的了解本文基于 API 26&#xff0c;用什么版本的源码并不重要&#…

【蓝图】p46角色上下车功能

这里写目录标题 p46角色上下车功能上车&#xff08;控制权切换&#xff09;让角色和汽车一起移动GetWorldTransform&#xff08;获取场景变换&#xff09;break&#xff08;拆分变换&#xff09;AttachActorToComponent&#xff08;附加Actor到组件&#xff09; 下车 p46角色上…

基于LNMP架构搭建Discuz论坛

LNMP: L---->linux系统&#xff0c;操作系统。 N----->nginx网站服务&#xff08;前端),提供前端的静态页面服务。同时具有代理、转发的作用。&#xff08;转发就是转发后端请求&#xff0c;转发PHP&#xff09;&#xff0c;nginx没有处理动态资源的功能&#xff0c;他有…