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类型
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中,所有的基础数据类型(如 uintbooladdress 等)在赋值传参都是值拷贝。

而其他非基础数据类型(也称为引用类型),比如**数组[]、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
// 会花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++) {
}
}

那那些情况是属于修改状态呢?除了修改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类型
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中,所有的基础数据类型(如 uintbooladdress 等)在赋值传参都是值拷贝。

而其他非基础数据类型(也称为引用类型),比如**数组[]、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
// 会花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++) {
}
}

那那些情况是属于修改状态呢?除了修改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