首页 > 其他分享 >solidity语言语法补充(进阶版)

solidity语言语法补充(进阶版)

时间:2024-04-07 11:01:34浏览次数:27  
标签:进阶 solidity gas 语法 uint 数组 memory public 函数

函数修改器:

在Solidity中,函数修改器(Function Modifiers)是一种用于修改函数行为的特殊类型。它可以在函数执行前、执行后或者在函数执行期间对函数的行为进行修改或增加额外的逻辑。函数修改器通常用于提高代码的重用性、简化代码结构,并确保一致的行为。

1. 定义函数修改器

函数修改器通过 modifier 关键字来定义,其结构类似于函数,但不包含函数体。

modifier modifierName() {
    // 这里可以编写额外的逻辑
    _; // 这个符号表示将控制权转交给函数本体
}

2. 使用函数修改器

在Solidity中,函数修改器可以通过在函数定义时使用 modifier 关键字来应用到函数上。

function functionName() visibilityModifier {
    // 函数逻辑
}

在这里,visibilityModifier 是一个函数修改器,它会在执行 functionName 函数时应用额外的逻辑。

3. 例子

下面是一个简单的示例,演示了如何定义和使用函数修改器:

pragma solidity ^0.8.0;

contract Example {
    address public owner;

    // 定义一个函数修改器,用于验证调用者是否是合约的所有者
    modifier onlyOwner() {
        require(msg.sender == owner, "Only the owner can call this function");
        _; // 控制权转交给函数本体
    }

    // 构造函数,初始化合约所有者
    constructor() {
        owner = msg.sender;
    }

    // 使用函数修改器 onlyOwner
    function changeOwner(address newOwner) public onlyOwner {
        owner = newOwner;
    }
}

在这个示例中,onlyOwner 函数修改器用于限制只有合约的所有者才能调用特定的函数,如 changeOwner 函数。这样可以增加合约的安全性,确保只有合约的所有者才能执行敏感操作。

总之,函数修改器是Solidity中一种强大的工具,可以用于简化代码、提高安全性,并确保合约行为的一致性。

call函数:

基本介绍

call 函数是 Solidity 中用于与其他合约或外部账户进行交互的重要函数之一。它允许智能合约向指定地址发送消息并执行函数调用,同时也可以处理调用结果。以下是 call 函数的语法和详细介绍:

(bool success, bytes memory data) = address.call(bytes memory payload);
  • address:要调用的合约或外部账户地址。
  • payload:要发送的消息数据,通常是函数调用的 ABI 编码。
  • success:布尔值,表示调用是否成功。如果调用成功,则为 true;否则为 false
  • data:调用的返回数据,以 bytes memory 类型返回。如果调用失败或者目标合约没有返回数据,则 data 为空。

需要注意的是,使用 call 函数需要小心处理返回值和异常情况,以避免安全风险。以下是一些关键注意事项:

  1. ABI 编码payload 参数通常是使用 abi.encodeWithSignature 或 abi.encodeWithSelector 等函数生成的 ABI 编码。这样可以确保正确传递函数名和参数,并保持类型安全。
  2. Gas 额度:调用 call 函数会消耗 gas。在调用合约函数时,应该确保提供足够的 gas 额度以完成调用操作。
  3. 返回值处理:调用后需要检查 success 来确定调用是否成功,并根据需要处理返回的 data 数据。
  4. 异常处理:调用可能会抛出异常。如果调用失败或者发生异常,应该采取适当的错误处理措施。

综上所述,call 函数是 Solidity 中实现合约间通信和与外部系统交互的重要手段之一,但在使用时需要谨慎处理,以确保安全性和可靠性。

  • ABI 编码:在 Solidity 中,合约之间进行通信或者与外部系统进行交互时,需要使用 ABI 编码来确保数据的正确传递和类型安全。ABI 是一种二进制接口规范,定义了合约函数调用的参数传递方式、返回值格式等信息。

  •  payload 参数 :在使用 call 函数或者发送交易时,需要构建正确的数据结构作为参数传递给目标合约或外部账户。这个参数通常称为 payload,它包含了要发送的消息数据,包括函数调用的 ABI 编码、参数值等信息。

  • abi.encodeWithSignatureabi.encodeWithSelector:这两个函数是 Solidity 提供的用于生成 ABI 编码的工具函数。

    • abi.encodeWithSignature 用于生成带有函数签名的 ABI 编码,确保正确传递函数名和参数值。例如:abi.encodeWithSignature("transfer(address,uint256)", recipient, amount)
    • abi.encodeWithSelector 用于生成带有函数选择器的 ABI 编码,选择器是函数名和参数类型的哈希值,用于唯一标识函数。例如:abi.encodeWithSelector(bytes4(keccak256("transfer(address,uint256)")), recipient, amount)

下面是使用call函数与另一个函数交互的示例:

pragma solidity ^0.8.0;

contract CallerContract {
    event CallResult(bool success, bytes data);

    function callAnotherContract(address _contractAddress, uint256 _value) external {
        // 构建要发送的消息数据,包括函数选择器和参数
        bytes memory payload = abi.encodeWithSignature("someFunction(uint256)", _value);
        
        // 使用 call 函数调用目标合约的函数
        (bool success, bytes memory data) = _contractAddress.call(payload);
        
        // 触发事件,记录调用结果
        emit CallResult(success, data);
    }
}

contract AnotherContract {
    uint256 public someValue;

    function someFunction(uint256 _newValue) external {
        someValue = _newValue;
    }
}

gas修改器:

在 Solidity 中,call 函数有一个可选的 gas 修改器,用于指定调用目标合约函数时所能消耗的 gas 数量。gas 是以太坊网络中的计价单位,用于支付合约执行所需的计算资源。

call 函数的 gas 修改器有两种形式:

  1. 在调用中直接指定 gas 数量。
  2. 在 Solidity 0.8.0 及更高版本中,通过 gas() 函数动态指定 gas 数量。

下面分别介绍这两种形式:

1. 直接指定 gas 数量

(bool success, bytes memory data) = _contractAddress.call{gas: 20000}(payload);

在这个示例中,call 函数调用了 _contractAddress 地址对应合约的函数,并指定了 20000 个 gas 用于执行。这种方式适用于在编译时已知调用所需 gas 量的情况。

2. 使用 gas() 函数动态指定 gas 数量

在 Solidity 0.8.0 及更高版本中,可以使用 gas() 函数动态指定 gas 数量。例如:

(uint256 myGas) = gasleft();
(bool success, bytes memory data) = _contractAddress.call{gas: myGas}(payload);

在这个示例中,使用了 gasleft() 函数获取当前剩余的 gas 数量,并将其动态指定为 call 函数的 gas 参数。这种方式适用于需要在运行时确定 gas 数量的情况,比如在迭代过程中调用其他合约函数。

需要注意的是,在使用 call 函数时,需要确保提供的 gas 数量足够覆盖合约函数的执行所需。如果 gas 不足以完成合约函数执行,调用会失败并返回 false,同时不会影响调用者合约的状态。因此,在确定 gas 数量时,需要进行充分的测试和评估,以确保合约调用的成功执行。

ps:call还支持value功能(可以理解为以太币修改器)

数组

在 Solidity 中,有两种类型的字节数组:定长字节数组和变长字节数组。

定长字节数组(Fixed-size byte array)

定长字节数组有一个固定的长度,在声明时就已经确定了大小,不能更改。它们存储在内存中,具有固定的大小,因此更加高效。定长字节数组的声明方式如下:

bytes32 public fixedArray; // 声明一个长度为32字节的定长字节数组

变长字节数组(Dynamic byte array)   

变长字节数组的长度可以动态增长或缩小。它们的长度是在运行时动态确定的,因此更加灵活。变长字节数组存储在存储器(storage)中或是作为函数的参数传递。声明方式如下:

bytes public dynamicArray; // 声明一个变长字节数组

需要注意的是,变长字节数组在存储器(storage)中存储时会消耗更多的 gas,因为它们的长度是动态的,可能会导致存储器的碎片化。因此,如果可能的话,应尽量使用定长字节数组,以提高效率和节省 gas 费用。

string数组:

可以使用 string.concat 连接任意数量的 string 字符串。 该函数返回一个 string memory ,包含所有参数的内容,无填充方式拼接在一起。 如果你想使用不能隐式转换为 string 的其他类型作为

参数,你需要先把它们转换为 string

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;

contract C {
    string s = "Storage";
    function f(bytes calldata bc, string memory sm, bytes16 b) public view {
        string memory concatString = string.concat(s, string(bc), "Literal", sm);//这里用到了所说的连接
//assert 语句后面的括号中包含一个条件表达式。在这个条件表达式中,合约在断言的位置检查是否满足某种条件。如果条件为真,则合约继续执行,如果条件为假,则合约会失败并触发回滚
        assert((bytes(s).length + bc.length + 7 + bytes(sm).length) == bytes(concatString).length);
//下面是byte数组的连接
        bytes memory concatBytes = bytes.concat(bytes(s), bc, bc[:2], "Literal", bytes(sm), b);
        assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) == concatBytes.length);
    }
}

assert 是一个断言语句,用于在合约执行期间检查条件是否满足。如果条件不满足,assert 将导致合约执行失败,并且会回滚所有的状态变化,包括 gas 的消耗。这有助于确保合约在执行期间的正确性。

assert 语句后面的括号中包含一个条件表达式。在这个条件表达式中,合约在断言的位置检查是否满足某种条件。如果条件为真,则合约继续执行,如果条件为假,则合约会失败并触发回滚。

在合约中使用 assert 可以用来进行一些必要的检查,例如检查函数执行过程中的一些关键条件,确保合约的安全性和正确性。

在给定的代码中,assert 语句用于检查拼接后的字符串的长度是否符合预期。如果长度不符合预期,将触发断言失败,并导致合约执行失败。这样可以确保合约在运行过程中正确地处理了字符串拼接的逻辑。

数组成员

length:

数组有 length 成员变量表示当前数组的长度。 一经创建,内存memory 数组的大小就是固定的(但却是动态的,也就是说,它可以根据运行时的参数创建)。

// 创建一个动态大小的存储数组,并获取其长度
uint[] public dynamicArray;

function getArrayLength() public view returns (uint) {
    return dynamicArray.length;
}
push():

动态的 存储storage 数组以及 bytes 类型( string 类型不可以)都有一个 push() 的成员函数,它用来添加新的零初始化元素到数组末尾,并返回元素引用. 因此可以这样:  x.push().t = 2 或 x.push() = b.

// 在动态存储数组末尾添加新的零初始化元素,并返回元素引用
struct Element {
    uint value;
}

Element[] public dynamicArray;

function addElement() public {
    dynamicArray.push();
}

function setElementValue(uint newValue) public {
    dynamicArray.push().value = newValue;
}
push(x):

动态的 存储storage 数组以及 bytes 类型( string 类型不可以)都有一个 push(x) 的成员函数,用来在数组末尾添加一个给定的元素,这个函数没有返回值.

// 在动态存储数组末尾添加给定的元素
uint[] public dynamicArray;

function addElement(uint newValue) public {
    dynamicArray.push(newValue);
}
pop():

变长的 存储storage 数组以及 bytes 类型( string 类型不可以)都有一个 pop() 的成员函数, 它用来从数组末尾删除元素。 同样的会在移除的元素上隐含调用 delete ,这个函数没有返回值。

// 从动态存储数组末尾删除元素
uint[] public dynamicArray;

function removeElement() public {
    dynamicArray.pop();
}

ps:通过 push() 增加 存储storage 数组的长度具有固定的 gas 消耗,因为 存储storage 总是被零初始化,而通过 pop() 减少长度则依赖移除与元素的大小(size). 如果元素是数组,则成本是很高的,因为它包括已删除的元素的清理,类似于在这些元素上调用 delete 。

数组切片:

数组切片是数组连续部分的视图,用法如:x[start:end] ,start 和 end 是 uint256 类型(或结果为 uint256 的表达式),x[start:end] 的第一个元素是 x[start] ,最后一个元素是 x[end-1] 。

如果 start 比 end 大或者 end 比数组长度还大,将会抛出异常。

start 和 end 都可以是可选的: start 默认是 0, 而 end 默认是数组长度。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ArraySlicing {
    uint[] public originalArray;

    constructor() {
        originalArray = [1, 2, 3, 4, 5];
    }

    function getSlice(uint start, uint end) public view returns (uint[] memory) {
        return originalArray[start:end];
    }
}

用户定义的值类型:

一个用户定义的值类型允许在一个基本的值类型上创建一个零成本的抽象。 这类似于一个别名,但有更严格的类型要求。

用户定义值类型使用 type C is V 来定义,其中 C 是新引入的类型的名称, V 必须是内置的值类型(”底层类型”)。 函数 C.wrap 被用来从底层类型转换到自定义类型。同样地,函数函数 C.unwrap 用于从自定义类型转换到底层类型。

// 定义自定义类型
type CustomString is string;

contract Example {
    // wrap 函数将底层类型转换为自定义类型
    function wrapString(string memory value) public pure returns (CustomString memory) {
        return CustomString(value);
    }
    
    // unwrap 函数将自定义类型转换为底层类型
    function unwrapString(CustomString memory custom) public pure returns (string memory) {
        return string(custom);
    }
}

在这个示例中,CustomString 是我们定义的自定义类型,它是 string 类型的一个包装器。wrapString 函数接受一个 string 类型的参数,并将其转换为 CustomString 类型;unwrapString 函数接受一个 CustomString 类型的参数,并将其转换为 string 类型。

memory和storage:

在 Solidity 中,memory 是一种关键字,用于声明临时性的数据存储位置。在 Solidity 中,数据可以存储在不同的位置,包括 storage(持久性存储,如合约状态变量)和 memory(临时性存储,如临时变量和函数参数)。

具体来说,memory 关键字用于指示数据应该存储在临时的内存中,而不是永久性存储在区块链的状态中。在 Solidity 中,函数参数和函数内部声明的临时变量默认会被分配在 memory 中。

使用 memory 的一些常见情况包括:

  1. 函数参数: Solidity 函数的参数默认会被分配到 memory 中。例如:

function foo(uint[] memory _data) public {
    // 函数参数 _data 存储在 memory 中
    // 可以在函数内部使用 _data 数组,但它不会永久存储在区块链上
}

   2.临时变量: 在函数内部声明的临时变量通常会被分配到 memory 中。例如:

function bar() public {
    uint[] memory tempArray = new uint[](10);
    // tempArray 是临时变量,存储在 memory 中
    // 只在函数执行期间存在,并在函数执行完毕后被清除
}

需要注意的是,memory 中存储的数据只在当前函数执行期间存在,函数执行结束后数据将被清除。相比之下,storage 中存储的数据是永久性的,会持久存储在区块链上,例如合约的状态变量。

使用 memory 的好处是它可以提高临时数据的访问效率,并避免占用永久性的存储空间。但需要注意的是,memory 中的数据需要手动分配和管理,并且有一定的 gas 成本用于在区块链上执行这些操作。

结构体:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {
    // 定义结构体
    struct Person {
        string name;
        uint age;
        address walletAddress;
    }

    // 声明结构体类型的变量
    Person public myPerson;

    // 修改结构体成员的函数
    function setPerson(string memory _name, uint _age, address _walletAddress) public {
        myPerson.name = _name;
        myPerson.age = _age;
        myPerson.walletAddress = _walletAddress;
    }

    // 获取结构体成员的函数
    function getPerson() public view returns (string memory, uint, address) {
        return (myPerson.name, myPerson.age, myPerson.walletAddress);
    }
}
/*我们定义了一个名为 Person 的结构体,它包含了三个成员变量:name(字符串类型)、age(无符号整数类型)、walletAddress(地址类型)。然后,我们声明了一个类型为 Person 的公共状态变量 myPerson。

setPerson 函数用于修改 myPerson 结构体的成员变量的值,接受三个参数:姓名、年龄和钱包地址。getPerson 函数则用于获取 myPerson 结构体的成员变量的值,并作为元组返回。

结构体在 Solidity 中通常用于组织和管理复杂的数据结构,例如用户信息、交易记录等。它们提供了一种方便的方式来组织和操作相关的数据项。*/

映射:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {
    // 定义一个映射,将地址映射到整数
    mapping(address => uint) public balances;

    // 存款函数,增加指定地址的余额
    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    // 提款函数,减少指定地址的余额
    function withdraw(uint amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }

    // 获取指定地址的余额
    function getBalance(address account) public view returns (uint) {
        return balances[account];
    }
}

结构体和映射的综合使用:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;

// 定义的新类型包含两个属性。
// 在合约外部声明结构体可以使其被多个合约共享。 在这里,这并不是真正需要的。
struct Funder {
    address addr;
    uint amount;
}

contract CrowdFunding {

    // 也可以在合约内部定义结构体,这使得它们仅在此合约和衍生合约中可见。
    struct Campaign {
        address beneficiary;
        uint fundingGoal;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    mapping (uint => Campaign) campaigns;

    function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
        campaignID = numCampaigns++; // campaignID 作为一个变量返回

        // 不能使用 "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)"
        // 因为RHS(right hand side)会创建一个包含映射的内存结构体 "Campaign"
        Campaign storage c = campaigns[campaignID];
        c.beneficiary = beneficiary;
        c.fundingGoal = goal;
    }

    function contribute(uint campaignID) public payable {
        Campaign storage c = campaigns[campaignID];
        // 以给定的值初始化,创建一个新的临时 memory 结构体,
        // 并将其拷贝到 storage 中。
        // 注意你也可以使用 Funder(msg.sender, msg.value) 来初始化。
        c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
        c.amount += msg.value;
    }

    function checkGoalReached(uint campaignID) public returns (bool reached) {
        Campaign storage c = campaigns[campaignID];
        if (c.amount < c.fundingGoal)
            return false;
        uint amount = c.amount;
        c.amount = 0;
        c.beneficiary.transfer(amount);
        return true;
    }
}

上面的合约只是一个简化版的 众筹合约,但它已经足以让我们理解结构体的基础概念。 结构体类型可以作为元素用在映射和数组中,其自身也可以包含映射和数组作为成员变量。

尽管结构体本身可以作为映射的值类型成员,但它并不能包含自身。 这个限制是有必要的,因为结构体的大小必须是有限的。

注意在函数中使用结构体时,一个结构体是如何赋值给一个存储位置是 存储storage 的局部变量。 在这个过程中并没有拷贝这个结构体,而是保存一个引用,所以对局部变量成员的赋值实际上会被写入状态。

可迭代映射:

在 Solidity 中,映射本身是一个无法迭代的数据结构,这意味着你不能直接在合约中对映射进行迭代操作(如循环遍历)。然而,有时候我们可能需要对映射中的所有键或所有值进行迭代操作,这时我们可以通过一些技巧实现类似的功能,这就是可迭代映射。

一种常见的方法是使用一个存储键的数组,然后通过遍历该数组来访问映射中的键或值

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyContract {
    mapping(address => uint) public balances;
    address[] public accounts;

    // 存款函数,增加指定地址的余额
    function deposit() public payable {
        if (balances[msg.sender] == 0) {
            accounts.push(msg.sender); // 将新地址添加到数组中
        }
        balances[msg.sender] += msg.value;
    }

    // 获取所有账户地址
    function getAllAccounts() public view returns (address[] memory) {
        return accounts;
    }

    // 获取所有账户的余额
    function getAllBalances() public view returns (uint[] memory) {
        uint[] memory result = new uint[](accounts.length);
        for (uint i = 0; i < accounts.length; i++) {
            result[i] = balances[accounts[i]];
        }
        return result;
    }
}

在这个示例中,我们通过一个动态数组 accounts 来存储所有的地址,当有新地址存款时,我们将其添加到数组中。然后,我们可以通过 getAllAccounts 函数获取所有的账户地址,通过 getAllBalances 函数获取所有账户的余额。这种方法虽然不直接对映射进行迭代,但实现了类似的功能。

需要注意的是,当映射中的键值对数量较大时,遍历整个数组可能会导致 gas 消耗过高,因此在设计合约时需要注意 gas 成本和效率。

标签:进阶,solidity,gas,语法,uint,数组,memory,public,函数
From: https://blog.csdn.net/2303_79193185/article/details/137422397

相关文章

  • C语言学习笔记--(2)基础语法
    我先写点,我不太擅长写,所以各位有问题可以评论说,我看到一定改一.C语言编程的格式    我们可以先看一个关于C语言的基础实例下面是一个简单的C语言程序,用于计算购买商品的总价,并根据折扣计算最终支付金额。#include<stdio.h>//计算购买商品的总价floatcalculat......
  • [转帖]JVM 内存分析工具 MAT 的深度讲解与实践——进阶篇
    https://juejin.cn/post/6911624328472133646  注:本文原创,转发需标明作者及原文链接。欢迎关注 【0广告微信公众号:Q的博客】。本系列共三篇文章, 本文是系列第2篇——进阶篇,详细讲解MAT各种工具的核心功能、用法、适用场景,并在具体实战场景下讲解帮大家学习如何针......
  • Vue.js梳理({}语法与指令)
    一、原生APIvs函数库vs框架原生API浏览器/平台已实现的,可直接使用的原生函数问题:代码繁琐函数库(library)基于原生API基础上,进一步封装的,更简化的一组函数的集合框架(framework)前人将多次成功项目的经验总结,形成的帮成品项目优:后人继续开发即可,项目整体代码和做事......
  • Python 语法检查、格式化工具 Ruff 的各项配置
    一、配置文件1.1配置文件的位置和优先级Ruff支持pyproject.toml、ruff.toml和.ruff.toml三种文件(同时出现时,右边的优先级高);最近的配置文件生效,父级的将被忽略;可使用extend继承其他配置;命令行指定参数时,会覆盖配置文件中的选项(即命令行优先级更高);可使用target-ver......
  • Go+云原生高级开发工程师进阶路线及资料推荐
    云原生这几年非常火,很多同学都在学习云原生相关技术,我也在如何进阶为Go+云原生高级开发工程师?中,详细介绍了如何学习,以使自己快速进阶为Go+云原生高级开发。这里我再快速总结下学习路线,并提供路线中涉及到的学习资料供你下载。学习路线本着只看优秀课程、不重复学习、学习......
  • “IT百科”进阶学习之“虚拟化技术全攻略”
    “IT百科”进阶学习之“虚拟化技术全攻略”“虚拟化技术"针对从事IT技术的相关工作人员应该都有听说或者应用过,而对于多数网友朋友们可能没有听说过,但是我相信多数人都听说过"云计算”、“云服务”、“虚拟机"这几个概念名词吧!而这几个概念主要应用的技术就是"神奇"的"虚拟......
  • Linux初学(十二)AWK进阶
    一、AWK1.1简介AWK是Linux中重要的文本处理工具Linux三剑客只一处理的对象可以是一个具体的文件,也可以是一个命令的执行结果AWK按行读取文件,将每一行视为一条记录案例一:获取系统中每个用户的uid方法一:cat/etc/passwd|awk-F":"'{print$3}'方法二:awk-F":"'{pr......
  • 操作系统综合题之“银行家算法,计算还需要资源数量和可用资源梳理和写出安全队列和银行
    一、设系统中有三种类型资源A、B、C,资源数量分别为15、7、18,系统有五个进程P1、P2、P3、P4、P5,其最大资源需求量分别为(5,4,9)、(4,3,5)、(3,0,5)、(5,2,5)、(4,2,4)。在T0时刻,系统为个进程已经分配的资源数量分别为(2,1,2)、(3,0,2)、(3,0,4)、(2,0,4)、(3,1,4)。若系统采用银行家算法实施死锁避免策略......
  • 30天拿下Rust之超级好用的“语法糖”
    概述        Rust语言的设计非常注重开发者的体验,因此它包含了许多实用的“语法糖”。这些“语法糖”让代码更简洁、易读,同时保持了语言的强大和灵活性。1、字符串插值        字符串插值允许我们在字符串中嵌入变量或表达式的值,使用{}作为占位符。fnmai......
  • Rust语言基础:语法、数据类型与操作符
    Rust语言基础:语法、数据类型与操作符Rust是一种系统编程语言,致力于安全、并发和实用性。它是由Mozilla基金会开发的,并得到了广泛的应用。在本篇文章中,我们将带你了解Rust的基础知识,包括语法、数据类型和操作符。1.Rust的语法Rust的语法类似于C++和Java,但同时又更加简洁......