来和我一起探索Solidity语言吧

 注意:本篇因为主要讲理论知识,所以会有点枯燥,你们要静下心来看,并且为了让你们方便理解,我已经尽量浓缩了精华,还加了例子。

1、结构体

结构体可以在合约中作为变量、函数参数或返回值使用。可以使用点(.)操作符来访问结构体中的字段,类似于其他编程语言中的对象属性访问。通过使用结构体,可以更好地组织和管理复杂的数据,提高代码的可读性和可维护性。

例子

在Solidity中,可以使用点(.)操作符来访问结构体中的字段。假设我们有一个名为Person的结构体,其中包含姓名(name)和年龄(age)两个字段,可以按照以下方式访问结构体中的字段:

声明结构体变量并赋值:

Person person;

person.name = "Alice";

person.age = 25;

在函数参数中使用结构体:

function updatePerson(Person memory _person) public {

    _person.name = "Bob";

    _person.age = 30;

扫描二维码关注公众号,回复: 17430041 查看本文章

}

在函数返回值中使用结构体:

function getPerson() public view returns (Person memory) {

    Person memory person;

    person.name = "Charlie";

    person.age = 35;

    return person;

}

2、函数修改器

在Solidity中,可以在合约中定义一个或多个函数修改器,并在需要的函数前面使用modifier关键字进行声明。然后,在函数定义中使用modifier关键字来指定应用的修改器。

函数修改器可以用于验证函数的调用者是否具有足够的权限、检查函数的输入参数是否合法、记录函数的执行日志等。

一般是和函数一起使用,在执行函数之前看一下是否符合条件,符合则执行函数,不符合则抛出错误。

通过使用函数修改器,可以提高代码的可读性和可维护性,同时也能够避免代码重复和错误。

3、事件 Event

事件可以被看作是合约与外部世界之间的通信工具。当合约中的某个条件满足时,可以触发一个事件,将相关的信息记录下来。这些信息可以被外部应用程序监听和处理,从而实现合约与外部世界的互动。

事件通常包含一些字段,用于记录与事件相关的数据。当事件被触发时,这些字段的值将被记录下来,并可以被外部应用程序读取和使用。

通过使用事件,可以实现合约的状态变化的可追溯性和可观察性,同时也能够方便地与外部应用程序进行交互和通信。

4、类型

1、布尔类型(bool):表示真或假的值。

2、整数类型(int、uint):表示有符号或无符号的整数值,可以指定位数(如int8、uint256)。

3、地址类型(address):表示以太坊网络上的账户地址。

4、字符串类型(string):表示字符串值。

5、字节数组类型(bytes):表示任意长度的字节数组。

6、固定长度字节数组类型(bytesN):表示固定长度的字节数组,其中N可以是1到32之间的整数。

7、动态数组类型(type[]):表示可变长度的数组,可以存储任意类型的元素。

8、结构体类型(struct):表示自定义的数据结构,可以包含多个字段。

9、枚举类型(enum):表示一组预定义的值。

5、引用类型

(1).存储位置

内存(memory):内存是临时存储数据的地方,用于存储函数参数、局部变量等。在函数调用中,参数默认存储在内存中。内存中的数据在函数执行完毕后会被清除。

存储器(storage):存储器是永久存储数据的地方,用于存储合约的状态变量。存储器中的数据在合约执行期间一直存在,可以被其他合约访问。

调用数据(calldata ):用来保存函数参数的特殊数据位置,是一个只读位置。

状态变量默认存储位置:在合约中声明的状态变量默认存储在存储器中。这些变量的值在合约执行期间一直存在,可以被合约的所有函数访问和修改。

(2).数组

Solidity中的数组是一种用于存储相同类型的多个元素的数据结构。数组可以是固定长度的,也可以是可变长度的。

固定长度数组:固定长度数组在声明时需要指定数组的长度,长度不能改变。例如,uint[3]表示一个包含3个无符号整数的固定长度数组。可以通过索引访问数组中的元素,索引从0开始。例如,myArray[0]表示访问数组myArray中的第一个元素。

可变长度数组:可变长度数组可以在声明时不指定数组的长度,长度可以根据需要进行动态调整。例如,uint[]表示一个可变长度的无符号整数数组。可以使用push()函数向可变长度数组中添加元素,使用length属性获取数组的长度。例如,myArray.push(10)将整数10添加到数组myArray的末尾,myArray.length将返回数组的长度。

数组切片:

数组切片是数组连续部分的视图,用法如:x[start:end] , start 和 end 是 uint256 类型(或结果为 uint256 的表达式)。 x[start:end] 的第一个元素是 x[start] , 最后一个元素是 x[end - 1]。

Solidity中的数组切片是指从一个数组中选取一部分元素组成一个新的数组。数组切片由两个索引值表示,分别是起始索引和结束索引(不包含在切片中)。例如,myArray[2:5]表示从数组myArray中选取从第二个元素到第四个元素(索引为2、3、4)组成一个新的数组。

下面是一个使用数组切片的例子:

uint[] myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

uint[] mySlice = myArray[2:5];

在上面的例子中,我们定义了一个包含10个元素的无符号整数数组myArray,然后使用数组切片选取从第二个元素到第四个元素(索引为2、3、4)组成一个新的数组mySlice。mySlice中的元素为[3, 4, 5]。

length:

数组有 length 成员变量表示当前数组的长度。 一经创建,内存memory 数组的大小就是固定的(但却是动态的,也就是说,它可以根据运行时的参数创建)。

push():

动态的 存储storage 数组以及 bytes 类型( string 类型不可以)都有一个 push() 的成员函数,它用来添加新的零初始化元素到数组末尾,并返回元素引用. 因此可以这样:  x.push().t = 2 或 x.push() = b.

push(x):

动态的 存储storage 数组以及 bytes 类型( string 类型不可以)都有一个 push(x) 的成员函数,用来在数组末尾添加一个给定的元素,这个函数没有返回值.

pop():

变长的 存储storage 数组以及 bytes 类型( string 类型不可以)都有一个 pop() 的成员函数, 它用来从数组末尾删除元素。 同样的会在移除的元素上隐含调用 delete ,这个函数没有返回值。

6、映射

在Solidity中,映射(Mapping)是一种键值对的数据结构,类似于其他编程语言中的字典或哈希表。映射允许将一个键(Key)与一个值(Value)相关联,可以通过键来访问和修改对应的值。

映射的声明格式为mapping(KeyType => ValueType) public myMapping;,其中KeyType表示键的类型,ValueType表示值的类型。

下面是一个使用映射的例子:

mapping(uint => string) public myMapping;

function setValue(uint key, string memory value) public {

    myMapping[key] = value;

}

function getValue(uint key) public view returns (string memory) {

    return myMapping[key];

}

在上面的例子中,我们声明了一个公共的映射myMapping,将无符号整数作为键,字符串作为值。函数setValue用于设置映射中指定键的值,函数getValue用于获取映射中指定键的值。

例如,我们可以通过调用setValue(1, "Hello")来设置键为1的值为"Hello",然后通过调用getValue(1)来获取键为1的值,将返回"Hello"。

映射提供了一种方便的方式来存储和检索键值对数据,可以用于实现各种功能,如存储用户信息、记录状态等。需要注意的是,映射中的键是唯一的,每个键只能对应一个值。

映射只能是 存储(storage) 的数据位置,因此只允许作为状态变量 作为函数内的 存储(storage) 引用 作为库函数的参数。 它们不能用合约公有函数的参数或返回值。

7、操作符

(1).三元运算符

在Solidity中,三元运算符是一种简洁的条件表达式,用于根据条件的真假选择不同的值。它的语法格式是condition ? value1 : value2,其中condition是一个布尔表达式,value1和value2是两个可能的值。

如果condition为真,则整个表达式的值为value1;如果condition为假,则整个表达式的值为value2。

下面是一个使用三元运算符的例子:

uint a = 10;

uint b = 5;

uint max = (a > b) ? a : b;

在上面的例子中,我们声明了两个无符号整数变量a和b,然后使用三元运算符比较它们的值。如果a大于b,则将max的值设置为a;否则将max的值设置为b。在这个例子中,a的值为10,b的值为5,因此max的值为10。

三元运算符在需要根据条件选择不同值的情况下非常有用,可以减少代码的复杂度并提高可读性。需要注意的是,三元运算符只能在赋值或返回语句中使用,不能用于控制流程或循环语句中。

(2). 复合操作及自增自减操作

如果 a 是一个 LValue(即一个变量或者其它可以被赋值的东西),以下运算符都可以使用简写:

a += e 等同于 a = a + e。其它运算符如 -=, *=, /=, %=, |=, &= , ^= , <<= 和 >>= 都是如此定义的。 a++ 和 a-- 分别等同于 a += 1 和 a -= 1,但表达式本身的值等于 a 在计算之前的值。 与之相反, --a 和 ++a 虽然最终 a 的结果与之前的表达式相同,但表达式的返回值是计算之后的值。

8、字面常量与基本类型的转换

Solidity支持将字面常量与基本类型进行转换。例如,可以将一个字面常量赋值给一个基本类型的变量,或者将一个基本类型的变量转换为另一种基本类型。

下面是一个例子,展示了字面常量与基本类型的转换:

uint8 a = 10; // 将字面常量10赋值给一个uint8类型的变量a

uint16 b = uint16(a); // 将uint8类型的变量a转换为uint16类型的变量b

在上面的例子中,我们首先将字面常量10赋值给一个uint8类型的变量a。然后,我们使用类型转换将uint8类型的变量a转换为uint16类型的变量b。

uint8 a = 12; //  可行

uint32 b = 1234; // 可行

9、合约

(1).状态变量可见性

状态变量有 3 种可见性:

public

对于 public 状态变量会自动生成一个 getter hanshu 函数(见下面)。 以便其他的合约读取他们的值。 当在用一个合约里使用是,外部方式访问 (如: this.x) 会调用getter 函数,而内部方式访问 (如: x) 会直接从存储中获取值。 Setter函数则不会被生成,所以其他合约不能直接修改其值。

internal

内部可见性状态变量只能在它们所定义的合约和派生合同中访问。 它们不能被外部访问。 这是状态变量的默认可见性。

private

私有状态变量就像内部变量一样,但它们在派生合约中是不可见的。

(2).函数可见性

由于 Solidity 有两种函数调用:外部调用则会产生一个 EVM 调用,而内部调用不会, 更进一步, 函数可以确定器被内部及派生合约的可访问性,这里有 4 种可见性:

external

外部可见性函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。 一个外部函数 f 不能从内部调用(即 f 不起作用,但 this.f() 可以)。

public

public 函数是合约接口的一部分,可以在内部或通过消息调用。

internal

内部可见性函数访问可以在当前合约或派生的合约访问,不可以外部访问。

private

private 函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。

(3).构造函数

Solidity中的构造函数是一种特殊的函数,用于在合约实例化时初始化合约的状态变量或执行其他必要的操作。构造函数的名称必须与合约的名称相同,没有返回类型。通过构造函数,我们可以在合约被实例化时提供参数来初始化合约的状态。

例子:

contract MyContract {

    uint public myNumber;

    constructor(uint _number) {

        myNumber = _number;

    }

}

在上面的例子中,我们定义了一个名为MyContract的合约。合约中有一个公共的无符号整数变量myNumber。构造函数的参数是一个无符号整数_number,它被用来初始化myNumber的值。

当我们实例化MyContract合约时,我们需要提供一个无符号整数作为构造函数的参数。构造函数将使用该参数来初始化myNumber的值。例如,如果我们实例化合约并传入参数5,那么myNumber的初始值将被设置为5。可实现合约的灵活性和可重写性。

(4).抽象合约

抽象合约是一种不能被实例化的合约,它只能被其他合约继承并实现其定义的函数。抽象合约用于定义接口,并规定了继承合约需要实现的函数和事件。通过使用抽象合约,可以实现代码的模块化和重用。

abstract contract MyAbstractContract {

    function myFunction() public virtual;

}

contract MyContract is MyAbstractContract {

    function myFunction() public override {

  // 实现函数的具体逻辑

    }

}

在上面的例子中,我们定义了一个抽象合约MyAbstractContract,它声明了一个名为myFunction的函数。该函数没有具体的实现。

然后,我们定义了一个合约MyContract,它继承自抽象合约MyAbstractContract。在MyContract中,我们必须实现抽象合约中的myFunction函数,并提供具体的实现。

Virtual:一般要跟抽象合约使用,因为在抽象合约中不被实现,说明是被继承。

Override:说明是继承的,实现父合约中的函数和事件。

(5).接口

Solidity中的接口是一种抽象合约,它只定义了合约的函数声明,没有具体的实现。接口可以用来定义合约之间的通信规范。通过使用接口,我们可以确保合约之间的交互是符合规范的。

下面是一个简单的接口的例子:

interface MyInterface {

    function myFunction() external;

}

contract MyContract {

    function myFunction() external {

        // 实现函数的具体逻辑

    }

}

在上面的例子中,我们定义了一个接口MyInterface,它声明了一个名为myFunction的函数。接口中的函数没有具体的实现,只有函数的声明。

然后,我们定义了一个合约MyContract,它实现了接口MyInterface中的myFunction函数,并提供了具体的实现。

(6).继承

Solidity中的继承是一种实现代码重用的机制,它允许一个合约继承另一个合约的状态变量和函数。通过继承,我们可以减少代码的重复,实现代码的模块化和可维护性。

下面是一个简单的继承的例子:

contract ParentContract {

    uint public parentVariable;

    function parentFunction() public {

        // 实现函数的具体逻辑

    }

}

contract ChildContract is ParentContract {

    uint public childVariable;

    function childFunction() public {

        // 实现函数的具体逻辑

    }

}

在上面的例子中,我们定义了一个父合约ParentContract,它包含一个名为parentVariable的状态变量和一个名为parentFunction的函数。

然后,我们定义了一个子合约ChildContract,它继承自父合约ParentContract。在ChildContract中,我们定义了一个名为childVariable的状态变量和一个名为childFunction的函数。

通过继承,ChildContract继承了ParentContract中的parentVariable状态变量和parentFunction函数。这样,我们就可以在ChildContract中重用ParentContract中的代码,同时也可以扩展ChildContract的功能。

(7).控制结构

Solidity中的控制结构用于控制程序的执行流程,根据条件来决定执行不同的代码块。主要的控制结构包括条件语句(if、else)和循环语句(for、while)。

条件语句(if、else)用于根据条件来选择执行不同的代码块。如果条件为真,则执行if代码块中的语句;如果条件为假,则执行else代码块中的语句(如果有)。

下面是一个简单的例子,演示了使用if语句来判断一个数是否为正数:

function checkPositive(int num) public pure returns (string memory) {

    if (num > 0) {

        return "Number is positive";

    } else if (num == 0) {

        return "Number is zero";

    } else {

        return "Number is negative";

    }

}

上述代码中,我们定义了一个函数checkPositive,它接收一个整数参数num。通过if语句,我们判断num的值是否大于0,如果是,则返回"Number is positive";如果等于0,则返回"Number is zero";否则,返回"Number is negative"。

循环语句(for、while)用于重复执行一段代码,直到满足特定条件为止。

下面是一个简单的例子,演示了使用for循环来计算1到10的和:

function calculateSum() public pure returns (uint) {

    uint sum = 0;

    for (uint i = 1; i <= 10; i++) {

        sum += i;

    }

    return sum;

}

上述代码中,我们定义了一个函数calculateSum,它使用for循环来计算1到10的和。我们初始化一个变量sum为0,然后使用for循环从1到10遍历,每次将当前的数值加到sum上。最后,返回计算得到的sum值。

(8).异常处理

在Solidity中,异常处理用于处理错误和异常情况。当某个条件不满足或发生错误时,可以通过抛出异常来中断程序的执行,并在需要的地方捕获和处理异常。

Solidity中的异常处理机制主要通过断言(assert)和要求(require)语句来实现。断言用于检查代码中的条件是否满足,如果条件不满足,则抛出异常中断程序的执行。要求语句类似于断言,但它还可以在抛出异常之前提供一个错误信息。

下面是一个简单的例子,演示了使用断言和要求来处理异常:

function divide(uint a, uint b) public pure returns (uint) {

    assert(b != 0); // 断言:确保除数不为0

    require(a > b, "Dividend must be greater than divisor"); // 要求:确保被除数大于除数,否则抛出异常并提供错误信息

    return a / b;

}

上述代码中,我们定义了一个函数divide,它接收两个无符号整数参数a和b。在函数体中,我们首先使用断言assert来确保除数b不为0,如果为0,则抛出异常中断程序的执行。接着,我们使用要求require来确保被除数a大于除数b,否则抛出异常并提供错误信息。最后,我们返回a除以b的结果。

通过异常处理机制,我们可以在代码中检查条件并处理异常情况,提高程序的健壮性和安全性。

(9). keccak256

在Solidity中,keccak256是一种哈希函数,用于将输入数据转换为固定长度的哈希值。它可以用来对任何数据进行哈希,包括字符串、整数、地址等。

最简单的方式解释keccak256是将输入数据混合并压缩成一个唯一的字符串。这个字符串是一个固定长度的十六进制数,通常为64个字符。

使用keccak256函数的一个常见用途是验证数据的完整性。通过将数据进行哈希,可以生成一个唯一的哈希值。如果数据发生了任何更改,即使是微小的更改,生成的哈希值也会完全不同。

因此,keccak256函数在Solidity中被广泛用于安全验证、加密和身份验证等方面。它提供了一种可靠的方法来确保数据的完整性和安全性。

猜你喜欢

转载自blog.csdn.net/2403_87679370/article/details/143411341