【跟乐乐学solidity】三 进阶:以太合约函数、切面特性、多重继承特性

【跟乐乐学solidity】三 进阶:以太合约函数、切面特性、多重继承特性,第1张

一.重载特性

solidity具有和java一样的方法重载概念,大致用法也和java一样。
但是有一点需要注意的是,有时需要避免在内部被调用的重载方法,因为其形参类型发生了冲突而导致编译出错的问题。
比如形参的类型如若是uint数字类型,则需要避免位数冲突。
下面我们做个举例,
testReloadMethod作为重载方法名,一个形参是uint16,一个是uint,而合约内部的loadMethod方法传递了123去调用重载方法,发生了报错。
为什么呢?因为123这个值既可以是单位数(uint)也可以是8位数(uint8)
编译器不知道,你应该传给哪个重载方法,所以报错。
而当改为256时,就不会报错。因为编译器知道,uint8只能容纳255及以下数字的数据,只能走形参类型为uint的重载方法。

二.指定参数名传递特性(函数命名参数)

格式:函数名({目标形参名1:目标数据值1,目标形参名2:目标数据值2,目标形参名n:目标数据值n})
例如:this.userInfo({_sex:“男”,_age:25,_name:“小野光”});

在java中,你调用一个方法传递参数时,如果有多个参数需要传递,括号内传递的参数和其类型是需要跟着其目标方法的形参顺序来的。
但是在solidity中,你可以通过指定参数名的方式,来不按照顺序传递参数。
需要注意一个规范是,在solidity中,形参命名以’_'下划线开头。

pragma solidity ^0.4.16;

contract loadMethodByParamName {
    
    mapping (uint => string) userMap;
    
    
    function loadMethodForUserinfo(){
        //第一种方式,和java一样。
        this.userInfo("小野光","男",25);
        
        //第二种方式,指定参数名传递,从而可以无视函数顺序。
        this.userInfo({_name:"小野光",_sex:"男",_age:25});
        this.userInfo({_sex:"男",_age:25,_name:"小野光"});
        
    }
    
    function userInfo(string _name,string _sex,uint _age){
        userMap[1] = _name;
        userMap[2] = _sex;
        userMap[3] = new string(_age);
    }
    
}
三.多个返回值特性

格式1(不可省略return):

function 自定义函数名() returns(自定义返回值类型1,自定义返回值类型2,自定义返回值类型3) { 
// 函数体
retrun (变量1,变量2,变量3);
	};

格式2(可省略return):

function 自定义函数名() returns(自定义返回值类型1 自定义返回值名1,自定义返回值类型2 自定义返回值名2,自定义返回值类型3 自定义返回值名3) {
 // 可省略return的函数体
自定义返回值名1 = xxx;
自定义返回值名2 = xxx;
自定义返回值名3 = xxx;
	};

在solidity中,我们可以给函数定义多个返回值类型,以达到同时返回多个返回值的目的。
需要注意的是有两点
1.如果你想要省略return,那么你需要在声明返回值类型的同时,给每个返回值类型命名,并且在函数体内给这些返回值变量赋值。

2.如果你不省略return,那么你可以只需要声明返回值类型,而不需要声明返回值类型名。

需要注意的是,如果你定义了多个返回值,是可以省略return的,但需要给每个返回值赋值。
代码如下:

pragma solidity ^0.4.16;


contract returnTest {
    
    uint uintVariable1 = 10;
    uint uintVariable2 = 5;
    string strVariable = "hello,world";
    
    // 多个返回值类型的函数,只需要声明返回值类型。不可以省略return。
    function returnsMethod1() returns(uint,uint,string){
         return(uintVariable1,uintVariable2,strVariable);
    }
    
    //多个返回值类型的函数,在声明返回值类型的同时还需要命名返回值命名。可以省略return。
    function returnsMethod2() returns(uint _u1,uint _u2,bytes8 _u3){
       /* uint a1 = 1;
        uint a2 = 2;
        bytes8 a3 = 0xa1b2c3f4e7f6fe8f;*/
        
         // 如果你不给返回值变量名赋值,那么该函数所返回的数据就是空的。
         // 因此需要对返回值变量进行赋值。
        _u1 = 1;
        _u2 = 2;
        _u3 = 0xa1b2c3f4e7f6fe8f;
       
    }
    
    
}
四.函数可见性修饰符:external

external属于可见性修饰符,代表仅外部可见
与之相对的,internal代表仅内部可见。
是不是有点抽象?那么通俗易懂的来说,
被external修饰的函数,只能被合约外部调用,不能被合约内的其它函数调用。
被internal修饰的函数,只能被合约内部调用,不能被合约外部调用。

external和internal除 public和 private属性之外,Solidity还使用了另外两个描述函数可见性的
修饰词:internal(内部)和 external(外部)。
internal和 private类似,不过,如果某个合约继承自其父合约,这个合约即可以访问父合约中
定义的“内部”函数。(有点类似于java中的protect)external与public类似,只不过这些函数只能
在合约之外调用 -它们不能被合约内的其他函数调用。
另外,external不能修饰属性,只能修饰函数。

五.构造函数特性

和java一样,在solidity中,也是存在构造函数的。
但是,在java中,构造函数是在创建对象时被调用;
而在solidity中,构造函数是在合约完成部署时被调用。
而且,合约中,无论是有参构造还是无参构造,只允许一个构造函数存在。

不过,构造函数的使用方法随着solidity的更新发生了变化,

or:0.5版本以下的定义格式

在0.5.0以下的版本中,通过定义一个和合约类名相同的函数名,来表示构造函数。

代码如下:

pragma solidity ^0.4.16;

contract constructorTest {
    
    uint a = 100;
    
    function constructorTest() {
        a = a+200;
    }
    
    
}
or:0.5版本以上的定义格式

在0.5.0以上的版本中,则是使用constructor关键字来表示构造函数。

pragma solidity ^0.5.17;

contract constructorTest {
    
    uint a = 100;
    
    // 必须被pulbic权限修饰
    constructor() public {
        a = a+200;
    }
    
    
}
六.modifire(切面特性):函数修改器

在java中,我们可以通过其灵魂框架spring实现切面。
在solidity中,我们同样可以实现切面,它就是modifire,也称之为函数修改器

步骤一 定义切面函数

格式: modifier 自定义函数修改器名(形参数据类型 自定义形参变量名){//函数体}
例如:

       modifier checkout(address accountAddress){
        require(accountAddress == 0xca35b7d915458ef540ade6068dfe2f44e8fa733c);
        _;    
    }
步骤二 定义切面目标函数

格式:

 function 自定义函数名() 欲绑定的函数修改器名1(传递参数1,传递参数2,传递参数n) 欲绑定的函数修改器名2(传递参数1,传递参数2,传递参数n) 欲绑定的函数修改器名n(传递参数1,传递参数2,传递参数n)  {
	// 函数体
    }

例如:

    function getContractBalance() checkout(msg.sender) returns(uint) {
        
        return this.balance;
        
    }
示例代码

下面,我们就结合这个特性写一个业务,我们有一个函数,可以查询合约的余额,但是只能允许指定的钱包地址调用。
所以我们把校验钱包地址的业务写在modifire函数里头,然后让查询合约余额的函数和它绑定。

pragma solidity ^0.4.16;

contract modifierTest{
    
    //函数修改器:校验账户地址
    modifier checkout(address accountAddress){
        
        // require是一个断言函数。
        //如果为true,则往下面继续执行;如果为false则抛异常。
        require(accountAddress == 0xca35b7d915458ef540ade6068dfe2f44e8fa733c);
        _; // _;表示运行其绑定了该函数修改器的目标函数。
        
    }
    
    //获取合约余额
    function getContractBalance() checkout(msg.sender) returns(uint) {
        
        return this.balance;
        
    }
    
    
}
七.多重继承特性

solidity中同样存在继承概念,但是和java不同的是,solidity中可以继承多个类,而java只能继承一个类。
格式: contract 子类名 is 父类A,父类B,父类N { // 类体}
例如:contract xiaoming is mama,papa {}

在多重继承的情况下,如果当所继承的多个父类之间具有相同的方法或属性时,子类将会继承最后一个
打个比方,子类继承父类A和父类B,两个父类都有一个叫做code的属性值,那么子类其继承的code属性值是父类B的而非父类A的。
请看下面的代码:

pragma solidity ^0.4.16;

contract papa{//爸爸类
    
    uint public stature = 175;//身高
    uint public vision = 5.0;//视力
    uint public money = 3000000000;//钱
    
}

contract mama {//妈妈类
    
    uint public stature = 170;//身高
    uint public vision = 4.0;//视力
    string public language = 'chinese';//语言
    
}

//子类小明,继承多个类,分别继承爸爸和妈妈两个类。
contract xiaoming is mama,papa {//子类:小明   父类:妈妈,爸爸
    
    
    
}

我们已知,小明继承爸爸和妈妈,其中爸爸和妈妈都有身高和视力的属性,而爸爸和妈妈属性唯一不相同的,则是爸爸的钱和妈妈的语言。
妈妈在顺序前面,爸爸在继承顺序后面。那么我们执行一下看看小明这边的值。

可以看到,小明作为子类,遗传的身高和视力都是爸爸的,而不是妈妈的,这是因为爸爸的继承顺序排在了后位。

除了多重继承以外,solidity和java的继承并无很大区别。
以下进行个大致介绍:
一.和java不同,子类可以继承多个父类,但父类见若存在相同的函数名或属性名,则子类会选择继承顺序靠后的父类。
二.和java一样,子类只能继承父类的public修饰的函数和属性
三.还是和java一样,子类可以重写父类函数。

八.自我销毁:selfdestruct

我们是否思考过这样一个问题,无论是c还是java,他们归根结底都是计算机的一种语言,最终是作为工具去编写成一个程序并去运行它,而程序也可以被停止;
那么,solidity同样作为语言,所编写的智能合约,应该也存在’停止‘的概念吗?答案是肯定的。
这在合约开发中叫做’自我销毁‘,一旦执行,该合约就不复存在,不再可用。

自我销毁是通过一个隐藏函数:selfdestruct()来实现。

代码如下:

pragma solidity ^0.4.16;

contract selfdestructTest {

    address admin;//权限地址。
    
    uint public currentMoney = 0;//钱
    
    //添加money
    function addMoney(uint money){
        currentMoney+= money;
    }
    
    //设置哪些地址具有销毁权限。
    function setAdmin(address account){
        admin = account;
    }
    
    //校验用函数修改器:判断当前地址是否是具有权限。
    modifier autoCheck(address account){
        require (admin == account);
        _;
        
    }
    
    //销毁合约。只准指定的地址才有权限销毁。
    function selfdestructMethod() autoCheck(msg.sender){
        //销毁函数'selfdestruct'。其中需要传参数,其传入的参数是任意的。一般是执行者地址。
        selfdestruct(msg.sender);
        //selfdestruct(1234567);
        
    }
    
    
}

按照以上的代码,我们进行验证。
1.运行’setAdmin‘方法,传入一个地址,表示该地址具有销毁合约的权限。
2.运行’addMoney‘方法,传入一个值,为money属性添加数值。
3.运行自动为Pulibc权限属性所生成的’currentMoney‘方法,可以看到其值就是上一步传入的值。
4.运行’selfdestructMethod’方法,进行销毁。
5.销毁后,我们运行‘currentMoney’方法,可以看到报错了:

error: Failed to decode output: TypeError: Cannot read properties of
undefined (reading ‘length’)

错误:无法解码输出:类型错误:无法读取未定义的属性(读取“长度”)

说明这个合约不存在了。

九.solidity实体类:结构体

在java中,我们会有实体类的概念,用于表达一些数据结构,比如说大家初学java对象时,会常常以student、person这类实体类为对象进行练习。
solidity也存在实体类的概念,这在solidity中被称之为结构体。
除了名字不同,他们的定义方式和用法也和java不同。
一.结构体的数据 *** 作

步骤一:定义结构体

和java不同,在java中是需要一个专门的类来定义实体类,而在solidity中,则是在类文件中,通过struct关键字来定义结构体。

格式: struct 自定义结构体名 {//结构体属性内容}
例如:

pragma solidity ^0.4.16;

contract storageTest {
    
    //结构体
    struct Person {//人
        string name;//名字
        uint age;//年龄
        string occupation;//工作
    }
 
}
步骤二:实例化结构体对象

结构体类型和string类型一样,都是引用类型而非基本类型,所以实例化时需要memory关键字。

格式一:结构体名 memory 自定义结构体对象名 = 结构体名(参数一,参数二,参数n);
例如:

Person memory personObj = Person("ono hikari",25,"it");

格式二:结构体名 memory 自定义结构体对象名 = 结构体名({目标形参名1:目标数据值1,目标形参名2:目标数据值2,目标形参名n:目标数据值n});
例如:

 Person memory personObjTwo = Person({name:"kang",occupation:"it",age:16});
步骤三:返回结构体对象

如果要将实例化后的结构体对象作为返回值返回给调用者,则有两种方式。

方式一:返回值类型为结构体

直接将结构体对象作为返回值类型返回,但需要可见性修饰符internal来表示仅被合约内部调用。

格式:function() 自定义方法名() internal returns(结构体名) {// 函数体}
例如:

    function instanPersonObj() internal view returns(Person){
        Person memory personObj = Person("ono hikari",25,"it");
        //Person memory personObjTwo = Person({name:"kang",occupation:"it",age:16});
        return personObj;
    }
方式二:多个返回值类型代替结构体

方式一因为只可以内部调用,所以显然有局限性。
那么,我们可以根据结构体的属性内容,来为函数定义多个对应类型的返回值,从而实现我们的需求
格式:function() 自定义方法名() returns(自定义返回值类型1,自定义返回值类型2,自定义返回值类型n) {// 函数体}

例如:

    // 多个返回值类型代替结构体 一
   function instanPersonObj() view returns(string,uint,string){
        Person memory personObj = Person("ono hikari",25,"shizuoka");
        return (personObj.name,personObj.age,personObj.occupation);
    }
    
    // 多个返回值类型代替结构体 二
    function instanPersonObjTwo() view returns(string _name,uint _age,string _occupation){
        Person memory personObj = Person("ono hikari",25,"shizuoka");
       return (personObj.name,personObj.age,personObj.occupation);
    }
    
    // 多个返回值类型代替结构体 三
    function instanPersonObjThree() view returns(string _name,uint _age,string _occupation){
        Person memory personObj = Person("ono hikari",25,"shizuoka");
        _name = personObj.name;
        _age = personObj.age;
        _occupation = personObj.occupation;
    }
扩展一:结构体作为函数形参

结构体作为函数形参时,其函数必须被internal修饰。
这样的设计也是符合业务逻辑的,因为你从合约外部调用的话,总不能是以其结构体的json文本形式来传参调用它。
所以才只准被内部调用。
格式:function() 自定义方法名(结构体名 自定义结构体形参名) internal {// 函数体}
例如:

    function paramStructPerson(Person personObj) internal view returns(string,uint,string){
            if(personObj.age < 18){
                personObj.occupation = "学生";
            }
        return (personObj.name,personObj.age,personObj.occupation);
        
    }
扩展二:结构体的嵌套

在java中,我们可以对实体类进行嵌套。
比如如下的java代码:

public class Student {
    private String name;
    private int age;
    private Student student;//该属性是其'Student'实体类本身。目的在于实现实体类自身的嵌套。
}    

那么,在solidity中,我们的结构体嵌套照着java这个样子,如果直接在结构体内部定义一个类型为当前结构体的属性将会编译报错。
所以,为了在solidity中实现结构体嵌套,我们将会采用mapping这个map集合来实现结构体自身的嵌套。

当然,我们不仅可以用mapping实现结构体自嵌套,也可以用数组实现。
总之,要灵活应用。

    struct Person {
        string name;
        uint age;
        string occupation;
        mapping(uint => Person) mapForpersonObj;//结构体自嵌套  mapping类型
        Person[] personArray;//结构体自嵌套 数组类型
    }
扩展三:结构作为成员变量

结构如果作为成员变量,其默认类型为storage指针类型,而非memory类型。

代码如下:

pragma solidity ^0.4.16;

contract structTest {

    struct Person {
        string name;
        uint age;
        string occupation;
        mapping(uint => Person) mapForpersonObj;
    }
    
    
    Person personObj;//结构体作为成员变量,默认为storage类型。
    
    function getPersonVariable() returns(string) {
        personObj = Person("123",12,"add");//结构体初始化时,可忽略初始化mapping属性。
        Person ps = personObj;
        ps.name = "aaa";
        
        return personObj.name;//输出'personObj'的name字段,发现结果为aaa。这是因为结构体作为成员变量,默认为storage指针类型。
    }

}
结构体用法总结

一.结构体作为返回值类型或函数形参时,其函数必须被internal修饰,只能被内部调用。
二.结构体若要嵌套,不能直接定义结构体属性,而是使用mapping或数组实现嵌套。
三.结构体作为成员变量时,默认为sotrage指针类型。
四.若想返回结构体数据又不想使用internal修饰的话,则可以定义其结构体对应的属性类型为返回值类型,如:returns(string _name,uint _age,string _address);

十.结构体在storage和memory的类型转换 什么是storage和memory? 值引用类型:memory

在【跟乐乐学solidity】一 基础:字节数组/普通数组与字符串 *** 作 小知识:memory(值引用)关键字 中介绍到,定义一些变量时,我们用memory来表示它是一个值引用类型的变量,这意味着当把值引用类型的A对象赋值给相同值引用类型的B对象时,B对象只是拷贝了A对象的数据,B的对象随后对其属性的任何修改,都不会对A对象产生影响。这意味这memory不具备指针特性。

指针传递类型:storage

storage是指针传递类型,假设现在声明了指针传递类型的对象A,随后又将其赋值给对象B,那么对象B后续的任何修改,都将影响到对象A。因为它们都指向一个内存、更改的都是同一个内存区域。
更多的介绍可以看这里:http://www.lianyi.com/hot/memory

一.结构体storage转memory

pragma solidity ^0.4.16;

contract structTest {
    
    
    struct Person {
        string name;
        uint age;
        string occupation;
        mapping(uint => Person) mapForpersonObj;
    }
    
    
    Person personObj;

    
    function getPersonVariable(Person storage personObj2) internal returns(Person) {
        Person ps = personObj2;
        ps.name = "test";
        
        return personObj2;
    }
    
    function load() view returns(string) {
       Person memory ps3 = getPersonVariable(personObj);//此处将从storage指针指向类型其转化为了memory值引用类型。
       
        return ps3.name;//得到结果:test
    }
    
    
    
}

二.结构体memory转storage

pragma solidity ^0.4.16;

contract structTest {

    struct Person {
        string name;
        uint age;
        string occupation;
        mapping(uint => Person) mapForpersonObj;
    }
    
    Person personObj;

    function getPersonVariableMemory(Person memory personObj2) internal returns(Person) {//此处结构体形参为memory
        personObj = personObj2;//将memory的形参赋值给了storage类型的成员变量,实现了memory到storage的转换。
        return personObj;
    }
    
    function load() view returns(string) {
        return  getPersonVariableMemory(Person("123",12,"321")).name;//得到结果 123
        
    }

}
十一.枚举体

和java一样,solidity也存在枚举概念,不过solidity中的枚举概念和其结构体一样,枚举体也是在合约类文件里面定义,而非专门创建一个枚举类。

步骤一.定义枚举体

枚举体需要使用enum关键字来定义。
定义枚举体时,需要注意以下几点:
1.枚举体元素直接写,不可以有双引号括起来
2.枚举体元素不可以有汉字
3.枚举体结尾不可以有;分号

格式:enum 自定义枚举体名{枚举体元素1,枚举体元素2,枚举体元素n}
例如:enum tradeType{Exchange,BNBSmartChain,Ethereum}

步骤二.使用枚举体

在下面,我将以一个代码做例子来应用枚举体。
下面代码的业务中,定义一个用于枚举交易类型的枚举体’tradeType’,元素分别是交易所、币安链、以太链。
然后我们定义一个枚举体成员变量对象’currentTradeType’,用于表示当前的交易类型。
在setCurrentTradeType函数中,我们可以对枚举体对象‘currentTradeType’设置交易类型。
随后,我们定义了一个‘getEthereumTradeLog’函数,用于获取以太链的交易日志,但是要求当前的交易类型自然而然就必须为Ethereum。因此该函数同checkTradeType进行了切面绑定,用以验证当前交易类型(即枚举体对象’currentTradeType’)。

pragma solidity ^0.4.16;

contract enumTest {
    
    enum tradeType{Exchange,BNBSmartChain,Ethereum}
    
    
    tradeType currentTradeType;
    
    
    function setCurrentTradeType(uint typeNum){
        if(typeNum == 1){
            currentTradeType = tradeType.Exchange;
        } else if(typeNum == 2){
            currentTradeType = tradeType.BNBSmartChain;
        } else if(typeNum == 3){
            currentTradeType = tradeType.Ethereum;
        }
        
        
    }
    
    modifier checkTradeType() {
        require(currentTradeType == tradeType.Ethereum);
        _;
    }
    
    function getEthereumTradeLog() checkTradeType() returns(string){
        return "test";
        
    }
    
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/1298515.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存