手把手教创建你的第一个以太智能合约:ETHEREUM PET SHOP(译)

原文地址 : http://truffleframework.com/tutorials/pet-shop

译者:lucia3

译者steemit主页:++https://steemit.com/@lucia3++

译者以太地址:++0x2a703d8ae21d5f23d6ffab3a10c62f0a64825867++

如果觉得这个教程对你有用,请不要吝啬打赏哟~

这个系列的教程将会手把手带你搭建你的第一个dapp应用——一个宠物商店的领养追踪系统。

这个教程是为那些有基本的以太链和智能合约知识,懂得一些HTML和JavaScript,但是从来没开发过dapp的人准备的。

注意:如果想要补充以太坊基础,请在继续学习本教程前阅读 Truffle的教程Ethereum Overview

在这个教程里,我们将会学习以下内容:

  • 搭建开发环境(Truffle框架下开发以太坊智能合约)
  • 通过使用Truffle Box创建一个Truffle项目
  • 编写一个智能合约
  • 编译合约以及将合约迁移到区块链上
  • 测试智能合约
  • 创建一个与智能合约交互的UI
  • 在浏览器里使用你创建的dapp

背景

皮特对于使用以太坊技术处理他们店里的宠物领养非常有兴趣。这家店有16只宠物等待领养,这些宠物已经录入了数据库。作为一个概念的初步证明,Pete希望看到一个将一个以太坊地址与一只个宠物关联起来的dapp。

本项目的网站结构和样式已经提供。 我们的工作是为其编写智能合同和前端逻辑。

搭建开发环境

在我们开始之前, 请安装以下内容:

  • ++Node.js v6+ LTS and npm (comes with Node)++
  • ++Git++

一旦我们安装了这些,我们只需要一个命令来安装Truffle:

npm install -g truffle

要验证Truffle是否正确安装,请在终端上输入truffle version

truffle version

如果您看到错误,请确保您的npm模块已添加到您的路径中。

通过使用Truffle Box创建一个Truffle项目

  1. Truffle会在当前的目录中初始化,所以首先在你选择的开发文件夹中创建一个目录,然后进入这个目录。
mkdir pet-shop-tutorialcd pet-shop-tutorial
  1. 我们已经为这个pet-shop教程创建了一个特殊的++Truffle Box++,其中包括基本的项目结构以及用户界面的代码。 使用truffle unbox命令解压这个Truffle Box。
truffle unbox pet-shop

> ##### 注意:truffle可以通过几种不同的方式进行初始化。 另一个有用的初始化命令是`truffle init`,它创建一个空的Truffle项目,不包含任何示例合同。 有关更多信息,请参阅[++创建项目文档++]( http://truffleframework.com/docs/getting_started/project )。

项目目录结构

默认的Truffle目录结构包含以下内容:

  • contracts/: 包含了我们的项目的智能合约的源文件(++Solidity++语言开发的)。 在这里有一个重要的合约Migrations.sol,我们稍后再讨论。
  • migrations/: Truffle uses a migration system to handle smart contract deployments. A migration is an additional special smart contract that keeps track of changes.truffle使用迁移系统来处理智能合约部署。 迁移是追踪变化的一种额外的特殊智能合约。
  • test/: contracts包含了对我们智能合约中JavaScript和Solidity的测试
  • truffle.js: Truffle配置文件

pet-shop Truffle Box项目里还有些其它的文件和文件夹,但我们目前不用关注它们。

编写一个智能合约

我们将从编写充当后端逻辑和存储的智能合约开始,来开发我们的dapp。

  1. contracts /目录下创建一个名为Adoption.sol的新文件。
  2. 将以下内容添加到文件中:
pragma solidity ^0.4.4;contract Adoption {}

注意事项:

  • 在合同的顶部注明了所需的最低版本:pragma solidity ^0.4.4;。 pragma意味着“只有编译器关心的附加信息”,而脱字符号(^)表示“指定的版本或更高版本”。

  • 像JavaScript或PHP一样,语句以分号结尾

定义变量

Solidity是一种静态类型的语言,意味着像字符串,整数和数组等数据类型必须被定义。 Solidity有一个称为address的独特类型。 Addresses址是以太坊地址,一个存储为20个字节的值。 以太坊区块链上的每个账户和智能合约都有一个地址,并可以通过此地址发送和接收以太币。

contract Adoption {之后,在下一行添加以下变量。

address[16] public adopters;

注意事项:

  • 我们已经定义了一个变量:adopters。 这是一个以太坊地址数组。 数组包含一种类型,可以有一个固定的或可变的长度。 在这个例子中,类型是address,长度是16。
  • 你还会注意到adopters是公共的。 公共变量具有自动getter方法,但是如果在公共变量是数组的情况下,键是必需的,给定了key的数组只会返回数组中的一个值。 稍后,我们将编写一个函数来返回整个数组,供我们的用户界面使用。

你的第一个函数:领养宠物

首先要让用户可以领养宠物。

  1. 在上面设置的变量声明之后,将下面的函数添加到智能合约中。
// Adopting a pet
function adopt(uint petId) public returns (uint) {require(petId >= 0 && petId <= 15);adopters[petId] = msg.sender;return petId;
}

注意事项:

  • 在Solidity中,必须指定函数参数和输出的类型。 在这种情况下,我们将获取一个petId(整数)并返回一个整数。

  • 首先要检查petId,以确保petId不会超出我们定义的数组范围。 Solidity中的数组起始索引为0,因此ID值将需要介于0和15之间。我们使用require()语句来确保ID在数组范围内。

  • 如果ID在范围内,我们往adopters数组中添加 address。 调用此函数的人员或智能合约的地址由msg.sender表示。

  • 最后,我们返回传入的petId作为确认。

你的第二个函数:获取领养人

如上所述,数组getters方法只从给定的键返回一个单一的值。 我们的UI需要更新所有的宠物收养状态,但是调用16次getters方法并不明智。 所以我们下一步是编写一个函数来返回整个数组。

将以下getAdopters()函数添加到智能合约中,在我们上面添加的adopt()函数之后:

// Retrieving the adopters
function getAdopters() public returns (address[16]) {return adopters;
}

由于adopters已经声明,我们可以简单地返回它。 确保将返回类型(在这个例子中是adopters的类型)指定为address[16]

编译合约以及将合约迁移到区块链上

现在我们已经编写了我们的智能合约,接下来的步骤是编译合约以及将合约迁移到区块链上。

Truffle有一个内置的开发者控制台,我们称之为Truffle Develop,它生成一个开发区块链,我们可以用来测试部署合同。 它还能够直接从控制台运行Truffle命令。 我们将使用Truffle Develop在本教程中执行我们合同中的大部分操作。

编译

Solidity是一种编译语言,这意味着我们需要将我们的Solidity编译为用于以太坊虚拟机(EVM)执行的字节码。 把它看作是将我们人类可读的“固体”(Solidity)翻译成EVM所理解的东西。

  1. 登陆Truffle Develop。 确保你在包含dapp的目录中执行下列命令。
truffle develop

你会看到一个提示,显示你现在在Truffle Develop中。 下文除非另有说明,否则所有命令都将从此控制台运行。

truffle(develop)>

注意:如果你在Windows上并遇到运行此命令的问题,请参阅有关++解决Windows上的命名冲突++的文档。

  1. 编译dapp
compile

在执行完上面那条命令后,你应该看到类似于下面的输出:

Compiling ./contracts/Migrations.sol...
Compiling ./contracts/Adoption.sol...
Writing artifacts to ./build/contracts

注意:如果你没有使用Truffle Develop,这些命令可以在你的终端上使用truffle前缀。 如在编译中,在终端上运行truffle compile
但是,如果你不使用Truffle Develop,你将不得不使用另一个测试区块链,如++TestRPC++。

将合约迁移到区块链上

现在我们已经成功编译了我们的合同,现在是时候将它们迁移到区块链了!

迁移是一个部署脚本,旨在改变应用程序合同的状态,将其从一个状态转移到另一个状态。 对于第一次迁移,您可能只是部署新的代码,但随着时间的推移,其他迁移可能会移动数据或用新的代码替换合同。

注意:在Truffle文档中关于++迁移++的信息。

你将在migrations /目录中看到一个JavaScript文件:1_initial_migration.js。 这将处理部署Migrations.sol合同以观察后续的智能合同迁移,并确保将来不会对未更改的合同进行双重迁移。

现在我们准备创建我们自己的迁移脚本。

  1. migrations /目录下创建一个名为2_deploy_contracts.js的新文件。

  2. 将以下内容添加到2_deploy_contracts.js文件中:

var Adoption = artifacts.require("Adoption");module.exports = function(deployer) {deployer.deploy(Adoption);
};
  1. 回到我们的控制台,将合同迁移到区块链。
migrate

在执行完上面那条命令后,你应该看到类似于下面的输出:

Using network 'develop'.Running migration: 1_initial_migration.jsDeploying Migrations...Migrations: 0x75175eb116b36ff5fef15ebd15cbab01b50b50d1
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.jsDeploying Adoption...Adoption: 0xb9f485451a945e65e48d9dd7fc5d759af0a89e21
Saving successful migration to network...
Saving artifacts...

您可以按顺序看到正在执行的迁移,然后是每个部署的合同的区块链地址。 (你的地址会有所不同。)

您现在已经写好了您的第一个智能合约,并将其部署到本地运行的测试区块链中。 现在是时候与我们的智能合约进行互动,以确保它符合我们的要求。

测试智能合约

在智能合约测试方面,Truffle非常灵活,因为测试可以用JavaScript或Solidity编写。 在本教程中,我们将在Solidity中编写我们的测试。

  1. test /目录下创建一个名为TestAdoption.sol的新文件。

  2. 将以下内容添加到TestAdoption.sol文件中:

pragma solidity ^0.4.11;import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";contract TestAdoption {Adoption adoption = Adoption(DeployedAddresses.Adoption());}

我们用三个imports开始合同:

  • Assert.sol:使我们在测试中使用的各种断言。 在测试中,一个断言会检查如平等,不平等或判空之类的事情,以便从我们的测试中返回通过/失败。 ++truffle包含的断言的完整列表++。
  • DeployedAddresses.sol:运行测试时,Truffle会将正在测试的合约的新实例部署到TestRPC。 这个智能合约获得了已经被部署的合约的地址。
  • Adoption.sol:我们想要测试的智能合约。

注意:
前两个imports是引用自全局Truffle文件,而不是truffle目录。 你不会看到你的test/目录里有truffle目录。

然后我们定义一个包含要测试的智能合约的合同范围的变量,调用DeployedAddresses智能合约来获得它的地址。

测试 adopt()函数

要测试adopt()函数,成功时返回给定的petId。 我们可以通过比较采用的返回值和我们传入的ID来确保返回的ID是正确的。

  1. Adoption的声明之后,在TestAdoption.sol智能合同中添加以下函数:
// Testing the adopt() function
function testUserCanAdoptPet() {uint returnedId = adoption.adopt(8);uint expected = 8;Assert.equal(returnedId, expected, "Adoption of pet ID 8 should be recorded.");
}

注意事项:

  • 我们调用前面声明的智能合约adoption,传入的参数ID为8。
  • 然后我们声明我们预期的函数返回值expected为8。
  • 最后,我们将实际得到的返回值returnedId,期望值expected和失败消息(如果测试未通过,将打印到控制台)作为入参传递给Assert.equal()。

测试根据给定宠物id获得领养人的函数

由于数组只能给定一个键返回一个值,所以我们为整个数组创建了自己的getter。

  1. TestAdoption.sol中先前添加的函数下添加此函数。
// Testing retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() {// Expected owner is this contractaddress expected = this;// Store adopters in memory rather than contract's storageaddress[16] memory adopters = adoption.getAdopters();Assert.equal(adopters[8], expected, "Owner of pet ID 8 should be recorded.");
}

请注意adoptersmemory属性。 memory属性告诉Solidity将数据临时存储在内存中,而不是将其保存到合同的存储。 由adopters是一个数组,我们从第一个领养函数的测试中知道我们领养了petId为 8的宠物,所以我们将测试合约地址与数组中的index为8的位置存储的地址进行比较。

执行测试函数

  1. 回到Truffle Develop中,执行下列命令:
test
  1. 如果所有的测试都通过了,你会看到类似这样的控制台输出:
Using network 'develop'.Compiling ./contracts/Adoption.sol...Compiling ./test/TestAdoption.sol...Compiling truffle/Assert.sol...Compiling truffle/DeployedAddresses.sol...TestAdoption✓ testUserCanAdoptPet (91ms)✓ testGetAdopterAddressByPetId (70ms)✓ testGetAdopterAddressByPetIdInArray (89ms)3 passing (670ms)

创建一个与智能合约交互的UI

现在,我们已经创建了智能合约,将其部署到我们的本地测试区块链中,并确认我们可以通过控制台与它进行交互,现在是时候创建一个UI,让Pete有一些东西可以用于他的宠物店!

这个应用程序的前端代码在pet-shop项目目录里。 存在于src /目录中。

本项目的前端不使用构建系统(webpack,grunt等),尽可能简单地开始。 该应用程序的结构已经提供; 我们将专注于编写以太坊特有的函数。 这样,您就可以将这些知识应用到您自己的前端开发中。

初始化 web3

  1. 在文本编辑器中打开/src/js/app.js

  2. 检查文件。 请注意,有一个全局App对象来管理我们的应用程序,在init()中加载宠物数据,然后调用函数initWeb3()。 ++web3 JavaScript++库与以太坊区块链交互。 它可以检索用户帐户,发送交易,与智能合约交互等等。

  3. initWeb3中删除多行注释并将其替换为以下内容:

// Is there is an injected web3 instance?
if (typeof web3 !== 'undefined') {App.web3Provider = web3.currentProvider;
} else {// If no injected web3 instance is detected, fallback to the TestRPCApp.web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
}
web3 = new Web3(App.web3Provider);

注意事项:

  • 首先,我们检查web3实例是否已经存在。 (以太坊浏览器(如++Mist++或者带有++MetaMask++扩展的Chrome)将注入自己的web3实例。)如果注入的web3实例存在,我们将获取它的提供者并使用它创建我们的web3对象。

  • 如果没有web3实例存在,我们将基于我们的本地提供者创建我们的web3对象。 (对于开发环境来说,这种回退很好,但不安全,不适合生产。)

初始化智能合约

现在我们可以通过web3与以太坊互动,我们需要实例化我们的智能合约,以便web3知道在哪里找到它,以及它如何工作。 Truffle有一个库来帮助实现这些——truffle-contract。 它将有关合同的信息与迁移保持同步,因此您不需要手动更改合同的部署地址。

  1. 仍然在/src/js/app.js中,从initContract中删除多行注释并将其替换为以下内容:
$.getJSON('Adoption.json', function(data) {// Get the necessary contract artifact file and instantiate it with truffle-contractvar AdoptionArtifact = data;App.contracts.Adoption = TruffleContract(AdoptionArtifact);// Set the provider for our contractApp.contracts.Adoption.setProvider(App.web3Provider);// Use our contract to retrieve and mark the adopted petsreturn App.markAdopted();
});

注意事项:

  • 首先我们获取我们的智能合约的artifact文件。 artifact是关于我们的合同的信息,例如其部署的地址和应用程序二进制接口(ABI)。 ABI是一个JavaScript对象,定义了如何与契约进行交互,包括变量,函数和参数。

  • 一旦我们在回调中获得了artifact,我们将它们传递给TruffleContract()。 这创建了一个我们可以与之交互的合同实例。

  • 实例化我们的合约之后,我们将合约提供者设置成App.web3Provider,这是之前设置web3提供者时存储的值。

  • 然后我们调用应用程序的markAdopted()函数,标记已经被领养的宠物。 我们把它封装在一个单独的函数中,因为我们需要在更改智能合约的数据时更新UI。

获取已经领养的宠物&更新UI

  1. 仍然在/src/js/app.js中,从markAdopted中删除多行注释,并将其替换为以下内容:
var adoptionInstance;App.contracts.Adoption.deployed().then(function(instance) {adoptionInstance = instance;return adoptionInstance.getAdopters.call();
}).then(function(adopters) {for (i = 0; i < adopters.length; i++) {if (adopters[i] !== '0x0000000000000000000000000000000000000000') {$('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);}}
}).catch(function(err) {console.log(err.message);
});

注意事项:

  • 我们访问已部署的Adoption合同,然后在该实例上调用getAdopters()

  • 我们首先在智能合约调用之外声明变量adoptionInstance,这样我们可以在初始化实例之后的函数里访问实例。

  • 使用call()允许我们从区块链读取数据,而不必发送完整的交易,这意味着我们不必花费任何代价。

  • 在调用getAdopters()之后,我们循环遍历所有的宠物,检查每个宠物是否存储了领养人的地址。 由于数组包含地址类型,以太坊使用16个空地址初始化数组。 这就是为什么我们检查一个空的地址字符串,而不是null或其他错误的值。

  • 一旦找到了一个有相应地址的petId,我们禁用其领养按钮,并将按钮文本改为“成功”,这样用户就知道哪些宠物已经被领养了。

  • 任何错误都被记录到控制台。

处理adopt()函数

  1. 仍然在/src/js/app.js中,从handleAdopt中删除多行注释,并将其替换为以下内容:
var adoptionInstance;web3.eth.getAccounts(function(error, accounts) {if (error) {console.log(error);}var account = accounts[0];App.contracts.Adoption.deployed().then(function(instance) {adoptionInstance = instance;// Execute adopt as a transaction by sending accountreturn adoptionInstance.adopt(petId, {from: account});}).then(function(result) {return App.markAdopted();}).catch(function(err) {console.log(err.message);});
});

注意事项:

  • 我们使用web3来获取用户的帐户。 在错误检查后的回调中,我们然后选择第一个帐户。
  • 从那里,我们像上面那样得到已部署的合约,并将实例存储在adoptionInstance中。 这一次,我们将发送一个交易,而不是一个调用。 交易需要"from"地址,并会产生相关费用。 这个费用是用以太币支付的,被称为gas。 gas是在智能合约中执行计算和/或存储数据产生的费用。
    我们通过执行adopt()函数来发送交易,函数的入参是宠物ID和一个包含我们先前存储在账户中的账户地址的对象。
  • 发送交易的返回是一个交易对象。 如果没有错误,我们继续调用我们的markAdopted()函数来同步UI和我们新存储的数据。

在浏览器里使用你创建的dapp

现在你可以开始使用你的dapp啦!

安装和配置MetaMask

在浏览器中与我们的dapp交互的最简单的方法是通过MetaMask,Chrome的扩展插件。

  1. 在您的浏览器中安装MetaMask。

  2. 安装完成后,您会看到地址栏旁边的MetaMask狐狸图标。 点击图标,你会看到这个屏幕出现

  3. 点击接受接受隐私声明。

  4. 那么你会看到使用条款。 阅读它们,滚动到底部,然后单击接受。

  5. 现在你会看到最初的MetaMask屏幕。 点击Import Existing DEN。

    6.在标有“Wallet Seed”的框中,输入登陆Truffle Develop时显示的助记词:

candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

警告:请勿在以太网主网络(mainnet)上使用此助记符。 如果您将ETH发送到由此助记符生成的任何帐户,您将会失去所有您发送到该地址上的ETH!

在下面输入密码,然后单击 OK。

  1. 现在我们需要将MetaMask连接到由Truffle Develop创建的区块链。 点击显示“Main Network”的菜单并选择Custom RPC。

  2. 在标题为“New RPC URL”的框中输入http:// localhost:9545,然后单击Save。

  3. 点击"Settings"旁边的向左箭头关闭页面并返回到“帐户”页面。Truffle Develop 

    创建的每个账户都有100个eth。 你会注意到它在第一个账户上稍微少一些,因为当合同本身被部署时使用了一些gas。

    现在配置完成。

安装和配置lite-server

我们现在可以启动一个本地Web服务器并使用dapp。 我们正在使用lite-server库来为我们的静态文件提供服务。 这是pet-shop Truffle Box里已经有的服务器,但让我们来看看它是如何工作的。

  1. 在文本编辑器(在项目的根目录下)中打开bs-config.json并检查其内容:
{"server": {"baseDir": ["./src", "./build/contracts"]}
}

这告诉lite-server哪些文件包含在我们的基础目录中。 我们为我们的网站文件添加./src目录,为合同工件添加./build/contracts目录。

我们还在项目的根目录下的package.json文件中的scripts对象中添加了一个dev命令。 scripts对象允许我们将控制台命令别名为单个npm命令。 在这种情况下,我们只是做一个单一的命令,但可能有更复杂的配置。 比如像:

"scripts": {"dev": "lite-server","test": "echo \"Error: no test specified\" && exit 1"
},

这告诉npm在我们从控制台执行npm run dev的时候运行我们的lite-server的本地安装。

使用dapp

  1. 启动本地Web服务器:
npm run dev

开发服务器将启动并自动打开一个新的浏览器选项卡,其中包含您的dapp。

Pete's Pet Shop

  1. 要使用dapp,请点击您选择的宠物上的 Adopt 按钮。

  2. 系统将自动提示您通过MetaMask批准交易。 点击 Submit 以批准交易。

    Adoption transaction review

  3. 就像我们期望的一样,你会看到被宠物宠物改变的旁边的按钮,说“成功”,并被禁用,因为宠物已经被领养。

    Adoption success

注意:如果按钮不会自动改变为"Success",刷新浏览器中的应用程序应该会触发它。

MetaMask transaction

恭喜! 你已经迈出了一大步,成为一个成熟的dapp开发者。 为了在本地进行开发,您可以使用所有工具开始制作更高级的绘图。 如果您希望让您的dapp能够让其他人使用,请继续关注我们将来部署到Ropsten测试网络的教程。
(完)

手把手教创建你的第一个以太智能合约:ETHEREUM PET SHOP(译)相关推荐

  1. [译] 使用 Web3 和 Vue.js 来创建你的第一个以太坊 dAPP(第二部分)

    原文地址:Create your first Ethereum dAPP with Web3 and Vue.JS (Part 2) 原文作者:Alt Street 译文出自:掘金翻译计划 本文永久链 ...

  2. 手把手教你在Imtoken上收录你的合约

    1.配置安装Git环境 点击此处进行下载安装 安装过程中默认一直下一步即可. 2.Fork项目到你的仓库 进入Github项目下(地址点击这里),没有Github账号的话去注册一个,以后必须要使用的. ...

  3. 手把手教你将普通wifi路由器变为智能广告路由器

    一步一步教你将普通的wifi路由器变为智能广告路由器 相信大家对WiFi智能广告路由器已经不再陌生了,现在很多公共WiFi上网,都需要登录并且验证,这也就是WiFi广告路由器的最重要的功能.大致就是下 ...

  4. 区块链智能合约Coursera(第一周)智能合约基础

    课程链接 https://www.coursera.org/learn/smarter-contracts/home/week/1 这是区块链专项课程的第二门课 智能合约 Smart Contract ...

  5. 「不良视频」如何消灭?她手把手教你走出第一步

    不严肃的开场白 视频社交已经成为了时下最in的社交方式,相较于传统的文字.语音聊天,使用亲身录制的短视频.幽默搞笑的图片.表情包与好友进行交流,不仅更加风趣且更具人情味. 而随着视频社交的流行,每天产 ...

  6. 「不良视频」如何消灭?她手把手教你走出第一步!

    不严肃的开场白 视频社交已经成为了时下最in的社交方式,相较于传统的文字.语音聊天,使用亲身录制的短视频.幽默搞笑的图片.表情包与好友进行交流,不仅更加风趣且更具人情味. 而随着视频社交的流行,每天产 ...

  7. 手把手教你用Python搭建一个AI智能问答系统

    导读:智能问答系统是自然语言处理的一个重要分支.今天我们将利用分词处理以及搜索引擎搭建一个智能问答系统. 本文经授权转自公众号CSDN(ID:CSDNnews) 作者:李秋键 具体的效果如下所示: 下 ...

  8. 手把手教你用Python搭建一个AI智能问答系统!

    导读:智能问答系统是自然语言处理的一个重要分支.今天我们将利用分词处理以及搜索引擎搭建一个智能问答系统. 具体的效果如下所示: 私信小编01  领取完整代码! 下面简单了解下智能问答系统和自然语言处理 ...

  9. python做一个问答系统_手把手教你用Python搭建一个AI智能问答系统

    导读:智能问答系统是自然语言处理的一个重要分支.今天我们将利用分词处理以及搜索引擎搭建一个智能问答系统. 本文经授权转自公众号CSDN(ID:CSDNnews) 作者:李秋键 具体的效果如下所示: 下 ...

最新文章

  1. 实时实例分割的Deep Snake:CVPR2020论文点评
  2. HTTP协议(HyperText Transfer Protocol,超文本传输协议)
  3. STM32之ADC实例(基于DMA方式)
  4. Netty详解(三):Netty 入门应用
  5. 关于养花---感叹一把
  6. android mvvm框架搭建_轻松搭建基于JetPack组件的MVVM框架
  7. ubuntu设置自动休眠
  8. malloc()与calloc区别
  9. 做自媒体和有没有文化没有太大关系
  10. linux中sar命令的选项很多,Linux sar 命令详解
  11. 【Hive】性能调优 - EXPLAIN
  12. 年龄是计数还是计量_MSA你只知道计量型和计数型?有哪些类型?分别是什么方法?...
  13. io 流 txt本地生成
  14. java面向对象面试怎么回答_Java面向对象面试题
  15. nachos中文教程java_Nachos3.4系列-2 Makefile 与Nachos中文教程 【转】
  16. java 6面骰子_Java实现的简单掷骰子游戏示例
  17. 生物信息学python常用脚本_生物信息工程师必备的编程语言清单
  18. 开发用于异构环境的可生存云多机器人框架
  19. 挂一张表,省的再瞎眼
  20. 笔记本电脑亮度无法调节问题

热门文章

  1. [转]避免PHP-FPM内存泄漏导致内存耗尽
  2. Java代码性能优化技巧
  3. self.view = nil 和[self.view release]的区别
  4. 基于马克思哲学原理论外在美与内在美2017-12-31
  5. [2DPIC调试笔记]parameter_antenna_radiation1013(3)
  6. 寻找免费的阿里云云盾隐秘购买入口,申请并下载ssl证书(https证书)的详细步骤
  7. Python-深拷贝-浅拷贝-时间消耗
  8. 轮廓线重建:二维平行轮廓线重建理论和方法
  9. VTK修炼之道19:图像基本操作_图像像素值的访问与修改
  10. javascript中的try finally