以太坊环境以及Solidity学习笔记

本文详细介绍了如何在Windows环境下搭建以太坊私有链,包括Geth和Mist钱包的安装、创世区块的初始化,以及私有链节点的启动和挖矿体验。随后,文章深入讲解了Solidity智能合约的基础知识,如数据类型、函数、修饰符和继承等内容。适合初学者了解以太坊开发的实践过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、以太坊环境搭建

以太坊 私有链搭建 Geth+Mist钱包

以太坊 链私有链环境搭建(windows)

S1:下载安装Geth、Mist客户端

S2:初始化创世纪节点

定义一个配置文件genesis.json

{
  "config": {
    "chainId": 666,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "ethash": {}
  },
  "nonce": "0x0",
  "timestamp": "0x5ddf8f3e",
  "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "gasLimit": "0x47b760",
  "difficulty": "0x00002",
  "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "alloc": { },
  "number": "0x0",
  "gasUsed": "0x0",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

通过如下指令,利用配置文件初始化创世块:

geth  -datadir data0 init genesis.json

上面的命令,会读取genesis.json文件,根据其中的内容,将创世区块写入到区块链中。如果看到log信息中含有Successfully wrote genesis state字样,说明初始化成功。

在系统文件夹中生成data0,geth?keystore?

S3:启动私有链节点

通过如下指令,即可启动:

geth -datadir data0 console

成功出现如下提示:

注:这里后面INFO输出Looking for peers,不影响操作!

S4:体验挖矿

启动挖矿指令:

miner.start()

可以看到DAG percentage逐渐增大

之后不想挖矿后,停止挖矿指令:

miner.stop()

挖矿收入检查:

eth.getBalance(eth.accounts[0])

二、智能合约学习资料

智能合约概述 — Solidity develop 文档 (solidity-cn.readthedocs.io)

基于以太坊的智能合约开发教程【Solidity】_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili

Remix - Solidity IDE - 中文版 (hubwiz.com)

三、Solidity初级篇

3.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
    function getName() public view returns(string){
        return Myname;
    }
    function changeName(string _newName) public{
        Myname=_newName;
    }
    //pure 修饰函数不能改也不能读状态变量,不消耗gas
    function pureTest(string _newName) public pure returns(string){
        return _newName;
    }
}
1、Solidity 旧版本只有constant,新版本将constant拆成了view和pure。view的作用和constant一模一样,可以读取状态变量但是不能改;pure则更为严格,pure修饰的函数不能改也不能读状态变量,否则编译通不过。
2、pragma solidity ^0.4.0; //^0.4.0代表向上版本兼容,编译器版本最高到0.5.0但不包括0.5.0

3.2 数据类型:bool、int、uint 、bytes、bytes1-bytes32

pragma solidity ^0.4.0;
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() spublic pure returns(uint){
        byte num=0x7a;
        return num.length;
    }//返回1

     function getIndex(uint index) public view returns(byte){
        return name[index];
    }//按照index获得字节数组的值

}
动态字节数组:bytes num=new bytes();长度、内部数据均可修改,push方法和修改长度,均是在数组末尾变化
pragma solidity ^0.4.0;
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;
    }//获取数组长度

    function setlength() public{
        num.length=5;
    }//修改数组长度

    function pushTest() public{
        num.push(0x56);
    }//push方法在数组末尾追加数据

}

3.3 字符串类型

pragma solidity ^0.4.0;
contract StringTest{
    string name='tyl';//0x74796c
    string name1="李知恩";//0xe69d8ee79fa5e681a9

    function getlength() view returns(uint){
        return bytes(name).length;
    }//获得字符串的字节长度

    function getstrValue() view returns(string){
        return name;
    }//获得字符串的str值

    function getValue() view returns(bytes){
        return bytes(name);
    }//获得字符串的bytes值

    function getValue1() view returns(bytes){
        return bytes(name1);
    }//获得字符串的bytes值


    function getfirst() view returns(byte){
        return bytes(name)[0];
    }//获得字符串首个字节存储的值

    function setstrValue(){
         bytes(name)[0]='T';
         bytes(name)[1]='Y';
         bytes(name)[2]='L';
    }//按字节修改字符串的值
}
字符串变量无法直接获得长度和修改字符串的值,需要进行bytes(字符串)强制转换后才能操作前面的操作。

3.3.1固定长度字节数组之间的转换

pragma solidity ^0.4.0;
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。

3.3.2 固定长度字节数组转换为动态长度字节数组

pragma solidity ^0.4.0;
contract StrTest1{
    bytes9 name=0xe69d8ee79fa5e681a9;

    function fixByte2dynamicByte() public view returns(bytes){
        bytes memory newByte=new bytes(name.length);
        for(uint i=0;i<name.length;i++){//细节问题:for循环里要求是uint变量
            newByte[i]=name[i];
        }
        return newByte;
    }
}

3.3.3 bytes to string

pragma solidity ^0.4.0;
contract StrTest1{
    bytes9 name=0xe69d8ee79fa5e681a9;
    function fixByte2dynamicByte() public view returns(bytes){
        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){
        bytes memory IU=fixByte2dynamicByte();
        return string(IU);
    }//返回"李知恩"
}
小结:bytes与string可互相转换。string(字节数组)、bytes(字符串)

3.3.4 固定字节数组转换为string

pragma solidity ^0.4.0;
contract StrTest2{
   function bytes32Tostring(bytes32 _newname)  public pure returns(string){
       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
   }
}

3.4 数组

3.4.1 固定数组

pragma solidity ^0.4.0;
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]){
        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

3.4.2 可变数组

pragma solidity ^0.4.0;
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);
    }

}

3.4.3 二维数组

定义静态二维数组时,行数和列数和其他语言不同,与其他语言刚好相反:数据类型[列数][行数]=[…….]。但在数组操作时,依旧是一样。静态二维数组同样不能改变数组长度
pragma solidity ^0.4.0;
contract ArrayTest2{
    //静态数组定义行数和列数刚好相反!!!
    uint[2][3]  grade=[[1,2],[3,4],[5,6]];
    //获取数组内容
    function getContent() public view returns(uint[2][3]){
        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.4.0;
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;
    }

}

3.4.4 数组字面量

一般用于输入需求,返回数组字面量时,返回参数列表以数组字面量最小类型为准。
pragma solidity ^0.4.0;
contract ArrayTest3{

   function getArray1() public pure returns(uint8[4]){
       return [1,2,3,4];
   }//最小类型为uint8

   function getArray2() public pure returns(uint16[4]){
       return [256,2,3,4];
   }//最小类型为uint16

   function getArray3() public pure returns(uint[4]){
       return [uint(1),2,3,4];
   }//可以强制将字面量转换为来适应返回参数列表类型

   function add(uint[4] grade) public pure returns(uint){
       uint sum=0;
       for(uint i=0;i<grade.length;i++){
           sum+=grade[i];
       }
       return sum;
   } //数组字面量用于输入

}

四、Solidity 进阶篇

4.1 地址 address

address 在存储上和uint160一样,且二者可以互相转换,地址之间也可以进行比较大小
pragma solidity ^0.4.0;
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.4.0;
contract arrayTest1{
    //payable关键字,允许转账操作(这里是外部账户转账给合约账户)
    function pay() public payable{

    } 
    //返回合约账户地址
    function getAddress() public view returns(address){
        return this;
    }//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;
    }


}

4.2 转账操作

外部账户之间进行转账: account.transfer(msg.value);
外部账户向合约账户转账:address(this).transfer(msg.value);+回滚函数
其中msg.value是全局变量,代表转账的数量
pragma solidity ^0.4.0;
contract arrayTest2{
   //运行该合约的账户向外部account账户进行转账
   function transfer() public payable{
       address 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  payable{

   }

}

4.3 全局变量

account.transfer(msg.value)中msg.value可以直接用相应的值替换,e.g. account.transfer(10 ether);

4.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方法,如果不传入相应的转账金额,会进行报错提示

4.5 mapping类型

pragma solidity ^0.4.0;
contract mappingTest{
    //定义两个mapping类型的变量
    mapping(address=>uint) public idmapping;
    mapping(uint=>string) public namemapping;
    //用于代表注册的人数
    uint sum=0;
    //注册,主要是合约调用者的地址=>id;id=>姓名两组映射关系
    function register(string 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){
        return namemapping[id];
    }
}

4.6 函数重载

函数一般类型:

function 函数名(参数列表){private|internal|external|public}[pure|constant|view|payable][returns(返回值类型)]

  • 1、函数名字相同
  • 2、函数的参数列表不同(类型、数量)
  • 3、不考虑函数的返回值类型
函数重载会遇到一个函数匹配的问题,例如add(1)与函数add(uint8)和add(uint)两个都匹配,发生冲突故报错!
其次参数列表类型是uint160和address时,无论输入的参数是什么类型,都会发生冲突,因为这两个本身机器是无法区分的。

4.7 关于函数命名参数说明

pragma solidity ^0.4.0;
contract Test{

    string public name;
    uint public id;
    function set(string _name,uint _id) public{
        name=_name;
        id=_id;
    }

    function test1() public{
        set("李知恩",123);
    }
    //通过这种给参数直接赋值的方式可以不考虑函数列表的先后顺序
    function test2() public{
        set({_name:"李知恩",_id:123});
    }

}

4.8 关于函数返回值说明

pragma solidity ^0.4.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);
    }

}

4.9 生命周期与作用域

全局变量名称在局部变量里可以出现重名的,此时修改局部变量不会影响全局变量。同时在函数里面,也不能出现重复定义的变量!
pragma solidity ^0.4.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
}

4.10 函数修饰符

pragma solidity ^0.4.0;
//public修饰符,可以在合约内部,继承合约内部,外部调用
contract father{
    //public修饰函数
    function dahan() public pure returns(string){
        return "dahan";
    }
    //合约内部调用
    function publictest() public pure returns(string){
        return dahan();
    }

}
contract son is father{
   //public修饰 继承合约可继承还可直接调用
   function test() public pure returns(string){
       return dahan();
   }

}
//=========================================================

//private修饰符,函数只能被本合约内部调用
contract father1{
     function dahan() private pure returns(string){
        return "dahan";
    }
    //合约内部调用
    function privatetest() public pure returns(string){
        return dahan();
    }
}
contract son1 is father1{
   //private修饰符,函数不能被继承合约继承和调用   
  /*  function test() public pure returns(string){
       return dahan();
    }*/

}
//==========================================================

//internal修饰符,函数只能被合约内部调用和继承合约内部调用
contract father2{
     function dahan() internal pure returns(string){
        return "dahan";
    }
    //合约内部调用
    function internaltest() public pure returns(string){
        return dahan();
    }
}
contract son2 is father2{
    //继承合约内部调用 
    function test() public pure returns(string){
       return dahan();
    }

}
//==========================================================

//external修饰符,只能在外部调用,或者在合约内部和继承合约内部间接调用(实际也是外部调用)
contract father3{
     function dahan() external pure returns(string){
        return "dahan";
    }
    //无法直接在合约内部调用
 /*   function externaltest() public pure returns(string){
        return dahan();
    }*/

    //间接调用
    function externaltest() public view returns(string){
        return this.dahan();
    }
}
contract son3 is father3{
     //无法直接在继承合约内部调用
  /*  function test() public pure returns(string){
       return dahan();
    }*/

    //间接调用
    function test() public view returns(string){
        return this.dahan();
    }

}
//通过另外不相关的合约,new一个合约对象,也可以间接调用
contract externaltest{
    father3 f=new father3();
    function  test() public view returns(string){
        return f.dahan();
    }
}

4.11 值传递与副本拷贝

变量赋值给变量,是值传递的方式;形参传入的是副本拷贝,修改它的值不会影响原来的变量。
pragma solidity ^0.4.0;
contract test{

    uint public a=100;
    uint public b=a;//变量赋值给变量,值传递

    function changeIt() public returns(uint){
        b=200;
        return b;//修改b不会影响原来的变量a
    }

    function changeIt1(uint m) public pure returns(uint){
        m++;
        return m;
    }

    function changeIt1test() public view{
        changeIt1(a);//形参传入的是变量的副本拷贝,不会影响原来的变量。
    }
}

4.12 constant修饰符

作用与view差不多,只能读取变量,没法修改变量,目前支持int、uint、string、bytes1-32
pragma solidity ^0.4.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
 }

4.13 构造函数

新版本用 constructor(…) {…}作为构造函数
pragma solidity ^0.4.0;
contract Test{
    uint public a;   
    function Test() public{
        a=100;
    }

 }

 contract Test1{
     uint public b;
     function Test1(uint _b) public{
         b=_b;
     }
 }

 contract Test2{
     address  public owner;
     constructor() public{
         owner=msg.sender;
     }
 }

4.14 modifier

函数修饰符函数modifier 函数名(参数列表){…}
pragma solidity ^0.4.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
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 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){
        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函数,直接嵌入替换即可!)
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

 }

4.15 继承

一、合约支持连续继承:儿子继承父亲,父亲继承祖父

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、继承时发生的函数重载以自身为准,父辈合约之间发生函数冲突,也是和属性一样,按继承顺序的最后一个继承者为准

}

4.16 合约销毁

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.4.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){
       return this.map(key);
   }
   //复杂一点的mapping原理也是如上面所述的原理,这里带三个输入参数
   mapping(uint=>mapping(uint=>mapping(uint=>string))) public mapx;

}

五、solidity 高级篇

5.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中。

举例说明
pragma solidity ^0.4.0;
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;
    }//返回数组长度
}

5.2 变量类型

Solidity中,变量类型有以下几大类:

  • 值类型
  • 地址类型
  • 引用类型

值类型

地址类型

地址类型表示以太坊地址,长度为20字节。地址可以使用.balance方法获得余额,也可以使用.transfer方法将余额转到另一个地址。

address x = 0x212;
address myAddress = this;

if (x.balance < 10 && myAddress.balance >= 10) 
    x.transfer(10);

引用类型/复合数据类型

Solidity中,有一些数据类型由值类型组合而成,相比于简单的值类型,这些类型通常通过名称引用,被称为引用类型。

引用类型包括:

  • 数组 (字符串与bytes是特殊的数组,所以也是引用类型)
  • struct (结构体)
  • map (映射)

5.2 struct

定义与初始化

pragma solidity ^0.4.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){
   /*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){
        student memory s =student({grade: 100, name: "IU1"});
        return(s.grade,s.name);
    }

}

结构体内的mapping

初始化结构体时不能初始化mapping,且操作结构体内的mapping需要storage类型
pragma solidity ^0.4.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,string){
      //结构体初始化不需要初始化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.4.0;
contract structTest{

    //结构体的定义
    struct  student{
         uint grade;
         string name;

    }
    /*1、必须是internal修饰  2、结构体变量stu是storage指针,不能和memory指针直接赋值(这就好像两个指向不同区域的指针是没法赋值一样的意思)*/
    function test1(student memory s) internal{
        student 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.4.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){
        test1(stu);//传递的实际是指向stu对象的指针
        return stu.name;
    }//返回IU

}

情形2:memory转storage

pragma solidity ^0.4.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){
        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.4.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){
        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){
        test1(stu);//传递的是指向stu的指针
        return (stu.grade,stu.name);
    }//返回(10,"李知恩")
    //由于internal外部无法调用,因此采用这种方式在外部间接调用test1,结果说明内存中的IU的值发生了改变
    function test() public view  returns(uint,string){
       return test1(stu);
    }//返回(10,"IU")

}

情形4:memory转memory

pragma solidity ^0.4.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){
        student memory stu=student(100,"李知恩");
        test1(stu);//memory上的对象stu,传递的是指向它的指针(这里比较特殊,属于本身对内存的一个优化操作)
        return (stu.grade,stu.name);
    }//返回(18,"IU")
}

5.3 enum

enum 枚举体名字{枚举1,枚举2…} 枚举1-N不能有汉字,本质上枚举1数值为0,枚举2数值为1,以此类推
枚举体主要用于状态的切换,例如从状态1切换到状态2,让代码具有可读性!
pragma solidity ^0.4.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){
        require(step==state.s1);//必须完成s1才能切换到s2
        step=state.s2;
        return "s1 over!";
    }

    function sencondStep() public returns(string){
        require(step==state.s2);//必须完成s2才能切换到s3
        step=state.s3;
        return "s1,s2 are over!";
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值