前言
版权声明:
表示合约采用MIT许可证:(补充:MIT 许可证是一种宽松的开源许可证,允许用户自由使用、修改和再发布代码,只需要包含原始许可声明即可。)
//SPDX-License-Indentifier:MIT
版本声明:(用来指示编译器按照solidity的哪一个版本来编译智能合约)
pragma solidity ^0.8.0;
//指定固定版本: 可以使用固定的版本号来声明 Solidity 的版本 pragma solidity 0.8.0; //指定兼容版本范围: 有时候我们希望指定一个兼容版本的范围,可以使用 ^ 符号,表示与指定版本兼容的范围以下例子(>=0.8.0,=, , =0.6.0 uint) balances;
3.结构体(struct):自定义数据结构,可以包含多个不同类型的数据成员
struct Person { string name; uint age; } Person myPerson;
变量
状态变量的可见性:
- public:状态变量被声明为 public 后,Solidity 会自动生成一个对应的 getter 函数,使得该变量可以被外部合约或外部调用者读取。如果合约是其他合约的基类,那么继承合约可以访问父合约中声明为 public 的状态变量。
contract Example { uint256 public myPublicVar; }
- private:状态变量被声明为 private 后,只有当前合约内部可以访问该变量,外部合约无法直接访问。
contract Example { uint256 private myPrivateVar; }
- internal:状态变量被声明为 internal 后,只有当前合约及其派生合约(继承合约)可以访问该变量,外部合约无法直接访问。
contract Example { uint256 internal myInternalVar; }
变量的默认值:
contract DefaultValues { // bool类型变量默认值为false bool defaultBool = false; // int类型变量默认值为0 int defaultInt = 0; // uint类型变量默认值为0 uint defaultUint = 0; // address 类型变量默认值为0X000...(40个0) address defaultAddress = address(0); // bytes类型变量默认值为0x000...(64个0) bytes32 defaultBytes = 0x0; // string类型变量默认值为""(空字符) string defaultString = ""; // 枚举类型变量默认值为它列表的第一项值 enum MyEnum { First, Second, Third } MyEnum defaultEnum = MyEnum.First; }
复合类型变量的默认值:
又每一项变量的默认值组成;
delete操作符:
在 Solidity 中用于将变量重置为其类型的默认值。它可以应用于各种类型的变量,包括基本类型、数组、映射和结构体等。使用 delete 操作符可以将变量恢复到其初始状态,这对于清理合约状态或重置变量非常有用
常量constant和immutable:
常量的命名规则和变量相同,但通常使用大写字母表示,单词之间用下划线”_”连接,
相同点:都能限制对状态变量的修改。
- 常量 (constant):常量是在编译时确定并存储在代码中的值。常量可以在合约内部或函数内部声明,并且作用域仅限于声明所在的作用域。常量在声明时必须进行初始化,并且不能被修改。
pragma solidity ^0.8.0; contract ConstantExample { // 在合约内部声明常量 uint constant internal MY_CONSTANT = 42; function getConstant() public pure returns (uint) { // 在函数内部使用常量 uint myValue = MY_CONSTANT; return myValue; } }
- 不可变 (immutable):不可变变量是在合约部署时确定并存储在区块链上的值。不可变变量在声明时必须进行初始化,并且只能在构造函数中赋值一次。不可变变量的值可以是在编译时可以确定的常量,也可以是在部署时可以确定的值
pragma solidity ^0.8.0; contract ImmutableExample { // 在合约内部声明不可变变量 uint immutable public MY_IMMUTABLE; constructor() { // 在构造函数中给不可变变量赋值 MY_IMMUTABLE = block.timestamp; } function getImmutable() public view returns (uint) { return MY_IMMUTABLE; } }
ps:常量声明时需要直接赋值,且不能在构造函数中给常量赋值;而不可变变量通常在构造函数中初始化,可以在构造函数中给不可变变量赋值
区别两个方面:
1.初始化时机:constant修饰的状态变量必须在声明时就立即显示赋值,之后就不在允许修改。Immutable修饰的状态变量既可以在声明时显示赋值,又可以在构造的函数中赋值。
2.Constant可以修饰任何数据类型,immutable只能修饰值类型(int ,uint, bool, address)
Ps:使用常量的好处
- 代码可读性
- 代码重用
- 预防错误
- 节省Gas
函数:
function 函数名称() return(){ return(变量1,变量2);//多个返回值时候适用 return 变量;//一个返回值适用 }
1.函数的语法:
function 函数名() return (){
}
命名规则:
1.函数是由字母,数字下划线组成的。
2.以小写字母或者下划线开头。
3.采用驼峰形式。
可见性:
函数的可见性分为四种:
- private:只能在所属(本地)的智能合约内部调用
- public修饰的函数可以从任何地方调用,既可以在智能合约的内部调用也可以在智能合约的外部调用
- internal可在所属的只能合约里调用,也可以在继承的合约里调用
- external只能在智能合约的外部调用不能在智能合约的内部调用
函数的返回值:
在solidity中函数可以没有返回值,也可以有一个或者多个返回值。Ps有多个返回值的时候需要用括号包裹全部返回值变量,
函数状态可变性:
1.pure(指状态函数不会修改和读取合约的状态(数据))
function sum() public pure returns(uint){ uint a = 2;//a是一个局部变量 uint b = 3;//b是一个局部变量 return a + b;//返回局部变量的和 }//该函数并没有使用任何状态变量因此其不会读取区块链的状态,也不会改变合约的状态。即该函数不会和区块阿里区块链发生任何的关系。
//如果函数中出现以下语句则被视为读取了状态数据,就不能使用pure函数否则无法通过编译 1.读取状态变量 2.访问.balance 3.访问任何区块,交易,msg等全局变量 4.调用任何不是纯函数的函数 5.使用包含特定操作码的内联汇编
2..view(函数会读取合约的状态但是并会修改,即函数会读取链上的数据但是并不会对数据进行修改)
contract View{ uint a = 1;//a即为状态变量 function test(uint num) public view returns(uint){ return num * a;//读取并使用了状态变量a } }
//函数中存在以下语句则被视为修改了状态数据,就不能使用可见性view了 1.修改状态变量 2.触发事件 3.创建其他合约 4.使用自毁函数:self destruct 5.调用发送以太币 6.调用非view或pure函数 7.使用底层调用 8.使用特定操作码的内联汇编
3.payable(表明这个函数可以接收以太币)
contract Payable{ function test(uint ID ) public payable{//标记为payable,表示它可以接收以太币 //这些以太币市调用者在调用函数时支付的,由于payble函数接受了以太币,所以它是改变合约状态的 } }
4.未标记 (意味着这个函数是要改变合约状态的,即修改合约的状态变量或者向其它合约发送交易等)
contract unmarked{ uint a = 1;//a即为状态变量 function test(uint num) public view returns(uint){ a = num; return num * a;//读取并修改了状态变量a } }
小结:函数的状态可变性的设计目的是为了确保合约的安全性,可靠性,和互操作性
1.安全性:编译时对函数的行为进行检验, 帮助开发者避免不经意间修改合约的状态或者方位未经授权的外部合约,从而减少潜在的安全漏洞。
2.可靠性:通过明确函数的状态可变性,合约的使用者可以更好地理解和预测函数的行为。
3.互操作性:通过标记函数的状态可变性,可以提供给其他合约和工具有关函数的重要信息。
ps:为了减少gas费用尽量使用pure和view函数。
特殊函数:构造函数constructor和receive函数
1.构造函数constructor:
1.自动执行 合约部署时自动执行,不能手动调用或再次执行 2.仅执行一次 仅仅在合约创建时执行一次 *3.通常用于状态变量初始化 可以在构造函数中传递参数来初始化合约的状态变量。这些参数可以在部署合约时提供。 *4.命名与可见性: 构造函数的名字必须与合约名相同,并且不能有返回类型或任何函数修饰符(如public、external等)。构造函数默认为internal可见性,不需要显式声明可见性。 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract MyContract { uint public myNumber; constructor(uint _number) { myNumber = _number; } } //构造函数的可见性默认为public可以被任何人调用 //构造函数的可变性不能设置为pure或者view,但可以为payable属性。因为构造函数通常是用来初始化变量,它是会修改合约状态的。
receive函数:
补充知识:账户类型
1.外部账户 EOA,
例如小狐狸里面的账户
账户地址是以"0x"开头长度为20字节的十六进制数
外部账户都有对应的私钥,只有持有私钥的人才能对交易进行签名, 所以外部账户适用于资金管理的身份验证。
2.内部账户 CA:
在以太坊部署一个合约之后都会产生一个对应的账户称为合约账户,合约账户只要用于托管只能合约,它里面包含着智能合约的二进制代码和状态消息,合约账户也会有一个账户地址是以"0x"开头长度为20字节的十六进制数。合约账户没有私钥只能由合约代码中的逻辑控制。在一定条件下可以存入ETH(以太币)。
只有定义了receive函数或者fallback函数才能接收以太币
//1.无需使用function声明 //2.参数为空 //3.可见性必须为external //4.可变性必须为payable pragma solidity ^0.8.0; contract MyContract { 、receive() external payable { // 处理接收的以太币 } }
事件
在 Solidity 中,事件(Event)是一种特殊的合约结构,用于记录合约中的重要状态变化或触发的操作。事件可以被合约内部的函数触发,并且可以被外部应用程序监听和响应。
事件是 Solidity 中用于记录重要状态变化和操作的机制,可以方便地与外部应用程序进行交互,提供更多的可观察性和可追溯性。
事件语法
event EventName(arg1Type arg1, arg2Type arg2, ...); //EventName:事件的名称,使用驼峰命名法。 //arg1Type、arg2Type:事件参数的类型。
触发事件:
emit EventName(arg1Value, arg2Value, ...);
pragma solidity ^0.8.0; contract EventExample { event NewUser(address indexed userAddress, string username); function registerUser(string memory username) public { // 用户注册逻辑 emit NewUser(msg.sender, username); } }
这是一个简单示例展示了如何定义触发一个事件
在上述示例中,合约 EventExample 定义了一个名为 NewUser 的事件。NewUser 事件有两个参数:userAddress(用户地址)和 username(用户名)。当调用 registerUser 函数进行用户注册时,会触发 NewUser 事件,并传递相应的参数值。
require
在 Solidity 中,require 是一种异常处理机制,用于在函数执行过程中验证条件是否满足。如果条件不满足,require 会抛出异常并中止函数执行,同时回滚所有状态更改。通常用于输入参数验证、前置条件检查和合约内部状态验证等场景。
语法
require(condition, errorMessage); //condition:要验证的条件表达式,如果为 false,则触发异常。 //errorMessage:可选参数,用于指定异常信息,将在异常发生时显示
使用背景:
1.输入参数验证
function deposit(uint256 amount) public { require(amount > 0, "Deposit amount must be greater than 0"); // Deposit logic }
2.前置条件检查:
function transfer(address to, uint256 amount) public { require(to != address(0), "Invalid recipient address"); require(amount