solidity学习一(数据类型、增删改查合约)

abi-to-sol在线转换
https://gnidan.github.io/abi-to-sol/

发布合约
https://ropsten.etherscan.io/verifyContract-solc?a=0x9bbc5c7eeb4ac6facce81aba65fd9a1b8bb28cd0&c=v0.8.1%2bcommit.df193b15&lictype=1

一、数据类型,学一门语言,先看数据类型:
1.基础类型 value type

  1. bit:位
      一个二进制数据0或1代表1bit
  2. byte:字节
    存储空间的基本计量单位,如sql server中的varchar(60)即是指60个字节
    1 byte = 8 bit
  3. 一个英文字母占一个字节
    1 英文字母 = 1 byte = 8 bit
  4. 一个汉字占2个字节
    1 汉字 = 2 byte = 16 bit
    在这里插入图片描述
    5.error ,0.8以上版本

2.引用类型 reference type
2.1数组 (字符串与bytes是特殊的数组,所以也是引用类型)
uint[5] array; 固定长度的数字,不能改变长度,不能用push等。

2.2struct (结构体),类似js object,用.访问成员
struct成员是任意type
value type、reference type、mapping都可以
也可以是别的struct、或者dynamic array
struct stu{
uint id;
string name;
mapping(uint=>string) maptest; //mapping即使在结构体内,初始化时也是可以忽略的
}
stu memory student2=stu({name:‘stu2’,id:5678}); // 设置

2.3map (映射)
mappingName[name] = “something”; //赋值
mappingName[name]; //获取值
第三方库:
mapping: github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol
mapping没有长度,无序,所以获取不到长度。不能用迭代访问。

// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
contract MappingExample{
    mapping(address => uint) public balances;
    
    function update(uint amount) public returns (address addr){
        balances[msg.sender] = amount;
        return msg.sender;
    }
}

数据位置data location类型,分为memory(内存-临时)和storage(区块链-永久),通过在变量名前声明memory还是storage来定义该变量的数据位置。一般来讲,函数参数默认为memory,局部复杂类型(作用域为局部)以及状态变量(作用域为全局)属于storage类型。还有一个calldata与memory差不多,专门用于存储函数参数的,也不是永久存储。额外提一点,EVM的memory是基于stack的,stack可以临时存储一些小的局部变量。这些变量存储消耗的gas是不同的,storage最大>memory(calldata与memory差不多)较小>stack几乎免费

  • 数组类型Arrays,长度可定可变,可以存储于storage和memory,元素类型可以是任何类型,但memory时不能是映射类型(就是键值对类型)。uint[5] array; 固定长度的数字,不能改变长度,不能用push等。
  • 结构体struct(如下例子),与Go语言相同的设定,自定义类型,使用方式也与Go极为相似。类似js的对象,使用前需要声明。
  • mapping 类型就是键值对,现在最新语言都会给自身增加键值对数据结构的封装支持。mapping的声明方式为:
    mapping(_KeyType => _ValueType)
    键值对中间通过一个“=>”连接。元素内容,Solidity类型均可,与其他键值对使用差不多,遇到问题再深入研究。

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

address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10)
    x.transfer(10);

address的四个方法
send,call,callcode,delegatecall
例子:
发送以太币的send方法
//下面是send方法,涉及到以太币的情况可能用到payable,senddemo方法是可以发送以太币过去的,add.transfer(u)

pragma solidity ^0.4.1;
contract addressDemo{
function addressDemo() payable{
}

function sendDemo(address add){
uint u=1 ether;//以太币的最小单位是wei,最大单位是ether
add.transfer(u)
}
}

call方法,注意地址.call,和地址.delegatecall方法的区别,call是自己本身不发生改变,被调用的值发生改变,delegatecall是自己本省的值发生改变,被调用的不发生改变

pragma solidity ^0.4.1;

//首先定义了两个合约
contract A{
uint public p;
event e(address add,uint p)//为了观察声明一个事件
//定义了一个方法
function fun(uint u1,uint u2) {
p=u1+u2;//改变了A合约中的p,改变被调用者合约方法中的变量
e(msg.sender,p)//下面的2,3传给了fun方法

}

}

contract B{

uint public q;
bool public b;

//当我们用B中的call方法的时候,调用A中的某个方法的执行,只会改变A中某个方法的值,B中的某个方法的值不会发生改变,被调用的合约本身发生改变

function call1(address add) returns(bool){
b=add.call(bytes4(keccak256("fun(uint256,uint256)")),2,3)
return b;//下面的add调用call方法
}

//下面的delegatecall是会改变B中的某个方法,而A中的某个方法是不会有任何的改变,下面是自己的合约发生变化
function call2(address add) returns(bool){
b=add.delegatecall(bytes4(keccak256("fun(uint256,uint256)")),1,3)
return b;
}

}

例子

pragma solidity ^0.4.0;

//定义一个合约
contract CA{
uint public p;
bytes public failmsg;
string public str;
event e(address add,uint p)
event e1(address add, bytes b);

//定义一个构造函数,构造方法 
function CA(string _str) {
str=_str;//str重新被赋值
}
function fun(uint u1,uint u2) {
p=u1+u2;
e(msg.sender,p)
}
//构造一个匿名函数
function () {//下面就会执行这个匿名函数
failmsg=msg.data;
e1(msg.sender,failmsg)//
}
}

contract CB{
uint public q;
bool public b;
function call1(address add) returns(bool){
b=add.call(bytes4(keccak256("fun(uint256,uint256)")),2,3);
return b
}
function call2(address add) returns(bool){
b=add.delegatecall(bytes4(keccak256("fun(uint256,uint256)")),2,3);
return b
}

 

//使用的是call方法,上面发生的值发生改变,下面不发生改变
function call3(address add) returns(bool){
b=false
b=add.call("aaaa",2,4,5,54,3);
return b
}


//下面是本身自己的值发生改变,b的值发生改变
function call4(address add) returns(bool){
b=false;
b=add.delegatecall("bbbb",5,"10x2323",43);//执行匿名函数
return b
}
}

二、变量类型:
1.状态变量
2.局部变量
3.全局变量:blockhash(uint blockNumber) returns (bytes32)、block.gaslimit (uint)、tx.gasprice (uint)

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // 状态变量
   constructor() public {
      storedData = 10; // 使用状态变量
   }
}

三、作用域:
Public – 公共状态变量可以在内部访问,也可以通过消息访问。对于公共状态变量,将生成一个自动getter函数。
external-外包合约才能访问,内部访问要加this
Internal – 内部状态变量只能从当前合约或其派生合约内访问。
Private – 私有状态变量只能从当前合约内部访问,派生合约内不能访问。

四、data location, 4个位置:(https://www.lidihuo.com/solidity/solidity-variabledatalocation.html)

Storage
该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储。
保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高。
Memory
内存位置是临时数据,比存储位置便宜。它只能在函数中访问。
通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)。
Calldata
Calldata是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置。
Stack
堆栈是由EVM (Ethereum虚拟机)维护的非持久性数据。EVM使用堆栈数据位置在执行期间加载变量。堆栈位置最多有1024个级别的限制。

花费gas:storage > memory(calldata) > stack

五、变量的数据位置
规则1 – 状态变量
状态变量总是存储在存储区storage中。(隐式地标记状态变量的位置)

pragma solidity ^0.5.0;
contract DataLocation {
   // storage
   uint stateVariable;
   uint[] stateArray;
    uint storage stateVariable; // 错误
   uint[] memory stateArray; // 错误
}

规则2 – 函数参数与返回值
函数参数(值类型)包括返回参数(值类型)都存储在内存memory中。
return

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
   }
}

规则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 – 局部storage的赋值,只能赋值给引用类型指针reference

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.8.0;
contract C{
    uint[] x;
    function f(uint[] memory memoryArray)public{
        x = memoryArray; // 会把整个memory array复制进去storage中
        uint[] storage y = x; // 把x的reference复制给y
        y[7]; //回传x的第八个元素
        y.pop(); // 会将x的最后一个元素删除
        delete x; // 把x清空
        // 下面2行会失败,要赋值指针,x就是指针
        // y = memoryArray;
        // delete y;

        g(x); // 只会传达reference
        h(x); // 会产生一个独立,暂时的复制,在memory中
    }
    function g(uint[] storage) internal pure{} // 因为只是修改指针,所以消耗gas少,便宜
    function h(uint[] memory) public pure{}// 全部复制一个新的对象,所以消耗gas多,贵。
}

规则5 – 外部函数的参数
Calldata是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置。
calldata
web3.sendTransaction(from,to,calldata)
calldata就是外部函数的参数

六、赋值数据的位置


七函数
状态可变性(mutability)

Private(私有):限制性最强,函数只能在所定义的智能合约内部调用。
Internal(内部):可以在所定义智能合约内部调用该函数,也可以从继承合约中调用该函数。
External(外部):只能从智能合约外部调用。 (如果要从智能合约中调用它,则必须使用 this。)
Public(公开):可以从任何地方调用。 (最宽松)

状态可变性(mutability)
view:用view声明的函数只能读取状态,而不能修改状态。不消化gas
pure:用pure声明的函数既不能读取也不能修改状态。不消化gas
payable:用payable声明的函数可以接受发送给合约的以太币,如果未指定,该函数将自动拒绝所有发送给它的以太币。

pragma solidity 0.8.10;
contract C {
  uint balance = 0;
// Payable function that allows other contracts
// to send ether to this contract.   
function deposit () payable public{
    balance += msg.value;
  } 
}

no-payable 如果未指定,该函数将自动拒绝所有发送给它的以太币。

pragma solidity 0.8.10;
contract D {  uint count = 0;
// non-payable function that reads and modifies state.
function increment() public returns(uint){
    count += 1;
    return count;  
}
}

增删改查例子:

// SPDX-License-Identifier: MIT
// 可见性:public 状态可变性:空
// 此函数将用户的ID删除,如果找到,则将其从数组中删除;如果用户不存在,则回退交易。
// 提示:由于最后三个函数都需要查找用户,因此你将需要创建一个私有函数,该函数将获取用户的ID并在数组中返回其索引(如果找到),以避免重复相同的代码。
pragma solidity ^0.7.0;
 
contract Crud {
    
    struct User {
        uint256 id;
        string name;
    }
    
    User[] public users;
    uint256 public nextId = 1;
    
    function add(string memory name) public {
        User memory user = User({id : nextId, name : name});
        users.push(user);
        nextId++;
    }
    
    function read(uint256 id) public view returns(string memory){
        uint256 i = find(id);
        return users[i].name;
    }
    
    function update(uint256 id, string memory newName) public {
        uint256 i = find(id);
        users[i].name = newName;
    }
    
    function destroy(uint256 id) public {
        uint256 i = find(id);
        delete users[i];
    }
    
    function find(uint256 id) private view returns(uint256){
        for(uint256 i = 0; i< users.length; i++) {
            if(users[i].id == id)
                return i;
        }
        revert("User not found");
    }
    
}

二、Solidity 0.4.10 版本发布了新的 assert() , require() 和 revert() 函数,解决了以前代码中有困惑的地方。
参考:https://blog.csdn.net/qq_33829547/article/details/80377689
https://remix.ethereum.org/#optimize=false&runs=200&evmVersion=null&version=soljson-v0.7.1+commit.f4a555be.js
Solidity 0.4.10之前(以及其后一段时间),这种强制授权处理方式很普遍:
if (msg.sender != owner) { throw; }

完全等价于如下三种形式(新方法):

if(msg.sender != owner) { revert(‘Something bad happened’); }

assert(msg.sender == owner);

require(msg.sender == owner, ‘Something bad happened’);// 参数1不成立,执行参数2. 如果参数1成立,执行require

注意在 assert() 和 require() 例子中的条件声明,是 if 例子中条件块取反,也就是用 ==代替了 != 。

revertrequireassert
代替throw
允许返回一个数值允许返回一个数值
将剩余gas返还调用者将剩余gas返还调用者不返还gas
if后面使用在函数最开始的地方使用在函数结尾处使用
一般地,尽量使用 require 函数,require 应该在函数最开始的地方使用

一般地,尽量少使用 assert 调用,assert 应该在函数结尾处使用
除非认为之前的检查(用 if 或 require )会导致无法验证 overflow,否则不应该盲目使用 assert 来检查 overflow”

一、程序的授权码// SPDX-License-Identifier: GPL-3.0
可以从这里挑:https://spdx.org/licenses/
不开发直接写:// SPDX-License-Identifier: UNLICENSED
二、版本:pragma solidity =0.8.7; //合约版本
三、合约
contract Vote {

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端段

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值