Solidity基础入门讲解

Solidity的语言类型:

  1. 静态类型的语言:编译前变量类型需要先确定
  2. 变量可以分为:
    值类型:赋值或者传参时总是进行值拷贝
    引用类型:传递的是地址
    这里先给出一个总览图:
    这里写图片描述

整型:

和其他语言类型,可以用int关键字声明:
有符号整数:int8,int16,int32,……int255,
无符号整数:uint8,uint16,uint32,……uint255,
跟在关键字后的数字表示多少字节,表示数大小的范围,跟默认int,表示255。
注意
整型的溢出问题分为:向上溢出和向下溢出,例如:
uint8表示的最大数==》255,如果此时加1,会出现向上溢出的错误,此时的结果为:0;uint8表示的最小值==》0,此时如果减1,会出现向下溢出的错误,结果为:255。
如何解决上述的溢出问题呢?
可以使用assert(断言)关键字,进行预判断处理。

定点浮点型:

关键字:fixedufixed,对应于其他语言的float和double.
3. 使用时需要声明位数,指明大小
4. 格式:
fixedMN,其中M为:数值,代表所占的空间位数,N为:小数点位数,M以8步进,可为8到256位,N可为0到80之间。

定长字节数组:

关键字:bytes
占空间固定的数组:
bytes1,bytes2,…bytes32,其中的数字的单位为字节。
1.通过属性.length获取字节数组的长度
2.可以像字符串一样使用:bytes2=”couse”
3.可以想整型一样运算比较和位运算
4.像数组一样使用索引:bytes8[k],返回第k+1个字节,0<=k<8

常量:

整型常量:1,23,1.55
字符串常量:不支持“+”运算
十六进制常量:hex”aabb”

枚举:

1.关键字enum用来自定义类型:如下
enum ColorChioce{Red,Bule,Yellow}
2.可与整数进行转换,但不能进行隐式转换
3.枚举类型应至少有一名成员

地址类型:

关键字:address,表示一个账户地址(20字节)
属性:.balance,获取余额,单位是wei,1eth(以太币)=10^18wei
成员函数:
1.transfer(),转账函数
2.send(),也是转账函数,错误时不发生异常返回false,使用时一定要检查返回值,大部分时候使用transfer()。
addr.transfer(y) 等价于 require(addr.send(y))
注意:
send()和transfer()使用时有2300gas限制,当合约接收以太币时,转账容易失败。
3.call(),可以调用另外一个合约地址的函数,如下:
addr.call(函数签名,参数):调用对应函数
addr.call.value(y)()功能上类似addr.transfer(y),但没有gas的限制。
addr.call.gas():指定gas,指定调用函数消耗的gas。
补充:如何区分合约地址及外部地址?
答:evm提供了一个操作码EXTCODESIZE,用来获取地址相关联的代码大小(长度),如果是外部帐号地址,没有代码返回0,因此我们可以使用以下方法判断合约地址及外部帐号地址:

uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}

函数类型

solidity中,函数是一种类型。像其他类型⼀一样,函数类型可以作为变量量类型,也可以作为返回值类型(函数式语⾔言的特点)
函数分为两种类型:
1.外部(external)函数:发起EVM消息调用,使用:( 地址.函数名) 进行调用
2.内部(internal)函数:代码跳转调用, gas小的多,但法在合约外部调用,使用: (函数名) 进行调用,默认是内部(internal)函数。
声明格式:
1.声明一个函数:function foo(int) 或者 function foo(int) external returns(int)
2.声明一个韩式类型变量:function(int) foo 或者 function(int) external returns(int) foo

数据储存位置:

1.storage(区块链中):永久储存在区块链链中。
例:①状态变量

contract A {
uint a;
}

②复杂类型的局部变量

contract A {
function fun() {
uint[] arr;
}
}

2.memory(EVM内存中):随着交易的结束,内存释放。
例:局部变量

contract A {
function fun(uint[] a) {
uint b;
}}

两者的赋值表现:
在memory和storage之间总是会创建一个完全独⽴立的拷贝。
状态变量量之间相互赋值,总是会创建一个完全独⽴立的拷贝。
同样的数据位置之间赋值只是引⽤用的传递。
示例代码:

contract TestLoc(){
    uint[] x;//x的储存位置是storage

    function f(uint[] memoryArray) public returns (uint[]){

    x=memoryArray;//从memory 复制到storage

    uint[] y=x;//storage引用传递给复杂局部变量y(y是一个storage引用)
    y[1]=2;     //此时x,y都会被修改

    g(x);//引用传递,g可以改变x的内容
    h(x);//拷贝到memory,h无法改变x的内容
}
    function g(uint[] storage storageArray) internal{}
    function h(uint[] memoryArray) public {}
}

数组:

数组声明的两种格式:
1.普通格式:T[k]
T: 元素类型, k: 数组长度(可选)
uint [10] tens; uint [ ] us;
uint [] public u = [1, 2, 3]; //public数组状态变量量会自动生成一个对应变量量的函数,数组长度省略
2.使用new 关键字:
uint[] c = new uint;、需要指定长度,不能省略
属性:
1.length,获取长度
uint [10] tens; //tens.length 为10
storage的变长数组,可以通过给.length赋值调整数组长度
memory数组一旦创建,大小不不可调整
2..push() 添加元素,返回新长度
仅针对变长数组,memory数组不支持

字节数组:

字节数组bytes:
类似 byte[ ] , 不过bytes作为外部函数的参数时占用的空间更更小,推荐使用,相比前面的定长字节数组,这个是不定长的。
声明形式: bytes mybs;
bytes mybs=“hello world”

字符串:

solidity中,字符串也是数组
声明: string mystr;
string mystr=”Solidity入门详解”;
注意:
字符串没有.length属性 ,但是可以通过bytes(s).length 获取字节数组的长度,但也只是字节数组的长度,没法获得字符串长度。
另外还可通过bytes(s)[k] : 获取字节数组下标k的UTF-8 编码。
bytes和string都可以储存字符串,那他们的区别是声明呢?
两者储存数据的方式不一样
bytes: 用来存储任意长度的字节数据
string: 用来存储UTF-8编码的字符串数据
前面说到我们没法获取字符串的长度,那怎么才能获取呢?
引入第三方库:stringutils,github地址:https://github.com/Arachnid/solidity-stringutils
该第三库支持很多强大的功能:
获取字符串长度
匹配字符串串:find(), startsWith(),endsWith()
字符串串拼接
….

映射

关键字:mapping
声明一个映射类型:
mapping(键=> 值) public mymapping
键类型不能是变长数组、合约类型、嵌套类型值类型无限制
如下:
mapping(address => uint) public balances;
访问形式:
索引访问,mymapping[键]
限制:
1.只能作为状态变量,不能放在函数里声明
2.无法遍历访问,没有长度,没有键集合,没有值集合
解决限制
因为solidity提供的这个mapping的功能实在有限,因此我们可以借助第三方库来实现一些有用的功能,第三方库github地址为:https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol

结构体

关键字: struct,类似C语言的结构体
定义一个结构体:

struct First{
bool myBool;
uint myInt;
}

其中结构体的类型可以为:基本类型、数组、结构体、映射
声明与初始化:
仅声明变量,不初始化:

First  f1;

按成员顺序初始化:

First  f1 = First(true,1);

命名方式初始化:

First  f1 = First({myBool:true,myInt:1});

访问与赋值:

First  f1 ;
f1.myBool=true;

使用限制:
结构体目前仅支持在合约内部使用,或继承合约内使用,如果要在参数和返回值中使用结构体,函数必须声明internal。

类型转换:

将一个类型转为另一个类型,转换可分为隐式转换和显式转换。
1.隐式转换:
运算符两边有不同的类型,不会丢失数据下,编译器会尝试隐式转换类型,如下:
uint8 -> uint16,uint256
uint16,uint256 -> uint8(出错)
2.显式转换:
如果编译器不允许隐式的自动转换,但你知道转换没有问题时,可以进行强转。
注意:不正确的转换会带来错误,如果转换为一个更小的类型,高位将被截断。

重置变量

对一个变量进行重置
关键字:delete
**使用:**delete 变量名;
使用重置变量后,变量会变为默认值,下面列出各个类型的默认值:

bool -> false;
uint -> 0
address -> 0x0
bytes -> 0x0
string ->""
注意:
delete 对映射无效

例如:

aa=true;
delete aa;//aa变为false

uint[] memory arr = new uint[](7);
delete arr; // a.length = 0;

CustomType memory ct = CustomType(true, 100);
delete ct; // ct.myBool = false ; myInt = 0;

特别注意:
delete 不影响值拷贝的变量,但是如果是传递引用的变量,就会受到影响。

内置API

使用时间日期:
1.now函数:获取当前时间
2.引入第三方库执行更多功能,推荐github·上一个第三方库:https://github.com/pipermerriam/ethereum-datetime
区块和交易相关API:

blockhash(uint blockNumber) returns (bytes32):返回给定区块号的哈希值,只支持最近256个区块,且不包含当前区块。
block.coinbase (address): 当前块矿工的地址。
block.difficulty (uint):当前块的难度。
block.gaslimit (uint):当前块的gaslimit。
block.number (uint):当前区块的块号。
block.timestamp (uint): 当前块的Unix时间戳(从1970/1/1 00:00:00 UTC开始所经过的秒数)
gasleft() (uint256): 获取剩余gas。
msg.data (bytes): 完整的调用数据(calldata)。
msg.gas (uint): 当前还剩的gas(弃用)。
msg.sender (address): 当前调用发起人的地址。
msg.sig (bytes4):调用数据(calldata)的前四个字节(例如为:函数标识符)。
msg.value (uint): 这个消息所附带的以太币,单位为wei。
now (uint): 当前块的时间戳(block.timestamp的别名)
tx.gasprice (uint) : 交易的gas价格。
tx.origin (address): 交易的发送者(全调用链)

什么是ABI?
ABI是Application Binary Interface的缩写,即应用程序二进制接口。
当我们向 合约地址发送一个交易,交易的内容就ABI编码数据。
作用:用来计算一个函数以及参数的ABI编码数据。
ABI的编码函数有如下这些,用来直接得到ABI编码信息:

* abi.encode(...) returns (bytes):计算参数的ABI编码。
* abi.encodePacked(...) returns (bytes):计算参数的紧密打包编码
* abi. encodeWithSelector(bytes4 selector, ...) returns (bytes): 计算函数选择器和参数的ABI编码
* abi.encodeWithSignature(string signature, ...) returns (bytes): 等价于* abi.encodeWithSelector(bytes4(keccak256(signature), ...)

错误处理:
solidity的错误处理如下:
当发生错误处理的时候,会出现事件的回滚。
do something1
do error
do something2
当执行到do error,错误发生,此时 do something2,do something1都不会执行。
错误处理函数:
1.assert(bool condition):用于判断内部错误,天剑不满足时抛出异常,此时会消耗掉所剩余的gas。
2.require(bool condition):用于判断输或者外部组件错误,条件不满足时判处异常,此时gas会返回还给调用者,区别于assert。
require(bool condition,string message):同时上,多了一个错误信息。
3.revert():终止执行还原改变的状态。
revert(string,reason):同上,提供一个错误提醒信息。
用require还是assert?
使用require的情况:
1.用于检查用户输入;
2.用于检查合约调用返回值,如require(addr.send(1));
3.用于检查状态,如:msg.sender==owner;
4.通常用于函数的开头;
5.不知道使用哪一个的时候
使用assert的情况:
1.用于检查溢出错误,如‘z=x+y;assert(z>=x)’;
2.用于检查不应该发生的异常情况;
3.用于在状态改变之后,检查合约状态;
4.尽量少用assert;
5.通常用于函数中间或者结尾;

函数修改器:

关键字:modifier
函数修改器可以改变一个函数的行为,通常用于在函数执行检查某种前置条件。

modifier onlyOwener{
        require(msg.sender==owner);
        _;
}

function my() public onlyOwener{
        //do something;
}

函数修改器修饰函数时,函数题被插入到“_”处。所以在执行my()函数前,会执行onlyOwener这个函数,如果检查通过,再执行my()函数。
一些补充:
1.函数修改器可被继承
2.函数修改器可接受参数
3.多个函数修改器一起使用(空格隔开,修改器会一次检查执行)

函数修饰符:

1.payable:边表示一个函数能附加以太币调用:
function f(uint a) public payable returns (uint):{
}
2.view:表示一个函数不能修改状态。
view函数和constant等价,constant在0.5版本之后会弃用。使用了这个修饰符的函数不会消耗gas。
3.pure:表示一个函数不读取状态,也不修改状态。本地执行不消耗gas。

solidity的继承:

1.继承的合约可以访问所有非private成员。
external:外部访问、
public:内外都可以、
internal:内部级继承、
private:内部,继承的也不行
2..is表示继承,通过复制代码的方式实现继承

contract  A{
}
contract  B is A{
}

3.如果父合约与构造函数,那么派生合约需要调用父合约的构造函数,如果有参数,派生合约需要提供参数调用父合约构造函数。
形式一:

contract  B is A(uint a){
}
//其中a为传递给父合约A的参数

形式二:

contract B is A{
    constructor(uint a) A(a) public{
    }
    }

3.多重继承:
is后接多个合约,基类合约在is后的顺序很重要。继承顺序原则是从最接近基类到最接近派生类。
4.抽象合约:
合约在没有函数体(实现)的函数
合约不能通过编译,只能继承
5.接口:
关键字:interface
里面的函数必须是未实现的。函数不能继承其他合约或者接口。不能定义构造器,变量,结构体,枚举类。
抽象合约和接口的作用主要是用做基类。

1.库其实就是一个特殊的合约:
可以像其他合约一样进行部署,但是没有状态变量,不能存以太币。
2.可重用:
部署一次,在不同的合约内反复使用。节约gas,相同功能的代码不用重复部署
3.定义库、使用库:
定义:

library  my{
    myfunction();
}

使用:
先导入:import “./my”,,然后my.myfunction();
4.网上好用的常用库:
openzeppelin,github地址是:https://github.com/Openzeppelin/openzeppelin-solidity
另外还有:ethereum-libraries,dapp-bin,Stringutils等

回调函数

定义:无名称。无参数,无返回值的函数
一个合约可以有一个回退函数。当给合约转以太币时,需要有payable回退函数。如果调用合约时,没有匹配上任何函数,就会调用回退函数。

未完。。。

猜你喜欢

转载自blog.csdn.net/qq_34493908/article/details/82120032