Solidity

1、Solidity的变量类型:

  • 值类型 :这类变量在赋值或传参时,总是进行值拷贝。
    • 布尔类型(Booleans)
    • 整型(Integers)
    • 定长浮点型(Fixed Point Numbers)
    • 定长字节数组(Fixed-size byte arrays)
    • 有理数和整型常量(Rational and Integer Literals)
    • 字符串常量(String literals)
    • 十六进制常量(Hexadecimal literals)
    • 枚举(Enums)
    • 函数类型(Function Types)
    • 地址类型(Address)
    • 地址常量(Address Literals)
    • 函数类型及地址类型(Address)
  • 引用类型 :这里变量在赋值或传参时,传递的是地址。
    • 数组(Arrays)
    • 结构体(Structs)

1.1 unit和string

contract Example {
  // 这个无符号整数将会永久的被保存在区块链中
  string name ="Jay";
  uint  age = 25;
}

字符串类型:string
string字符串用于保存任意长度的 UTF-8 编码数据。 如: string greeting = “Hello world!”

无符号整数: uint
uint 无符号数据类型, 指其值不能是负数对于有符号的整数存在名为 int 的数据类型

注: Solidity中, uint 实际上是 uint256代名词, 一个256位的无符号整数。你也可以定义位数少的uints — uint8, uint16, uint32 等,但一般来讲我们都使用简单的 uint。

注: 通常情况下我们不会考虑使用 uint 变种,因为无论如何定义 uint的大小,Solidity 为它保留256位的存储空间。例如,使用 uint8 而不是uint(uint256)不会为你节省任何 gas

除非,把 uint 绑定到 struct 里面。

如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间。

struct NormalStruct {
  uint a;
  uint b;
  uint c;
}
//下面的比上面的省gas
struct MiniMe {
  uint32 a;
  uint32 b;
  uint c;
}

// 因为使用了结构打包,`mini` 比 `normal` 占用的空间更少
NormalStruct normal = NormalStruct(10, 20, 30);
MiniMe mini = MiniMe(10, 20, 30);

并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置),这样 Solidity 可以将存储空间最小化。例如,有两个 struct:

前者比后者需要的gas更少,因为前者把uint32放一起了。

uint c; uint32 a; uint32 b; 和 uint32 a; uint c; uint32 b;

1.2 结构体

struct Person {
  uint age;
  string name;
}

Person satoshi = Person(172, "Satoshi");

1.3 数组

静态数组 和 动态数组

// 固定长度为2的静态数组:
uint[2] fixedArray;
// 固定长度为5的string类型的静态数组:
string[5] stringArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;
Person[] people;

array.push() 在数组的 尾部 加入新元素,所以元素在数组中的顺序就是我们添加的顺序,如:

uint[] numbers;
numbers.push(5);
numbers.push(10);
numbers.push(15);
// numbers is now equal to [5, 10, 15]

1.4 公共数组

定义 public 数组,Solidity 会自动创建 getter 方法。语法如下:

Person [] public people;

// 创建一个新的Person:
Person satoshi = Person(172, "Satoshi");

// 将新创建的satoshi添加进people数组:
people.push(satoshi);

//也可以两步并一步
people.push(Person(16, "Vitalik"));

1.5 定义函数

习惯上函数里的变量都是以“_”开头 (但不是硬性规定) 以区别全局变量

function eatHamburgers(string _name, uint _amount) {
}

调Yong:eatHamburgers(“vitalik”, 100);

1.5.1 私有 / 公共函数

函数的属性默认为公共(public),意味着任何一方 (或其它合约) 都可以调用你合约里的函数。所以将自己的函数定义为 私有(private) 是一个好的编程习惯,只有当你需要外部世界调用它时才将它设置为公共。

在函数名字后面使用关键字 private 即可。和函数的参数类似,私有函数的名字用(_)起始

uint[] numbers;

function _addToArray(uint _number) private {
  numbers.push(_number);
}

1.5.2 函数的更多属性

1、返回值 returns

string greeting = "What's up dog";

function sayHello() public returns (string) {
 	 return greeting;
}

2、函数的修饰符 view、pure、payable

  • view:只能读取数据不能更改数据 function sayHello() public view returns (string)
  • pure:表明这个函数甚至都不访问应用里的数据function _multiply(uint a, uint b) private pure returns (uint)
  • payable:比如向一个合约要求支付一定的钱来运行一个函数
contract OnlineStore {
  function buySomething() external payable {
    // 检查以确定0.001以太发送出去来运行函数:
    require(msg.value == 0.001 ether);
    // 如果为真,一些用来向函数调用者发送数字内容的逻辑
    transferThing(msg.sender);
  }
}

一些人会从 web3.js 调用这个函数 (从DApp的前端), 像这样 :

// 假设 `OnlineStore` 在以太坊上指向你的合约:
OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))

注:

  • 如果一个 view 函数在另一个函数的内部被调用,而调用函数与 view 函数的不属于同一个合约,也会产生调用成本
  • 标记为 view 的函数只有在外部调用时才是免费的
  • 如果一个函数没标记为payable, 而你尝试利用上面的方法发送以太,函数将拒绝你的事务

3、处理多返回值

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 这样来做批量赋值:
  (a, b, c) = multipleReturns();
}

// 或者如果我们只想返回其中一个变量:
function getLastReturnValue() external {
  uint c;
  // 可以对其他字段留空:
  (,,c) = multipleReturns();  //使用()括起来并用“,”做占位符
}

4、提现

学习了如何向合约发送以太,在你发送以太之后,它将被存储进以合约的以太坊账户中, 并冻结在哪里 —— 除非你添加一个函数来从合约中把以太提现。

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

注意我们使用Ownable 合约中的owner 和 onlyOwner,假定它已经被引入了。

你可以通过transfer 函数向一个地址发送以太, 然后 this.balance 将返回当前合约存储了多少以太。 所以如果100个用户每人向我们支付1以太, this.balance将是100以太。

你可以通过transfer 向任何以太坊地址付钱。 比如,你可以有一个函数在msg.sender超额付款的时候给他们退钱

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

或者在一个有卖家和卖家的合约中, 你可以把卖家的地址存储起来, 当有人买了它的东西的时候,把买家支付的钱发送给它 seller.transfer(msg.value)

1.6 Keccak256 和 类型转换

Ethereum 内部有一个散列函数keccak256,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。

这在 Ethereum 中有很多应用,但是现在我们只是用它造一个伪随机数

例子:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256(“aaaab”);
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256(“aaaac”);

显而易见,输入字符串只改变了一个字母,输出就已经天壤之别了。

注: 在区块链中安全地产生一个随机数是一个很难的问题, 本例的方法不安全,但是在我们的Zombie DNA算法里不是那么重要,已经很好地满足我们的需要了。

类型转换
有时你需要变换数据类型。例如:

uint8 a = 5;
uint b = 6;
// 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 我们需要将 b 转换为 uint8:
uint8 c = a * uint8(b);

上面, a * b 返回类型是 uint, 但是当我们尝试用 uint8 类型接收时, 就会造成潜在的错误。如果把它的数据类型转换为 uint8, 就可以了,编译器也不会出错。

1.7 事件

事件: 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。

event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public {
  uint result = _x + _y;
  //触发事件,通知app
  IntegersAdded(_x, _y, result);
  return result;
}
你的 app 前端可以监听这个事件。JavaScript 实现如下:

YourContract.IntegersAdded(function(error, result) { 
  // 干些事
}

1.8 Web3.js

// 下面是调用合约的方式:
var abi = /* abi是由编译器生成的 */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 发布之后在以太坊上生成的合约地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 能访问公共的函数以及事件

// 某个监听文本输入的监听器:
$("#ourButton").click(function(e) {
  var name = $("#nameInput").val()
  //调用合约的 `createRandomZombie` 函数:
  ZombieFactory.createRandomZombie(name)
})

// 监听 `NewZombie` 事件, 并且更新UI
var event = ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  generateZombie(result.zombieId, result.name, result.dna)
})

// 获取 Zombie 的 dna, 更新图像
function generateZombie(id, name, dna) {
  let dnaStr = String(dna)
  // 如果dna少于16位,在它前面用0补上
  while (dnaStr.length < 16)
    dnaStr = "0" + dnaStr

  let zombieDetails = {
    // 前两位数构成头部.我们可能有7种头部, 所以 % 7
    // 得到的数在0-6,再加上1,数的范围变成1-7
    // 通过这样计算:
    headChoice: dnaStr.substring(0, 2) % 7 + 1,
    // 我们得到的图片名称从head1.png 到 head7.png

    // 接下来的两位数构成眼睛, 眼睛变化就对11取模:
    eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
    // 再接下来的两位数构成衣服,衣服变化就对6取模:
    shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
    //最后6位控制颜色. 用css选择器: hue-rotate来更新
    // 360度:
    skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),
    eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),
    clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),
    zombieName: name,
    zombieDescription: "A Level 1 CryptoZombie",
  }
  return zombieDetails
}

1.9 映射(Mapping)和地址(Address)

Addresses (地址): 以太坊区块链由 account (账户)组成,你可以把它想象成银行账户。一个帐户的余额是以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样。地址是账户唯一的标识符

0x0cE446255506E92DF41614C46F1d6df9Cc969183
(这是CryptoZombies作者的ETH地址)

所以我们可以指定“地址”作为僵尸主人的 ID。当用户通过与我们的应用程序交互来创建新的僵尸时,新僵尸的所有权被设置到调用者的以太坊地址下。

Mapping(映射): 典型的K-V结构,可以使用key来查找value

mapping (address => uint) public accountBalance;

1.20 Msg.sender

在 Solidity 中,有一些全局变量可以被所有函数调用其中一个就是 msg.sender,它指的是当前调用者(或智能合约)的 address

    function getmsg () view returns (address){
        return msg.sender ;
    }

假设我们当前地址为:0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c

那么调用getmsg()将给我返回 0: address: 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c

由此可见 msg.sender就是调用者的地址

注意:在 Solidity 中,功能执行始终需要从外部调用者开始。 一个合约只会在区块链上什么也不做,除非有人调用其中的函数,所以 msg.sender总是存在的。

// 得到僵尸的id
uint id = zombies.push(Zombie(_name, _dna)) - 1; 
// 僵尸id的YongYou者是msg.sender
zombieToOwner[id] = msg.sender; 
// msg.sender地址的僵尸数量+1
ownerZombieCount[msg.sender]++;

1.21 Require

require:使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行

function sayHiToVitalik(string _name) public returns (string) {
  // 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序
  /*
   (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较
  两字符串的 keccak256 哈希值来进行判断)
  */
  require(keccak256(_name) == keccak256("Vitalik"));
  // 如果返回 true, 运行如下语句
  return "Hi!";
}

1.22 继承(Inheritance)

当代码过于冗长的时候,最好将代码和逻辑分拆到多个不同的合约中,以便于管理。继承能够让我们访问被继承合约中的公共方法和属性

继承格式为: 当前合约 is 被继承的合约

contract Doge {
  function catchphrase() public returns (string) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}

1.23 引入(Import)

在 Solidity 中,当你有多个文件并且想把一个文件导入另一个文件时,可以使用 import 语句:

import "./someothercontract.sol";

contract newContract is SomeOtherContract {

}

这样当我们在合约(contract)目录下有一个名为 someothercontract.sol 的文件( ./ 就是同一目录的意思),它就会被编译器导入。

1.24 Storage与Memory

在 Solidity 中,有两个地方可以存储变量 —— storage 或 memory。

  • Storage 变量会指永久存储在区块链中的变量
  • Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除

大多数时候你都用不到这些关键字,默认情况下 Solidity 会自动处理它们。 状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失

然而也有一些情况下,你需要手动声明存储类型,主要用于处理函数内的 结构体 和 数组 时

// 所以你应该明确定义 `storage`:
Sandwich storage mySandwich = sandwiches[_index];

// 如果你只想要一个副本,可以使用`memory`:
Sandwich memory anotherSandwich = sandwiches[_index + 1];

1.25 internal 和 external

除 public 和 private 属性之外,Solidity 还使用了另外两个描述函数可见性的修饰词:internal(内部) 和 external(外部)

  • internal 和 private 类似,不过,如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。

  • external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用

1.26 定义接口

请注意,这个过程虽然看起来像在定义一个合约,但其实内里不同:

  • 首先,我们只声明了要与之交互的函数 —— 在本例中为 getNum —— 在其中我们没有使用到任何其他的函数状态变量

  • 其次,我们并没有使用大括号({ 和 })定义函数体,我们 单单用分号(;) 结束了函数声明。

编译器就是靠这些特征认出它是一个接口的。

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

在合约中使用接口:

contract MyContract {
  address NumberInterfaceAddress = 0xab38...;
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
  // 现在变量 `numberContract` 指向另一个合约对象

  function someFunction() public {
    // 现在我们可以调用在那个合约中声明的 `getNum`函数:
    uint num = numberContract.getNum(msg.sender);
    // ...在这儿使用 `num`变量做些什么
  }
}

1.27 函数修饰符

1、onlyOwner: 可以限制第三方对 setKittyContractAddress的访问,除了我们自己,谁都无法去修改它

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

  // 修改这个函数:
function setKittyContractAddress(address _address) external onlyOwner {
   kittyContract = KittyInterface(_address);
}

2、函数修饰符也可以带参数

// 存储用户年龄的映射
mapping (uint => uint) public age;

// 限定用户年龄的修饰符
modifier olderThan(uint _age, uint _userId) {
  require(age[_userId] >= _age);
  _;
}

// 必须年满16周岁才允许开车 (至少在美国是这样的).
// 我们可以用如下参数调用 `olderThan` 修饰符:
function driveCar(uint _userId) public olderThan(16, _userId) {
  // 其余的程序逻辑
}

1.28 时间单位

Solidity 使用自己的本地时间单位。变量 now 将返回当前的unix时间戳(自1970年1月1日以来经过的秒数)

2、数学运算

在 Solidity 中,数学运算很直观明了,与其它程序设计语言相同:

  • 加法: x + y
  • 减法: x - y,
  • 乘法: x * y
  • 除法: x / y
  • 取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5,余3)
  • 次方:x ** y (x 的 y次方 // 例如: 5 ** 2 = 25)

例如: uint x = 5 ** 2; // equal to 5^2 = 25

你可能感兴趣的:(以太坊相关)