通过Omnet++官网tictoc教程学习在Omnet++中构建和运行仿真 Part3

TicToc Part3

  • 增强2节点 TicToc
    • 增加图标
    • 增加 日志
    • 添加状态变量
    • 增加参数
    • 使用NED 继承
    • 模拟处理延时
    • 随机数字和参数
    • 超时、取消计时器
    • 重传同样的消息

官方文档
在官方文档中,你可以看见所有的代码

增强2节点 TicToc

增加图标

为了使模型在GUI中看起来更好看,可以在ned文件中添加显示字符串来实现。如下代码指定了 块/路由图标(文件images/block/routing.png),并将tic绘制为青色,将toc绘制为黄色。

	simple Txc2
{
    parameters:
        @display("i=block/routing"); // add a default icon
    gates:
        input in;
        output out;
}

//
// Make the two module look a bit different with colorization effect.
// Use cyan for `tic', and yellow for `toc'.
//
network Tictoc2
{
    submodules:
        tic: Txc2 {
            parameters:
                @display("i=,cyan"); // do not change the icon (first arg of i=) just colorize it
        }
        toc: Txc2 {
            parameters:
                @display("i=,gold"); // here too
        }
    connections:
        tic.out --> {  delay = 100ms; } --> toc.in;
        tic.in <-- {  delay = 100ms; } <-- toc.out;
}

可以看见显示效果如下图:
在这里插入图片描述

总结:这就是教我们可以对图标、颜色等进行设置,让其仿真显示更好看

增加 日志

可以修改C++代码,将日志语句添加到Txc1中,打印出它现在正在做的事。
omnet++提供了一个复杂的日志记录功能,包括日志级别、日志通道、过滤等,对于大型和复杂的模型非常有用,但在这个模型中,我们将使用其最简单的形式EV:

	        EV << "Sending initial message\n";
	        	    EV << "Received message `" << msg->getName() << "', sending it out again\n";

在这里插入图片描述
在运行代码时,要运行的是tictoc2相关的代码,记得在ini文件中进行更改;
在这里插入图片描述
这样在运行仿真时会出现选择,选择要进行的仿真:Tictoc2
在这里插入图片描述

我们可以在日志窗口中查看:
在这里插入图片描述
在这里插入图片描述
使用右键单击某个模块查看日志在大型仿真中会非常有用

总结:这就是在cc文件中添加日志,在仿真时可以查看日志,帮助我们了解仿真运行过程中的细节,在调试时也会有用

添加状态变量

在这一步骤中我们添加一个计数器到模型中,在进行10次数据交换后会删除消息

将计数器作为一个类成员添加:

class Txc3 : public cSimpleModule
{
  private:
    int counter;  // Note the counter here

  protected:

我们在initialize()中将该变量设置为10,并在handleMessage()中减1,即在每条消息到达时减1。当它达到0时,模拟将耗尽事件并终止。

注意:

WATCH(counter)

这一行可以让我们可以在图形化运行界面中看见计数器的值

如果你点击tic的图标,主窗口左下角的inspector窗口将显示tic的详细信息。确保从顶部的工具栏中选择Children模式。检查器现在显示counter变量。
在这里插入图片描述

当继续运行模拟时,可以跟踪计数器的递减过程,直到达到0。

总结: 在cc文件中添加变量,该变量可以仿真的运行进行控制,并且可以通过WATCH()然后在仿真运行时在左下角界面的 children模型中查看该变量,了解仿真运行的一些参数细节

增加参数

在这一步中,你将学习如何在模拟中添加输入参数:我们将把“魔数”10变成一个参数,并添加一个布尔参数来决定模块是否应该在其初始化代码中发送第一个消息(无论是tic还是toc模块)。

模块参数必须在NED文件中声明。数据类型可以是数值型、字符串型、布尔型或xml(后者便于访问xml配置文件)等。

simple Txc4
{
    parameters:
        bool sendMsgOnInit = default(false); // whether the module should send out a message on initialization
        int limit = default(2);   // another parameter with a default value
        @display("i=block/routing");
    gates:

我们要在C++代码中读取该参数,并将其分配给变量counter

 35    counter = par("limit");

我们可以用第二个参数决定是否开启最初的消息发送

 39    if (par("sendMsgOnInit").boolValue() == true) {

现在,我们可以在NED文件或omnetpp.ini中分配参数。NED文件中的赋值优先。如果在NED文件中使用default(…)语法,则可以定义参数的默认值。在这种情况下,您可以设置omnetpp.ini中的参数值,或者使用NED文件提供的默认值。

我们在下面分配一个参数在NED文件中:

network Tictoc4
{
    submodules:
        tic: Txc4 {
            parameters:
                sendMsgOnInit = true;
                @display("i=,cyan");
        }
        toc: Txc4 {
            parameters:
                sendMsgOnInit = false;
                @display("i=,gold");
        }
    connections:

分配另一个在 omnetpp.ini中:

17   Tictoc4.toc.limit = 5

请注意,因为omnetpp.ini支持通配符,并且NED文件分配的参数优先于omnetpp.ini中的参数,所以我们可以使用

Tictoc4.t*c.limit=5
Tictoc4.*.limit=5
**.limit=5

这些表述的作用是相同的

在图形化运行时环境中,您可以在主窗口左侧的对象树中,或者在模块检查器的参数页中检查模块参数(单击模块后,信息显示在主窗口的左下角)。
在这里插入图片描述

限制较小的模块将删除消息,从而结束仿真。

总结 :上一节我们是在cc文件中定义参数在cc文件中使用,然后通过WATCH()让其可以在仿真界面中查看 ;而本节,我们是在NED文件中定义参数(数值型、字符串型、布尔型或xml),然后在cc文件中也可以通过par()直接使用,并且可以在NED文件或ini文件中对在NED文件中声明的参数进行初始化(NED中的初始化优先级较高),在仿真界面左侧可以查看在NED中声明的这些参数的值。

使用NED 继承

如果我们仔细看一下NED文件,我们会意识到tic和toc的不同之处在于它们的参数值和显示字符串。我们可以通过继承另一个模块类型并指定或覆盖它的一些参数来创建一个新的简单模块类型。在我们的例子中,我们将派生出两个简单的模块类型(Tic和Toc)。稍后我们可以在定义网络中的子模块时使用这些类型。

从现有的简单模块派生很容易。这是基本模块:

simple Txc5
{
    parameters:
        bool sendMsgOnInit = default(false);
        int limit = default(2);
        @display("i=block/routing");
    gates:
        input in;
        output out;
}

注:我们会感觉这和之前的Txc很像,但之前是创建了一个Txc模块,在构建网络时再创建tic和toc,两者基于Txc添加了一些参数,这样之后是无法直接拿来使用,并且每使用一次都要再创建一次;而这里是先创建Txc5模块,然后直接根据Txc5继承出Tic5和Toc5,在所有网络中都可以快捷的使用这两个模块

如下为派生模块,简单地指定参数值并添加一些显示属性:

simple Tic5 extends Txc5
{
    parameters:
        @display("i=,cyan");
        sendMsgOnInit = true;   // Tic modules should send a message on init
}
simple Toc5 extends Txc5
{
    parameters:
        @display("i=,gold");
        sendMsgOnInit = false;  // Toc modules should NOT send a message on init
}

注:c++实现继承自基本简单模块(Txc5)。

我们创建了新的简单模块之后,我们就可以在网络中使用它们作为子模块类型:

network Tictoc5
{
    submodules:
        tic: Tic5;  // the limit parameter is still unbound here. We will get it from the ini file
        toc: Toc5;
    connections:

正如您所看到的,网络定义现在更短、更简单。继承允许您在网络中使用通用类型,并避免冗余定义和参数设置。

总结:面对结构相似而只有部分参数等不同的模块,我们可以先创建基类,在继承基类并添加或覆盖一些参数来创建模块,可以使编程更快捷与简单。

模拟处理延时

在之前的模型中,tic和toc会立即将接收到的消息发送回来。在这里,我们将添加一些计时:tic和toc将在发送回消息之前保存消息模拟1秒。在omnet++中,这种定时是通过模块向自身发送消息来实现的。这样的消息称为自消息(但这只是因为它们的使用方式,否则它们就是普通的消息对象)。

我们在类中添加了两个cMessage *变量event和tictocMsg,以记住我们用于计时的消息和我们模拟的处理延迟的消息。

class Txc6 : public cSimpleModule
{
  private:
    // Set the pointers to nullptr, so that the destructor won't crash
    // even if initialize() doesn't get called because of a runtime
    // error or user cancellation during the startup process.
    cMessage *event = nullptr;  // pointer to the event object which we'll use for timing
    cMessage *tictocMsg = nullptr;  // variable to remember the message until we send it back

  public:

我们使用scheduleAt()函数“发送”self-message,指定何时将其传递回模块。

92	        scheduleAt(simTime()+1.0, event);

在handleMessage()中,现在我们必须区分新消息是通过输入门到达的还是通过自消息返回的(定时器过期)。这里我们使用

78	    if (msg == event) {

也可以这样写:

 if (msg->isSelfMessage())

为了保持源代码短小,我们省略了计数器。

运行模拟时,你会看到以下日志输出:
在这里插入图片描述

总结:本节的主要目的是为了仿真处理时延,时延的产生是通过向自身发送自消息来实现的,使用scheduleAt()函数来指定何时发送自消息,函数两个变量第一个是发送消息的时间,第二个变量是发送的内容,其中还使用到函数simTime()该函数是获得当前的仿真时间,配合 simTime()+1可以获得当前时间的下一秒的时间,控制消息在1s后发送。

注:Txc6.cc文件完整源代码
另外在完整cc代码中,Txc6中定义了两个消息,event指向自消息,tictocMsg是发送给对方的消息。程序运行的流程是,在initialize()中,会在第5s时发送自消息,所以程序会在开头等待5s,我们可以把这5s视作系统启动,在tic在第5s向自己发送了自消息event后,在tic的handleMessage(cMessage *msg)函数中接收到event,判断msg == event成立,会往网口 out发送 tictocMsg ;然后toc接受到该消息,使用tic的handleMessage(cMessage *msg)函数处理该消息,判断 msg = = event 不成立,在else中调用 scheduleAt(simTime()+1.0, event);向自己发自消息,该消息我们视作toc的处理时延,在1s后toc接收到自己的自消息,调用handleMessage(cMessage *msg)函数处理,判断msg = = event成立,也往网口 out发送 tictocMsg 到tic 。就这样循环下去。

随机数字和参数

在这一步中,我们将引入随机数。我们将延迟从1更改为随机值,该值可以从NED文件或omnetpp.ini中设置。模块参数能够返回随机变量;然而,为了利用这个特性,每次使用handleMessage()时都必须读取它的形参。

            // The "delayTime" module parameter can be set to values like
            // "exponential(5)" (tictoc7.ned, omnetpp.ini), and then here
            // we'll get a different delay every time.
            simtime_t delay = par("delayTime");

            EV << "Message arrived, starting to wait " << delay << " secs...\n";
            tictocMsg = msg;

此外,我们会以很小的(硬编码的)概率“丢失”(删除)数据包。

        if (uniform(0, 1) < 0.1) {
            EV << "\"Losing\" message\n";
            delete msg;
        }

我们将在omnetpp.ini中分配参数:

Tictoc7.tic.delayTime = exponential(3s)
Tictoc7.toc.delayTime = truncnormal(3s,1s)

可以查看仿真日志如下:
在这里插入图片描述

无论您重新运行模拟多少次(或重新启动它,Simulate -> Rebuild network菜单项),您都可以尝试,您将得到完全相同的结果。 这是因为omnet++使用确定性算法(默认为Mersenne Twister RNG)来生成随机数,并将其初始化为相同的seed
。这对于可重复性的模拟很重要。你可以在omnetpp.ini中添加以下几行代码来试验不同的seed:

[General]
seed-0-mt=532569  # or any other 32-bit value

根据语法,您可能已经猜到omnet++支持多个rng。没错,然而,本教程中的所有模型都使用RNG 0。(RNG,Random Number Generators,随机数生成器)

总结:这里就是想给处理时延添加随机性,为了便于后续调整该参数,在Ned文件中定义处理时延,在cc文件中每次模拟处理时延时使用par()函数来调用,在Ini文件中对时延参数进行设置。

代码注释:

uniform(0, 1) < 0.1

uniform(0, 1): 这是一个随机数生成表达式,表示生成一个在区间[0, 1]内均匀分布的随机数

volatile double delayTime @unit(s);   // delay before sending back message

volatile :volatile 用于声明一个变量是“易失性”的,意味着该变量的值可以在未经优化的情况下被访问和修改。这通常用于与仿真时间或其他可能在仿真期间变化的值相关的变量。这个修饰符确保了在仿真运行过程中,该变量的值能够被准确地获取和修改。
@unit(s): 这是一个单位注解。在OMNeT++中,你可以使用 @unit 注解来指定变量的单位。在这里,(s) 表示该变量的单位是秒(seconds)

Tictoc7.tic.delayTime = exponential(3s)
Tictoc7.toc.delayTime = truncnormal(3s,1s)

exponential(3s)表示使用指数分布生成随机数,其中(3s)是指定的参数,表示指数分布的均值为3秒。这意味着tictoc7.tic.delayTime的初始值将以指数分布的方式随机生成,其中平均值为3秒。

truncnormal(3s,1s)表示使用截断正态分布生成随机数,其中(3s)是指定的均值,(1s)是指定的标准差。

超时、取消计时器

为了离网络协议建模更近一步,让我们将模型转换为停等仿真。这次我们将为tic和toc分为不同类。基本场景与前面的类似:tic和toc将相互传递一条消息。然而,toc会以一定的非零概率“丢失”消息,在这种情况下,tic将不得不重新发送它。

toc的代码如下:

void Toc8::handleMessage(cMessage *msg)
{
    if (uniform(0, 1) < 0.1) {
        EV << "\"Losing\" message.\n";
        bubble("message lost");  // making animation more informative...
        delete msg;
    }
    else {

由于代码中调用了bubble(), toc在删除消息时将显示一个调出框。
在这里插入图片描述
当定时器到期时,我们假定消息丢失并发送另一条消息。如果toc的回复到达,定时器必须取消。计时器将是(还有什么?)一条自消息。

 65       scheduleAt(simTime()+timeout, timeoutEvent);

取消定时器可以通过调用cancelEvent()来完成。请注意,这并不妨碍我们反复重用相同的超时消息。

71	        cancelEvent(timeoutEvent);

在日志中我们可以看见:
在这里插入图片描述

总结: 该例子就是仿真toc会有一定丢包的概率,而tic对丢包有处理能力,在一定时间内未获得toc回传的消息时tic将会重发消息。因为tic与toc工作方式差异较大,所以不能再基于同一个类Txc,而是要各自创建一个类Tic、Toc。
在toc丢包时,使用delete删除收到的消息,并且为了在仿真界面上展现出丢包使用bubble()函数。
tic使用timeout定义设置超时时间,使用timeoutEvent作为超时事件的消息,在handleMessage(cMessage *msg)中正常接收到消息时会进入else,先取消timeoutEvent,这样就不会发送timeoutEvent的自消息,这样就不会进入timeoutEvent的if中,然后会正常发送消息,如果toc及时返回消息,会再次进入else重复这个过程;而如果toc没有及时返回消息,scheduleAt(simTime()+timeout, timeoutEvent);就会发送timeoutEvent的自消息,if (msg == timeoutEvent)成立,进入到超时重发。

代码解析:

 cancelEvent(timeoutEvent);

cancelEvent()函数用于取消当前正在处理的仿真事件,使得该事件不再执行。这可以用于在仿真运行过程中取消已经计划的事件,以便在需要时中止特定的仿真行为或进行动态调整。后续事件都不会受到取消事件的影响,它们将继续按照预定的顺序执行。

重传同样的消息

在这一步中,我们完善之前的模型。在这里,如果需要重传,我们刚刚创建了另一个数据包。这样做没有问题,因为数据包包含的内容不多,但在现实生活中,通常更实际的做法是保留原始数据包的副本,这样我们就可以重新发送它,而不需要重新构建。保留一个指向已发送消息的指针——以便我们可以再次发送它——可能看起来更容易,但当消息在另一个节点被销毁时,指针就变得无效了。

我们在这里做的是保留原始数据包,只发送它的副本。收到toc的确认后,我们删除了原件。为了便于可视化地验证模型,我们将在消息名称中包含一个消息序列号。

为了避免handleMessage()变得太大,我们将把相应的代码放在两个新函数中,generateNewMessage()和sendCopyOf(),并在handleMessage()中调用它们。

cMessage *Tic9::generateNewMessage()
{
    // Generate a message with a different name every time.
    char msgname[20];
    sprintf(msgname, "tic-%d", ++seq);
    cMessage *msg = new cMessage(msgname);
    return msg;
}
void Tic9::sendCopyOf(cMessage *msg)
{
    // Duplicate message and send the copy.
    cMessage *copy = (cMessage *)msg->dup();
    send(copy, "out");
}

总结 : 发送消息的副本,保留原始消息方可能需要的重传

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/548653.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络—TCP协议详解:协议构成、深度解析(1)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;マリンブルーの庭園—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 3:34 &#x1f504; ◀️…

vs2008使用 openmp

目录 1 在项目中找到property pages>>c/c>>language>>openmp支持 2 在环境变量中增加“OMP_NUM_THREADS”变量&#xff0c;数值自己根据你的CPU的性能来设置&#xff0c;一般2、4、8等 3 在项目中输入如下代码&#xff0c;并编译运行 4 结果与不使用omp的…

浅谈Java IO流

Java中的IO流&#xff08;Input/Output streams&#xff09;是Java程序用来处理数据输入和输出的核心工具集。IO流抽象了数据流动的概念&#xff0c;允许Java程序与外部世界进行数据交换&#xff0c;无论是从文件、网络、键盘输入还是向屏幕、文件或网络发送数据。Java IO流按照…

RAG 如何消除大模型幻觉

什么是大模型幻觉 假设我们有一个基于大型生成模型&#xff08;如GPT-3&#xff09;的问答系统&#xff0c;该系统用于回答药企内部知识库中的问题。我们向其提出一个问题&#xff1a;“阿司匹林的主要药理作用是什么&#xff1f;” 正确的答案应该是&#xff1a;“阿司匹林主…

Qt 的内存管理机制

目录 Qt 的内存管理机制 Qt 的对象树 利用代码查看自动释放 Qt 的内存管理机制 Qt 的对象树 Qt 中所有的控件都是被一颗多叉树管理起来的&#xff0c;这样就是为了方便释放资源的时候方便释放&#xff0c;而我们在编写代码的时候&#xff0c;创建对应的控件&#xff0c;然…

Samtec科普 | 一文入门连接器电镀的QA

【摘要/前言】 像大多数电子元件一样&#xff0c;无数子元件和工艺的质量直接影响到成品的质量和性能。对于PCB级连接器&#xff0c;这些因素包括针脚材料、塑料类型、模制塑料体的质量、尾部的共面性、表面处理&#xff08;电镀&#xff09;的质量、选择正确的连接器电镀、制…

【C++算法模板】数论:欧拉筛,线性查找质数的算法

文章目录 1&#xff09;传统找质数的方法&#xff08;优化筛选次数&#xff09;2&#xff09;欧拉筛 1&#xff09;传统找质数的方法&#xff08;优化筛选次数&#xff09; bool isPrime(int num) {for(int i2;i<sqrt(num)) {if(num%i0)return false;}return true; }如果要…

跑马拉松跑成骨坏死?!马拉松赛事密集,提前了解运动损伤很重要

跑步狂热者右脚疼痛未重视 日积月累右脚终于“撑”不住了 近日&#xff0c;徐大爷来院就诊说自己半年前右脚莫名出现酸痛&#xff0c;一直没当回事&#xff0c;结果1个月前跑完步疼痛加重&#xff0c;最近严重到影响日常走路&#xff0c;无奈只能找医生。在医生的详细检查和认真…

基于arduino的ESP32上蓝牙midi音乐设备开发教程

目录 简介 开发环境 开发过程 函数介绍 相关文章 简介 首先看几个视频&#xff0c;大佬们做的东西&#xff0c;都是基于esp32。 自制卡林巴电子琴&#xff0c;可通过蓝牙连接手机库乐队 MIDI Boy【理科生的第一件乐器】_哔哩哔哩_bilibili 【Totoro】模仿“埙”的电子吹…

win11电脑驱动怎么更新,windows11更新驱动

驱动是指计算机里软件的程序,硬件的运作离不开驱动的支持,因为驱动就是使得硬件和电脑系统沟通的桥梁。既然驱动如此重要,那么不装肯定不行,如果有问题,也要及时地修复和更新。最近,有位win11用户,想要了解win11电脑驱动怎么更新?接下来,教程会带来两种更新win11驱动的…

vscode i18n Ally插件配置项

.vscode文件&#xff1a; {"i18n-ally.localesPaths": ["src/lang"], //显示语言&#xff0c; 这里也可以设置显示英文为en,// 如下须要手动配置"i18n-ally.keystyle": "nested", // 翻译路径格式 (翻译后变量格式 nested&#xff1a…

[C++初阶]类和对象(一)

1.面向过程和面向对象的区分 我们之前都是用C语言写的代码,我们知道C语言是一个面向过程的语言,但是现在我们学的时C,我们都知道C是一种面向对象的语言,那么什么叫面向过程?什么叫面向对象呢? 这里我们来举个例子: 比如我们是开饭店的&#xff0c;客人点了一道菜&#xff0c…

开源模型应用落地-LangChain试炼-CPU调用QWen1.5(一)

一、前言 尽管现在的大语言模型已经非常强大&#xff0c;可以解决许多问题&#xff0c;但在处理复杂情况时&#xff0c;仍然需要进行多个步骤或整合不同的流程才能达到最终的目标。然而&#xff0c;现在可以利用langchain来使得模型的应用变得更加直接和简单。 通过langchain框…

什么是T型槽铸铁平板中内应力——河北北重厂家

T型槽铸铁平板中的内应力指的是平板内部受到的内部力&#xff0c;包括拉应力和剪应力。在T型槽铸铁平板使用过程中&#xff0c;由于自身重量、外力加载等原因&#xff0c;会产生内部应力。这些内应力是平板内部各部分之间的相互作用力&#xff0c;使得平板各部分受到不同的拉伸…

C++ 为什么不能在构造函数中调用虚函数

最近在Clion编辑器中看到构造函数中调用虚函数提示&#xff1a; Do not invoke virtual member functions from constructor 这里记录一下为什么不能在构造函数中调用虚函数。 #include <iostream> #include <string>using namespace std;class BaseClass {publi…

大模型时代:普通人该如何获利?

随着科技的飞速发展&#xff0c;我们正处在一个大模型的时代。所谓大模型&#xff0c;就是指那些拥有数十亿、甚至千亿参数的深度学习模型。这些大模型的出现&#xff0c;不仅推动了人工智能技术的进步&#xff0c;也为普通人创造了众多的获利机会。那么&#xff0c;在这个大模…

【Java开发指南 | 第六篇】Java成员变量(实例变量)、 类变量(静态变量)

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 成员变量&#xff08;实例变量&#xff09;类变量&#xff08;静态变量&#xff09;定义方式静态变量的使用场景 成员变量&#xff08;实例变量&#xff09; 成员变量声明在一个类中&#xff0c;但在方法、构造…

GAMS104 现代游戏引擎 2

渲染的难点可以分为一下三部分&#xff1a;如何计算入射光线、如何考虑材质以及如何实现全局光照。 渲染的难点之一在于阴影&#xff0c;或者说是光的可见性。如何做出合适的阴影效果远比想象中要难得多&#xff0c;在实践中往往需要通过大量的技巧才能实现符合人认知的阴影效…

AI数字人对话之RealChar框架源码解读

零.功能介绍 与虚拟角色(非形象)进行文本或语音会话 体验地址:RealChar. 代码库:GitHub - Shaunwei/RealChar: 🎙️🤖Create, Customize and Talk to your AI Character/Companion in Realtime (All in One Codebase!). Have a natural seamless conversation with AI…

3.3 Ax=b 的完全解

一、Ax b 在求解 A x 0 A\boldsymbol x\boldsymbol 0 Ax0 时&#xff0c;我们将其转化成 R x 0 R\boldsymbol x\boldsymbol 0 Rx0&#xff0c;将自由变量赋予特殊值&#xff08;1 或 0&#xff09;&#xff0c;主元变量即可通过回代求出。这个过程中我们没有关注右侧的 …