这里拿java来进行对比一下,java和solidity都是面向对象的编程语言,也都是静态类型的。所以写过java的再写solidity上手会比较快。因为很多概念都是相通的,但是也不要大意,solidity还是有些小坑在里面的。我们需要理清楚这些数据类型在使用时的注意事项、操作技巧、变量在赋值传参时是值拷贝还是引用传递,以及如何省钱?
一、bool、uint(uint8、uint16、uint32、…、uint256) 基础数据类型没什么看头,和java差不多。
二、address 、address payable address类型是solidity自带的数据类型,类似java中自带的类。address类型有很多常用的成员函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 // 定义address类型 address _walletAddress = 0x356faDD245d35ff8F1a207aC83BE7EEa911abeEE // address类型的成员函数 把它想成java的类和对象 _walletAddress .balance() 返回地址余额 _walletAddress .transfer() 向地址发送以太币,失败时抛出异常。只会使用2300的gas _walletAddress .send() 像地址发送以太币,失败时返回false.gas 使用无限制 // 特殊类函数 <address>.call() :通常用于合约交互,直接控制编码的方式调用合约函数。 会切换上下文,附加gas,附加value。 addr.call{value:1 ether}(””) 等价于transfer(1 ether),但是没有gas限制。失败不是发生异常,而是返回false,所以一定要检查返回值。 <address>.delegatecall() 保持上下文,不支持附加value{value: }
三、合约类型 solidity中的contract就和java中class关键字一样。都是用来定义类型的,可以理解为合约就是java中的类。合约默认继承address,所以可以将显示转为address类型,这样就能使用address类型中的成员函数。
1 2 address(this).balance() address(this).transfer(to,amount)
四、数组类型 1、数组的循环
和java不一样,java中可以直接遍历一个超大的数组,而solidity中遍历超大型数组则无法直接遍历,需要点小技巧。
2、数组如何删除某个元素?
1 2 3 4 5 6 7 8 9 10 11 12 13 function remove(uint index) public { // 获取数组长度 uint len = numbers.length; // 用最后一个元素来覆盖index的数据实现删除效果。 if( index == len -1){ numbers.pop() break; }else { numbers[index] = numbers[len - 1] numbers.pop() break; } }
五、变量传参和赋值 和其他任何编程语言一样,在对变量进行赋值或传参时需要意识到当前是值拷贝还是引用传递(传递指针)
那在solidity中赋值传参时,到底进行的是值拷贝?还是传递指针?我们该如何区分呢?
简单来说,在Solidity中,所有的基础数据类型(如 uint
、 bool
和 address
等)在赋值传参都是值拷贝。
而其他非基础数据类型(也称为引用类型),比如**数组[]、struct 和 mapping
** ,在赋值或传参时是值拷贝还是**引用传递(传递指针)**取决于两者的类型是否一样。
在使用引用类型时,我们必须使用位置存储关键字(memory,storage,calldata)来指定数据存储的位置,否则无法编译通过!有且只有类成员变量无需指定,因为它们默认是storage。我们的合约类型是不需要指定存储关键字的,因为他默认就是storage。
下面,我们用实际案例来演示,引用类型之间传参赋值到底是**引用传递(传递指针)**还是值拷贝?
总结 :相同类型之间的赋值是引用传递,其余都是值拷贝。其中sotrage 到 storage 的赋值是值拷贝
以下是引用传递
1、memory和memory之间的赋值是引用传递。
2、storage和local storage之间的赋值是引用传递。
以下是值拷贝
1、storage和memory之间的赋值是值拷贝,反过来memory赋值给storage是不行的!
2、sotrage 到 storage 的赋值是值拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 pragma solidity ^0.8.0; contract memorytest{ uint public a ; uint256[] public b ; uint256[] public c ; //情况1:值类型的都是值拷贝 function add1(uint t) private{ // 对t的修改不会影响到a a = t; t = 3; } function testAdd1() public { add1(2); } //情况2:memory和memory之间的赋值是引用 function add2(uint256[] memory t) private{ b = t; // 对t的修改不会影响到b,但是会影响到temp t[0] = 999; } function testAdd2() public returns(uint256[] memory temp ) { uint256[] memory temp = new uint256[](3); temp[0] = 100; temp[1] = 200; temp[1] = 300; add2(temp); return temp; } // 情况3: storage和storage之间的赋值是引用传递 function add3(uint256[] storage cc) private{ uint256[] storage tt; tt = cc; //对tt的操作会影响到c tt[0]= 222; //把tt赋值给b storage和sotrage之间的赋值 b = tt; b[0] = 333; //对b的改变是否会对tt改变是否会影响c的值? c[0]是333 还是222? } function testAdd3() public { c = new uint256[](3); c[0] = 100; c[1] = 200; c[2] = 300; add3(c); } //情况4: sotrage和memory之间的赋值是值传递 function add4(uint256[] memory c) private{ c[0] = 888; } function testAdd4() public { c = new uint256[](3); c[0] = 100; c[1] = 200; c[2] = 300; add4(c); } //情况5: memory和storage之间是无法传递的 // function add5(uint256[] storage c) private{ // c[0] = 888;// 对c的修改 无法影响到cc // } // function testAdd5() public returns(uint256[] memory cc) { // uint256[] memory cc = new uint256[](3); // cc[0] = 100; // cc[1] = 200; // cc[2] = 300; // add5(cc); // return cc; // } }
六、如何省钱? 花不花gas取决于两个层面,一是你有没有修改storage变量 的状态?二是你函数调用有没有声明为pure或者view。两个条件都需要满足,否则你调用一次函数就会花一次钱。
1 2 3 4 5 6 7 8 9 10 11 12 // 会花gas function iterateMapping(uint256 startIndex) public { for (uint256 i = startIndex; i < endIndex; i++) { } } // 不会花gas function iterateMapping2(uint256 startIndex) public view { uint256 endIndex2 = startIndex+ 10 ; for (uint256 i = startIndex; i < endIndex2; i++) { } }
2023-5-12
在solidity中,用户调用的你的函数花不花gas主要取决于两个条件,有没有修改状态和你的函数是不是pure和view函数。这两个条件必须同时满足时,用户调用你的函数才不会花gas,否则要花gas。
1 2 3 4 5 6 7 8 9 10 11 12 function iterateMapping (uint256 startIndex ) public { for (uint256 i = startIndex; i < endIndex; i++) { } } function iterateMapping2 (uint256 startIndex ) public view { uint256 endIndex2 = startIndex+ 10 ; for (uint256 i = startIndex; i < endIndex2; i++) { } }
那那些情况是属于修改状态呢?除了修改sotrage类型的变量是一定是修改状态外,以下几种情况也是会修改状态产生gas的。
1、修改stroage状态变量
2、发出事件 emit event
3、创建合约、销毁合约selfdestruct
4、支付send transfer操作
那pure和view这两个有什么区别吗?这两个关键字在消耗gas层面上都是不花gas的,区别主要在于业务上。pure函数更多的是工具函数,用pure函数修饰的函数往往只是提供工具,它不进行读状态变量。而view函数主要用在我们的业务函数里,业务函数一般要对状态变量进行读取的。
下面对pure和view这俩做一个对比,
solidity语言特性之数据类型 这里拿java来进行对比一下,java和solidity都是面向对象的编程语言,也都是静态类型的。所以写过java的再写solidity上手会比较快。因为很多概念都是相通的,但是也不要大意,solidity还是有些小坑在里面的。我们需要理清楚这些数据类型在使用时的注意事项、操作技巧、变量在赋值传参时是值拷贝还是引用传递,以及如何省钱?
一、bool、uint(uint8、uint16、uint32、…、uint256) 基础数据类型没什么看头,和java差不多。
二、address 、address payable address类型是solidity自带的数据类型,类似java中自带的类。address类型有很多常用的成员函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 address _walletAddress = 0x356faDD245d35ff8F1a207aC83BE7EEa911abeEE _walletAddress .balance () 返回地址余额 _walletAddress .transfer () 向地址发送以太币,失败时抛出异常。只会使用2300 的gas _walletAddress .send () 像地址发送以太币,失败时返回false .gas 使用无限制 <address>.call () :通常用于合约交互,直接控制编码的方式调用合约函数。 会切换上下文,附加gas,附加value。 addr.call {value :1 ether}(””) 等价于transfer(1 ether),但是没有gas限制。失败不是发生异常,而是返回false ,所以一定要检查返回值。 <address>.delegatecall () 保持上下文,不支持附加value{value : }
三、合约类型 solidity中的contract就和java中class关键字一样。都是用来定义类型的,可以理解为合约就是java中的类。合约默认继承address,所以可以将显示转为address类型,这样就能使用address类型中的成员函数。
1 2 address(this).balance() address(this).transfer(to,amount)
四、数组类型 1、数组的循环
和java不一样,java中可以直接遍历一个超大的数组,而solidity中遍历超大型数组则无法直接遍历,需要点小技巧。
2、数组如何删除某个元素?
1 2 3 4 5 6 7 8 9 10 11 12 13 function remove(uint index) public { // 获取数组长度 uint len = numbers.length; // 用最后一个元素来覆盖index的数据实现删除效果。 if( index == len -1){ numbers.pop() break; }else { numbers[index] = numbers[len - 1] numbers.pop() break; } }
五、变量传参和赋值 和其他任何编程语言一样,在对变量进行赋值或传参时需要意识到当前是值拷贝还是引用传递(传递指针)
那在solidity中赋值传参时,到底进行的是值拷贝?还是传递指针?我们该如何区分呢?
简单来说,在Solidity中,所有的基础数据类型(如 uint
、 bool
和 address
等)在赋值传参都是值拷贝。
而其他非基础数据类型(也称为引用类型),比如**数组[]、struct 和 mapping
** ,在赋值或传参时是值拷贝还是**引用传递(传递指针)**取决于两者的类型是否一样。
在使用引用类型时,我们必须使用位置存储关键字(memory,storage,calldata)来指定数据存储的位置,否则无法编译通过!有且只有类成员变量无需指定,因为它们默认是storage。我们的合约类型是不需要指定存储关键字的,因为他默认就是storage。
下面,我们用实际案例来演示,引用类型之间传参赋值到底是**引用传递(传递指针)**还是值拷贝?
总结 :相同类型之间的赋值是引用传递,其余都是值拷贝。其中sotrage 到 storage 的赋值是值拷贝
以下是引用传递
1、memory和memory之间的赋值是引用传递。
2、storage和local storage之间的赋值是引用传递。
以下是值拷贝
1、storage和memory之间的赋值是值拷贝,反过来memory赋值给storage是不行的!
2、sotrage 到 storage 的赋值是值拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 pragma solidity ^0.8.0; contract memorytest{ uint public a ; uint256[] public b ; uint256[] public c ; //情况1:值类型的都是值拷贝 function add1(uint t) private{ // 对t的修改不会影响到a a = t; t = 3; } function testAdd1() public { add1(2); } //情况2:memory和memory之间的赋值是引用 function add2(uint256[] memory t) private{ b = t; // 对t的修改不会影响到b,但是会影响到temp t[0] = 999; } function testAdd2() public returns(uint256[] memory temp ) { uint256[] memory temp = new uint256[](3); temp[0] = 100; temp[1] = 200; temp[1] = 300; add2(temp); return temp; } // 情况3: storage和storage之间的赋值是引用传递 function add3(uint256[] storage cc) private{ uint256[] storage tt; tt = cc; //对tt的操作会影响到c tt[0]= 222; //把tt赋值给b storage和sotrage之间的赋值 b = tt; b[0] = 333; //对b的改变是否会对tt改变是否会影响c的值? c[0]是333 还是222? } function testAdd3() public { c = new uint256[](3); c[0] = 100; c[1] = 200; c[2] = 300; add3(c); } //情况4: sotrage和memory之间的赋值是值传递 function add4(uint256[] memory c) private{ c[0] = 888; } function testAdd4() public { c = new uint256[](3); c[0] = 100; c[1] = 200; c[2] = 300; add4(c); } //情况5: memory和storage之间是无法传递的 // function add5(uint256[] storage c) private{ // c[0] = 888;// 对c的修改 无法影响到cc // } // function testAdd5() public returns(uint256[] memory cc) { // uint256[] memory cc = new uint256[](3); // cc[0] = 100; // cc[1] = 200; // cc[2] = 300; // add5(cc); // return cc; // } }
六、如何省钱? 花不花gas取决于两个层面,一是你有没有修改storage变量 的状态?二是你函数调用有没有声明为pure或者view。两个条件都需要满足,否则你调用一次函数就会花一次钱。
1 2 3 4 5 6 7 8 9 10 11 12 // 会花gas function iterateMapping(uint256 startIndex) public { for (uint256 i = startIndex; i < endIndex; i++) { } } // 不会花gas function iterateMapping2(uint256 startIndex) public view { uint256 endIndex2 = startIndex+ 10 ; for (uint256 i = startIndex; i < endIndex2; i++) { } }
2023-5-12
在solidity中,用户调用的你的函数花不花gas主要取决于两个条件,有没有修改状态和你的函数是不是pure和view函数。这两个条件必须同时满足时,用户调用你的函数才不会花gas,否则要花gas。
1 2 3 4 5 6 7 8 9 10 11 12 function iterateMapping (uint256 startIndex ) public { for (uint256 i = startIndex; i < endIndex; i++) { } } function iterateMapping2 (uint256 startIndex ) public view { uint256 endIndex2 = startIndex+ 10 ; for (uint256 i = startIndex; i < endIndex2; i++) { } }
那那些情况是属于修改状态呢?除了修改sotrage类型的变量是一定是修改状态外,以下几种情况也是会修改状态产生gas的。
1、修改stroage状态变量
2、发出事件 emit event
3、创建合约、销毁合约selfdestruct
4、支付send transfer操作
那pure和view这两个有什么区别吗?这两个关键字在消耗gas层面上都是不花gas的,区别主要在于业务上。pure函数更多的是工具函数,用pure函数修饰的函数往往只是提供工具,它不进行读状态变量。而view函数主要用在我们的业务函数里,业务函数一般要对状态变量进行读取的。
下面对pure和view这俩做一个对比,
状态读取
状态修改
用户调用是否花gas
合约调用是否会花gas
view
是
否
否
是
pure
否
否
否
是
本文转载自:https://ouhuang.notion.site/solidity-2b5eeb3fe9c8472f85bc4628b1d6e1d9