solidity中的字符串

自从我开始撰写博客以来,感觉已经很久了,直到现在它一直致力于撰写有关统计数据的文章。我不是特别喜欢这个monody,它很快就会变得枯燥乏味。此外,这个博客从一开始就是为了探究两个不同的领域,经过这么长时间的专注于其中一个,我一直感觉到写另一个的冲动。所以,今天我给你一篇关于使用Solidity语言编写以太坊区块链编程的帖子。我不会遵循这样做的任何计划:我的目标只是写下我学习这门语言的障碍和我在日常工作中遇到的实际困难。

我希望在没有首先介绍初步材料的情况下自由撰写任何主题,因为如果我正在编写教科书,我必须这样做。如果你注意到我在谈论我之前没有解释的事情,那就是设计。在下面给我留言,我会在稍后的帖子中回复他们。

基本访问

今天,我想谈谈Solidity中的字符串。首先,Solidity在语法上类似于Javascript和其他类C语言。正因为如此,对于一个具有几种常见和广泛语言之一的基础的新手来说,很容易快速掌握Solidity程序的功能。尽管如此,Solidity在众所周知的困难中隐藏着众所周知的困难。这是类型string和相关类型bytes

这两种都是动态数组类型,这意味着它们可以存储任意大小的数据。不出所料,字节类型变量的每个元素都是一个字节。string类型变量的每个元素都是string的一个字符。到目前为止一切都很好,但最初看起来很欺骗。来自其他语言的人可能希望string类型提供一些有用的功能,例如:

  • 确定字符串的长度。
  • 读取或更改字符串中给定位置的字符。
  • 加入两个字符串。
  • 提取字符串的一部分。

坏消息:Solidity的string没有这个!如果我们需要上述任何一项,我们必须手动完成。

所以,让我们探讨其中的一些困难,看看我们能做些什么。我打开Remix并在名为string.sol的新文件中键入以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
pragma solidity ^0.4.24;
contract String {
string store = "abcdef";

function getStore() public view returns (string) {
return store;
}

function setStore(string _value) public {
store = _value;
}
}

屏幕的右侧是Remix,由开发人员区域拍摄。在Compile选项卡中,我检查Auto-Compile选项,以便Remix在编写代码时通知我错误和代码分析警告。静态代码分析由选项卡Analysis中的选项控制,我通常选择所有选项。

在目前的情况下,Remix将报告2个相同类型的警告:我所写的方法可能具有高到无限的gas成本。我会在这篇文章中忽略它。上述合约非常小。它定义了一个string类型的状态变量store,一个设置它的方法和一个获取它的方法。我们来试试吧。

在运行选项卡run中,我点击了部署deploy,如果合约没有问题,则会在该按钮下方显示一个新区域,其中包含合约所在的地址和可用的功能。

在工作区域下方,Remix显示交易结果的详细记录。最初,它只显示一行表示部署合约的帐户,被调用的合约和方法,即String.(constructor),以及向执行传递了多少以太(最初这显示在Wei,这是最小的以太单位,相当于10^-18以太),但我们可以通过点击标题,显示日志,执行和交易成本,可用gas,最终结果等来扩展它。

此时,我只想按下右侧的按钮getStore,并注意其下方显示的结果:

1
0: string: abcdef

同样,左侧有一个新的交易日志,单击它我们可以看到:

1
2
3
{
"0": "string: abcdef"
}

在解码输出中。一切都很好。

现在,我在setStore右侧的文本框中键入0123456789并点击该按钮。然后我再次调用getStore并收到该字符串。竖起大拇指,我们可以用字符串做基本的存储/检索!

现在让我们去寻找更有趣的事情。

创建新字符串:数据位置

到目前为止,我已经访问了一个字符串文字,我们已经看到了如何通过赋值来改变它。但这只是处理字符串的一种非常粗略的方式。让我们按字符创建一个字符串。这将向我们介绍Solidity编程的一个特点:数据位置。

我创建了一个新方法,只返回一个包含3个特定字符的新字符串:Abc

1
2
3
4
5
6
7
function createString() public returns (string) {
string newString = new string(3);
newString[0] = "A";
newString[1] = "b";
newString[2] = "c";
return newString;
}

这是一个善意的努力,但不起作用。Remix非常友好,可以立即指出4个错误和1个警告:

  • 其中2个在同一行:string newString = new string(3)
    • 警告:变量被声明为存储指针。使用明确的storage关键字来消除此警告。
    • TypeError:类型字符串内存不可隐式转换为预期的类型字符串存储指针。
  • 其他三个出现在以下行中,例如newString[0] = "A";并且都是相同的类型:
    • TypeError:无法对字符串进行索引访问。

要了解第一个问题,我必须告诉您有关数据位置的信息。写入区块链非常昂贵。运行交易的每个节点都必须执行相同的写操作,这会使交易更加昂贵并且区块链更大。当节点下载包含此交易的块时,由于此写入,将导致更大的存储成本。在以太坊中,每笔交易都有相关的成本,称为gas ,以激励程序员尽可能经济。

在编写合约时,作者可以选择使用哪种数据:内存便宜,即成本相对较低,但数据在函数完成执行后会出现波动并丢失;存储是最昂贵的,并且对于合约状态是绝对必要的,它必须从函数调用到函数调用;还有一个calldata位置,它对应于正在执行的函数的堆栈帧中的值。这是最便宜的使用位置,但长度有限。特别是,这意味着函数的参数数量可能会受到限制。

每种数据类型都有一个默认位置。这来自文档

  • 强制数据位置:

    • 外部函数的参数(不返回):calldata。
    • 状态变量:存储。
  • 默认数据位置:

    • 函数的参数(也返回):内存。
    • 所有其他局部变量:存储。

注意微妙不同:函数参数默认存储在内存中,除非函数是外部的,在这种情况下它们将存储在堆栈中(即calldata)。这意味着当public时,一个完全正常的函数在external时会突然出现太多的参数。

现在,让我们回到我们的代码并检查该行:

1
string newString = new string(3);

这是函数内部的局部变量,因此默认情况下它位于存储中。new关键字用于指定内存动态数组的初始大小。内存阵列无法调整大小。另一方面,我们可以通过更改其length属性来更改存储动态数组的大小,但不能使用new

这是我们错误的根源。在这种情况下,我们想要使用此字符串创建它并将其返回到外部。让外界决定如何处理它,无论是完全暂时的还是重要的,都要坚持区块链。对于此示例,存储不重要,并且将在内存中创建字符串。为此,我们在声明中添加了memory关键字,如下所示:

1
string memory newString = new string(3);

直接访问字符串:与字节等效。

我们现在看第二种错误。这很简单且不可避免:Solidity目前不允许索引访问字符串。来自FAQ

stringbytes基本相同,只是假设它保存了真实字符串的UTF-8编码。由于string以UTF-8编码存储数据,因此计算字符串中的字符数非常昂贵(某些字符的编码需要多于一个字节)。因此,

1
string s; s.length;

尚不支持,甚至索引访问s[2]

另一种方法是首先将字符串转换为字节,然后直接访问它。这是因为string是一种数组类型,尽管有一些限制。

但是有一个陷阱需要注意。bytes存储原始数据;string存储UTF-8字符。以下代码并不总是返回_s的字符数:

1
2
3
4
5
  //这个代码有错误
function getStringLength(string _s)return(uint){
bytes memory bs = bytes(_s);
return bs.length;
}

如果_s包含任何以UTF表示超过1个字节的字符,则会出现此问题。在这种情况下,函数返回输入字符串的字节表示的长度,并且将超过字符数。

这在尝试寻址字符串的特定字符时也会产生影响,因为我们无法预测字符的字节将在哪个位置。我们必须线性地解析字符串以识别任何多字节字符,否则我们确保将输入限制为固定长度的字符。例如,如果我们专门使用ASCII字符串,我们将是安全的。

回到我们之前的功能,这有效:

1
2
3
4
5
6
7
8
9
 function createString()public pure returns(string){
string memory newString = new string(3);
bytes memory byteString = bytes(newString);
byteString [0] =“A”;
byteString [1] =“b”;
byteString [2] =“c”;

return string(byteString);
}

但是,例如,以下尝试将字符串的第三个字符设置为X的代码在收到多字节字符时将失败。

1
2
3
4
5
6
//这个代码有错误
function makeThirdCharacterX(string _s)return(string){
bytes memory byteString = bytes(_s);
byteString [2] =“X”;
return string(byteString);
}

这将返回AbXdef作为Abcdef的输入,但返回XbÁnç!输入€bÁnç!

结论

关于这个话题还有很多东西可以说,但这已经是一个足够长的帖子,所以我将结束。关于类型string的关键概念是这是一个UTF-8字符数组,可以无缝转换为bytes。这是完全操纵字符串的唯一方法。但重要的是要注意UTF-8字符与字节不完全匹配。任一方向的转换都是准确的,但每个字节索引和相应的字符串索引之间没有直接的关系。对于大多数情况,将字符串直接表示为类型bytes(避免转换)可能有一个优点,并且在使用以UTF编码多个字节的字符时要非常小心。

这就足够了。在另一天见到你,在这个编码冒险中有更多的步骤。

======================================================================

分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:

  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
  • EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
  • java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。

汇智网原创翻译,转载请标明出处。这里是solidity中的字符串