掌控 Solidity:事件日志、继承和接口的深度解析
Solidity 是以太坊智能合约的主要编程语言,它的强大之处在于能够帮助开发者构建安全、高效的去中心化应用。在我参与的多个项目中,事件日志、继承和接口这三个概念始终贯穿其中,成为构建复杂智能合约的关键技术。今天就来聊聊Solidity中的错误处理、事件日志、继承和接口。
Solidity中的错误处理
在 Solidity 中,错误处理是非常重要的,它可以帮助开发者捕获和处理合约执行过程中可能出现的问题,从而提高合约的健壮性和安全性。Solidity 提供了多种机制来处理错误,包括 require、assert、revert 和自定义错误。
require 语句
require 语句用于在条件不满足时抛出异常并回滚交易。通常用于验证输入参数和外部状态。
语法:
require(condition, "Error message");
示例:
function donate(uint256 projectId) public payable {require(projectId <= projectCount, "Invalid project ID");require(block.timestamp <= projects[projectId].deadline, "Project deadline has passed");require(msg.value > 0, "Donation amount must be greater than 0");// 其他逻辑
}
assert 语句
assert
语句用于在条件不满足时抛出异常并回滚交易。通常用于检测内部错误,例如不变量检查。
语法:
assert(condition);
示例:
function withdrawFunds(uint256 projectId) public {require(projectId <= projectCount, "Invalid project ID");require(projects[projectId].creator == msg.sender, "Only the project creator can withdraw funds");require(projects[projectId].isFunded, "Project is not funded");uint256 amountToWithdraw = projects[projectId].raisedAmount;projects[projectId].raisedAmount = 0;(bool success, ) = projects[projectId].creator.call{value: amountToWithdraw}("");assert(success); // 确保转账成功emit Funded(projectId, amountToWithdraw);
}
revert 语句
revert
语句用于显式地抛出异常并回滚交易。可以传递一个字符串作为错误消息。
语法:
revert("Error message");
示例:
function createProject(string memory title, string memory description, uint256 targetAmount, uint256 duration) public {if (targetAmount == 0) {revert("Target amount must be greater than 0");}if (duration == 0) {revert("Duration must be greater than 0");}projectCount++;uint256 deadline = block.timestamp + duration;projects[projectCount] = Project(msg.sender, title, description, targetAmount, 0, deadline, false);emit ProjectCreated(projectCount, msg.sender, title, targetAmount, deadline);
}
自定义错误
从 Solidity 0.8.0 版本开始,引入了自定义错误功能,可以提高错误处理的可读性和效率。
定义自定义错误:
error InvalidProjectId();
error DeadlinePassed();
error ZeroDonation();
error NotProjectCreator();
error NotFunded();
抛出自定义错误:
function donate(uint256 projectId) public payable {if (projectId > projectCount) {revert InvalidProjectId();}if (block.timestamp > projects[projectId].deadline) {revert DeadlinePassed();}if (msg.value == 0) {revert ZeroDonation();}projects[projectId].raisedAmount += msg.value;emit Donated(projectId, msg.sender, msg.value);if (projects[projectId].raisedAmount >= projects[projectId].targetAmount) {projects[projectId].isFunded = true;emit Funded(projectId, projects[projectId].raisedAmount);}
}
错误处理的最佳实践
明确错误消息:
使用清晰、具体的错误消息,帮助调试和理解问题。
避免冗余检查:
不要在多个地方重复相同的检查,尽量集中处理。
使用自定义错误:
自定义错误可以提高代码的可读性和可维护性,减少 gas 费用。
合理使用 assert 和 require:assert
用于检测内部错误,require
用于验证外部输入和状态。
测试错误处理:
编写单元测试来验证错误处理逻辑是否正确。
示例合约
以下是一个完整的示例合约,展示了如何使用 require
、assert
、revert
和自定义错误:
pragma solidity ^0.8.0;import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";contract CrowdfundingPlatform is Ownable, ReentrancyGuard {struct Project {address creator;string title;string description;uint256 targetAmount;uint256 raisedAmount;uint256 deadline;bool isFunded;}mapping(uint256 => Project) public projects;uint256 public projectCount;event ProjectCreated(uint256 projectId, address creator, string title, uint256 targetAmount, uint256 deadline);event Donated(uint256 projectId, address donor, uint256 amount);event Funded(uint256 projectId, uint256 totalRaised);error InvalidProjectId();error DeadlinePassed();error ZeroDonation();error NotProjectCreator();error NotFunded();function createProject(string memory title, string memory description, uint256 targetAmount, uint256 duration) public {if (targetAmount == 0) {revert("Target amount must be greater than 0");}if (duration == 0) {revert("Duration must be greater than 0");}projectCount++;uint256 deadline = block.timestamp + duration;projects[projectCount] = Project(msg.sender, title, description, targetAmount, 0, deadline, false);emit ProjectCreated(projectCount, msg.sender, title, targetAmount, deadline);}function donate(uint256 projectId) public payable {if (projectId > projectCount) {revert InvalidProjectId();}if (block.timestamp > projects[projectId].deadline) {revert DeadlinePassed();}if (msg.value == 0) {revert ZeroDonation();}projects[projectId].raisedAmount += msg.value;emit Donated(projectId, msg.sender, msg.value);if (projects[projectId].raisedAmount >= projects[projectId].targetAmount) {projects[projectId].isFunded = true;emit Funded(projectId, projects[projectId].raisedAmount);}}function withdrawFunds(uint256 projectId) public nonReentrant {if (projectId > projectCount) {revert InvalidProjectId();}if (projects[projectId].creator != msg.sender) {revert NotProjectCreator();}if (!projects[projectId].isFunded) {revert NotFunded();}uint256 amountToWithdraw = projects[projectId].raisedAmount;projects[projectId].raisedAmount = 0;(bool success, ) = projects[projectId].creator.call{value: amountToWithdraw}("");assert(success); // 确保转账成功emit Funded(projectId, amountToWithdraw);}
}
Solidity中的事件和日志
什么是事件?
在 Solidity 中,事件是一种允许智能合约与外部世界进行通信的机制。通过触发事件,可以记录合约执行中的关键操作,并将这些操作发送到链上。事件的记录会以日志的形式存储在区块中,不会直接改变合约的状态。
为什么使用事件?
- 成本低:事件数据存储在日志中,比存储在合约状态中更便宜。
- 可检索:事件数据可以被链外应用轻松检索和解析。
- 异步通知:事件可以用于异步通知链外应用,实现实时更新。
定义和触发事件
定义事件
在 Solidity 中,事件的定义使用 event 关键字。事件可以带有参数,这些参数可以在触发事件时传递值。
event ProjectCreated(uint256 indexed projectId, address indexed creator, string title, uint256 targetAmount, uint256 deadline);
event Donated(uint256 indexed projectId, address indexed donor, uint256 amount);
event Funded(uint256 indexed projectId, uint256 totalRaised);
indexed
关键字:标记参数为索引参数,可以在日志中快速查找。最多可以有三个索引参数。
触发事件
在合约方法中,使用 emit
关键字来触发事件。
function createProject(string memory title, string memory description, uint256 targetAmount, uint256 duration) public {projectCount++;uint256 deadline = block.timestamp + duration;projects[projectCount] = Project(msg.sender, title, description, targetAmount, 0, deadline, false);emit ProjectCreated(projectCount, msg.sender, title, targetAmount, deadline);
}function donate(uint256 projectId) public payable {require(projectId <= projectCount, "Invalid project ID");require(block.timestamp <= projects[projectId].deadline, "Project deadline has passed");require(msg.value > 0, "Donation amount must be greater than 0");projects[projectId].raisedAmount += msg.value;emit Donated(projectId, msg.sender, msg.value);if (projects[projectId].raisedAmount >= projects[projectId].targetAmount) {projects[projectId].isFunded = true;emit Funded(projectId, projects[projectId].raisedAmount);}
}
监听和检索事件
监听事件
在链外应用中,可以使用 Web3.js 或其他以太坊客户端库来监听事件。
const projectCreatedEvent = crowdfundingPlatform.events.ProjectCreated();
projectCreatedEvent.on('data', (event) => {console.log(`Project created: ${event.returnValues.projectId}`);
});const donatedEvent = crowdfundingPlatform.events.Donated();
donatedEvent.on('data', (event) => {console.log(`Donated to project ${event.returnValues.projectId}: ${event.returnValues.amount} wei`);
});
检索事件
可以通过过滤器来检索历史事件。
const filter = {fromBlock: 0,toBlock: 'latest'
};crowdfundingPlatform.getPastEvents('ProjectCreated', filter, (error, events) => {if (error) {console.error(error);} else {console.log(events);}
});
实战经验分享
在我开发的一个众筹平台项目中,事件和日志发挥了重要作用。通过定义和触发事件,我能够记录每个项目的创建、捐款和资金到位的关键操作。这些事件不仅帮助我调试和优化合约,还为前端应用提供了实时更新的能力。
例如,在 createProject
方法中,我定义了一个 ProjectCreated
事件,每当有新项目创建时,都会触发这个事件。前端应用通过监听这个事件,可以实时显示新创建的项目列表。
event ProjectCreated(uint256 indexed projectId, address indexed creator, string title, uint256 targetAmount, uint256 deadline);function createProject(string memory title, string memory description, uint256 targetAmount, uint256 duration) public {projectCount++;uint256 deadline = block.timestamp + duration;projects[projectCount] = Project(msg.sender, title, description, targetAmount, 0, deadline, false);emit ProjectCreated(projectCount, msg.sender, title, targetAmount, deadline);
}
Solidity中的继承和接口
随着项目的复杂度增加,我遇到了一个常见的问题:代码复用。在传统的面向对象编程语言中,我们可以通过继承和接口来实现代码复用和模块化设计。那么在 Solidity 中,如何实现这一点呢?
继承:代码复用的利器
什么是继承?
在 Solidity 中,继承是一种允许一个合约继承另一个合约的功能和属性的机制。通过继承,子合约可以重用父合约的代码,从而减少重复代码,提高代码的可维护性和可读性。
单继承
最简单的继承形式是单继承,即一个子合约只继承一个父合约。下面是一个简单的例子:
// 父合约
contract Base {uint256 public baseValue;constructor(uint256 _baseValue) {baseValue = _baseValue;}function baseFunction() public pure returns (string memory) {return "Base Function";}
}// 子合约
contract Child is Base {uint256 public childValue;constructor(uint256 _baseValue, uint256 _childValue) Base(_baseValue) {childValue = _childValue;}function childFunction() public pure returns (string memory) {return "Child Function";}
}
在这个例子中,Child
合约继承了 Base 合约。Child
合约可以访问 Base
合约的 baseValue
变量和 baseFunction
方法。
多继承
Solidity 还支持多继承,即一个子合约可以继承多个父合约。多继承可以实现更复杂的代码复用和模块化设计。下面是一个多继承的例子:
// 父合约 1
contract Base1 {uint256 public value1;constructor(uint256 _value1) {value1 = _value1;}function function1() public pure returns (string memory) {return "Function 1";}
}// 父合约 2
contract Base2 {uint256 public value2;constructor(uint256 _value2) {value2 = _value2;}function function2() public pure returns (string memory) {return "Function 2";}
}// 子合约
contract Child is Base1, Base2 {uint256 public childValue;constructor(uint256 _value1, uint256 _value2, uint256 _childValue) Base1(_value1) Base2(_value2) {childValue = _childValue;}function childFunction() public pure returns (string memory) {return "Child Function";}
}
在这个例子中,Child
合约继承了 Base1
和 Base2
合约。Child
合约可以访问 Base1
和 Base2
合约的变量和方法。
构造函数的调用顺序
在多继承的情况下,构造函数的调用顺序非常重要。Solidity 会按照继承列表从右到左的顺序调用父合约的构造函数。如果父合约之间存在依赖关系,需要特别注意构造函数的调用顺序。
contract A {uint256 public a;constructor(uint256 _a) {a = _a;}
}contract B {uint256 public b;constructor(uint256 _b) {b = _b;}
}contract C is A, B {uint256 public c;constructor(uint256 _a, uint256 _b, uint256 _c) A(_a) B(_b) {c = _c;}
}
在这个例子中,C 合约的构造函数会先调用 B 合约的构造函数,再调用 A 合约的构造函数。
方法重写
在继承中,子合约可以重写父合约的方法。通过重写方法,子合约可以实现不同的功能或优化父合约的行为。下面是一个方法重写的例子:
contract Base {function baseFunction() public pure virtual returns (string memory) {return "Base Function";}
}contract Child is Base {function baseFunction() public pure override returns (string memory) {return "Child Function";}
}
在这个例子中,Child 合约重写了 Base 合约的 baseFunction 方法。virtual 关键字表示该方法可以被子合约重写,override 关键字表示当前方法是在重写父合约的方法。
接口:定义行为规范
什么是接口?
接口是一种定义合约行为规范的方式。接口不包含任何实现,只包含方法签名、事件和常量。通过接口,可以确保合约实现特定的行为,而不关心具体的实现细节。
定义接口
在 Solidity 中,接口的定义使用 interface
关键字。接口中的方法必须是 external
类型,且不能包含任何实现。下面是一个简单的接口定义:
interface IERC20 {function totalSupply() external view returns (uint256);function balanceOf(address account) external view returns (uint256);function transfer(address recipient, uint256 amount) external returns (bool);function allowance(address owner, address spender) external view returns (uint256);function approve(address spender, uint256 amount) external returns (bool);function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);event Transfer(address indexed from, address indexed to, uint256 value);event Approval(address indexed owner, address indexed spender, uint256 value);
}
在这个例子中,IERC20 接口定义了 ERC20 标准中的方法和事件。
实现接口
合约可以通过 is 关键字实现接口,并提供接口中定义的方法的具体实现。下面是一个实现 IERC20 接口的合约示例:
contract MyToken is IERC20 {mapping(address => uint256) private _balances;mapping(address => mapping(address => uint256)) private _allowances;uint256 private _totalSupply;constructor(uint256 initialSupply) {_mint(msg.sender, initialSupply);}function totalSupply() public view override returns (uint256) {return _totalSupply;}function balanceOf(address account) public view override returns (uint256) {return _balances[account];}function transfer(address recipient, uint256 amount) public override returns (bool) {_transfer(msg.sender, recipient, amount);return true;}function allowance(address owner, address spender) public view override returns (uint256) {return _allowances[owner][spender];}function approve(address spender, uint256 amount) public override returns (bool) {_approve(msg.sender, spender, amount);return true;}function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {_transfer(sender, recipient, amount);_approve(sender, msg.sender, _allowances[sender][msg.sender] - amount);return true;}function _transfer(address sender, address recipient, uint256 amount) internal {require(sender != address(0), "ERC20: transfer from the zero address");require(recipient != address(0), "ERC20: transfer to the zero address");require(_balances[sender] >= amount, "ERC20: insufficient balance");_balances[sender] -= amount;_balances[recipient] += amount;emit Transfer(sender, recipient, amount);}function _mint(address account, uint256 amount) internal {require(account != address(0), "ERC20: mint to the zero address");_totalSupply += amount;_balances[account] += amount;emit Transfer(address(0), account, amount);}function _approve(address owner, address spender, uint256 amount) internal {require(owner != address(0), "ERC20: approve from the zero address");require(spender != address(0), "ERC20: approve to the zero address");_allowances[owner][spender] = amount;emit Approval(owner, spender, amount);}
}
在这个例子中,MyToken 合约实现了 IERC20 接口,并提供了所有方法的具体实现。
实战经验分享
在我的实际开发过程中,继承和接口发挥了重要作用。以下是一些具体的实战经验分享:
项目背景
我参与了一个去中心化金融(DeFi)项目,该项目需要实现多个不同类型的代币合约,包括标准的 ERC20 代币、可升级的代币、治理代币等。为了提高代码的可维护性和可扩展性,我们采用了继承和接口的设计模式。
使用继承实现代码复用
我们定义了一个基础的 Token 合约,包含了通用的代币逻辑,如转账、批准等。然后,我们通过继承 Token 合约,实现了不同类型的代币合约。
// 基础代币合约
contract Token {mapping(address => uint256) private _balances;mapping(address => mapping(address => uint256)) private _allowances;uint256 private _totalSupply;event Transfer(address indexed from, address indexed to, uint256 value);event Approval(address indexed owner, address indexed spender, uint256 value);function totalSupply() public view returns (uint256) {return _totalSupply;}function balanceOf(address account) public view returns (uint256) {return _balances[account];}function transfer(address recipient, uint256 amount) public returns (bool) {_transfer(msg.sender, recipient, amount);return true;}function allowance(address owner, address spender) public view returns (uint256) {return _allowances[owner][spender];}function approve(address spender, uint256 amount) public returns (bool) {_approve(msg.sender, spender, amount);return true;}function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {_transfer(sender, recipient, amount);_approve(sender, msg.sender, _allowances[sender][msg.sender] - amount);return true;}function _transfer(address sender, address recipient, uint256 amount) internal {require(sender != address(0), "Token: transfer from the zero address");require(recipient != address(0), "Token: transfer to the zero address");require(_balances[sender] >= amount, "Token: insufficient balance");_balances[sender] -= amount;_balances[recipient] += amount;emit Transfer(sender, recipient, amount);}function _mint(address account, uint256 amount) internal {require(account != address(0), "Token: mint to the zero address");_totalSupply += amount;_balances[account] += amount;emit Transfer(address(0), account, amount);}function _approve(address owner, address spender, uint256 amount) internal {require(owner != address(0), "Token: approve from the zero address");require(spender != address(0), "Token: approve to the zero address");_allowances[owner][spender] = amount;emit Approval(owner, spender, amount);}
}// 标准 ERC20 代币合约
contract StandardToken is Token {string public name;string public symbol;uint8 public decimals;constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 initialSupply) {name = _name;symbol = _symbol;decimals = _decimals;_mint(msg.sender, initialSupply);}
}// 可升级代币合约
contract UpgradableToken is StandardToken {address public owner;constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 initialSupply) StandardToken(_name, _symbol, _decimals, initialSupply) {owner = msg.sender;}function upgrade(address newContract) public {require(msg.sender == owner, "UpgradableToken: only owner can upgrade");// 实现升级逻辑}
}// 治理代币合约
contract GovernanceToken is StandardToken {mapping(address => bool) public isGovernor;constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 initialSupply) StandardToken(_name, _symbol, _decimals, initialSupply) {isGovernor[msg.sender] = true;}function addGovernor(address governor) public {require(isGovernor[msg.sender], "GovernanceToken: only governors can add governors");isGovernor[governor] = true;}function removeGovernor(address governor) public {require(isGovernor[msg.sender], "GovernanceToken: only governors can remove governors");isGovernor[governor] = false;}
}
通过这种方式,我们避免了大量的代码重复,提高了代码的可维护性和可扩展性。
使用接口确保行为规范
在项目中,我们还定义了一些接口,确保各个合约实现特定的行为。例如,我们定义了一个 IGovernance 接口,确保治理代币合约实现特定的治理功能。
interface IGovernance {function addGovernor(address governor) external;function removeGovernor(address governor) external;function isGovernor(address account) external view returns (bool);
}
然后,我们在治理代币合约中实现了这个接口:
contract GovernanceToken is StandardToken, IGovernance {mapping(address => bool) public isGovernor;constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 initialSupply) StandardToken(_name, _symbol, _decimals, initialSupply) {isGovernor[msg.sender] = true;}function addGovernor(address governor) public override {require(isGovernor[msg.sender], "GovernanceToken: only governors can add governors");isGovernor[governor] = true;}function removeGovernor(address governor) public override {require(isGovernor[msg.sender], "GovernanceToken: only governors can remove governors");isGovernor[governor] = false;}function isGovernor(address account) public view override returns (bool) {return isGovernor[account];}
}
通过接口,我们确保了治理代币合约实现了特定的治理功能,提高了代码的规范性和一致性,https://t.me/gtokentool 。
相关文章:
掌控 Solidity:事件日志、继承和接口的深度解析
Solidity 是以太坊智能合约的主要编程语言,它的强大之处在于能够帮助开发者构建安全、高效的去中心化应用。在我参与的多个项目中,事件日志、继承和接口这三个概念始终贯穿其中,成为构建复杂智能合约的关键技术。今天就来聊聊Solidity中的错误…...
新手教学系列——善用 VSCode 工作区,让开发更高效
引言 作为一名开发者,你是否曾经在项目中频繁地切换不同文件夹,打开无数个 VSCode 窗口?特别是当你同时参与多个项目或者处理多个模块时,这种情况更是家常便饭。很快,你的任务栏上挤满了 VSCode 的小图标,切换起来手忙脚乱,工作效率直线下降。这时候,你可能会问:“有…...
Vue3 虚拟列表组件库 virtual-list-vue3 的使用
Vue3 虚拟列表组件库 virtual-list-vue3 的基本使用 分享个人写的一个基于 Vue3 的虚拟列表组件库,欢迎各位来进行使用与给予一些更好的建议😊 概述:该组件组件库用于提供虚拟化列表能力的组件,用于解决展示大量数据渲染时首屏渲…...
WebRTC实现双端音视频聊天(Vue3 + SpringBoot)
目录 概述 相关概念 双端连接整体实现步骤概述 文章代码实现注意点 STUN和TURN服务器的搭建 开发过程描述 后端开发流程 前端开发流程 效果演示 Gitee源码地址 概述 文章描述使用WebRTC技术实现一对一音视频通话。 由于设备摄像头限制(一台电脑作测试无法…...
第6章详细设计-6.9 PCB审查
6.9 PCB审查 6.9.1 布局阶段注意事项1.结构设计要求在PCB布局之前弄清楚产品的结构2.布局要求 6.9.2 布线注意事项6.9.3 接地处理(1)射频链路接地。(2)腔壳接地孔。(3)螺钉放置(需要了解结构知识…...
docker与大模型(口语化原理和实操讲解)
文章目录 一、镜像images1)下载安装2)docker images相关命令(保存、删除、上传、别名、搜索镜像) 二、容器container1)展现所有在跑的容器服务ps2)start /restart / kill / stop /rm3)exec /cp4)run/create…...
Linux之vim模式下全选命令
在Linux系统中,使用Vim编辑器进行全选操作可以通过以下几种方式实现: 1.使用键盘快捷键 按下 ”ggVG”(先按下”g”,再按下”g”,再按下”V”,最后按下”G”)可以全选当前文件内容。其中 ”g…...
云原生周刊:Kubernetes v1.32 要来了
开源项目推荐 Woodpecker Woodpecker 是一款轻量级且功能强大的 CI/CD 引擎,以其高度可扩展性和易用性著称。它支持多种版本控制系统与编程语言,能够灵活适配不同开发流程,帮助团队实现高效的持续集成与交付。无论是个人项目还是大型团队&a…...
# JVM学习
JVM JVM是什么? Java虚拟机(JVM) 是一个抽象的计算机,它是一个运行时环境,用于执行Java字节码或编译后的Java程序。JVM屏蔽了底层操作系统的差异,使得Java程序可以在任何支持JVM的操作系统上运行。 JVM能…...
【代码随想录day33】【C++复健】62.不同路径;63. 不同路径 II;343. 整数拆分;96.不同的二叉搜索树
感觉dp的题真的很适合背,当然不是死记硬背,而是当做一种模板题,出来一道新的题就往模板题上面去靠,如果套对模板的话剩下的事情其实就简单了。所以只要看一遍解法知道大致思路其实就够了,毕竟大部分dp的代码也不算难写…...
《勇者斗恶龙3:HD-2D重制版》找幽灵船攻略分享
《勇者斗恶龙3:HD-2D重制版》中的幽灵船是游戏里非常独特的一个区域,而想要找到幽灵船的话还是比较麻烦的,首先是听到关于幽灵船在世界海域上航行的传闻,包括在海盗巢穴中,但幽灵船的出现有一些具体条件。 勇者斗恶龙3…...
基于 MATLAB 的模拟退火算法详解及实现
以下是一篇更详细的关于 模拟退火算法 (Simulated Annealing) 的 MATLAB 实现的教程和代码示例,涵盖基本概念、核心思想和代码实现。 一、模拟退火算法简介 模拟退火算法(Simulated Annealing,简称 SA)是一种随机优化算法&#x…...
MQTT 服务器常用的有哪些?
MQTT(Message Queuing Telemetry Transport)是一种轻量级的消息传输协议,常用于物联网(IoT)设备之间的通信。以下是一些常用的 MQTT 服务器(也称为 MQTT Broker): 1.Eclipse Mosqui…...
【android USB 串口通信助手】stm32 源码demo 单片机与手机通信 Android studio 20241118
android 【OTG线】 接 下位机STM32【USB】 通过百度网盘分享的文件:USBToSerialPort.apk 链接:https://pan.baidu.com/s/122McdmBDUxEtYiEKFunFUg?pwd8888 提取码:8888 android 【OTG线】 接 【USB转TTL】 接 【串口(下位机 SMT32等)】 需…...
汽车资讯新探索:Spring Boot技术引领
摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了汽车资讯网站的开发全过程。通过分析汽车资讯网站管理的不足,创建了一个计算机管理汽车资讯网站的方案。文章介绍了汽车资讯网站的系统分析部分&…...
简单的MCU与FPGA通过APB总线实现通讯(fpga mcu APB):乘法器为例
测试平台: GW1N4器件内置 M1内核;并且可以设置 APB总线与fpga 逻辑进行交互; 框图: +---------------------+ | | | M1 Microprocessor | <-----------------+ | | | | +-----------------…...
css uniapp背景图宽度固定高度自适应可以重复
page {height: 100%;background-image: url(https://onlinekc.a.hlidc.cn/uploads/20241115/350f94aaf493d05625a7ddbc86c7804e.png);background-repeat: repeat;background-size: contain;} 如果不要重复 把background-repeat: repeat;替换background-repeat: no-repeat;...
深度学习--优化器
笔记内容侵权联系删 优化器 在梯度下降算法中,有各种不同的改进版本。在面向对象的语言实现中,往往把不同的梯度下降算法封装成一个对象,称为优化器。 算法改进的目的,包括但不限于: 加快算法收敛速度; 尽量避过或冲过局部极值; …...
【嵌入式】关于push老仓库到新仓库的方法
1. 背景 公司项目经常会有需要从开源项目中镜像代码过来的活,所以常常会在自己的服务器上创建一个对应的仓库,然后使用命令将期push过去。为方便日后抄命令,这里记录一下使用的命令。 2. 操作步骤 2.1. 已下载的代码push 特别提醒: 使用此脚本前请确保你修改的代码已保存…...
从线下到线上,上门洗衣服务如何实现智能化升级?
在现代快节奏生活的推动下,上门洗衣服务作为一种新兴的服务模式正逐渐崭露头角。它以其便捷性和创新性,改变了传统洗衣行业的格局,为消费者提供了全新的选择,同时也为洗衣品牌带来了新的机遇与挑战。 一、上门洗衣服务的市场现状1…...
SQL字段来源表的解析
测试例子: SELECT e.NAME, d.DEPT_NAME,d.DEPT_ID,EMP_ID,100EMP_ID100 FROM EMP e JOIN DEPT d ON e.DEPT_ID d.DEPT_ID WHERE e.EMP_ID IN (SELECT EMP_ID FROM EMP WHERE DEPT_ID 10) 代码示例: package com.test; import org.apache.calcite.jd…...
理解 Python 解释器:CPython 与 IPython 的比较及选择指南
理解 Python 解释器:CPython 与 IPython 的比较及选择指南 在选择适合自己需求的 Python 解释器时,理解 CPython 和 IPython 之间的主要差异至关重要。本文将详细解释 CPython 和 IPython 的特性、优势和适用场景,以帮助用户做出明智的选择。…...
Java NIO 深度解析:构建高效的 I/O 操作
在 Java 编程领域,I/O 操作一直是至关重要的部分,它直接影响着应用程序的性能和响应能力。Java NIO(New I/O)作为传统 I/O 的增强版本,为处理大量并发连接和高效的数据传输提供了更强大的工具和机制。本文将深入探讨 J…...
总结拓展十六:特殊采购业务——VMI采购模式
1、VMI的定义 VMI采购模式(Vendor Managed Inventory)是一种合作性策略,旨在通过供应商管理库存,使供应链中的企业和供应商双方都能获得最低成本。在这种模式下,供应商根据共享的用户企业库存和实际耗用数据&#x…...
vue2 + iview(view-design) 中封装使用 vxe-table 处理表格渲染大量数据卡顿现象
今天遇到需求,iview组件分页每页100页时候页面卡顿现象严重,改造为使用vxe-table cell-mouseenter"handleCellMouseEnter" cell-mouseleave"handleCellMouseLeave" 这两个用来处理vxe-table 内容过多鼠标悬浮上去滚动 tooltip直接…...
初学者指南:知识库问答(KBQA)多跳路径的核心与应用
初学者指南:知识库问答(KBQA)多跳路径的核心与应用 知识库问答(Knowledge Base Question Answering, KBQA)旨在利用结构化知识库(如Wikidata、Freebase)回答自然语言问题。在实际应用中&#x…...
创建springboot+vue项目相关配置问题
安装并配置jdk23 在官网下载jdk Java Downloads | Oracle 中国 下载完成后双击即可安装。 安装完成后配置环境变量 此电脑->右键->属性->高级系统设置 然后一直点击确定即可。 键盘上win r java -version 可以验证是否配置成功 下载并配置maven 在官网下…...
基于AOA算术优化的KNN数据聚类算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于AOA算术优化的KNN数据聚类算法matlab仿真。通过AOA优化算法,搜索最优的几个特征数据,进行KNN聚类,同时对比不同个数特征下…...
【机器学习】在泊松分布中,当λ值较大时,其近似正态分布的误差如何评估?
在泊松分布中,当参数 λ 较大时,其近似正态分布的有效性可以通过 中心极限定理 和误差分析来理解和评估。以下内容结合理论推导和实际案例展开说明: 1. 泊松分布的定义 泊松分布是用于建模单位时间或单位空间内随机事件发生次数的概率分布&a…...
ABAP开发-面向对象开发_2
系列文章目录 文章目录 系列文章目录[TOC](文章目录) 前言接口和类1、首先创建一个接口2、在创建的接口的基础上创建一个类PERSON3、创建子类STUDENT4、创建子类TEACHER5、SE38使用创建的类 总结 前言 接口和类 全局类 SE24 创建一个接口-》创建一个实现接口的类-》再创建两个…...
b站推广入口mba智库/官网百度
UDP数据包接收逻辑的优化修改以及对性能的影响 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <signal.h> #include <arpa/inet.h> #in…...
网站建设设计设计公司哪家好/爱站网络挖掘词
在Windows 运维过程中,经常会遇到问题,需要故障重现,以下是模拟操作系统蓝屏的方法,a) 当问题再次发生,我们可以收集full memory dump.配置的具体方法:1. 关闭 ASR 功能 防止计算机在收集dump时重启2. 在系…...
Asp网站开发入门/网络营销的推广方式
1、基本概念内容提供器的主要作用在于跨应用程序的数据共享,例如共享通讯录里的数据android 6.0 引入了运行时权限功能,对于危险权限需要通过动态申请的方式获取,否则默认失败内容提供器有两种:1、使用现有的内容提供器来获取别的…...
水果网站系统的建设与实现/河南品牌网络推广外包
PCB学习心得: (1)EJC然后输入元件的名字,比如R1,立马跳到元件的位置; (2)选择模块电路然后依次按TS,切换到PCB布板区域后按IL,用鼠标画一个矩形区域即可;...
政府门户网站建设的意义/软文广告经典案例300字
1 引言上回说到自定义扩展的第一步,是需要声明行为的类型。也就是通过实现一个行为接口,实现接口中的方法来声明行为的类型。2 附件自定义行为到Operaiton或者是Endpoint实现自定义的行为,第二步就是将自定义的行为类挂(附加&…...
做网站的主营业务/网络营销该如何发展
.m文件分为脚本文件和函数,其中函数可以接收参数和输出,脚本不可以 脚本中往往包含多个函数,是一系列命令行的集合 创建脚本文件需要使用文本编辑器,使用edit(文件名.m)打开文本编辑器 输入文件名即可运行脚本 mkdir创建文件夹…...