一,运行智能合约推荐两种途径
1,自己搭建私有链,推荐我写的另一篇文章,从头到尾教你怎么搭建私有链,并运行合约等一系列操作,喜欢的话帮忙点赞收藏一下。。。
https://blog.youkuaiyun.com/mlynb/article/details/117410575
2,在一种方法就是使用网站,推荐两个挺好用的
http://remix.hubwiz.com/
http://remix.ethereum.org/
如果你觉得看视频比较好的话推荐一个b站up主的视频
https://www.bilibili.com/video/BV1St411a7Pk?p=2
二,solidity初级篇
1.pragma+view+pure
pragma solidity ^0.4.0;
//^0.4.0代表向上版本兼容,编译器版本最高到0.5.0但不包括0.5.0
contract Helloworld{
string Myname="tyl";
//view 修饰函数只能读取状态变量,不消耗gas
//这里的函数格式也跟c和java不一样,需要一个function在前面,然后函数名加(),而public这种的修饰词放在函数名后边,如果有返回值需要写returns,注意不是return,这里返回一个string,要加memory否则编译失败报这样的错:“TypeError: Data location must be "memory" or "calldata" for return parameter in function, but none was given. ”
function getName() public view returns(string memory){
return Myname;
}
//这里也要加memory
function changeName(string memory _newName) public{
Myname=_newName;
}
//pure 修饰函数不能改也不能读状态变量,不消耗gas
function pureTest(string memory _newName) public pure returns(string memory){
return _newName;
}
}
注意:Solidity
旧版本只有constant,新版本将constant拆成了view和pure。view的作用和constant一模一样,可以读取状态变量但是不能改;pure则更为严格,不读取更不修改区块上的变量,使用本机的CPU资源计算我们的函数。所以不消耗任何的资源这是很容易的理解的。要求的时不能读取,否则会报错,但是现在好像还没有严格实施,在remix上运行是可以的。
2.数据类型:bool、int、uint 、bytes、bytes1-bytes32
pragma solidity ^0.8.4;
contract dataClass{
//布尔类型 bool 数据操作:逻辑与、逻辑或、逻辑非 ||、 && 、!
int num1=100;
int num2=200;
function boolcheck() public view returns(bool){
return num1==num2;
}//返回false
function boolcheck1() public view returns(bool){
return num1!=num2;
} //返回true
//整型 int=int256 uint=uint256 数据操作:加减乘除求余、求平方 +-*/%、**
function intcheck(uint a,uint b) public pure returns(uint){
return a**b;
} //计算a的b次方
//位运算:按位与、按位或、按位取反、按位异或、左移、右移 &、|、~、^、<<、>>
uint8 a=3; uint8 b=4;
function weicheck() public view returns(uint8){
return a|b;
}//按位或,结果为7
//整型字面量:在solidity里面运算是计算出结果再赋值
function IntergerTest() public pure returns(uint){
uint num=1/2*1000;
return num;
}//返回500
//字节(数组)类型:bytes1(byte)、bytes2...bytes32,长度固定且内部数据不可修改
//属性:length 可以进行比较,位运算
bytes9 name=0xe69d8ee79fa5e681a9;
function byteTest() public pure returns(uint){
bytes1 num=0x7a;
return num.length;
}//返回1
function getIndex(uint index) public view returns(bytes32){
return name[index];
}//按照index获得字节数组的值
}
动态字节数组:bytes num=new bytes();长度、内部数据均可修改,push方法和修改长度,均是在数组末尾变化
pragma solidity ^0.5.15;
contract DynamicByte{
bytes public num=new bytes(2);//创建动态字节数组
function InitNum() public{
num[0]=0x12;
num[1]=0x34;
}//初始化数组
function getlength() public view returns(uint){
return num.length;
}//获取数组长度
//0.6.0以上的版本都不能使用length修改数组长度
function setlength() public{
num.length=5;
}//修改数组长度
function pushTest() public{
num.push(0x56);
}//push方法在数组末尾追加数据
}
3.字符串类型
pragma solidity ^0.5.15;
contract StringTest{
string name='tyl';//0x74796c
string name1="李知恩";//0xe69d8ee79fa5e681a9
function getlength()public view returns(uint){
return bytes(name).length;
}//获得字符串的字节长度
function getstrValue()public view returns(string memory){
return name;
}//获得字符串的str值
function getValue()public view returns(bytes memory){
return bytes(name);
}//获得字符串的bytes值
function getValue1()public view returns(bytes memory){
return bytes(name1);
}//获得字符串的bytes值
function getfirst()public view returns(byte){
return bytes(name)[0];
}//获得字符串首个字节存储的值
function setstrValue()public{
bytes(name)[0]='T';
bytes(name)[1]='Y';
bytes(name)[2]='L';
}//按字节修改字符串的值
}
字符串变量无法直接获得长度和修改字符串的值,需要进行bytes(字符串)强制转换后才能操作前面的操作
4,固定长度字节数组之间的转换
pragma solidity ^0.5.15;
contract StrTest{
bytes9 name= 0xe69d8ee79fa5e681a9;
function change1() public view returns(bytes1){
return bytes1(name);
}//0xe6
function change2() public view returns(bytes10){
return bytes10(name);
}//0xe69d8ee79fa5e681a900
}
固定长度字符节数组长的变短,截取前面部分;短的变长的,在后面添0。
5,固定长度字节数组转换为动态长度字节数组
pragma solidity ^0.5.15;
contract StrTest1{
bytes9 name=0xe69d8ee79fa5e681a9;
function fixByte2dynamicByte() public view returns(bytes memory){
bytes memory newByte=new bytes(name.length);
for(uint i=0;i<name.length;i++){//细节问题:for循环里要求是uint变量
newByte[i]=name[i];
}
return newByte;
}
}
6,bytes to string
pragma solidity ^0.5.15;
contract StrTest1{
bytes9 name=0xe69d8ee79fa5e681a9;
function fixByte2dynamicByte() public view returns(bytes memory){
bytes memory newByte=new bytes(name.length);
for(uint i=0;i<name.length;i++){
newByte[i]=name[i];
}
return newByte;
}
function bytes2string() public view returns(string memory){
bytes memory IU=fixByte2dynamicByte();
return string(IU);
}//返回"李知恩"
}
小结:bytes与string可互相转换。string(字节数组)、bytes(字符串)
7,小结:bytes与string可互相转换。string(字节数组)、bytes(字符串)
pragma solidity ^0.5.15;
contract StrTest2{
function bytes32Tostring(bytes32 _newname) public pure returns(string memory){
uint count=0;
for(uint i=0;i<_newname.length;i++){
bytes1 char=_newname[i];
if(char!=0){
count++;
}
}//找到字节数组中有用数据个数
bytes memory newname=new bytes(count);
for(uint j=0;j<count;j++){
newname[j]=_newname[j];
}//固定字节数组转换为动态字节数组
return string(newname);//最终实现固定字节数组转换为string
}
}
8,固定数组
pragma solidity ^0.5.15;
contract ArrayTest{
//固定数组初始化
uint[5] arr=[1,2,3,4,5];
//获取数组元素并修改
function Init() public{
arr[0]=100;
arr[1]=200;
}
//获取数组元素内容
function getArrayContent() public view returns(uint[5] memory){
return arr;
}
//对数组元素求和
function getGrade() public view returns(uint){
uint grade=0;
for(uint i=0;i<arr.length;i++){
grade+=arr[i];
}
return grade;
}
}
固定数组没有push方法,也无法修改数组的length
9,可变数组
contract ArrayTest1{
// 可变数组初始化
uint[] grade=[1,2,3,4,5];
//获取数组元素内容
function getContent() public view returns(uint[]) {
return grade;
}
//获取可变数据长度
function getLength() public view returns(uint){
return grade.length;
}
//获取可变数组元素并修改
function changeContent() public{
grade[0]=100;
grade[1]=200;
}
//对数组元素求和
function add() public view returns(uint){
uint sum=0;
for(uint i=0;i<grade.length;i++){
sum+=grade[i];
}
return sum;
}
//改变可变数组长度(缩短,只保留前面的数组元素)
function changLength() public{
grade.length=2;
}
//改变可变数组长度(增长,在数组后面添0)
function changLength1() public{
grade.length=10;
}
//push方法在数组后面追加
function pushContent() public{
grade.push(6);
}
}
10,二维数组
定义静态二维数组时,行数和列数和其他语言不同,与其他语言刚好相反:数据类型[列数][行数]=[…….]。但在数组操作时,依旧是一样。静态二维数组同样不能改变数组长度
pragma solidity ^0.5.15;
contract ArrayTest2{
//静态数组定义行数和列数刚好相反!!!
uint[2][3] grade=[[1,2],[3,4],[5,6]];
//获取数组内容
function getContent() public view returns(uint[2][3] memory){
return grade;
}
function getRowLength() public view returns(uint){
return grade.length;
}//3行
function getColumnsLength() public view returns(uint){
return grade[0].length;
}//2列
function add() public view returns(uint){
uint sum=0;
for(uint i=0;i<grade.length;i++){//行
for(uint j=0;j<grade[0].length;j++){//列
sum+=grade[i][j];//取数组元素依旧和之前的语言一样
}
}
return sum;
}
}
动态二维数组,可修改数组长度,暂时还无法支持动态二维数组作为返回值,获得数组内容
pragma solidity ^0.5.15;
contract ArrayTest3{
//定义动态二维数组
uint[][] grade=[[1,2],[3,4],[5,6]];
function getRowLength() public view returns(uint){
return grade.length;
}//3
function getColumnsLength() public view returns(uint){
return grade[0].length;
}//2
//改变行数
function setRowLength() public{
grade.length=4;
}
//改变列数,可针对任一行修改列数。
function setColumnsLength() public {
grade[0].length=4;
}
function add() public view returns(uint){
uint sum=0;
for(uint i=0;i<grade.length;i++){
for(uint j=0;j<grade[i].length;j++){//注意和静态数组差别,每行的列数不一定相同
sum+=grade[i][j];
}
}
return sum;
}
}
11,数组字面量
一般用于输入需求,返回数组字面量时,返回参数列表以数组字面量最小类型为准。
pragma solidity ^0.5.15;
contract ArrayTest3{
function getArray1() public pure returns(uint8[4] memory){
return [1,2,3,4];
}//最小类型为uint8
function getArray2() public pure returns(uint16[4] memory){
return [256,2,3,4];
}//最小类型为uint16
function getArray3() public pure returns(uint[4] memory) {
return [uint(1),2,3,4];
}//可以强制将字面量转换为来适应返回参数列表类型
function add(uint[4] memory grade) public pure returns(uint){
uint sum=0;
for(uint i=0;i<grade.length;i++){
sum+=grade[i];
}
return sum;
} //数组字面量用于输入
}
三,Solidity 进阶篇
1,地址 address
address 在存储上和uint160一样,且二者可以互相转换,地址之间也可以进行比较大小
pragma solidity ^0.5.15;
contract arrayTest{
address public account=0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;
function changeIt() public view returns(uint160){
return uint160(account);
}//地址转换为uint160:1154414090619811796818182302139415280051214250812
function Itchange() public view returns(address){
return address(changeIt());
}//uint160转换为地址:0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c
}
地址分为外部账户地址和合约账户地址,合约是由外部账户发布的合约,每个合约都有自己的地址。和外部账户一样,合约账户也可以有钱(以太币)
pragma solidity ^0.5.15;
contract arrayTest1{
//payable关键字,允许转账操作(这里是外部账户转账给合约账户)
function pay() public payable{
}
//返回合约账户地址
function getAddress() public view returns(address){
return msg.sender;
}//0x038f160ad632409bfb18582241d9fd88c1a072ba
//this关键字代表合约账户,账户都有balance(钱),这里“钱”单位默认是wei,返回合约账户的钱
function getBalance() public view returns(uint){
return address(this).balance;
}
//可以获得任意账户的钱
function getBalance_by_addr(address account) public view returns(uint){
return account.balance;
}
}
2.转账操作
外部账户之间进行转账: account.transfer(msg.value);
外部账户向合约账户转账:address(this).transfer(msg.value);+回滚函数
其中msg.value是全局变量,代表转账的数量
pragma solidity ^0.5.0;
contract arrayTest2{
//运行该合约的账户向外部account账户进行转账
function transfer() public payable{
address payable account=0x14723A09ACff6D2A60DcdF7aA4AFf308FDDC160C;
account.transfer(msg.value);
}
//按照地址获得账户余额
function getBlance_by_addr(address account) public view returns(uint){
return account.balance;
}
//外部账户向改合约账户进行转账,必须要有回滚函数
function transfer1() public payable{
address(this).transfer(msg.value);
}
//必须要有的回滚函数!!!
/* function() public external payable{
}*/}
3.全局变量
account.transfer(msg.value)中msg.value可以直接用相应的值替换,e.g. account.transfer(10 ether);
4.send方法与transfer方法
function sendMoney() public payable returns(bool){
address account=0x14723a09acff6d2a60dcdf7aa4aff308fddc160c;
return account.send(10 ether);
}//send方法比较底层,如果不传入相应的转账金额,不会报错,只会返回false
function transfer() public payable{
address account=0x14723a09acff6d2a60dcdf7aa4aff308fddc160c;
account.transfer(msg.value);
}//transfer方法,如果不传入相应的转账金额,会进行报错提示
5.mapping类型
pragma solidity ^0.5.0;
contract mappingTest{
//定义两个mapping类型的变量
mapping(address=>uint) public idmapping;
mapping(uint=>string) public namemapping;
//用于代表注册的人数
uint sum=0;
//注册,主要是合约调用者的地址=>id;id=>姓名两组映射关系
function register(string memory name) public{
address account=msg.sender;
sum++;
idmapping[account]=sum;
namemapping[sum]=name;
}
//根据地址来获得id
function getIdbyAddr(address account) public view returns(uint){
return idmapping[account];
}
//根据id来获得姓名
function getNamebyId(uint id) public view returns(string memory){
return namemapping[id];
}
}
6. 函数重载
函数一般类型:
function 函数名(参数列表){private|internal|external|public}[pure|constant|view|payable][returns(返回值类型)]
1、函数名字相同
2、函数的参数列表不同(类型、数量)
3、不考虑函数的返回值类型
函数重载会遇到一个函数匹配的问题,例如add(1)与函数add(uint8)和add(uint)两个都匹配,发生冲突故报错!
其次参数列表类型是uint160和address时,无论输入的参数是什么类型,都会发生冲突,因为这两个本身机器是无法区分的。
7.关于函数命名参数说明
pragma solidity ^0.5.0;
contract Test{
string public name;
uint public id;
function set(string memory _name,uint _id) public{
name=_name;
id=_id;
}
function test1() public{
set("李知恩",123);
}
//通过这种给参数直接赋值的方式可以不考虑函数列表的先后顺序
function test2() public{
set({_name:"李知恩",_id:123});
}
}
8.关于函数返回值说明
pragma solidity ^0.5.0;
contract Test{
//函数返回值可以有名称
function test1() public pure returns(uint mul){
return 123;
}
//函数返回值可以不用return返回,直接给返回值赋值
function test2() public pure returns(uint mul){
mul=123;
}
//函数返回值最终以return为主
function test3() public pure returns(uint mul){
mul=123;
uint a=100;
return a;
}
//函数可有多个返回值
function test4() public pure returns(uint a, uint b){
return(10,20);
}
}
9.生命周期与作用域
全局变量名称在局部变量里可以出现重名的,此时修改局部变量不会影响全局变量。同时在函数里面,也不能出现重复定义的变量!
pragma solidity ^0.5.0;
contract test{
//全局变量
uint public a=100;
function test0() public view returns(uint){
return a;
}//返回100
function test1() public pure returns(uint){
uint a=200;
return a;
}//重新定义局部变量a,返回200,但全局变量a依旧是100
function test2(uint a) public pure returns(uint){
// uint a=300;无法重复定义a,在参数列表内出现了重复
a=300;
return a;
}//返回300
}
10.函数修饰符
pragma solidity ^0.5.0;
//public修饰符,可以在合约内部,继承合约内部,外部调用
contract father{
//public修饰函数
function dahan() public pure returns(string memory){
return "dahan";
}
//合约内部调用
function publictest() public pure returns(string memory){
return dahan();
}
}
contract son is father{
//public修饰 继承合约可继承还可直接调用
function test() public pure returns(string memory){
return dahan();
}
}
//=========================================================
//private修饰符,函数只能被本合约内部调用
contract father1{
function dahan() private pure returns(string memory){
return "dahan";
}
//合约内部调用
function privatetest() public pure returns(string memory){
return dahan();
}
}
contract son1 is father1{
//private修饰符,函数不能被继承合约继承和调用
/* function test() public pure returns(string){
return dahan();
}*/
}
//==========================================================
//internal修饰符,函数只能被合约内部调用和继承合约内部调用
contract father2{
function dahan() internal pure returns(string memory){
return "dahan";
}
//合约内部调用
function internaltest() public pure returns(string memory){
return dahan();
}
}
contract son2 is father2{
//继承合约内部调用
function test() public pure returns(string memory){
return dahan();
}
}
//==========================================================
//external修饰符,只能在外部调用,或者在合约内部和继承合约内部间接调用(实际也是外部调用)
contract father3{
function dahan() external pure returns(string memory){
return "dahan";
}
//无法直接在合约内部调用
/* function externaltest() public pure returns(string){
return dahan();
}*/
//间接调用
function externaltest() public view returns(string memory){
return this.dahan();
}
}
contract son3 is father3{
//无法直接在继承合约内部调用
/* function test() public pure returns(string){
return dahan();
}*/
//间接调用
function test() public view returns(string memory){
return this.dahan();
}
}
//通过另外不相关的合约,new一个合约对象,也可以间接调用
contract externaltest{
father3 f=new father3();
function test() public view returns(string memory){
return f.dahan();
}
}
11.constant修饰符
作用与view差不多,只能读取变量,没法修改变量,目前支持int、uint、string、bytes1-32
在0.5.0版本以后被view和pure替代,用了会报错
pragma solidity ^0.5.0;
contract constantTest{
// 测试可以支持的数据类型
uint constant num=100;
uint public num1=100;
int constant num2=100;
string constant num3="100";
bytes1 constant num4=0x11;
bytes constant public num5=new bytes(2);//编译没有报错,但无法修改,只能是0x0000;
//constan全局变量无法修改,编译报错!
/* function changeIt() public{
num=0;
}*/
function change() public pure returns(string){
num5[0]=0x12;
num5[1]=0x12;
return string(num5);
}//并没有修改成功!
function changeIt() public constant returns(uint){
num1=200;
return num1;
}//返回200,但是全局变量num1依旧是100
}
12.构造函数
新版本用 constructor(…) {…}作为构造函数
pragma solidity ^0.5.0;
constructor ()public {
owner = msg.sender;
len = 0;
}
13.modifier
函数修饰符函数modifier 函数名(参数列表){…}
pragma solidity ^0.5.0;
contract modifierTest{
address public owner;
uint public num=0;
constructor() public{
owner=msg.sender;
}
modifier Onlyowner() {
require(msg.sender==owner);
_;
}//定义的函数修饰符函数,_;在满足require的条件时,会被替换成num=100;否则函数回滚,不会执行num=100;
function change() Onlyowner public{
num=100;
}//用Onlyowner修饰
}
modifier修饰函数作用举例1
pragma solidity ^0.5.0;
contract mappingTest{
//定义两个mapping类型的变量
mapping(address=>uint) public idmapping;
mapping(uint=>string) public namemapping;
//用于代表注册的人数
uint sum=0;
modifier control(){
require(idmapping[msg.sender]==0);
_;
}//通过这个修饰,可以确保每个账户只能注册一次!
//注册,主要是合约调用者的地址=>id;id=>姓名两组映射关系
function register(string memory name ) control public{
address account=msg.sender;
sum++;
idmapping[account]=sum;
namemapping[sum]=name;
}
//根据地址来获得id
function getIdbyAddr(address account) public view returns(uint){
return idmapping[account];
}
//根据id来获得姓名
function getNamebyId(uint id) public view returns(string memory){
return namemapping[id];
}
}
modifier修饰函数作用举例2
contract modifierTest2{
uint public level=9;
string public name="李知恩";
uint public age=20;
//modifier可以有参数列表
modifier controlLevel(uint _level){
require(level>_level);
_;
}
//不同level才能有不同的权限!
function changename() controlLevel(10) public{
name="IU";
}
function changeage() controlLevel(5) public{
age=18;
}
}
一个函数可以被多个modifier函数修饰符修饰,且执行顺序非常关键。(实际上就是按照修饰符顺序,依次执行,遇到_;会将后面的modifier函数,直接嵌入替换即可!
pragma solidity ^0.5.0;
contract modifierTest3{
uint public a;
modifier mod1 {
a=1;
_;
a=2;
}
modifier mod2{
a=3;
_;
a=4;
}
//执行顺序:a=1,a=3,a=100,a=4,a=2
function change() mod1 mod2 public{
a=100;
}//最终a=2
//执行顺序:a=3,a=1,a=100,a=2,a=4
function change1() mod2 mod1 public{
a=100;
}//最终a=4
}
14.继承
一、合约支持连续继承:儿子继承父亲,父亲继承祖父
contract father is grandfater{...}
contract son is father{...}
1、修饰变量的修饰符在继承中作用:默认、public、internal 可以被继承,private不能被继承,且修饰变量没有external
2、修饰函数的修饰符在继承中的作用:public internal、external可以被继承,private不能被继承
二、合约支持多重继承:儿子继承父亲和母亲
contract son is mom,father{
/1、继承的属性如果mon和father里面都有,那么按照继承的顺序(这里是mom,father),选择最后一个继承者(这里是father)覆盖掉其他的合约属性///2、自身的属性如果和is后面的父辈合约重复了,那么自身属性会覆盖掉其他合约属性
//3、继承时发生的函数重载以自身为准,父辈合约之间发生函数冲突,也是和属性一样,按继承顺序的最后一个继承者为准
}
15.合约销毁
selfdestruct(合约发布者的地址)
pragma solidity ^0.4.0;
contract selfdestructTest{
uint public money;
address owner;
constructor() public{
owner=msg.sender;
}
function increase() public returns(uint){
money++;
return money;
}
//销毁合约
function kill() public{
if(msg.sender==owner){
selfdestruct(owner);
}
}
}
函数知识小结
1、private 不能被继承,不能在外部被调用,可以在内部被调用
2、internal可以在内部被调用,不能在外部被调用,可以被继承
3、external 不能在内部被调用,只能够在外部调用,可以被继承,如果强行执行,通过”地址.“
4、public权限最大,可以在外部和内部调用,可以被继承
5、pure不会读取全局变量,更不会修改全局变量,一个固定的输入就会有一个固定的输出
6、constant在函数中,和view相同,在全局变量中,只用于bytes1-32,uint,int,string代表数据不能被修改
7、view只读取全局变量的值,不修改它,不消耗gas
8、payable转账的时候必须要加的关键字
9、命名参数{形参1名字:值1,形参2名字:值2}
10、函数可以有多个返回值
public 修饰变量时,会默认生成一个getter方法
pragma solidity ^0.5.0;
contract getter{
uint public num=100;
//相当于生成了下面注释的getter方法,external属性的
/* function num() external view returns(uint){
return num;
}*/
function getnum() public view returns(uint){
return this.num();
}
mapping(uint=>string) public map;
constructor() public{
map[0]="李知恩";
map[1]="IU";
mapx[0][1][2]="IU";
}
//mapping类型比较特殊,生成的getter方法带有输入参数
/* function map(uint key) external view returns(string){
return map[key];
}*/
function getmap(uint key) public view returns(string memory){
return this.map(key);
}
//复杂一点的mapping原理也是如上面所述的原理,这里带三个输入参数
mapping(uint=>mapping(uint=>mapping(uint=>string))) public mapx;
}
solidity 高级篇
1.memor与storage
规则1 – 状态变量
状态变量总是存储在存储区storage中。
pragma solidity ^0.5.0;
contract DataLocation {
// storage
uint stateVariable;
uint[] stateArray;
}
此外,不能显式地标记状态变量的位置。
pragma solidity ^0.5.0;
contract DataLocation {
uint storage stateVariable; // 错误
uint[] memory stateArray; // 错误
}
规则2 – 函数参数与返回值
函数参数包括返回参数都存储在内存memory中
pragma solidity ^0.5.0;
contract DataLocation {
// storage
uint stateVariable;
uint[] stateArray;
function calculate(uint num1, uint num2) public pure returns (uint result) {
return num1 + num2
}
}
此处,函数参数 uint num1 与 uint num2,返回值 uint result 都存储在内存中。
规则3 – 局部变量
值类型的局部变量存储在内存中。但是,对于引用类型,需要显式地指定数据位置。
pragma solidity ^0.5.0;
contract Locations {
/* 此处都是状态变量 */
// 存储在storage中
bool flag;
uint number;
address account;
function doSomething() public {
/* 此处都是局部变量 */
// 值类型
// 所以它们被存储在内存中
bool flag2;
uint number2;
address account2;
// 引用类型,需要显示指定数据位置,此处指定为内存
uint[] memory localArray;
}
}
不能显式覆盖具有值类型的局部变量。
function doSomething() public {
/* 此处都是局部变量 */
// 值类型
bool memory flag2; // 错误
uint Storage number2; // 错误
address account2;
}
规则4 – 外部函数的参数
外部函数的参数(不包括返回参数)存储在Calldata中。
举例说明
contract memoryTest{
//状态变量,默认是storage,且不能显示说明!
uint[] public arrx;
/* uint[] memory public arrx;
uint[] storage public arrx;*/
function test(uint[] array) public returns(uint){
//array存储在memory内,将值传递给存储在storage内的arrx
arrx=array;
//这里arrx由于是数组,属于引用变量,因此存储都在storage里的Z和arrx等同,且数组在storage内可以修改长度
uint[] storage Z=arrx;
Z[0]=10;
Z.length=10;
//由于Y是存储在memory的,它和arrx除了赋值的关系外,并没有等价的关系,且不能修改长度,Y修改对arrx不影响
uint[] memory Y=arrx;
//Y.length=20;
Y[1]=20;
}
function test2() public view returns(uint[]){
return arrx;
}//返回整个数组内容
function test3() public view returns(uint){
return arrx.length;
}//返回数组长度
}
变量类型
Solidity中,变量类型有以下几大类:
值类型
地址类型
引用类型
值类型
地址类型
地址类型表示以太坊地址,长度为20字节。地址可以使用.balance方法获得余额,也可以使用.transfer方法将余额转到另一个地址。
address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10)
x.transfer(10);
引用类型/复合数据类型
Solidity中,有一些数据类型由值类型组合而成,相比于简单的值类型,这些类型通常通过名称引用,被称为引用类型。
引用类型包括:
数组 (字符串与bytes是特殊的数组,所以也是引用类型)
struct (结构体)
map (映射)
struct
定义与初始化
pragma solidity ^0.5.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
}
//结构体的定义
struct student1{
uint grade;
string name;
// student1 stu;结构体内部不能包含自己本身,但是可以是动态长度的数组,也可以是映射
student1[] stu;
mapping(uint=>student1) hahah;
}
//结构体的初始化
function init() public pure returns(uint,string memory){
/*struct在函数体内默认是storage,而具体画的结构体student(100, "IU");则存储在memory中,因此需要显示定义结构体指针s在memory*/
student memory s=student(100, "IU");
return(s.grade,s.name);
}
//初始化的第二种方式
function init2() public pure returns(uint,string memory){
student memory s =student({grade: 100, name: "IU1"});
return(s.grade,s.name);
}
}
结构体内的mapping
初始化结构体时不能初始化mapping,且操作结构体内的mapping需要storage类型
pragma solidity ^0.5.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
mapping(uint=>string) map;
}
student IU;//1、默认是storage类型,只有storage类型才能操作结构体内的mapping
//结构体的初始化
function init() public returns(uint,string memory,string memory){
//结构体初始化不需要初始化mapping
student memory s=student(100, "IU");
//2、memory对象不能操作结构体内的mapping
//s.map[0]="李知恩";编译出错
//3、通过将memory对象s赋值给storage对象,才能操作结构体内的mapping
IU=s;
IU.map[0]="李知恩";
return(s.grade,s.name,IU.map[0]);
// return(IU.grade,IU.name,IU.map[0]);返回值一样
}
}
结构体作为形参
前提:1、函数必须是internal 2、不能直接将形参赋值给storage指针
pragma solidity ^0.5.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
}
/*1、必须是internal修饰 2、结构体变量stu是storage指针,不能和memory指针直接赋值(这就好像两个指向不同区域的指针是没法赋值一样的意思)*/
function test1(student memory s) internal{
student memory stu=s;
}//编译不通过
//默认就是memory,编译不通过
/* function test1(student s)internal{
student stu=s;
}*/
//编译通过了
/* function test1(student storage s)internal{
student stu=s;
}*/
}
情况1:storage转storage
pragma solidity ^0.5.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
}
//stu是状态变量,存储在storage区域
student stu=student(100,"李知恩");
function test1(student storage s)internal{
student storage IU=s;//内存中的s指针通过test1(stu)后,指向了storage区域的stu对象
IU.name="IU";//通过IU指针间接操作了storage区域的stu对象
}
function call() public returns(string memory){
test1(stu);//传递的实际是指向stu对象的指针
return stu.name;
}//返回IU
}
情形2:memory转storage
pragma solidity ^0.5.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
}
//stu是状态变量,存储在storage区域
student stu=student(10,"李知恩");
function test1(student memory s)internal{
stu=s;//将s的值赋给了stu,stu变为student(100,"IU")
s.name="love";//修改memory上的s,不会影响storage上的stu
}
function call() public returns(uint,string memory){
student memory IU=student(100,"IU");//IU是局部变量,存储在memory中
test1(IU);//值传递,将IU的值传递给了形参s(也存储在memory当中)
return (stu.grade,stu.name);//返回100,"IU"
}
}
情形3:storage转memory
pragma solidity ^0.5.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
}
//stu是状态变量,存储在storage区域
student stu=student(10,"李知恩");
function test1(student storage s) pure internal returns(uint,string memory){
student memory IU=s;//在memory中,s根据传递过来的stu指针,对stu对象进行了副本copy,赋值给了IU
IU.name="IU";//在memory内的IU进行修改,不会影响存储在storage中的stu
return(IU.grade,IU.name);
}
function call() public view returns(uint,string memory){
test1(stu);//传递的是指向stu的指针
return (stu.grade,stu.name);
}//返回(10,"李知恩")
//由于internal外部无法调用,因此采用这种方式在外部间接调用test1,结果说明内存中的IU的值发生了改变
function test() public view returns(uint,string memory){
return test1(stu);
}//返回(10,"IU")
}
情形4:memory转memory
pragma solidity ^0.5.0;
contract structTest{
//结构体的定义
struct student{
uint grade;
string name;
}
function test1(student memory s) pure internal {
student memory IU=s;//s和IU都指向了stu的存储的memory区域
IU.name="IU";//通过IU间接修改了stu对象
IU.grade=18;
}
function call() public pure returns(uint,string memory){
student memory stu=student(100,"李知恩");
test1(stu);//memory上的对象stu,传递的是指向它的指针(这里比较特殊,属于本身对内存的一个优化操作)
return (stu.grade,stu.name);
}//返回(18,"IU")
}
enum
enum 枚举体名字{枚举1,枚举2…} 枚举1-N不能有汉字,本质上枚举1数值为0,枚举2数值为1,以此类推
枚举体主要用于状态的切换,例如从状态1切换到状态2,让代码具有可读性!
pragma solidity ^0.5.0;
contract enumTest{
enum state{s1,s2,s3,s4,s5}
state step=state.s1;
function getEnum() public view returns(state){
return step;
}//返回uint8:0(也是看枚举个数,按最小标准存储)
function firstStep() public returns(string memory){
require(step==state.s1);//必须完成s1才能切换到s2
step=state.s2;
return "s1 over!";
}
function sencondStep() public returns(string memory){
require(step==state.s2);//必须完成s2才能切换到s3
step=state.s3;
return "s1,s2 are over!";
}
}
未完待续。。。