整型uint 和 int
uint 是默认指uint256,只能是非负的整数,以太坊严格规范,金额类型的数据都使用uint
int 默认是int256,可正可负。
uint和int之间不能相互转化
对于智能合约一定要合理安排整数类型声明,防止整数溢出的问题。
固定长度bytes类型
和uint是一样的,bytes默认是bytes1,byte1相当于uint8,bytes可以从1写到bytes32,bytes32其实就是uint256,它的长度其实就是对应的后缀数字,1-32,以一增加,但是其长度属性是不可以修改的。
动态长度bytes类型
定义方式需要bytes对象
bytes name = new bytes(2) //定义一个长度为2的bytes类型
可以修改每个值,也可以修改长度,或通过数组的push方法也可以改变长度
name[0] = 0x7a
name[1] = 0x88
name.length=5
name.push(0x99)
bytes类型一般用于十六进制的数据存储,固定长度bytes数据可以进行长度的转换,通过bytes1,截取长度为1,bytes2截取长度为2,如果截取长度大于该bytes字节的长度,那么补0。
bytes name = 0x7a68
bytes1(name) //0x7a
bytes2(name) //0x7a68
bytes3(name) //0x7a6800
固定长度bytes数据可以转动态长度字节数组,通过new bytes(bytes)的方式,然后创建一个新的动态长度bytes对象,通过for循环的形式将每个字节添加到新的bytes对象即可。
bytes2 name = 0x7a68
bytes newName = new bytes(name.length)
for(uint i;i<name.length;i++){
newName[i] = name[i]
}
string类型
string类型不能直接获取长度,必须通过转为bytes类型后才可以,通过bytes的[index]获取内容,获取到的是十六进制的数据,修改单个字节也是通过bytes类型。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Demo{
string name = "name";
function getLength() view public returns (uint){
return bytes(name).length;
}
function getName() view public returns (bytes memory){
return bytes(name);
}
function changeName() public returns (string memory){
bytes(name)[0] = 'L';
return name;
}
}
如果name为汉字,那么所一个汉字占三个,特殊字符占一个字节。
动态长度bytes转string:
bytes name = new Bytes(2);
name[0] = 0x7a;
name[1] = 0x68;
function transfer() public view returns(string){
return string(name);
}
固定长度bytes转string:分两步,固定长度bytes转为动态长度bytes,将动态长度bytes转为string
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Transfer{
bytes2 name = 0x7a68;
function changeToDynamic() public returns (string memory){
bytes memory newName = new bytes(name.length);
//转为动态数组
for(uint i=0;i<name.length;i++){
newName[i] = name[i];
}
return this.transferTostring(newName);
}
function transferTostring(bytes memory _name) public returns (string memory){
return string(_name); //动态长度字节转为字符串
}
}
固定数组
可以获取长度,但是长度length是不可以改变的
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Array{
uint [5] arr = [1,2,3,4,5];
function Init () public{
arr[0] = 1;
arr[1] = 100;
}
}
动态数组:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Array{
uint [] arr = [1,2,3,4,5];
function Init () public{
arr[0] = 1;
arr[1] = 100;
}
function getArray() view public returns (uint [] memory){
return arr;
}
function changeLength() public {
arr.push(); //添加长度
}
function changeLength2() public {
arr.pop(); //减少数组长度
}
}
固定长度二维数组
定义语法法与其他语言有区别,一维数组的长度取决于后一个长度参数,二维长度取决于第一个参数,但是获取时和其他的一致,下面固定长度的也不能修改长度:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract TwoArray {
uint256[2][3] arr = [[1, 2], [3, 4], [5, 6]];
function add() view public returns (uint256) {
uint256 sum=0;
for (uint256 i = 0; i < arr.length; i++) {
for (uint256 j = 0; j < arr[0].length; j++) {
sum += arr[i][j];
}
}
return sum;
}
function change() public returns (uint256 [2][3] memory) {
for (uint256 i = 0; i < arr.length; i++) {
for (uint256 j = 0; j < arr[0].length; j++) {
arr[i][j]=4;
}
}
return arr;
}
}
动态长度二维数组
可修改长度。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract TwoArray {
uint256[][] arr = [[1, 2], [3, 4], [5, 6]];
function add() public view returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < arr.length; i++) {
for (uint256 j = 0; j < arr[0].length; j++) {
sum += arr[i][j];//求和
}
}
return sum;
}
function change() public returns (uint256[][] memory) {
for (uint256 i = 0; i < arr.length; i++) {
for (uint256 j = 0; j < arr[0].length; j++) {
arr[i][j] = 4; //改值
}
}
return arr;
}
function getData() public view returns (uint256[][] memory) {
return arr;
}
//修改长度
function changeLength() public returns (uint256[][] memory) {
arr[0].push(3);
arr[1].pop();
return arr;
}
}
数组字面量:
不可以修改数组的内容以及长度 。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Array{
function getArray() pure public returns ( uint8[3] memory){
return [1,2,3];
}
function getArray2() pure public returns ( uint[3] memory){
return [uint(1),2,3];
}
}
上图使用字面量的形式返回数组,在定义数组时,uint会默认最小匹配原则,从uint8开始,如果存储数据超过255,则使用uint256,所以在上述function的返回值中,使用uint8数据类型。第二种方式可以指定存储数据的类型,给数组第一个元素添加即可,剩下的元素则默认为uint数据,此时默认是uint256。
address类型
在比特币的网络中是没有账户地址的概念,但在以太坊中是有的,包括合约账户地址和外部账户地址,是一串16进制的数据字符串。address是通过uint160进行存储的,两种类型可以相互强制转化,地址之间是可以进行比较大小的。
address account = 0x4D26100f60525C5Ef35DA5B4dFc7B80d55050574
uint160 num = uint160(account)
address addr = address(num)
payable,支付功能(remix调试)
函数中需要使用payable关键字,我们可以通过这个函数给合约地址转账,这里的金额为wei的单位。可以看到当前的this其实就是当前合约的地址,既然我们可以通过this.balance获取余额,那么我们也可以使用地址来获取不同账户的余额。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract PayTest{
function pay() payable public {
}
function getBalance() public view returns(uint){
return address(this).balance;
}
function geThis() public view returns(address){
return address(this);
}
function getOtherBalance(address account) public view returns(uint){
return account.balance;
}
}
给不同的账户进行转账
function transfer() public payable {
address account = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB;//其他的账户
payable (account).transfer(msg.value);
}
如果此transfer函数函数内部函数体为空,那么就会默认转到当前的合约账户,同时也可以修改为有参数的函数,不使用msg.value。如果出现msg.value的以太币数量大于代码里转账的以太币数量,那么多余的数量会直接转至当前合约账户。
常用全局变量
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Global{
function getDlobal() public view returns (address){
return msg.sender;
}
}
msg.sender(address):合约调用者账户地址。
msg.data(bytes):完整的调用数据。
msg.sender(address):当前调用发起人的地址。
msg.value(address):这个消息所携带的以太币,单位是wei。
block.coinbase(address):当前矿工地址。
block.difficulty(uint):当前矿的难度。
block.number(uint):当前区块号。
block.timestamp(uint):时间戳。
now(uint):当前块的时间戳。
tx.gasprice(uint):交易的gas价格。
tx.origin(address):交易的发送者。
send函数
function send() public payable returns(bool){
address account = 0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB;
return payable (account).send(msg.value);
}
一般不用,底层函数会有点问题,有时候出现问题也不会报错,与transfer的用法一样。
调用递归深度不能超过1024,如果gas不够会执行失败,使用这个方法要检查结果是否成功,transfer相对比较安全。
mapping 映射
一对一的映射关系,一个mappig对象可以有多个数据,但是必须遵循相应的映射关系。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Mapping{
mapping(address => uint) id;
mapping( uint => string) name;
uint sum = 0;
function register(string memory _name) public {
sum++;
id[msg.sender] = sum;
name[sum] = _name;
}
function getId(address _address) public view returns (uint){
return id[_address];
}
function getName(uint _id) public view returns (string memory){
return name[_id];
}
}
函数
遵循以下模版条件
function funName(paramType paramName) { public | internal | external | public} [pure | constant | view | payable] [ returns (type) ]
重载:函数名相同,传入参数不同会有不同的功能,不考虑函数返回值是否相同。但是要注意如果参数类型为uint以及uint8,如果传参255以内的数据,会出现报错的情况,因为两者都包含对应的数据,如果传参256就不会报错了,uint8的最大值就是255。
还有个特殊的类型,uint160和address类型本质是一样的,所以这两个无论何时报错的。不可以作为判断的条件
function test(address _address) public{}
function test(uint160 _id) public{}
function test2(uint id) public{}
function test2(uint8 id) public{}
函数传参
在进行传参时,可以正常传参,也可以以对象的形式传参(可以不考虑传参的顺序),在直接调用时可以只传一个参数(函数设置两个参数),但是如果在其他函数中调用时,必须严格遵守传入所有的参数。
函数返回值
返回值要有数据类型,同时可以有参数名称,也可以没有,有个小技巧,修改函数参数不用return。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Return{
function mul() public pure returns(uint num){
num = 100;
}
function mul2() public pure returns(uint num){
num = 100;
return 10
}
function getResult(uint a,uint b) public pure returns(uint add,uint x){
add = a+b;
x = a*b;
}
}
如果Return和参数修改都存在的话,以Return的值为准,同时可以返回多个参数,可以进行简化:
function getResult(uint a,uint b) public pure returns(uint add,uint x){
return (a+b,a*b)
}
可以用于值的交换。
作用域
同一作用域中不能重复定义相同变量,遵循作用域和作用域规则。函数内部创建变量,默认是storage类型,但是storage类型不会释放,储存在链上,所以我们要声明存储类型,一般为memory或calldate,调完函数就直接释放。
权限修饰
public,internal,external函数可以被继承,private只能合约内部使用,不能被继承,外部不能被调用。
public在合约的内部外部,子合约都可以调用。
internal不能被外部调用,但是可以在合约内部以及子合约的内部使用。
external:只能在合约外部调用,继承的合约内部也不可以调用。但也可以通过this来调用,在这里是通过地址来调用,原理也是通过外部的形式调用。
在new关键字下创建的合约对象也可以使用external方法。
继承:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract father{
function test() public pure returns(string memory){
return "Father";
}
}
contract son is father{
function oops() public pure returns(string memory){
return test();
}
}
contract Test{
father f =new father();
function opt() public view returns(string memory){
return f.test();
}
}
constant关键字
函数内部的constant关键字,在4.0版本中和view关键字等价,在5.0版本被废弃,不消耗gas,所以只做了解。
可以定义全局常量,值不可以修改,局部常量是不存在的。也支持bytes-bytes32的字节类型。
function opt() public constant returns(string memory){
return f.test();
}
uint constant num = 100
bytes32 constant num2 = 0x78
构造函数constructor
构造函数只能有一个,在合约创建时执行一次,类似于初始化,可以接受参数,为参数做初始化的作用
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Transfer{
uint public a;
constructor(uint _a){
a=_a;
}
}
函数modifier
能够为函数附加判断条件
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Transfer{
address public owner;
uint public num;
constructor(){
owner = msg.sender;
}
modifier OnlyOwner{
require(msg.sender == owner);
_;
}
//附加了modifier,会先执行判断当前操作人是否是合约的部署者,如果不是就不会执行下面代码
function changeNum(uint _num) public OnlyOwner{
num = _num;
}
}
之前写过的mapping注册的方法,由于我们可以不断的使用同一个账号注册,导致对应的id在一直不停地变化,所以应该是一个账号注册一条信息,所以我们可以添加限制条件判断:
1.直接在函数内部使用require判断。
2.使用modifier来添加限制。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract ModifierMappig{
mapping(address => uint) id;
mapping( uint => string) name;
uint sum = 0;
modifier control{
//require(id[msg.sender]==0);
_;
}
function register(string memory _name) public control{
require(id[msg.sender]==0);
sum++;
id[msg.sender] = sum;
name[sum] = _name;
}
function getId(address _address) public view returns (uint){
return id[_address];
}
function getName(uint _id) public view returns (string memory){
return name[_id];
}
}
modifier也可以传递参数来添加限制条件:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Modifier2{
string name;
uint level = 3;
modifier control(uint needLevel){
require(level>needLevel);
_;
}
function changeName() public control(2){
require(level>2);
name = "jerry";
}
}
函数可以有多个modifier,modifier执行顺序:
下图a =100会替换 _; 所以a最终的值为2.
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Modifier2{
uint public a = 0;
modifier control{
a=1;
_;
a=2;
}
function test() public control{
a=100;
}
}
多重modifier执行:
顺序为:a=1 a=3 a=100 a=4 a=2 具体的执行顺序可以理解为一个袋状,有点像二次函数抛物线的感觉,函数体的代码就像是在顶点处纸执行,先正向走每个modifier的上层(_; 以上的代码),然后是最后一个modifier的_;(也就是函数体代码);最后逆向执行每个modifier的下层(_; 以下的代码)。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Modifier2{
uint public a = 0;
modifier control{
a=1;
_;
a=2;
}
modifier control2{
a=3;
_;
a=4;
}
modifier control3{
a=5;
_;
a=6;
}
function test() public control control2{
a=100;
}
}
合约继承
使用is关键字,可以继承父亲的属性,包括函数
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract father{
uint money = 10000;
function dahan() public pure returns(string memory){
return "father";
}
}
contract son is father{
function getMoney() view public returns (uint){
return money;
}
function test() public pure returns(string memory) {
return dahan();
}
}
合约连续继承,可以多层的被继承。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract grandFather{
uint gudong = 200;
}
contract father is grandFather{
uint money = 10000;
function dahan() public pure returns(string memory){
return "father";
}
}
contract son is father{
function getMoney() view public returns (uint,uint){
return (money,gudong);
}
function test() public pure returns(string memory) {
return dahan();
}
}
继承中的权限:
合约中的不添加任何修饰符,默认都是可以被继承的。
public属性:属性和函数能够被继承
internal属性:属性和函数能够被继承
external:属性不存在此修饰符,函数中只能被外部访问,函数也可以被继承,可以通过this来调用。
private:私有属性,只有父亲合约可以访问使用,属性和函数都不能被继承访问。
pure:函数修饰符,不会读取全局变量,更不会修改全局变量。
getter函数
全局变量如果添加了pubic修饰符,那么会自动默认生成一个get方法供外部使用,等价于一个同名external函数。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract getter{
uint public num = 100;
mapping (uint=>string) public map;
function test() external view returns(uint){
return this.num();
}
function test2() external{
map[2] = "Jerry";
}
function test3() external view returns(string memory){
return this.map(2);
}
}
复杂mappng:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract getter{
mapping (uint=>mapping(uint=>mapping(uint=>string))) public map;
function test() public {
map[0][1][2]="Jerry";
}
}
重写(继承重载)
子合约能够重写父合约的方法,直接定义相同名称的属性或方法即可。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract father {
function dahan() virtual public pure returns(string memory){
return "father";
}
}
contract son is father{
function dahan() override pure public returns (string memory){
return "son";
}
}
注意以下几点:
- 被重写的函数必须标注为
virtual
,表示当前合约的继承者可以重写该函数。 - 重写别人的函数必须标注为
override
,表示我重写了父合约的函数。 - 对于属性的重写在新版中已经不允许修改,会报变量已经声明的错误。
多重继承
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract father {
uint money=100;
function dahan() public pure returns(string memory){
return "father";
}
}
contract mother {
uint car=100000;
}
contract son is father,mother{
}
多个继承属性以及方法的重复,在新版中也会报错,需要再子合约内部重写该方法。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract father {
uint money=100;
uint car = 3;
function dahan() virtual public pure returns(string memory){
return "father";
}
}
contract mother {
function dahan() virtual public pure returns(string memory){
return "mother";
}
}
contract son is father,mother{
function dahan() override(father,mother) public pure returns (string memory){
}
}
父子合约的传参
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract father {
uint money;
constructor(uint _money){
money=_money;
}
}
contract son is father(1000){
}
contract son2 is father{
constructor (uint _money) father(_money){
}
}
析构
只有合约的拥有者才能清理删除合约,要注意在使用自毁函数selfdestruct()时,参数必须为payable。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract trash{
address payable owner;
constructor(){
owner = payable(msg.sender);
}
function kill() public {
require(msg.sender==owner);
selfdestruct(owner);
}
}
struct结构体
类似于js中的object对象
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Struct{
struct student {
string name;
uint age;
}
struct student2 {
//student stu 不能包含自己本身,但是可以是动态长度的数组,也可以是映射
student2[] stu;
mapping(uint=>student2) haha;
}
function getInfo() public pure returns(string memory,uint){
student memory s = student({name:"Jerry",age:20});
//student memory s = student("Jerry",20);
return (s.name,s.age);
}
}
在新版本中,mapping不建议放在struct中,被认为是不安全的,会报错。函数里的变量没有声类型时,还是storage类型,此时形参是memory类型不能赋值给函数变量。
结构体传参:
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Struct{
struct student {
string name;
uint age;
}
function test(student memory s) public returns (uint, string memory){
stu = s;
return (stu.age,stu.name);
}
function init () public returns(uint,string memory){
student memory a=student({name:"jeeu",age:20});
return test(a);
}
}
storage和memory存储的空间不同,修改值不会影响到另一个。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Struct{
struct student {
string name;
uint age;
}
student stu;
function test(student memory s) public returns (uint, string memory){
stu = s;
stu.name="iiii";
return (stu.age,stu.name);
}
function init () public returns(uint,string memory){
stu=student({name:"jeeu",age:20});
return test(stu);
}
}
memory数据之间是通过地址来进行赋值传递,如果其中一个属性发生变化,另一个值也会变,是引用类型(包括形参)。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract Struct{
struct student {
string name;
uint age;
}
student stu=student('gg',33);
function test(student memory s) pure public returns (string memory){
student memory b = s;
b.name="iiii";
return (b.name);
}
function init () public pure returns(string memory){
student memory a=student({name:"jeeu",age:20});
test(a);
return a.name;
}
}
枚举
枚举enum,必须要有成员对象,不能存在汉字,不能写分号,适用于状态转移。返回的索引是uint
类型,匹配最小原则,从uint8开始匹配。
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
contract enumTest{
enum girl{chifan,heshui,shuijiao}
girl dateGirl = girl.heshui;
function getEnum() public pure returns (girl){
return girl.chifan;//获取到索引
}
function getDate()public view returns (girl){
require(dateGirl==girl.heshui);
return dateGirl;
}
}