Solidity学习1

在线编辑器:remix https://remix.ethereum.org/

Solidity的基础知识

  1. 文件名后缀:sol

  2. 文件结构:

    	// SPDX-License-Identifier: MIT				//声明
    	pragma solidity ^0.8.7;						//声明solidity版本
    
    	contract Hello{								//合约名
    		 string public name = "hello";
    
    	}
    
  3. 变更分为 本地变量(存函数内的,调用时才有),链上变量(公共变量),全局变量(比如链的时间戳,调用合约的地址变量)

  4. 常量:命名规范是全大写字母加下划线,是不能修改的。例: address public constant MY_ADDRESS = 123;

  5. 不可变变量,类似于常量,只允许在合约的构造函数中修改,命名规范是全大写字母加下划线。例:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.26;
    
    contract Immutable {
    	address public immutable myAddr;
    	uint256 public immutable myUint;
    	constructor(uint256 _myUint) {
    		myAddr = msg.sender;
    		myUint = _myUint;
    	}
    }
    
  6. 映射 mapping(_KeyType => _ValueType) public myMapping; 类似数组,_KeyType:键名(可以是任何基本数据类型(如 uint, address 等)或合约类型。),_ValueType:键值,myMapping:数组变量

    1. 访问效率:映射提供非常快速的键值对访问。在内部,它们使用哈希表实现,使得查找、插入和删除操作的时间复杂度接近O(1)。

    2. 内存使用:由于映射使用哈希表,它们可能会消耗较多的内存,特别是当存储大量的键值对时。因此,在设计智能合约时需要考虑内存使用。

    3. 不可迭代:与数组不同,映射不是可迭代的。这意味着你不能直接遍历一个映射来访问所有的键或值。要遍历所有条目,你需要使用额外的数据结构(如数组)来辅助存储键或值的列表。

    4. 默认值:如果尝试访问一个尚未设置的键,Solidity将返回该类型的默认值。例如,对于uint类型,默认值是0;对于address类型,默认值是0x0000000000000000000000000000000000000000。

  7. 以映射为KeyType,相当于二维数组。例子:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.26;
    
    contract Immutable {
    	mapping (address => mapping(int => int)) public myAdd;
    
    	function get_myAdd(address _address) public view returns(int){
    		return myAdd[_address][1];
    	}
    
    	function set_MyAddress(int money) public{
    		myAdd[msg.sender][1] = money;
    	}
    
    	function del(address _address) public {
    		delete myAdd[_address][1];
    	}
    }
    
  8. 数组操作,例子如下:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.26;
    
    contract Immutable {
    	int[] public myNums;
    	uint[] public myMoney = [1,2,3,4];		//设置初始值
    	int[4] public myArray;			//固定长度的数组,不能使用push,pop
    
    	function pusharr(int x) public{
    		myNums.push(x);				//在数组尾添加
    	}
    
    	function poparr() public{
    		myNums.pop();			//在数组尾删除一个
    	}
    	
    	function arrlength() public view returns (uint256){
    		return myNums.length;		//获取数组长度
    	}
    
    	function delarr(uint x) public{
    		delete myNums[x];			//并不删除指定数组下标,只是把值设置为默认值0
    	}
    }
    
  9. 有几种方法可以返回函数的输出。

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.26;
    
    contract Function {
    	// 返回多个值
    	function returnMany() public pure returns (uint256, bool, uint256) {
    		return (1, true, 2);
    	}
    
    	// 给多个返回值命名
    	function named() public pure returns (uint256 x, bool b, uint256 y) {
    		return (1, true, 2);
    	}
    
    	// 给多个返回值命名
    	// 这种情况下可以不使用return
    	function assigned() public pure returns (uint256 x, bool b, uint256 y) {
    		x = 1;
    		b = true;
    		y = 2;
    	}
    
    	//  在调用返回多个值的另一个函数时,使用解构赋值。
    	function destructuringAssignments()
    		public
    		pure
    		returns (uint256, bool, uint256, uint256, uint256)
    	{
    		(uint256 i, bool b, uint256 j) = returnMany();
    
    		// Values can be left out.
    		(uint256 x,, uint256 y) = (4, 5, 6);
    
    		return (i, b, j, x, y);
    	}
    
    	// 输入或输出均不能使用映射(map)。
    
    	// 可以使用数组作为输入
    	function arrayInput(uint256[] memory _arr) public {}
    
    	// 可以使用数组进行输出
    	uint256[] public arr;
    
    	function arrayOutput() public view returns (uint256[] memory) {
    		return arr;
    	}
    }
    
    // 使用键值输入调用函数
    contract XYZ {
    	function someFuncWithManyInputs(
    		uint256 x,
    		uint256 y,
    		uint256 z,
    		address a,
    		bool b,
    		string memory c
    	) public pure returns (uint256) {}
    
    	function callFunc() external pure returns (uint256) {
    		return someFuncWithManyInputs(1, 2, 3, address(0), true, "c");
    	}
    
    	function callFuncWithKeyValue() external pure returns (uint256) {
    		return someFuncWithManyInputs({
    			a: address(0),
    			b: true,
    			c: "c",
    			x: 1,
    			y: 2,
    			z: 3
    		});
    	}
    }
    
  10. Solidity智能合约的Pure 和 View
    Pure, view 和 payable约束函数的行为,如果函数没有被特定的关键词约束,那么它是既可读又可写的,View函数只可读,不可以改变区块链数据的状态。pure函数更严格,既不可读也不可写。 payable函数则可以用来接收以太币。

    View函数是只读的,不能修改区块链的状态,Getter函数默认是只读的 ,View函数无法:

    1. 写入状态变量,更改区块链的状态
    2. 发起事件events
    3. 创建智能合约
    4. 发送以太币

    Pure函数约束性更强,不能修改或读取区块链的状态变量,pure函数无法:

    1. 读取区块链上的数据
    2. 访问账户
    3. 调用非pure函数
  11. Solidity:require()、assert()、revert()区别到底在哪?
    Require()使用场景

    1. 验证用户输入,即: require(input<20); 
    2. 验证外部合约响应,即: require(external.send(amount)); 
    3. 执行合约前,验证状态条件,即: require(block.number > SOME_BLOCK_NUMBER) 或者 require(balance[msg.sender]>=amount)
    4. require应该被最常使用到;一般用于函数的开头处。
    

    Assert()使用场景

    检查 overflow/underflow,
    例:c = a+b; assert(c > b);
    检查非变量(invariants),
    例:assert(this.balance >= totalSupply);
    验证改变后的状态,不应该发生的条件
    一般地,尽量少使用 assert 调用,如要使用assert 应该在函数结尾处使用
    基本上,require() 应该被用于函数中检查条件,assert() 用于预防不应该发生的情况,但不应该使条件错误。
    另外,“除非认为之前的检查(用 if 或 require )会导致无法验证 overflow,否则不应该盲目使用 assert 来检查 overflow
    

    Revert()特点

    允许你返回一个值;
    例:revert(‘Something bad happened’);等价于require(condition, ‘Something bad happened’);
    它会把所有剩下的gas退回给caller
    

    Require对比Revert,复杂时候用Revert
    Gas消耗 两者一样都会向剩余的gas费返还给调用者 使用差异 revert()处理与 require() 同样的类型,但是需要更复杂处理逻辑的场景 如果有复杂的 if/else 逻辑流,那么应该考虑使用 revert() 函数而不是require()。记住,复杂逻辑意味着更多的代码。
    总结:Assert就是内部判断直接中断,没有返回值;require就是相当只有一个IF判断时用的,revert就相当于复杂判断时使用if/elseif 时使用;
    例子:

    function test(uint x) public pure{
    //多用于判断函数传参的
        require(x > 10, "message");
    }
    
    function testrevert(uint x) public pure{
    //用于多个判断的复杂逻辑判断的
        if(x > 10){
            revert("munt 10");
        }else if(x > 20){
            revert("munt 20");
        }
    }
    
    function testassert(uint x) public pure{
    //没有返回值,只能中断
        assert(x < 8);
    }
    
  12. 函数修饰符
    修饰符是可以在函数调用之前和/或之后运行的代码。
    修饰符可用于:
    • 限制访问
    • 验证输入
    • 防止重入黑客攻击

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.26;
    
    contract FunctionModifier {
    	address public owner;
    	uint256 public x = 10;
    	bool public locked;
    
    	constructor() {
    		// 将交易发送者设置为合约的所有者。
    		owner = msg.sender;
    	}
    
    	// 修饰器,用于检查调用者是否为合约的所有者。
    	modifier onlyOwner() {
    		require(msg.sender == owner, "Not owner");
    		// 下划线是一个特殊字符,仅用于函数修饰符内部,它告诉 Solidity 执行其余代码。
    		_;
    	}
    
    	// 修饰器可以接收输入。此修饰器用于检查传入的地址是否不是零地址。
    	modifier validAddress(address _addr) {
    		require(_addr != address(0), "Not valid address");
    		_;
    	}
    
    	function changeOwner(address _newOwner)
    		public
    		onlyOwner
    		validAddress(_newOwner)
    	{
    		owner = _newOwner;
    	}
    
    	// 修饰符可以在函数调用之前和/或之后调用。
    	// 此修饰符用于防止函数在其仍在执行时被调用。
    	modifier noReentrancy() {
    		require(!locked, "No reentrancy");
    
    		locked = true;
    		_;
    		locked = false;
    	}
    
    	function decrement(uint256 i) public noReentrancy {
    		x -= i;
    
    		if (i > 1) {
    			decrement(i - 1);
    		}
    	}
    }
    
  13. 事件
    Events允许记录到 Ethereum 区块链。事件的一些用例包括:
    • 侦听事件和更新用户界面
    • 一种廉价的存储形式

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.26;
    
    contract Event {
    	// 事件声明
    	// 最多可对3个参数进行索引。
    	// 索引参数可帮助你根据索引参数筛选日志
    	event Log(address indexed sender, string message);
    	event AnotherLog();
    
    	function test() public {
    		emit Log(msg.sender, "Hello World!");
    		emit Log(msg.sender, "Hello EVM!");
    		emit AnotherLog();
    	}
    }
    
posted @ 2025-04-21 17:46  张志健  阅读(16)  评论(0)    收藏  举报