solidity 全局变量是否会有地址变量

Solidity官方文档中文版pdf下载_爱问共享资料
Solidity官方文档中文版.pdf
Solidity官方文档中文版.pdf
Solidity官方文档中文版.pdf
简介:本文档为《Solidity官方文档中文版pdf》,可适用于IT/计算机领域,主题内容包含汇智网HubwizcomSolidity官方文档中文版导读以太坊是什么?以太坊是一个全新开放的区块链平台它允许任何人在平台中建立和使用通过区块链技术符等。
侵权或盗版
*若权利人发现爱问平台上用户上传内容侵犯了其作品的信息网络传播权等合法权益时,请按照平台要求书面通知爱问!
赌博犯罪类
在此可输入您对该资料的评论~
添加成功至
资料评价:solidity编写智能合约(入门) - Ellon_Daris - 博客园
一个简单的智能合约
先从一个非常基础的例子开始,不用担心你现在还一点都不了解,我们将逐步了解到更多的细节。
contract SimpleStorage {
uint storedD
function set(uint x) {
storedData =
function get() constant returns (uint retVal) {
return storedD
在Solidity中,一个合约由一组代码(合约的函数)和数据(合约的状态)组成。合约位于以太坊区块链上的一个特殊地址。uint storedD 这行代码声明了一个状态变量,变量名为storedData,类型为 uint (256bits无符号整数)。你可以认为它就像数据库里面的一个存储单元,跟管理数据库一样,可以通过调用函数查询和修改它。在以太坊中,通常只有合约 的拥有者才能这样做。在这个例子中,函数 set 和 get 分别用于修改和查询变量的值。
跟很多其他语言一样,访问状态变量时,不需要在前面增加 this. 这样的前缀。
这个合约还无法做很多事情(受限于以太坊的基础设施),仅仅是允许任何人储存一个数字。而且世界上任何一个人都可以来存取这个数字,缺少一个(可靠 的)方式来保护你发布的数字。任何人都可以调用set方法设置一个不同的数字覆盖你发布的数字。但是你的数字将会留存在区块链的历史上。稍后我们会学习如 何增加一个存取限制,使得只有你才能修改这个数字。
代币的例子
接下来的合约将实现一个形式最简单的加密货币。空中取币不再是一个魔术,当然只有创建合约的人才能做这件事情(想用其他货币发行模式也很简单,只是实现细节上的差异)。而且任何人都可以发送货币给其他人,不需要注册用户名和密码,只要有一对以太坊的公私钥即可。
对于在线solidity环境来说,这不是一个好的例子。如果你使用在线solidity环境
来尝试这个例子。调用函数时,将无法改变from的地址。所以你只能扮演铸币者的角色,可以铸造货币并发送给其他人,而无法扮演其他人的角色。这点在线
solidity环境将来会做改进。
contract Coin {
//关键字&public&使变量能从合约外部访问。
mapping (address =& uint)
//事件让轻客户端能高效的对变化做出反应。
event Sent(address from, address to, uint amount);
//这个构造函数的代码仅仅只在合约创建的时候被运行。
function Coin() {
minter = msg.
function mint(address receiver, uint amount) {
if (msg.sender != minter)
balances[receiver] +=
function send(address receiver, uint amount) {
if (balances[msg.sender] & amount)
balances[msg.sender] -=
balances[receiver] +=
Sent(msg.sender, receiver, amount);
这个合约引入了一些新的概念,让我们一个一个来看一下。
address public minter 这行代码声明了一个可公开访问的状态变量,类型为address。address类型的值大小为160 bits,不支持任何算术操作。适用于存储合约的地址或其他人的公私钥。public关键字会自动为其修饰的状态变量生成访问函数。没有public关键 字的变量将无法被其他合约访问。另外只有本合约内的代码才能写入。自动生成的函数如下:
function minter() returns (address) { }
当然我们自己增加一个这样的访问函数是行不通的。编译器会报错,指出这个函数与一个状态变量重名。
下一行代码mapping (address =& uint) public balances; 创建了一个public的状态变量,但是其类型更加的复杂。该类型将一些address映射到无符号整数。mapping可以被认为是一个哈希表,每一个可能的key对应的value被虚拟的初始化为全0.这个类比不是很严谨,对于一个mapping,无法获取一个包含其所有key或者value 的链表。所以我们得自己记着添加了哪些东西到mapping中。更好的方式是维护一个这样的链表,或者使用其他更高级的数据类型。或者只在不受这个缺陷影 响的场景中使用mapping,就像这个例子。在这个例子中由public关键字生成的访问函数将会更加复杂,其代码大致如下:
function balances(address _account) returns (uint balance) {
return balances[_account];
我们可以很方便的通过这个函数查询某个特定账号的余额。
event Sent(address from, address to, uint value)这行代码声明了一 个&事件&。由send函数的最后一行代码触发。客户端(服务端应用也适用)可以以很低的开销来监听这些由区块链触发的事件。事件触发时,监听者会同时接 收到from,to,value这些参数值,可以方便的用于跟踪交易。为了监听这个事件,你可以使用如下代码:
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins were sent from " + result.args.from +
" to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
注意在客户端中是如何调用自动生成的 balances 函数的。
这里有个比较特殊的函数 Coin。它是一个构造函数,会在合约创建的时候运行,之后就无法被调用。它会永久得存储合约创建者的地址。msg(以及tx和block)是一个神奇的 全局变量,它包含了一些可以被合约代码访问的属于区块链的属性。msg.sender 总是存放着当前函数的外部调用者的地址。
最后,真正被用户或者其他合约调用,用来完成本合约功能的函数是mint和send。如果合约创建者之外的其他人调用mint,什么都不会发生。而 send可以被任何人(拥有一定数量的代币)调用,发送一些币给其他人。注意,当你通过该合约发送一些代币到某个地址,在区块链浏览器中查询该地址将什么 也看不到。因为发送代币导致的余额变化只存储在该代币合约的数据存储中。通过事件我们可以很容易创建一个可以追踪你的新币交易和余额的&区块链浏览器&。
区块链基础
对于程序员来说,区块链这个概念其实不难理解。因为最难懂的一些东西(挖矿,哈希,椭圆曲线加密,点对点网络等等)只是为了提供一系列的特性和保 障。你只需要接受这些既有的特性,不需要关心其底层的技术。就像你如果仅仅是为了使用亚马逊的AWS,并不需要了解其内部工作原理。
区块链是一个全局共享的,事务性的数据库。这意味着参与这个网络的每一个人都可以读取其中的记录。如果你想修改这个数据库中的东西,就必须创建一个 事务,并得到其他所有人的确认。事务这个词意味着你要做的修改(假如你想同时修改两个值)只能被完完全全的实施或者一点都没有进行。
此外,当你的事务被应用到这个数据库的时候,其他事务不能修改该数据库。
举个例子,想象一张表,里面列出了某个电子货币所有账号的余额。当从一个账户到另外一个账户的转账请求发生时,这个数据库的事务特性确保从一个账户中减掉的金额会被加到另一个账户上。如果因为某种原因,往目标账户上增加金额无法进行,那么源账户的金额也不会发生任何变化。
此外,一个事务会被发送者(创建者)进行密码学签名。这项措施非常直观的为数据库的特定修改增加了访问保护。在电子货币的例子中,一个简单的检查就可以确保只有持有账户密钥的人,才能从该账户向外转账。
区块链要解决的一个主要难题,在比特币中被称为&双花攻击&。当网络上出现了两笔交易,都要花光一个账户中的钱时,会发生什么?一个冲突?
简单的回答是你不需要关心这个问题。这些交易会被排序并打包成&区块&,然后被所有参与的节点执行和分发。如果两笔交易相互冲突,排序靠后的交易会被拒绝并剔除出区块。
这些区块按时间排成一个线性序列。这也正是&区块链&这个词的由来。区块以一个相当规律的时间间隔加入到链上。对于以太坊,这个间隔大致是17秒。
作为&顺序选择机制&(通常称为&挖矿&)的一部分,一段区块链可能会时不时被回滚。但这种情况只会发生在整条链的末端。回滚涉及的区块越多,其发生的概率越小。所以你的交易可能会被回滚,甚至会被从区块链中删除。但是你等待的越久,这种情况发生的概率就越小。
以太坊虚拟机
以太坊虚拟机(EVM)是以太坊中智能合约的运行环境。它不仅被沙箱封装起来,事实上它被完全隔离,也就是说运行在EVM内部的代码不能接触到网络、文件系统或者其它进程。甚至智能合约与其它智能合约只有有限的接触。
以太坊中有两类账户,它们共用同一个地址空间。外部账户,该类账户被公钥-私钥对控制(人类)。合约账户,该类账户被存储在账户中的代码控制。
外部账户的地址是由公钥决定的,合约账户的地址是在创建改合约时确定的(这个地址由合约创建者的地址和该地址发出过的交易数量计算得到,地址发出过的交易数量也被称作"nonce")
合约账户存储了代码,外部账户则没有,除了这点以外,这两类账户对于EVM来说是一样的。
每个账户有一个key-value形式的持久化存储。其中key和value的长度都是256bit,名字叫做storage.
另外,每个账户都有一个以太币余额(单位是&Wei"),该账户余额可以通过向它发送带有以太币的交易来改变。
一笔交易是一条消息,从一个账户发送到另一个账户(可能是相同的账户或者零账户,见下文)。交易可以包含二进制数据(payload)和以太币。
如果目标账户包含代码,该代码会执行,payload就是输入数据。
如果目标账户是零账户(账户地址是0),交易将创建一个新合约。正如上文所讲,这个合约地址不是零地址,而是由合约创建者的地址和该地址发出过的交 易数量(被称为nonce)计算得到。创建合约交易的payload被当作EVM字节码执行。执行的输出做为合约代码被永久存储。这意味着,为了创建一个 合约,你不需要向合约发送真正的合约代码,而是发送能够返回真正代码的代码。
以太坊上的每笔交易都会被收取一定数量的gas,gas的目的是限制执行交易所需的工作量,同时为执行支付费用。当EVM执行交易时,gas将按照特定规则被逐渐消耗。
gas price(gas价格,以太币计)是由交易创建者设置的,发送账户需要预付的交易费用 = gas price * gas amount。 如果执行结束还有gas剩余,这些gas将被返还给发送账户。
无论执行到什么位置,一旦gas被耗尽(比如降为负值),将会触发一个out-of-gas异常。当前调用帧所做的所有状态修改都将被回滚。
存储,主存和栈
每个账户有一块持久化内存区域被称为存储。其形式为key-value,key和value的长度均为256比特。在合约里,不能遍历账户的存储。相对于另外两种,存储的读操作相对来说开销较大,修改存储更甚。一个合约只能对它自己的存储进行读写。
第二个内存区被称为主存。合约执行每次消息调用时,都有一块新的,被清除过的主存。主存可以以字节粒度寻址,但是读写粒度为32字节(256比特)。操作主存的开销随着其增长而变大(平方级别)。
EVM不是基于寄存器,而是基于栈的虚拟机。因此所有的计算都在一个被称为栈的区域执行。栈最大有1024个元素,每个元素256比特。对栈的访问 只限于其顶端,方式为:允许拷贝最顶端的16个元素中的一个到栈顶,或者是交换栈顶元素和下面16个元素中的一个。所有其他操作都只能取最顶的两个(或一 个,或更多,取决于具体的操作)元素,并把结果压在栈顶。当然可以把栈上的元素放到存储或者主存中。但是无法只访问栈上指定深度的那个元素,在那之前必须 要把指定深度之上的所有元素都从栈中移除才行。
EVM的指令集被刻意保持在最小规模,以尽可能避免可能导致共识问题的错误实现。所有的指令都是针对256比特这个基本的数据类型的操作。具备常用的算术,位,逻辑和比较操作。也可以做到条件和无条件跳转。此外,合约可以访问当前区块的相关属性,比如它的编号和时间戳。
合约可以通过消息调用的方式来调用其它合约或者发送以太币到非合约账户。消息调用和交易非常类似,它们都有一个源,一个目标,数据负载,以太币,gas和返回数据。事实上每个交易都可以被认为是一个顶层消息调用,这个消息调用会依次产生更多的消息调用。
一个合约可以决定剩余gas的分配。比如内部消息调用时使用多少gas,或者期望保留多少gas。如果在内部消息调用时发生了out-of-gas 异常(或者其他异常),合约将会得到通知,一个错误码被压在栈上。这种情况只是内部消息调用的gas耗尽。在solidity中,这种情况下发起调用的合 约默认会触发一个人工异常。这个异常会打印出调用栈。
就像之前说过的,被调用的合约(发起调用的合约也一样)会拥有崭新的主存并能够访问调用的负载。调用负载被存储在一个单独的被称为calldata的区域。调用执行结束后,返回数据将被存放在调用方预先分配好的一块内存中。
调用层数被限制为1024,因此对于更加复杂的操作,我们应该使用循环而不是递归。
代码调用和库
存在一种特殊类型的消息调用,被称为callcode。它跟消息调用几乎完全一样,只是加载自目标地址的代码将在发起调用的合约上下文中运行。
这意味着一个合约可以在运行时从另外一个地址动态加载代码。存储,当前地址和余额都指向发起调用的合约,只有代码是从被调用地址获取的。
这使得Solidity可以实现&库&。可复用的库代码可以应用在一个合约的存储上,可以用来实现复杂的数据结构。
在区块层面,可以用一种特殊的可索引的数据结构来存储数据。这个特性被称为日志,Solidity用它来实现事件。合约创建之后就无法访问日志数 据,但是这些数据可以从区块链外高效的访问。因为部分日志数据被存储在布隆过滤器(Bloom filter) 中,我们可以高效并且安全的搜索日志,所以那些没有下载整个区块链的网络节点(轻客户端)也可以找到这些日志。
合约甚至可以通过一个特殊的指令来创建其他合约(不是简单的向零地址发起调用)。创建合约的调用跟普通的消息调用的区别在于,负载数据执行的结果被当作代码,调用者/创建者在栈上得到新合约的地址。
只有在某个地址上的合约执行自毁操作时,合约代码才会从区块链上移除。合约地址上剩余的以太币会发送给指定的目标,然后其存储和代码被移除。
注意,即使一个合约的代码不包含自毁指令,依然可以通过代码调用(callcode)来执行这个操作。1. 赋值规则
赋值是将一个变量赋值给另一个变量。赋值时遵循以下规则:
1.1 赋值给状态变量
赋值给状态变量,总是值传递。
从状态状态赋值给状态变量总是创建一个无关的拷贝。
pragma solidity ^0.4.0;
contract StateToState{
uint8[] arr1;
uint8[] arr2;
function f() returns (uint8, uint8, uint8, uint8){
arr1 = new uint8[](2);
arr1[0] = 100;
arr2 = arr1;
arr1[0] = 150;
return (arr1[0], arr2[0], i1, i2);//150, 100, 250, 200
上面的例子中,将arr2赋值为arr1后,修改arr1的第一个元素的值为150,并未影响到arr2。对于基本类型也是如此,通过i2 = i1;赋值后,修改i1完全不影响i2。
由于赋值时是值传递,总是创建一份拷贝,上述代码的执行结果是150,100,250,200。
从局部变量赋值到状态变量也总是创建一个无关的拷贝。
pragma solidity ^0.4.0;
contract LocalToState{
uint8[] stateV
uint8 stateI;
function f() returns(uint8, uint8, uint8, uint8){
uint8[] memory arr = new uint8[](3);
stateVal =
arr[0] = 100;
var i = 200;
return (arr[0], stateVal[0], i, stateI);//100, 0, 250, 200
在上面的例子中,当局部变量赋值给状态变量后,其后对局部变量的更改都不会影响到状态变量。
1.2 赋值给本地变量
从状态变量到局部变量,其中基本类型是创建一份无关的拷贝。但对于引用类型,如结构体,数组(包含bytes和string)则是传递的引用。
pragma solidity ^0.4.0;
contract StateToLocal{
uint8[] stateA
uint8 stateI;
function f() returns(uint8, uint8, uint8, uint8){
stateArr = new uint8[](2);
stateArr[0] = 100;
uint8[] arr = stateA
arr[0] = 150;
stateI = 200;
uint8 i = stateI;
return (arr[0], stateArr[0], i, stateI);//150, 150, 250, 200
上面的例子中,状态状态通过uint8 i = stateI;赋值给局部变量后,对i的修改,并不影响状态变量。而对于引用类型则是引用传递,使用arr[0] = 150赋新值后,会影响到状态变量stateArr[0]。
所以上述代码的运行结果是150,150,250,200。
而对于本地变量到本地变量,基本类型是值传递,总是拷贝一个完全无关的对象。而对于复杂类型,如结构体和数组,则是引用传递。
pragma solidity ^0.4.0;
contract LocalToLocal{
function f() returns (uint, uint, uint, uint){
uint a = 100;
uint[] memory arr1 = new uint[](2);
uint[] memory arr2 = new uint[](2);
arr1[0] = uint(200);
arr2[0] = uint(200);
arr2 = arr1;
arr1[0] = 250;
return (a, b, arr1[0], arr2[0]);//150,100,250,250
上面的例子告诉我们,对于基本类型通过b =赋值后,对a的修改并不会影响b。而对于引用类型arr2,通过arr2 = arr1;,修改arr1[0] = 250;,会影响到arr2[0]。
所以上述代码的执行结果是150,100,250,250。
2 元组的解构赋值
Solidity还允许元组解构赋值。它是指一个长度在编译期知道的,不同类型的对象列表,即元组,可以同时赋值给多个变量,这其实与Javascript里提出的解构赋值的概念类似,但相比Javascript提供的特性只是小巫见大巫。
Solidity的元组,是由括号包含的,以英文逗号分隔的一个或多个值,如(var1, var2, var3)。一个元组对象,同时赋值给多个变量,这就是解构赋值。下面我们来看一个元组解构赋值的例子。
pragma solidity ^0.4.0;
contract TupleAssign{
function f() returns(uint8, string, uint8[]){
var (a, b, c) = (1, &Hello world&, new uint8[](1));
return (a, b, c);
我们可以看到,等号两边的对象数量相等时,右边的值会一一赋值到左边的变量。
并且Solidity并不允许我们,左右两边的数量不相等。如果变量过多,会报错Not enough components in value to assign all variables;而如果值过多,会报错Too many components in value for variable assignment.。
不过Solidity其实支持变量与值非一一对应。
pragma solidity ^0.4.0;
contract TupleIgnore{
function f() returns(uint8, uint8, uint8, uint8, uint8, uint8, uint8, uint8){
//按前对齐赋值
var (a, b, ) = (1, 2, 3, 4);//1, 2
//跳过一些值
var (c, , d) = (4, 5, 6);//4, 6
//后对齐赋值
var (,e) = (7, 8, 9);//9
var (, f, , g) = (10, 11, 12, 13, 14);//12, 14
//特殊的单元组
var (h) = (15);
//Stack too deep, try removing local variables.
//var(, i, ) = (16, 17, 18);
return (a, b, c, d, e, f, g, h);
通过上面的例子可以看出,我们可以在变量处以逗号结束,来按前对齐赋值。即使后面有更多值都将被忽略,如var (a, b, ) = (1, 2, 3, 4);。如果我们想后对齐来进行快速赋值,可以以逗号开头,如var (,e) = (7, 8, 9);。另外我们可以随时用空白来跳过不感兴趣的值。但是不能像var(, i, ) = (16, 17, 18);那样前后都用逗号分隔,会报错Stack too deep, try removing local variables。
2.1 解构赋值的应用场景
2.1.1 交换变量
解构赋值可以用来做什么呢,一个最简单的实践场景是交换两个变量值,下面是一个使用元组交换对象值的例子。
pragma solidity ^0.4.0;
contract TupleExchange{
function f() returns (uint8, uint8){
var a = 1;
var b = 2;
(a, b) = (b, a);
return (a, b);//2,1
上面的例子返回的结果将为2, 1,实现了变量的值交换。
2.1.2 函数返回值
使用元组,函数可以同时返回多个值,所以我们可以使用元组的解构赋值来方便的取出元组值。
pragma solidity ^0.4.0;
contract FunctionTuple{
uint8 public recevierA
function f()
private returns(string, uint8){
string memory name = &jack&;
uint8 age = 12;
return (name, age);
//更新其它值
(recevier, recevierAge) = f();
上面的例子中,函数的返回元组值,被解构赋值给了状态变量recevier和recevierAge。
专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。
版权所有,转载注明出处
感谢您的支持
处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。
Please enable JavaScript to view the
友情链接:区块链(7)
Solidity中合约有点类似面向对象语言中的类。合约中有用于数据持久化的状态变量(state variables),和可以操作他们的函数。调用另一个合约实例的函数时,会执行一个EVM函数调用,这个操作会切换执行时的上下文,这样,前一个合约的状态变量(state variables)就不能访问了。
1.1 创建合约
IDEs,例如Remix,UI更好。
通过使用JavaScript API web3. js,可以在Ethereum上以编程方式创建合约。该方法称作web3 . eth。
当创建合约时,它的构造函数(与契约同名的函数)将被执行一次。构造函数是可选的。只允许一个构造函数,这意味着不支持重载。
在内部,构造函数的参数在合同代码之后通过ABI编码,但是如果使用web3 . js,则不必关心这个问题。
如果一个合约想要创建另一个合约,那么创建的合约的源代码(和二进制)必须被创建者知道。这意味着循环创建依赖项是不可能的。
pragma solidity ^0.4.16;
contract OwnedToken {
function OwnedToken(bytes32 _name) public {
owner = msg.
creator = TokenCreator(msg.sender);
function changeName(bytes32 newName) public {
if (msg.sender == address(creator))
name = newN
function transfer(address newOwner) public {
if (msg.sender != owner) return;
if (creator.isTokenTransferOK(owner, newOwner))
owner = newO
contract TokenCreator {
function createToken(bytes32 name)
returns (OwnedToken tokenAddress)
return new OwnedToken(name);
function changeName(OwnedToken tokenAddress, bytes32 name)
tokenAddress.changeName(name);
function isTokenTransferOK(address currentOwner, address newOwner)
returns (bool ok)
address tokenAddress = msg.
return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
Visibility and Getters
1.2.1 可见性和权限控制
Solidity有两种函数调用方式,一种是内部调用,不会创建一个EVM调用(也叫做消息调用),另一种则是外部调用,会创建EVM调用(会发起消息调用)。Solidity对函数和状态变量提供了四种可见性。分别是external,public,internal,private。其中函数默认是public。状态变量默认的可见性是internal。
(1)external
外部函数是合约接口的一部分,所以我们可以从其它合约或通过交易来发起调用。一个外部函数f,不能通过内部的方式来发起调用,(如f()不可以,但可以通过this.f())。外部函数在接收大的数组数据时更加有效。
(2)internal
公开函数是合约接口的一部分,可以通过内部,或者消息来进行调用。对于public类型的状态变量,会自动创建一个访问器(详见下文)。
(3)private
私有函数和状态变量仅在当前合约中可以访问,在继承的合约内,不可访问。
所有在合约内的东西对外部的观察者来说都是可见,将某些东西标记为private仅仅阻止了其它合约来进行访问和修改,但并不能阻止其它人看到相关的信息。
可见性的标识符的定义位置,对于state variable是在类型后面,函数是在参数列表和返回关键字中间。来看一个定义的例子:
pragma solidity ^0.4.16;
contract C {
function f(uint a) private pure returns (uint b) { return a + 1; }
function setData(uint a) internal { data = }
uint public
在下面的例子中,D可以调用c.getData()来访问data的值,但不能调用f。合约E继承自C,所以它可以访问compute函数。
pragma solidity ^0.4.0;
contract C {
uint private
function f(uint a) private returns(uint b) { return a + 1; }
function setData(uint a) public { data = }
function getData() public returns(uint) { return }
function compute(uint a, uint b) internal returns (uint) { return a+b; }
contract D {
function readData() public {
C c = new C();
uint local = c.f(7);
c.setData(3);
local = c.getData();
local = c.compute(3, 5);
contract E is C {
function g() public {
C c = new C();
uint val = compute(3, 5);
1.2.2 访问函数(Getter Function)
pragma solidity ^0.4.0;
contract C{
uint public c = 10;
contract D{
C c = new C();
function getDataUsingAccessor() returns (uint){
return c.c();
访问函数有外部(external)可见性。如果通过内部(internal)的方式访问,比如直接访问,你可以直接把它当一个变量进行使用,但如果使用外部(external)的方式来访问,如通过this.,那么它必须通过函数的方式来调用。
pragma solidity ^0.4.0;
contract C{
uint public c = 10;
function accessInternal() returns (uint){
function accessExternal() returns (uint){
return this.c();
在acessExternal函数中,如果直接返回return this.c;,会出现报错Return argument type function () constant external returns (uint256) is not implicitly convertible to expected type (type of first return variable) uint256.。原因应该是通过外部(external)的方式只能访问到this.c作为函数的对象,所以它认为你是想把一个函数转为uint故而报错。
参考文献:
1.3 函数修改器
修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg. }
modifier onlyOwner {
if (msg.sender != owner)
contract mortal is owned {
function close() onlyOwner {
selfdestruct(owner);
contract priced {
modifier costs(uint price) {
if (msg.value &= price) {
contract Register is priced, owned {
mapping (address =& bool) registeredA
function Register(uint initialPrice) { price = initialP }
function register() payable costs(price) {
registeredAddresses[msg.sender] = true;
function changePrice(uint _price) onlyOwner {
这个合同只定义了一个修饰符,但是没有使用它 - 它将被用在派生的合同中。 函数体被插入特殊符号“_;” 在修饰符的定义中出现。 这意味着如果所有者调用这个函数,那么这个函数会被执行,否则会抛出一个异常。
修改器可以被继承,使用将modifier置于参数后,返回值前即可。
特殊_表示使用修改符的函数体的替换位置。
从合约Register可以看出全约可以多继承,通过,号分隔两个被继承的对象。
修改器也是可以接收参数的,如priced的costs。
使用修改器实现的一个防重复进入的例子。
pragma solidity ^0.4.0;
contract Mutex {
modifier noReentrancy() {
if (locked) throw;
locked = true;
locked = false;
This function is protected by a mutex, which means that
reentrant calls from within msg.sender.call cannot call f again.
The `return 7` statement assigns 7 to the return value but still
executes the statement `locked = false` in the modifier.
function f() noReentrancy returns (uint) {
if (!msg.sender.call()) throw;
例子中,由于call()方法有可能会调回当前方法,修改器实现了防重入的检查。
如果同一个函数有多个修改器,他们之间以空格隔开,修饰器会依次检查执行。
需要注意的是,在Solidity的早期版本中,有修改器的函数,它的return语句的行为有些不同。
在修改器中和函数体内的显式的return语句,仅仅跳出当前的修改器和函数体。返回的变量会被赋值,但整个执行逻辑会在前一个修改器后面定义的”_”后继续执行。
修改器的参数可以是任意表达式。在对应的上下文中,所有的函数中引入的符号,在修改器中均可见。但修改器中引入的符号在函数中不可见,因为它们有可能被重写。
1.4 合约状态变量
状态变量可以被定义为常量constant,这样的话,它必须在编译期间通过一个表达式赋值。赋值的表达式不允许:1)访问storage;2)区块链数据,如now,this.balance,block.number;3)合约执行的中间数据,如msg.gas;4)向外部合约发起调用。也许会造成内存分配副作用表达式是允许的,但不允许产生其它内存对象的副作用的表达式。内置的函数keccak256,keccak256,ripemd160,ecrecover,addmod,mulmod可以允许调用,即使它们是调用的外部合约。
允许内存分配,从而带来可能的副作用的原因是因为这将允许构建复杂的对象,比如,查找表。虽然当前的特性尚未完整支持。
编译器并不会为常量在storage上预留空间,每个使用的常量都会被对应的常量表达式所替换(也许优化器会直接替换为常量表达式的结果值)。
不是所有的类型都支持常量,当前支持的仅有值类型和字符串。
pragma solidity ^0.4.0;
contract C {
uint constant x = 32**22 + 8;
string constant text = "abc";
bytes32 constant myHash = keccak256("abc");
函数也可被声明为常量,这类函数将承诺自己不修改区块链上任何状态。
pragma solidity ^0.4.0;
contract C {
function f(uint a, uint b) constant returns (uint) {
return a * (b + 42);
访问器(Accessor)方法默认被标记为constant。当前编译器并未强制一个constant的方法不能修改状态。建议对于不会修改数据的标记为constant。
(1)View 函数
函数可以被生命为view,但是这种情况下要保证不修改状态。
下面的情况被认为是修改状态:
1. 写入状态变量;
2. (emitting??)发送事件;
3. 创建其他合约;
4. 使用selfdestruct;
5. 通过调用发送Ether;
6. 调用没有被声明为view和pure的函数
7. 使用低级调用;
8. 使用包含特定操作码的内联程序集。
pragma solidity ^0.4.16;
contract C {
function f(uint a, uint b) public view returns (uint) {
return a * (b + 42) +
constant被认为是view的别名。
Getter方法被标记为view。
(2)pure函数
函数可以被声明为pure,不能修改状态不能读。
除了上面列出的情况,下面的情况也被认为是有读和修改状态的行为:
1. 读取状态变量;
2. 访问this.balance 或者&address&.balance;
3. 访问block,tx,msg(除了msg.sig和msg.data);
4. 调用没有标记为pure的函数;
5. 使用包含特定操作码的内联程序集。
pragma solidity ^0.4.16;
contract C {
function f(uint a, uint b) public pure returns (uint) {
return a * (b + 42);
(3)fallback函数
每一个合约有且仅有一个没有名字的函数。这个函数无参数,也无返回值。如果调用合约时,没有匹配上任何一个函数(或者没有传哪怕一点数据),就会调用默认的回退函数。
此外,当合约收到ether时(没有任何其它数据),这个函数也会被执行。在此时,一般仅有少量的gas剩余,用于执行这个函数(准确的说,还剩2300gas)。所以应该尽量保证回退函数使用少的gas。
下述提供给回退函数可执行的操作会比常规的花费得多一点。
1. 写入到存储(storage);
2. 创建一个合约;
3. 执行一个外部(external)函数调用,会花费非常多的gas;
4. 发送ether。
请确保在部署合同之前,彻底测试fallback函数,以确保执行成本小于2300。
一个没有定义一个回退函数的合约。如果接收ether,会触发异常,并返还ether(solidity v0.4.0开始)。所以合约要接收ether,必须实现回退函数。下面来看个例子:
pragma solidity ^0.4.0;
contract Test {
function() { x = 1; }
contract Sink {
function() payable { }
contract Caller {
function callTest(Test test) {
test.call(0xabcdef01);
test.send(2 ether);
在浏览器中跑的话,记得要先存ether。
(4)函数重载
一个合约可以有多个相同名称的函数,但可以使用不同的参数。这也适用于继承函数。下面的例子显示了合约A范围内f函数的重载。
pragma solidity ^0.4.16;
contract A {
function f(uint _in) public pure returns (uint out) {
function f(uint _in, bytes32 _key) public pure returns (uint out) {
在外部接口中也存在重载的函数。如果两个外部可见的函数不同于它们的Solidity类型,而不是它们的外部类型。
pragma solidity ^0.4.16;
contract A {
function f(B _in) public pure returns (B out) {
function f(address _in) public pure returns (address out) {
contract B {
以上两个函数重载都接受了ABI的地址类型,虽然它们在Solidity中被认为是不同的。
(5)重载解析和参数匹配
通过将当前范围内的函数声明与函数调用中提供的参数相匹配,可以选择重载函数。如果所有参数都可以隐式地转换为预期类型,则选择函数作为重载候选项。如果没有一个候选,解析失败。
返回参数没有考虑到重载解析。
pragma solidity ^0.4.16;
contract A {
function f(uint8 _in) public pure returns (uint8 out) {
function f(uint256 _in) public pure returns (uint256 out) {
调用f(50)会有一个类型错误,因为250可以隐式地转换为uint8和uint256类型。另一方面,f(256)将解析为f(uint256)重载,因为256不能隐式地转换为uint8。
参考(可以说是复制粘贴):
事件是使用EVM日志内置功能的方便工具,在DAPP的接口中,它可以反过来调用Javascript的监听事件的回调。
事件在合约中可被继承。当被调用时,会触发参数存储到交易的日志中(一种区块链上的特殊数据结构)。这些日志与合约的地址关联,并合并到区块链中,只要区块可以访问就一直存在。日志和事件在合约内不可直接被访问,即使是创建日志的合约。
日志的SPV(简单支付验证)是可能的,如果一个外部的实体提供了一个这样证明的合约,它可以证明日志在区块链是否存在。但需要留意的是,由于合约中仅能访问最近的256个区块哈希,所以还需要提供区块头信息。
可以最多有三个参数被设置为indexed,来设置是否被索引。设置为索引后,可以允许通过这个参数来查找日志,甚至可以按特定的值过滤。
如果数组(包括string和bytes)类型被标记为索引项,会用它对应的Keccak-256哈希值做为topic。
除非是匿名事件,否则事件签名(比如:Deposit(address,hash256,uint256))是其中一个topic,同时也意味着对于匿名事件无法通过名字来过滤。
所有未被索引的参数将被做为日志的一部分被保存起来。
被索引的参数将不会保存它们自己,你可以搜索他们的值,但不能检索值本身。
pragma solidity ^0.4.0;
contract ClientReceipt {
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
function deposit(bytes32 _id) {
// Any call to this function (even deeply nested) can
// be detected from the JavaScript API by filtering
// for `Deposit` to be called.
Deposit(msg.sender, _id, msg.value);
下述是使用javascript来获取日志的例子。
var abi = ;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at(0x123 );
var event = clientReceipt.Deposit();
event.watch(function(error, result){
if (!error)
console.log(result);
var event = clientReceipt.Deposit(function(error, result) {
if (!error)
console.log(result);
通过函数log0,log1,log2,log3,log4,可以直接访问底层的日志组件。logi表示总共有带i + 1个参数(i表示的就是可带参数的数目,只是是从0开始计数的)。其中第一个参数会被用来做为日志的数据部分,其它的会做为主题(topics)。前面例子中的事件可改为如下:
msg.value,
0x50cb9fe53daa46f04de75fad5adb20,
msg.sender,
其中的长16进制串是事件的签名,计算方式是keccak256(“Deposit(address,hash256,uint256)”)
更对细节看上面的链接~(@^_^@)~
Solidity通过复制代码包括多态性来支持多重继承。
所有函数调用是虚拟(virtual)的,这意味着最远的派生方式会被调用,除非明确指定了合约。
当一个合约从多个其它合约那里继承,在区块链上仅会创建一个合约,在父合约里的代码会复制来形成继承合约。
基本的继承体系与python有些类似,特别是在处理多继承上面。
下面用一个例子来详细说明:
pragma solidity ^0.4.0;
contract owned {
function owned() { owner = msg. }
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
contract Config {
function lookup(uint id) returns (address adr);
contract NameReg {
function register(bytes32 name);
function unregister();
contract named is owned, mortal {
function named(bytes32 name) {
Config config = Config(0xd5f9d8de474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).register(name);
function kill() {
if (msg.sender == owner) {
Config config = Config(0xd5f9d8de474c3fb14fd43e2f23970);
NameReg(config.lookup(1)).unregister();
mortal.kill();
contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) {
if (msg.sender == owner) info = newI
function get() constant returns(uint r) { return }
上面的例子的named合约的kill()方法中,我们调用了motal.kill()调用父合约的销毁函数(destruction)。但这样可能什么引发一些小问题。
pragma solidity ^0.4.0;
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
contract Base1 is mortal {
function kill() {
mortal.kill(); }
contract Base2 is mortal {
function kill() {
mortal.kill(); }
contract Final is Base1, Base2 {
对Final.kill()的调用只会调用Base2.kill(),因为派生重写,会跳过Base1.kill,因为它根本就不知道有Base1。一个变通方法是使用super。
pragma solidity ^0.4.0;
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
contract Base1 is mortal {
function kill() {
super.kill(); }
contract Base2 is mortal {
function kill() {
super.kill(); }
contract Final is Base2, Base1 {
如果Base1调用了函数super,它不会简单的调用基类的合约函数,它还会调用继承关系图谱上的下一个基类合约,所以会调用Base2.kill()。需要注意的最终的继承图谱将会是:Final,Base1,Base2,mortal,owned。使用super时会调用的实际函数在使用它的类的上下文中是未知的,尽管它的类型是已知的。这类似于普通虚函数查找(ordinary virtual method lookup)
基类构造器的方法(Arguments for Base Constructors)
派生的合约需要提供所有父合约需要的所有参数,所以用两种方式来做,见下面的例子:
pragma solidity ^0.4.0;
contract Base {
function Base(uint _x) { x = _x; }
contract Derived is Base(7) {
function Derived(uint _y) Base(_y * _y) {
或者直接在继承列表中使用is Base(7),或像修改器(modifier)使用方式一样,做为派生构造器定义头的一部分Base(_y * _y)。第一种方式对于构造器是常量的情况比较方便,可以大概说明合约的行为。第二种方式适用于构造的参数值由派生合约的指定的情况。在上述两种都用的情况下,第二种方式优先(一般情况只用其中一种方式就好了)。
多继承与线性化(Multiple Inheritance and Linearization)
1.8 抽象合约
1.10 函数库
1.11 Using for

我要回帖

更多关于 solidity 全局变量 的文章

 

随机推荐