<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>ArronHC的博客</title><description>Stay hungry, stay foolish</description><link>https://blog.arronhc.cyou</link><item><title>muduo学习笔记</title><link>https://blog.arronhc.cyou/blog/muduo%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/muduo%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</guid><description>学习muduo</description><pubDate>Fri, 19 Dec 2025 19:31:23 GMT</pubDate><content:encoded>&lt;p&gt;为了精进自己的后端开发能力，打算深入陈硕大佬的 muduo 项目，学习网络编程中的Reactor 多线程并发模型，如何处理高并发的情况。&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;动起来！&lt;/h1&gt;
&lt;p&gt;我们在拆解“发动机”之前，不妨先看看这个“发动机”动起来是什么样的：&lt;/p&gt;
&lt;h2&gt;1. 安装依赖&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get update
sudo apt-get install git cmake g++ gcc
# 安装 boost 库
sudo apt-get install libboost-dev libboost-test-dev libboost-program-options-dev libboost-system-dev libboost-filesystem-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 下载并编译&lt;/h2&gt;
&lt;p&gt;陈硕老师提供了编译脚本&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 克隆仓库
git clone https://github.com/chenshuo/muduo.git
cd muduo

# 运行构建脚本
./build.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 看一下效果&lt;/h2&gt;
&lt;p&gt;我们以老师提供的例子之一 &lt;code&gt;Echo Server&lt;/code&gt;（回显服务器，你发什么他回什么）来看一下效果：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;建立服务端：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./echoserver_unittest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;建立客户端：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nc 127.0.0.1 2000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251219195127423.png&quot; alt=&quot;&quot;&gt;
可以看到，这是个回显服务器~&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;解剖&lt;/h1&gt;
&lt;p&gt;下一步，我们要了解这背后的原理，其大致由一下核心部分组成，我们用银行来举例子：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;EventLoop&lt;/strong&gt;：银行的大堂经理，用来唤醒柜台干活，一个银行有且仅有一个大堂经理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poller&lt;/strong&gt;：取号机，客户从这里取号，知道要去哪个柜台处理业务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channel&lt;/strong&gt;：处理具体事务
这里需要说明的是，Channel 本身不处理事务，比方说客户要来存钱，Channel 只负责按下“存钱”按钮，至于按钮控制的是“存钱”还是发射核弹，Channel 一概不知&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TcpConnection&lt;/strong&gt;：这是负责把具体操作绑定到“按钮”上的人，Channel 把状态传过来之后，TcpConnection 只负责把“包裹”拿进来放到 &lt;code&gt;InputBuffer&lt;/code&gt; 中，然后告诉 &lt;code&gt;User&lt;/code&gt; 有你的包裹！放在 &lt;code&gt;InputBuffer&lt;/code&gt; 了，接收时间是 xx！TcpConnection 仍然不知道这个包裹装的是什么，只管接收就好了。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此外，我们还需要了解&lt;strong&gt;主线程、子线程群&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主线程&lt;/strong&gt;：银行总门口，只进行“&lt;strong&gt;迎宾&lt;/strong&gt;”（Accept）操作，把客户领进门后直接分发到子银行&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子线程群&lt;/strong&gt;：负责具体业务的银行，也就是上面我们提到的这些业务&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样，我们就实现了完整的流程。下面，我们看一下每个核心的核心代码：&lt;/p&gt;
&lt;h2&gt;Eventloop&lt;/h2&gt;
&lt;p&gt;这段代码是 muduo 的&lt;strong&gt;绝对核心&lt;/strong&gt;，所有的操作都是在这个死循环中进行的，是子线程群的业务逻辑。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void EventLoop::loop()
{
  assert(!looping_);
  assertInLoopThread();
  looping_ = true;
  quit_ = false;  // FIXME: what if someone calls quit() before loop() ?
  LOG_TRACE &amp;#x3C;&amp;#x3C; &quot;EventLoop &quot; &amp;#x3C;&amp;#x3C; this &amp;#x3C;&amp;#x3C; &quot; start looping&quot;;

  while (!quit_)
  {
    activeChannels_.clear();
    pollReturnTime_ = poller_-&gt;poll(kPollTimeMs, &amp;#x26;activeChannels_);
    ++iteration_;
    if (Logger::logLevel() &amp;#x3C;= Logger::TRACE)
    {
      printActiveChannels();
    }
    // TODO sort channel by priority
    eventHandling_ = true;
    for (Channel* channel : activeChannels_)
    {
      currentActiveChannel_ = channel;
      currentActiveChannel_-&gt;handleEvent(pollReturnTime_);
    }
    currentActiveChannel_ = NULL;
    eventHandling_ = false;
    doPendingFunctors();
  }

  LOG_TRACE &amp;#x3C;&amp;#x3C; &quot;EventLoop &quot; &amp;#x3C;&amp;#x3C; this &amp;#x3C;&amp;#x3C; &quot; stop looping&quot;;
  looping_ = false;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们来拆解这段绝对核心：&lt;/p&gt;
&lt;h3&gt;1. 阻塞与等待（The wait）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;pollReturnTime_ = poller_-&gt;poll(kPollTimeMs, &amp;#x26;activeChannels_);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Eventloop 问：在最近 &lt;code&gt;kPollTimeMs&lt;/code&gt; 这段时间，有哪些窗口有事儿要做？&lt;/li&gt;
&lt;li&gt;假如没有事件，这个线程就会挂起，直到有“客户”再进行处理，而挂起的时候不占用 CPU&lt;/li&gt;
&lt;li&gt;假如有事件，那么就会把需要进行业务的 Channels 收集到 &lt;code&gt;activeChannels_&lt;/code&gt; 中，再进行接下来的操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 处理网络事件（The Action）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;for (Channel* channel : activeChannels_)
    {
      currentActiveChannel_ = channel;
      currentActiveChannel_-&gt;handleEvent(pollReturnTime_);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其实很简单吧！一下就能看懂，遍历每个有事件的窗口，每个窗口“按一下对应的按钮”就可以了&lt;/p&gt;
&lt;h3&gt;3. 处理“任务队列”（The Pending Tasks）&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;pending: 代办的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;doPendingFunctors();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Eventpoll&lt;/strong&gt;在等待客户（休眠状态），但是这个时候另外一个线程想让这个这个线程干点事情，如何调用&lt;strong&gt;Eventpoll&lt;/strong&gt;？就用这个函数：
外面的线程把任务塞到一个队列里，然后把&lt;strong&gt;Eventpoll&lt;/strong&gt;叫醒，&lt;strong&gt;Eventpoll&lt;/strong&gt;醒来之后，处理完网络事件，走到这里，就会把队列里的任务执行一遍&lt;/p&gt;
&lt;h1&gt;Channel&lt;/h1&gt;
&lt;p&gt;Channel 只用考虑这个业务的状态就可以了，而不用管这个业务是什么，这个具体业务由 &lt;strong&gt;TcpConnection&lt;/strong&gt; 负责
而这些状态是 Linux 中写死的东西，不会增不会减：
&lt;code&gt;POLLIN&lt;/code&gt;（可读）、&lt;code&gt;POLLOUT&lt;/code&gt;（可写）、&lt;code&gt;POLLHUP&lt;/code&gt;（挂断）、&lt;code&gt;POLLERR&lt;/code&gt;（错误）&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void Channel::handleEventWithGuard(Timestamp receiveTime)
{
  eventHandling_ = true;
  LOG_TRACE &amp;#x3C;&amp;#x3C; reventsToString();
  if ((revents_ &amp;#x26; POLLHUP) &amp;#x26;&amp;#x26; !(revents_ &amp;#x26; POLLIN))
  {
    if (logHup_)
    {
      LOG_WARN &amp;#x3C;&amp;#x3C; &quot;fd = &quot; &amp;#x3C;&amp;#x3C; fd_ &amp;#x3C;&amp;#x3C; &quot; Channel::handle_event() POLLHUP&quot;;
    }
    if (closeCallback_) closeCallback_();
  }

  if (revents_ &amp;#x26; POLLNVAL)
  {
    LOG_WARN &amp;#x3C;&amp;#x3C; &quot;fd = &quot; &amp;#x3C;&amp;#x3C; fd_ &amp;#x3C;&amp;#x3C; &quot; Channel::handle_event() POLLNVAL&quot;;
  }

  if (revents_ &amp;#x26; (POLLERR | POLLNVAL))
  {
    if (errorCallback_) errorCallback_();
  }
  if (revents_ &amp;#x26; (POLLIN | POLLPRI | POLLRDHUP))
  {
    if (readCallback_) readCallback_(receiveTime);
  }
  if (revents_ &amp;#x26; POLLOUT)
  {
    if (writeCallback_) writeCallback_();
  }
  eventHandling_ = false;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Channel 要干的事情很无脑，就是个&lt;strong&gt;分类&lt;/strong&gt;的活，在接到 &lt;strong&gt;Poller&lt;/strong&gt;递过来的events 之后，去按对应的按钮就行了，至于具体的逻辑，我们留给 &lt;strong&gt;TcpConnection&lt;/strong&gt;考虑&lt;/p&gt;
&lt;h2&gt;TcpConnection&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;TcpConnection::TcpConnection(EventLoop* loop,
                             const string&amp;#x26; nameArg,
                             int sockfd,
                             const InetAddress&amp;#x26; localAddr,
                             const InetAddress&amp;#x26; peerAddr)
  : loop_(CHECK_NOTNULL(loop)),
    name_(nameArg),
    state_(kConnecting),
    reading_(true),
    socket_(new Socket(sockfd)),
    channel_(new Channel(loop, sockfd)),
    localAddr_(localAddr),
    peerAddr_(peerAddr),
    highWaterMark_(64*1024*1024)
{
  channel_-&gt;setReadCallback(
      std::bind(&amp;#x26;TcpConnection::handleRead, this, _1));
  channel_-&gt;setWriteCallback(
      std::bind(&amp;#x26;TcpConnection::handleWrite, this));
  channel_-&gt;setCloseCallback(
      std::bind(&amp;#x26;TcpConnection::handleClose, this));
  channel_-&gt;setErrorCallback(
      std::bind(&amp;#x26;TcpConnection::handleError, this));
  LOG_DEBUG &amp;#x3C;&amp;#x3C; &quot;TcpConnection::ctor[&quot; &amp;#x3C;&amp;#x3C;  name_ &amp;#x3C;&amp;#x3C; &quot;] at &quot; &amp;#x3C;&amp;#x3C; this
            &amp;#x3C;&amp;#x3C; &quot; fd=&quot; &amp;#x3C;&amp;#x3C; sockfd;
  socket_-&gt;setKeepAlive(true);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们拿出其中一条来看看是怎么干活的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;channel_-&gt;setReadCallback(
      std::bind(&amp;#x26;TcpConnection::handleRead, this, _1));
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bind&lt;/code&gt; 函数是用来写“委托书”的&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handleRead&lt;/code&gt; 委托 &lt;code&gt;handleRead&lt;/code&gt; 来干活！&lt;/li&gt;
&lt;li&gt;&lt;code&gt;this&lt;/code&gt; 我们还需要传一下调用者过去，告诉是哪个对象的 &lt;code&gt;handleRead&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_1&lt;/code&gt; 占位符，用来存储时间戳&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实就是告诉这个 &lt;strong&gt;TcpConnection&lt;/strong&gt; 对象，假如我们有一个 read 请求发过来了，你就调用 &lt;code&gt;handleRead&lt;/code&gt; 函数就好&lt;/p&gt;
&lt;p&gt;接下来看看 &lt;code&gt;handleRead&lt;/code&gt; 干了啥：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void TcpConnection::handleRead(Timestamp receiveTime)
{
  loop_-&gt;assertInLoopThread();
  int savedErrno = 0;
  ssize_t n = inputBuffer_.readFd(channel_-&gt;fd(), &amp;#x26;savedErrno);
  if (n &gt; 0)
  {
    messageCallback_(shared_from_this(), &amp;#x26;inputBuffer_, receiveTime);
  }
  else if (n == 0)
  {
    handleClose();
  }
  else
  {
    errno = savedErrno;
    LOG_SYSERR &amp;#x3C;&amp;#x3C; &quot;TcpConnection::handleRead&quot;;
    handleError();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先，&lt;code&gt;n&lt;/code&gt;：用来看我们这个“包裹”的字节数，当然有 &lt;code&gt;&gt;0&lt;/code&gt;, &lt;code&gt;=0&lt;/code&gt;, &lt;code&gt;&amp;#x3C;0&lt;/code&gt; 三种状态，对应三种不同的处理，我们来看 &lt;code&gt;&gt;0&lt;/code&gt; 的情况：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;if (n &gt; 0)
  {
    messageCallback_(shared_from_this(), &amp;#x26;inputBuffer_, receiveTime);
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shared_from_this&lt;/code&gt;：连接数据本身，假如要回传数据就找这个对象&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inputBuffer_&lt;/code&gt;：存满数据的包裹&lt;/li&gt;
&lt;li&gt;&lt;code&gt;receiveTime&lt;/code&gt;：包裹到达的时间
至此，大致流程就很清晰了：&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注册阶段&lt;/strong&gt;：我在写某一个服务的，把 &lt;code&gt;onMessage&lt;/code&gt; 这个函数（也就是 “callback”的对象）塞给了 &lt;code&gt;TcpConnection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;等待阶段&lt;/strong&gt;：由 &lt;code&gt;Eventloop&lt;/code&gt; 等待着客户&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;触发阶段&lt;/strong&gt;：客户拿到号，找到对应的窗口（channel），Channel 触发信号，&lt;code&gt;TcpConnection&lt;/code&gt; 开始 &lt;code&gt;handleRead&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;回调阶段&lt;/strong&gt;：&lt;code&gt;TcpConnection&lt;/code&gt; “callback”了我留下的 &lt;code&gt;onMessage&lt;/code&gt;，其实就是调用了我留给 &lt;code&gt;TcpConnection&lt;/code&gt; 的代码
可以看得出来，这是一个通用的网络库，我们只要传入不同的 callback，就能实现不同的功能&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;好了，我们基本上搞清楚这个业务逻辑了，最后来看看具体的业务&lt;code&gt;onMessage&lt;/code&gt; 是怎么处理的，我们还是拿 &lt;code&gt;EchoServer&lt;/code&gt; 来举例子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void EchoServer::onMessage(const muduo::net::TcpConnectionPtr&amp;#x26; conn,
                           muduo::net::Buffer* buf,
                           muduo::Timestamp time)
{
  muduo::string msg(buf-&gt;retrieveAllAsString());
  LOG_INFO &amp;#x3C;&amp;#x3C; conn-&gt;name() &amp;#x3C;&amp;#x3C; &quot; echo &quot; &amp;#x3C;&amp;#x3C; msg.size() &amp;#x3C;&amp;#x3C; &quot; bytes, &quot;
           &amp;#x3C;&amp;#x3C; &quot;data received at &quot; &amp;#x3C;&amp;#x3C; time.toString();
  conn-&gt;send(msg);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;干了两件事：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;  muduo::string msg(buf-&gt;retrieveAllAsString());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里，我们把之前“包裹”里面的数据装到 &lt;code&gt;msg&lt;/code&gt; 里面&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;conn-&gt;send(msg);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后这不是个回显服务器吗，直接把数据发送回 Channel 就可以了，这里，Muduo 会启动 Channel 的 &lt;code&gt;POLLOUT&lt;/code&gt; 写事件，等 socket 准备好了，EventLoop 会自动帮你发出去，而用户只管把没发送完的数据放到缓存区，就可以离开了。&lt;/p&gt;
&lt;h1&gt;串起来看看！&lt;/h1&gt;
&lt;p&gt;现在，让我们把时间轴动起来。假设一个客户端发来了 &lt;code&gt;&quot;Hello&quot;&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;第一幕：沉睡与唤醒 (Monitoring)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;静默&lt;/strong&gt;：&lt;code&gt;EventLoop&lt;/code&gt; (经理) 正在休息（阻塞在 &lt;code&gt;Poller::poll&lt;/code&gt;）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;到达&lt;/strong&gt;：网线上传来电信号，操作系统把 &lt;code&gt;&quot;Hello&quot;&lt;/code&gt; 放入了内核缓冲区。&lt;strong&gt;Socket (进货口)&lt;/strong&gt; 的红灯亮了（Readable）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;感知&lt;/strong&gt;：&lt;code&gt;Poller&lt;/code&gt; (监控) 瞬间捕捉到红灯，立马把 &lt;code&gt;EventLoop&lt;/code&gt; 摇醒：“经理！3号门有动静！”&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第二幕：分发与搬运 (Dispatch &amp;#x26; Read)&lt;/h2&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分发&lt;/strong&gt;：&lt;code&gt;EventLoop&lt;/code&gt; 醒来，找到负责3号门的 &lt;code&gt;Channel&lt;/code&gt; (安保)，说：“去处理一下。”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;回调&lt;/strong&gt;：&lt;code&gt;Channel&lt;/code&gt; 看到是“读事件”，立刻按下按钮，调用了 &lt;code&gt;TcpConnection::handleRead&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;搬运&lt;/strong&gt;：&lt;code&gt;TcpConnection&lt;/code&gt; (专员) 跑过来，手里拿着 &lt;code&gt;Buffer&lt;/code&gt; (篮子)，调用 &lt;code&gt;inputBuffer_.readFd()&lt;/code&gt;，把 &lt;code&gt;&quot;Hello&quot;&lt;/code&gt; 从内核搬到了应用的内存里。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第三幕：决策与执行 (Business Logic)&lt;/h2&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;上报&lt;/strong&gt;：货搬完了，&lt;code&gt;TcpConnection&lt;/code&gt; 拿出你一开始给它的“电话号码” (&lt;code&gt;onMessage&lt;/code&gt;)，打了过去。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;处理&lt;/strong&gt;：&lt;strong&gt;你 (EchoServer)&lt;/strong&gt; 接到电话。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;你：“收到啥了？”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;专员：“Hello”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你：“行，给它转成大写，发回去。” (逻辑处理)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;你调用 &lt;code&gt;conn-&gt;send(&quot;HELLO&quot;)&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;第四幕：发送与离场 (Send &amp;#x26; Loop)&lt;/h2&gt;
&lt;ol start=&quot;9&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发送&lt;/strong&gt;：&lt;code&gt;TcpConnection&lt;/code&gt; 尝试直接把 &lt;code&gt;&quot;HELLO&quot;&lt;/code&gt; 塞回 Socket。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果一次塞完了，结束。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果塞不完，就把剩下的暂存在 &lt;code&gt;OutputBuffer&lt;/code&gt;，并让 &lt;code&gt;Channel&lt;/code&gt; 关注“写事件”。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;回归&lt;/strong&gt;：一切处理完毕，&lt;code&gt;EventLoop&lt;/code&gt; 看看表（更新时间），发现没别的事了，又回到 &lt;code&gt;Poller&lt;/code&gt; 那里继续打盹，等待下一个信号。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;从零开始自己造&lt;/h1&gt;
&lt;h2&gt;前置知识 ：&lt;/h2&gt;
&lt;h3&gt;protected/private&lt;/h3&gt;
&lt;p&gt;前者可以被子类访问，后者只能自己访问&lt;/p&gt;
&lt;h3&gt;static&lt;/h3&gt;
&lt;p&gt;用 &lt;code&gt;static&lt;/code&gt; 修饰的函数，表示这个类可以直接调用，不用声明对象，比方说&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;Timestamp::now();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们在类中使用 &lt;code&gt;static const&lt;/code&gt; 来定义常量，比方说&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;static const int kNoneEvent;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;const&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;放在函数定义后面，表示这个函数不会修改类中的任何数据&lt;/li&gt;
&lt;li&gt;放在函数定义前面，修饰这个函数的返回值，表示这个返回值不可修改&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;explicit&lt;/h3&gt;
&lt;p&gt;用于修饰构造函数，&lt;strong&gt;防止编译器进行隐式类型转换&lt;/strong&gt;，防止出现不必要的 bug
为了防止这种情况发生：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Socket {
public:
    Socket(int fd) { /* ... */ } // 注意：这里没加 explicit
};

void checkSocket(Socket s) { /* ... */ }

int main() {
    // 编译器会悄悄把 10 转换成 Socket(10)
    // 这在语义上很奇怪：10 只是个数字，怎么就变成对象了？
    checkSocket(10); 
    
    // 甚至允许这种写法：
    Socket s = 20; 
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;类中的私有变量&lt;/h3&gt;
&lt;p&gt;比方说 &lt;code&gt;Socket&lt;/code&gt; 类中的 &lt;code&gt;sockfd_&lt;/code&gt;，我们习惯性地在私有变量后面加上 &lt;code&gt;_&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;using&lt;/h3&gt;
&lt;p&gt;typedef 的现代化写法
我们可以这样来新建一个类型：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;using ChannelList = std::vector&amp;#x3C;Channel*&gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很好懂，意思是我们建立一个新的类型 &lt;code&gt;ChannelList&lt;/code&gt;，这个类型实际上是一个 &lt;code&gt;Channel&lt;/code&gt; 指针的 &lt;code&gt;vector&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;继承&lt;/h3&gt;
&lt;p&gt;当 B 类是 A 类时，我们使用继承，比方说狗是动物，就可以狗继承动物&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;// 父类（基类）
class Animal {
public:
    void eat() { std::cout &amp;#x3C;&amp;#x3C; &quot;正在进食...&quot; &amp;#x3C;&amp;#x3C; std::endl; }
};

// 子类（派生类）继承自 Animal
class Dog : public Animal {
public:
    void bark() { std::cout &amp;#x3C;&amp;#x3C; &quot;汪汪叫！&quot; &amp;#x3C;&amp;#x3C; std::endl; }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有 public 继承、protected 继承、private 继承，主要改变的就是 public 和 protected 的访问权限&lt;/p&gt;
&lt;p&gt;🌟我们可以在父类中设置虚函数，利用 &lt;code&gt;virtual&lt;/code&gt; 关键字，可以在子类中重构父类的虚函数，使用 &lt;code&gt;final&lt;/code&gt; 关键词可以阻止某个函数被继承&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Shape {
public:
    // 虚函数：允许子类重写
    virtual void draw() { std::cout &amp;#x3C;&amp;#x3C; &quot;画一个图形&quot; &amp;#x3C;&amp;#x3C; std::endl; }
    // 纯虚函数：该类变为“抽象类”，不能实例化，子类必须实现
    virtual void area() = 0; 
    
    // 核心点：虚析构函数（防止内存泄漏）
    virtual ~Shape() {} 
};

class Circle : public Shape {
public:
    // C++11 建议加上 override 明确表示重写
    void draw() override { std::cout &amp;#x3C;&amp;#x3C; &quot;画一个圆&quot; &amp;#x3C;&amp;#x3C; std::endl; }
    void area() override { /* 实现逻辑 */ }
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;static_cast&amp;#x3C;类型&gt;&lt;/h3&gt;
&lt;p&gt;用来进行强制类型准换，用法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;double pi = 3.14159;
int num = static_cast&amp;#x3C;int&gt;(pi); // 去掉小数部分，变为 3
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;接下来我们按照顺序来构建每个类：&lt;/p&gt;
&lt;h2&gt;Noncopyable 类&lt;/h2&gt;
&lt;p&gt;这个类是一个继承类，许多不可复制的类可以直接继承它&lt;/p&gt;
&lt;h3&gt;Noncopyable. h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once
/*
NonCopyable类
所有继承了这个类的类都无法被拷贝
*/
class NonCopyable
{
public:
    //删除拷贝构造函数
    NonCopyable(const NonCopyable&amp;#x26;) = delete;
    //删除复制运算符
    NonCopyable&amp;#x26; operator=(const NonCopyable&amp;#x26;) = delete;

protected:
    NonCopyable() = default;
    ~NonCopyable() = default;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;InetAddress 类&lt;/h2&gt;
&lt;p&gt;这个类的主要作用就是把底层繁琐的网络地址数据&lt;strong&gt;封装&lt;/strong&gt;起来，&lt;strong&gt;屏蔽了复杂的字节序转换和结构体操作&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Linux 底层，网络地址使用结构体来实现的（比方说 &lt;code&gt;sockaddr_in&lt;/code&gt; 用于 ipv4），直接操作的话很麻烦，于是用 &lt;code&gt;InetAddress&lt;/code&gt; 把它封装起来，极大简化了代码量&lt;/p&gt;
&lt;h3&gt;InetAddress.h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once

#include &amp;#x3C;arpa/inet.h&gt;
#include &amp;#x3C;netinet/in.h&gt;
#include &amp;#x3C;string&gt;

class InetAddress
{
public:
    explicit InetAddress(uint16_t port, std::string ip = &quot;127.0.0.1&quot;);

    explicit InetAddress(const struct sockaddr_in&amp;#x26; addr)
        : addr_(addr)
    {}

    void setSockAddr(const struct sockaddr_in&amp;#x26; addr) { addr_ = addr; }
    
    std::string toIp() const;

    std::string toIpPort() const;

    uint16_t toPort() const;

    const struct sockaddr_in* getSockAddr() const { return &amp;#x26;addr_; }

private:
    struct sockaddr_in addr_;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;InetAddress.cc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;InetAddress.h&quot;
#include &amp;#x3C;strings.h&gt;
#include &amp;#x3C;string.h&gt;

InetAddress::InetAddress(uint16_t port, std::string ip)
{
    bzero(&amp;#x26;addr_, sizeof addr_);

    addr_.sin_family = AF_INET;

    addr_.sin_port = htons(port);

    inet_pton(AF_INET, ip.c_str(), &amp;#x26;addr_.sin_addr.s_addr);
}

std::string InetAddress::toIp() const
{
    char buf[64] = {0};
    ::inet_ntop(AF_INET, &amp;#x26;addr_.sin_addr, buf, sizeof buf);
    return buf;
}

std::string InetAddress::toIpPort() const
{
    char buf[64] = {0};
    ::inet_ntop(AF_INET, &amp;#x26;addr_.sin_addr, buf, sizeof buf);
    size_t end = strlen(buf);
    uint16_t port = ntohs(addr_.sin_port);
    sprintf(buf+end, &quot;:%u&quot;, port);
    return buf;
}

uint16_t InetAddress::toPort() const
{
    return ntohs(addr_.sin_port);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里稍微理解一下就行，以后当做底层工具来调用就好了&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;Timestamp 类&lt;/h3&gt;
&lt;p&gt;这个类也是用来封装底层复杂逻辑的类，用处就是获取时间戳&lt;/p&gt;
&lt;h3&gt;Timestamp.h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;string&gt;

class Timestamp 
{
public:
    Timestamp();
    explicit Timestamp(int64_t microSecondsSinceEpoch);
    static Timestamp now();
    std::string toString() const;
    int64_t microSecondsSinceEpoch() const { return microSecondsSinceEpoch_;}

private:
    int64_t microSecondsSinceEpoch_;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Timestamp.cc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;Timestamp.h&quot;
#include &amp;#x3C;time.h&gt;

Timestamp::Timestamp() : microSecondsSinceEpoch_(0) {}

Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
    : microSecondsSinceEpoch_(microSecondsSinceEpoch)
{}

Timestamp Timestamp::now()
{
    return Timestamp(time(NULL));
}

std::string Timestamp::toString() const
{
    char buf[128] = {0};
    time_t seconds = static_cast&amp;#x3C;time_t&gt;(microSecondsSinceEpoch_);
    struct tm *tm_time = localtime(&amp;#x26;seconds);
    snprintf(buf, sizeof(buf), &quot;%4d/%02d/%02d %02d:%02d:%02d&quot;,
             tm_time-&gt;tm_year + 1900,
             tm_time-&gt;tm_mon + 1,
             tm_time-&gt;tm_mday,
             tm_time-&gt;tm_hour,
             tm_time-&gt;tm_min,
             tm_time-&gt;tm_sec);
    return buf;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同样是直接调用就好了，简化代码用的。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🌟Socket 类&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] socket 是什么？
socket（网络连接） 是传输层和应用层之间的桥梁，把复杂的 TCP/IP 协议隐藏在 socket 接口后面。socket 相当于一个&lt;strong&gt;电话机&lt;/strong&gt;，用来接发文件的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 linux 中，“&lt;strong&gt;一切皆文件&lt;/strong&gt;”，socket 也不例外，每个 socket 都和一个 fd（文件描述符，其实就是个 ID） 一一对应&lt;/p&gt;
&lt;p&gt;socket 的作用就是绑定 IP 地址、接发数据包&lt;/p&gt;
&lt;h3&gt;Socket. h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once

#include &quot;NonCopyable.h&quot;

class InetAddress;

class Socket : NonCopyable
{
public:
    explicit Socket(int sockfd)
        : sockfd_(sockfd)
    {}

    ~Socket();

    int fd() const { return sockfd_; }

    void bindAddress(const InetAddress&amp;#x26; localaddr);

    void listen();

    int accept(InetAddress* peeraddr);

    void setReuseAddr(bool on);

private:
    const int sockfd_;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Socket.cc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;Socket.h&quot;
#include &quot;InetAddress.h&quot;
#include &amp;#x3C;unistd.h&gt;
#include &amp;#x3C;sys/types.h&gt;
#include &amp;#x3C;sys/socket.h&gt;
#include &amp;#x3C;netinet/tcp.h&gt;

Socket::~Socket()
{
    ::close(sockfd_);
}

void Socket::bindAddress(const InetAddress&amp;#x26; localaddr)
{
    int ret = ::bind(sockfd_,
                    (const struct sockaddr*)localaddr.getSockAddr(),
                    sizeof(struct sockaddr_in));
    if(ret&amp;#x3C;0)
    {
        perror(&quot;bind sockfd error!&quot;);
    }
}

void Socket::listen()
{
    int ret = ::listen(sockfd_, 1024);
    if(ret&amp;#x3C;0)
    {
        perror(&quot;listen sockfd error!&quot;);
    }
}

int Socket::accept(InetAddress* peeraddr)
{
    struct sockaddr_in addr;
    socklen_t len = sizeof addr;

    int connfd = ::accept(sockfd_, (struct sockaddr*)&amp;#x26;addr, &amp;#x26;len);

    if(connfd &gt;= 0)
    {
        peeraddr-&gt;setSockAddr(addr);
    }

    return connfd;
}

void Socket::setReuseAddr(bool on)
{
    int optval = on ? 1 : 0;
    ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &amp;#x26;optval, sizeof optval);
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这里我们就能看出 RAII（Resource Acquisition Is Initialization，&lt;strong&gt;资源获取即初始化&lt;/strong&gt;）原则，&lt;strong&gt;将资源的生命周期与对象作绑定&lt;/strong&gt;，对象死了资源自动释放，构造函数中请求资源，析构函数中释放资源（C++在函数结束、抛出异常等情况保证能调用析构函数），防止了内存泄漏，不用再手动管理资源了&lt;/p&gt;
&lt;p&gt;这里面讲几个点：&lt;/p&gt;
&lt;h3&gt;Ip 地址&lt;/h3&gt;
&lt;p&gt;在 linux 内核中，用 &lt;code&gt;strcut sockaddr&lt;/code&gt; 来存储地址，这是个通用接口，长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct sockaddr {
    unsigned short sa_family; // 地址族 (如 AF_INET 代表 IPv4)
    char sa_data[14];         // 包含 IP 和端口的原始数据（难以直接读写）
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外还有个便于程序员读写的、专为 IPv4 设计的 &lt;code&gt;strcut sockaddr_in&lt;/code&gt;，长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;struct sockaddr_in {
    short            sin_family;   // 地址族 (固定为 AF_INET)
    unsigned short   sin_port;     // 16位端口号 (必须是网络字节序 htons)
    struct in_addr   sin_addr;     // 32位 IP 地址结构
    char             sin_zero[8];  // 填充位，为了和 sockaddr 长度保持一致
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么我们在设置 IP 的时候就这样：在 &lt;code&gt;sockaddr_in&lt;/code&gt; 中设好参数，在强制类型转换成 &lt;code&gt;sockaddr&lt;/code&gt; 供程序底层使用，也就有了这个代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void Socket::bindAddress(const InetAddress&amp;#x26; localaddr)
{
    int ret = ::bind(sockfd_,
                    (const struct sockaddr*)localaddr.getSockAddr(),
                    sizeof(struct sockaddr_in));
    if(ret&amp;#x3C;0)
    {
        perror(&quot;bind sockfd error!&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;🌟Channel 类&lt;/h2&gt;
&lt;p&gt;每个 socket 都对应一个 Channel，Channel 选择监听哪些信息，但是 &lt;strong&gt;Channel 本身不负责监听&lt;/strong&gt;，他只是列个表，然后由 Poller 监听 socket 是否传输了表中的内容。
Poller 监听到之后，再汇报给 EventLoop，EventLoop 拿到 activeChannels 清单，对照着清单一一唤醒Channels
Channel 被唤醒之后，再去叫别的类去进行接下来的操作
我们不直接拿 Channel 去监听 socket，因为这样的话，一万个 scoket 就对应一万个 Channel，每个 Channel 始终监视着 socket，会占用极大的内存
我们用One Loop Per Thread（一个线程一个循环）原则，能让一个线程同时处理成千上万的链接&lt;/p&gt;
&lt;h3&gt;Channel.h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once

#include &quot;NonCopyable.h&quot;
#include &amp;#x3C;functional&gt;
#include &amp;#x3C;memory&gt;

class EventLoop;

class Channel : NonCopyable
{
public:
    using EventCallback = std::function&amp;#x3C;void()&gt;;

    Channel(EventLoop* loop, int fd);
    ~Channel();

    void handleEvent();

    void setReadCallback(EventCallback cb) { readCallback_ = std::move(cb); }
    void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); }
    void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); }
    void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); }


    int index() { return index_; }
    void set_index(int idx) { index_ = idx; }

    int fd() const { return fd_; }
    //获取我要关注的事件都有哪些
    int events() const { return events_; }

    void set_revents(int revt) { revents_ = revt; }

    bool isNoneEvents() const { return events_ == kNoneEvent; }

    //自己更新，然后再让EventLoop去更新Poller
    void enableReading() { events_ |= kReadEvent; update(); }
    void disableReading() { events_ &amp;#x26;= ~kReadEvent; update(); }
    void enableWriting() { events_ |= kWriteEvent; update(); }
    void disableWriting() { events_ &amp;#x26;= ~kWriteEvent; update(); }
    void disableAll() { events_ = kNoneEvent; update(); }

    //你开启监听/写入了吗
    bool isWriting() const { return events_ &amp;#x26; kWriteEvent; }
    bool isReading() const { return events_ &amp;#x26; kReadEvent; }

private:
    void update();

    static const int kNoneEvent;
    static const int kReadEvent;
    static const int kWriteEvent;

    EventLoop* loop_;
    const int fd_;

    int events_;
    int revents_;

    //表示channel在Poller中的状态
    int index_;

    EventCallback readCallback_;
    EventCallback writeCallback_;
    EventCallback errorCallback_;
    EventCallback closeCallback_;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Channel.cc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;Channel.h&quot;
#include &quot;EventLoop.h&quot;
#include &amp;#x3C;sys/epoll.h&gt;
#include &amp;#x3C;iostream&gt;

const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI;
const int Channel::kWriteEvent = EPOLLOUT;

Channel::Channel(EventLoop* loop, int fd)
    : loop_(loop),
      fd_(fd),
      events_(0),
      revents_(0),
      index_(-1)
{}

Channel::~Channel()
{

}

void Channel::update()
{
    loop_-&gt;updateChannel(this);

    std::cout&amp;#x3C;&amp;#x3C;&quot;Channel updated: fd=&quot;&amp;#x3C;&amp;#x3C; fd_ &amp;#x3C;&amp;#x3C; &quot; events=&quot;&amp;#x3C;&amp;#x3C;events_&amp;#x3C;&amp;#x3C;std::endl;

}

void Channel::handleEvent()
{
    std::cout &amp;#x3C;&amp;#x3C; &quot;Channel::handleEvent revents: &quot; &amp;#x3C;&amp;#x3C; revents_ &amp;#x3C;&amp;#x3C; std::endl;

    if((revents_ &amp;#x26; EPOLLHUP) &amp;#x26;&amp;#x26; !(revents_ &amp;#x26; EPOLLIN)) 
    {
        if(closeCallback_) closeCallback_();
    }

    if(revents_ &amp;#x26; EPOLLERR) 
    {
        if(errorCallback_) errorCallback_();
    }

    if(revents_ &amp;#x26; (EPOLLIN | EPOLLPRI)) 
    {
        if(readCallback_) readCallback_();
    }

    if(revents_ &amp;#x26; EPOLLOUT)
    {
        if(writeCallback_) writeCallback_();
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看出，&lt;strong&gt;Channel 的作用就是设置自己需要响应的事件、响应事件&lt;/strong&gt;
讲几个点：&lt;/p&gt;
&lt;h3&gt;kReadEvent&lt;/h3&gt;
&lt;p&gt;其中 k 是 const 的缩写，代码中是这么定义的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;const int Channel::kNoneEvent = 0;

const int Channel::kReadEvent = EPOLLIN | EPOLLPRI;

const int Channel::kWriteEvent = EPOLLOUT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这其实相当于 01 开关，通过位运算来控制我们监听的事件，这么用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;	void enableReading() { events_ |= kReadEvent; update(); }

    void disableReading() { events &amp;#x26;= ~kReadEvent; update(); }

    void enableWriting() { events_ |= kWriteEvent; update(); }

    void disableWriting() { events_ &amp;#x26;= ~kWriteEvent; update(); }

    void disableAll() { events_ = kNoneEvent; update(); }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样来添加或者删减 Channel 监听的类型&lt;/p&gt;
&lt;h3&gt;update ()&lt;/h3&gt;
&lt;p&gt;其实只需要明确一点就明白了：
Channel 的 events 是自己想要监听的内容，但是需要把这个内容发给 Poller，由他来监听，自然需要 update 一下了&lt;/p&gt;
&lt;h2&gt;🌟Poller 类、EpollPoller 类&lt;/h2&gt;
&lt;p&gt;Poller 提供接口，EpollPoller 提供底层逻辑&lt;/p&gt;
&lt;h3&gt;用处：&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;轮询（epoll_wait），直到有活动 channel，抓取到 &lt;code&gt;activeChannels&lt;/code&gt; 列表，把当前事件传给每个 channel 的 &lt;code&gt;revents_&lt;/code&gt;；唤醒 EventLoop，EventLoop 去让列表中的 channels 去 &lt;code&gt;handleEvent()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;维护 epoll 红黑树，其中维护的是 channel 的 fd、指针、愿望清单&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Poller.h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once

#include &quot;NonCopyable.h&quot;
#include &amp;#x3C;vector&gt;
#include &amp;#x3C;unordered_map&gt;

class Channel;
class EventLoop;

class Poller : NonCopyable
{
public:
    using ChannelList = std::vector&amp;#x3C;Channel*&gt;;

    Poller(EventLoop* loop);
    virtual ~Poller();

    //不停轮询，寻找active的channel
    virtual void poll(int timeoutMs, ChannelList* activeChannel) = 0;

    //更新channel的愿望清单
    virtual void updateChannel(Channel* channel) = 0;

    //移除channel
    virtual void removeChannel(Channel* channel) = 0;

    //有没有这个Channel
    bool hasChannel(Channel* channel) const;

    static Poller* newDefaultPoller(EventLoop* loop);

protected:
    //维护Poller的监听channel都有哪些
    //channel的fd和channel本身指针作对应
    using ChannelMap = std::unordered_map&amp;#x3C;int, Channel*&gt;;
    ChannelMap channels_;//由Poller负责

private:
    EventLoop* loop_;
};
/*
Poller:
1、维护监听的channel列表
2、轮询，看哪些channel active了
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Poller.cc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;Poller.h&quot;
#include &quot;EpollPoller.h&quot;
#include &quot;Channel.h&quot;

Poller::Poller(EventLoop* loop)
    : loop_(loop)
{}

Poller::~Poller() = default;

bool Poller::hasChannel(Channel* channel) const
{
    auto it = channels_.find(channel-&gt;fd());
    return it != channels_.end() &amp;#x26;&amp;#x26; it-&gt;second == channel;
}

Poller* Poller::newDefaultPoller(EventLoop* loop)
{
    return new EpollPoller(loop);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;EpollPoller.h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once

#include &quot;Poller.h&quot;
#include &amp;#x3C;vector&gt;
#include &amp;#x3C;sys/epoll.h&gt;

class EpollPoller : public Poller
{
public:
    EpollPoller(EventLoop* loop);
    ~EpollPoller() override;

    void poll(int timeoutMs, ChannelList* activeChannels) override;
    void updateChannel(Channel* channel) override;
    void removeChannel(Channel* channel) override;

private:
    //填activeChannels
    void fillActiveChannel(int numEvents, ChannelList* activeChannels) const;
    //具体的在epoll中更新channel愿望清单的方式
    void update(int operation, Channel* channel);

    int epollfd_;

    //即将发生事件的列表
    using EventList = std::vector&amp;#x3C;struct epoll_event&gt;;
    EventList events_;

    //Channel在Poller中的状态
    static const int kNew;//没有在红黑树注册过
    static const int kAdded;//注册了，且在工作
    static const int kDeleted;//注册了，但是没有在工作
};

/*
EpollPoller：
使用epoll进行活跃channel的维护
1、维护channel在epoll中的状态
2、找到活跃的channel
*/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;EpollPoller.cc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;EpollPoller.h&quot;
#include &quot;Channel.h&quot;
#include &amp;#x3C;iostream&gt;
#include &amp;#x3C;unistd.h&gt;
#include &amp;#x3C;string.h&gt;

const int EpollPoller::kNew = -1;
const int EpollPoller::kAdded = 1;
const int EpollPoller::kDeleted = 2;

/*
epoll是socket管理器，用的是红黑树
每一个节点存了一份详细的监听清单，对应一个socket
节点中有：
1、节点信息，包含父子节点指针以及颜色(struct rb_node rbn)
2、socket的fd以及对应的指针(struct epoll_filefd ffd)
3、关注的事件，存用户关注的事件(struct epoll_event event)
4、就绪链表指针，socket活跃时就把这个指针挂载到就绪链表中(struct list_head rdllink)
5、等待队列项，这里面设置了回调函数，用来触发挂载到活跃链表的操作 (wait_queue_t pwq)
6、容器引用，指向节点所属的epoll实例 (struct eventpoll *ep)
*/

EpollPoller::EpollPoller(EventLoop* loop)
    : Poller(loop),
      epollfd_(::epoll_create1(EPOLL_CLOEXEC)),
      events_(16)
{
    if(epollfd_ &amp;#x3C; 0)
    {
        perror(&quot;epoll_create1 error&quot;);
    }
}


EpollPoller::~EpollPoller()
{
    ::close(epollfd_);
}

//轮询的逻辑
void EpollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
    //把epoll中的活跃socket填到events_，再返回活跃数量
    //events_是using EventList = std::vector&amp;#x3C;struct epoll_event&gt;类型的
    int numEvents = ::epoll_wait(epollfd_,
                                 &amp;#x26;*events_.begin(),
                                 static_cast&amp;#x3C;int&gt;(events_.size()),
                                 timeoutMs);
    
    int saveErrno = errno;

    if(numEvents&gt;0)
    {
        fillActiveChannel(numEvents, activeChannels);

        //已经把这个events_列表填满了
        if(numEvents == static_cast&amp;#x3C;int&gt;(events_.size()))
        {
            events_.resize(events_.size()*2);
        }
    }
    else if(numEvents==0)
    {
        std::cout &amp;#x3C;&amp;#x3C; &quot;nothing happened&quot; &amp;#x3C;&amp;#x3C; std::endl;
    }
    else 
    {
        //假如不是interrupt（自行暂停），那就是真出错了
        if(saveErrno != EINTR)
        {
            perror(&quot;EpollPoller::poll Error!&quot;);
        }
    }
}

//填充activeChannels，这些channels活跃了，要处理事件了
void EpollPoller::fillActiveChannel(int numEvents, ChannelList* activeChannels) const
{
    for(int i=0;i&amp;#x3C;numEvents;i++)
    {
        Channel* channel = static_cast&amp;#x3C;Channel*&gt;(events_[i].data.ptr);

        channel-&gt;set_revents(events_[i].events);

        activeChannels-&gt;push_back(channel);
    }
}

//把channel的清单更新同步到内核中（channel活跃了）
void EpollPoller::updateChannel(Channel* channel)
{
    //看看状态是啥，怎么更新
    const int index = channel-&gt;index();

    if(index==kNew||index==kDeleted)//epoll里没有这个channel，加进来
    {
        if(index==kNew)
        {
            int fd = channel-&gt;fd();
            channels_[fd] = channel;
        }

        channel-&gt;set_index(kAdded);
        update(EPOLL_CTL_ADD, channel);
    }
    else//kAdded，有了，更新一下清单
    {
        if(channel-&gt;isNoneEvents())
        {
            update(EPOLL_CTL_DEL,channel);
            channel-&gt;set_index(kDeleted);
        }
        else 
        {
            update(EPOLL_CTL_MOD, channel);
        }
    }
}

void EpollPoller::removeChannel(Channel* channel)
{
    int fd = channel-&gt;fd();
    channels_.erase(fd);

    int index = channel-&gt;index();
    if(index == kAdded)
    {
        update(EPOLL_CTL_DEL, channel);
    }
    //Channel被移除了，直接注销了
    channel-&gt;set_index(kNew);
}

//具体怎么更新？
void EpollPoller::update(int operation, Channel* channel)
{
    //组装事件包
    struct epoll_event event;
    bzero(&amp;#x26;event, sizeof event);

    event.events = channel-&gt;events();
    event.data.ptr = channel;

    int fd = channel-&gt;fd();
    //把愿望清单发给内核
    if (::epoll_ctl(epollfd_, operation, fd, &amp;#x26;event) &amp;#x3C; 0)
    {
        perror(&quot;epoll_ctl error&quot;);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;🌟EventLoop 类&lt;/h2&gt;
&lt;p&gt;这个类主要起到命令别的类的作用，操控者程序的主循环。被 Poller 唤醒，再让 Channel 处理事件；Channel 让 EventLoop 更新 Channel 自己的愿望清单，EventLoop 让 Poller 更新愿望清单&lt;/p&gt;
&lt;h3&gt;EventLoop.h&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#pragma once

#include &amp;#x3C;vector&gt;
#include &amp;#x3C;atomic&gt;
#include &amp;#x3C;memory&gt;

#include &quot;NonCopyable.h&quot;
#include &quot;Timestamp.h&quot;
#include &quot;CurrentThread.h&quot;

class Channel;
class Poller;

class EventLoop : NonCopyable
{
public:
    using ChannelList = std::vector&amp;#x3C;Channel*&gt;;

    EventLoop();
    ~EventLoop();

    void loop();

    void quit();

    void updateChannel(Channel* channel);
    void removeChannel(Channel* channel);
    bool hasChannel(Channel* channel);

    bool isInLoopThread() const;

private:
    void abortNotInLoopthread();

    std::atomic_bool looping_;
    std::atomic_bool quit_;

    const pid_t threadId_;

    std::unique_ptr&amp;#x3C;Poller&gt; poller_;
    ChannelList activeChannels_;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;EventLoop.cc&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;EventLoop.h&quot;
#include &quot;Poller.h&quot;
#include &quot;Channel.h&quot;
#include &quot;CurrentThread.h&quot;
#include &amp;#x3C;iostream&gt;

__thread EventLoop* t_loopInThisThread = nullptr;

const int kPollTimeMs = 100000;
EventLoop::EventLoop()
    : looping_(false),
     quit_(false),
     threadId_(CurrentThread::tid()),
     poller_(Poller::newDefaultPoller(this))
{
    std::cout &amp;#x3C;&amp;#x3C; &quot;EventLoop created &quot; &amp;#x3C;&amp;#x3C; this &amp;#x3C;&amp;#x3C; &quot; in thread &quot; &amp;#x3C;&amp;#x3C; threadId_ &amp;#x3C;&amp;#x3C; std::endl;

    if(t_loopInThisThread)
    {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;Another EventLoop &quot; &amp;#x3C;&amp;#x3C; t_loopInThisThread 
                  &amp;#x3C;&amp;#x3C; &quot; exists in this thread &quot; &amp;#x3C;&amp;#x3C; threadId_ &amp;#x3C;&amp;#x3C; std::endl;
        exit(1); // 严禁一个线程搞两个 Loop
    }
    else 
    {
        t_loopInThisThread = this;
    }
}

EventLoop::~EventLoop()
{
    looping_ = false;
    t_loopInThisThread = nullptr;
}

void EventLoop::loop()
{
    looping_ = true;
    quit_ = false;

    std::cout &amp;#x3C;&amp;#x3C; &quot;EventLoop &quot; &amp;#x3C;&amp;#x3C; this &amp;#x3C;&amp;#x3C; &quot; start looping&quot; &amp;#x3C;&amp;#x3C; std::endl;

    while(!quit_)
    {
        activeChannels_.clear();

        poller_-&gt;poll(kPollTimeMs,&amp;#x26;activeChannels_);

        for(Channel* channel : activeChannels_)
        {
            channel-&gt;handleEvent();
        }
    }

    std::cout &amp;#x3C;&amp;#x3C; &quot;EventLoop &quot; &amp;#x3C;&amp;#x3C; this &amp;#x3C;&amp;#x3C; &quot; stop looping&quot; &amp;#x3C;&amp;#x3C; std::endl;
    looping_=false;
}

void EventLoop::quit()
{
    quit_=true;
}

void EventLoop::updateChannel(Channel* channel)
{
    if(isInLoopThread())
    {
        poller_-&gt;updateChannel(channel);
    }
    else 
    {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;EventLoop::updateChannel called from different thread!&quot; &amp;#x3C;&amp;#x3C; std::endl;
    }
}

void EventLoop::removeChannel(Channel* channel)
{
    if(isInLoopThread())
    {
        poller_-&gt;removeChannel(channel);
    }
}

bool EventLoop::hasChannel(Channel* channel)
{
    return poller_-&gt;hasChannel(channel);
}

bool EventLoop::isInLoopThread() const
{
    return threadId_ == CurrentThread::tid();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;整体流程图：&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251224222903743.png&quot; alt=&quot;6c9ad7879c4aad951aaa6a2a7541f2c0.jpg&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>记录倒腾服务器的全过程</title><link>https://blog.arronhc.cyou/blog/%E8%AE%B0%E5%BD%95%E5%80%92%E8%85%BE%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E5%85%A8%E8%BF%87%E7%A8%8B</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E8%AE%B0%E5%BD%95%E5%80%92%E8%85%BE%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E5%85%A8%E8%BF%87%E7%A8%8B</guid><description>这个小伙买了个服务器，这是他大脑发生的变化</description><pubDate>Mon, 08 Dec 2025 10:20:49 GMT</pubDate><content:encoded>&lt;p&gt;这几天看到阿里云的新人优惠活动，38 块钱一年！果断下单！
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/82ed6d7e8567cbb016b8644ca71f30a6.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;但是 ：&lt;/h2&gt;
&lt;p&gt;在倒腾服务器之前，我得先解决我的一个痛点——博客图片
我的博客是放在 github 上，通过 vercel 部署的，其中的包含的图片也一并上传到了 github，这必然会拖慢我的 git 速度，也会导致很多人看我的博客加载不出图片来！&lt;/p&gt;
&lt;p&gt;我用阿里云 OSS（对象存储）完美解决了这个痛点，OSS 采用键值对的方式存储数据，1 次存 N 次取，而且量大管饱还便宜，一年一杯奶茶钱！因此，OSS 非常适合用来&lt;strong&gt;做图床&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] 提醒！
由于通过外网访问 OSS 资源需要产生流量资费（0.15 元/G），所以建议先在账户中留一些钱，按照 pay as you go 来扣费&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208110115660.png&quot; alt=&quot;&quot;&gt;
当然由此会衍生出一个痛点：每次都要执行上传到图床-复制链接-写Markdown，步骤变得繁琐了，为了解决这个问题，我找到了 &lt;a href=&quot;https://github.com/Kuingsmile/PicList&quot;&gt;picList&lt;/a&gt; 这个开源软件
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208110712120.png&quot; alt=&quot;image.png&quot;&gt;
再配合插件，就可以无感使用图床来放图片啦！&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;另外关于博客是放在 vercel 部署还是放在服务器，我最后的选择还是 vercel，vercel 能免费部署、自带全球 DNS 加速，SSL 证书自动续期，而且极其稳定，能保证至少在未来几年内能保证访问&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;好了，接下来就正式开始倒腾服务器啦：
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208133758762.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;这次试试 Termius 这个终端如何&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208133853379.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;界面确实要清爽漂亮很多，目前用下来没什么大问题&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;上次建站使用的是宝塔，这次用 1Panel 看看效果如何&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208135612361.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;首先安装一个 alist 试试水：
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208130144936.png&quot; alt=&quot;image.png&quot;&gt;
在这过程中就遇到不少问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;镜像无法拉取：&lt;/strong&gt;
需要去阿里云的官网找加速镜像地址进行加速&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;无法通过端口访问：&lt;/strong&gt;
需要开放阿里云的防火墙、1Panel 的防火墙，这两者相当于保安大门和家里的大门，缺一不可，能有效避免被爬虫被扫&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;明明安装了 docker，为什么在命令行中还是提示找不到？&lt;/strong&gt;
因为 1Panel 给这些终端的命名逻辑是 1Panel-alist-XXXX，所以需要先去找一下名字，再做对应指令，或者直接使用 1Panel 中的终端会更方便一些&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;明明在 cf 设置了 DNS 解析，为什么还是不能通过域名+端口访问？&lt;/strong&gt;
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208130537045.png&quot; alt=&quot;image.png&quot;&gt;
问题就出在 cf 的代理上，小橙云只允许几个标准端口通过（比如 443、80），遇到 5244 自然通过不了，我们可以通过关闭代理、或者使用反向代理来解决，这里我选择用&lt;strong&gt;反向代理&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] DNS？反向代理？
DNS 解析用来告诉浏览器，遇到这个域名要去对应的 IP 找！
浏览器拿着这个域名找到服务器，服务器看了看反向代理，发现是找 alist.arronhc.cyou 的，于是就把内部的 127.0.0.1:5244 发送给浏览器&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208133523811.png&quot; alt=&quot;image.png&quot;&gt;
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/20251208133707066.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!danger] 注意注意！
解析到阿里云的云服务器的域名需要先备案！过程比较繁琐，要留足时间... 在验证过程中需要取消 DNS 解析，建议备案后再设置解析&lt;/p&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>【转载】华为手机安装GMS最终教程</title><link>https://blog.arronhc.cyou/blog/%E5%8D%8E%E4%B8%BA%E6%89%8B%E6%9C%BA%E5%AE%89%E8%A3%85gms%E6%9C%80%E7%BB%88%E6%95%99%E7%A8%8B</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E5%8D%8E%E4%B8%BA%E6%89%8B%E6%9C%BA%E5%AE%89%E8%A3%85gms%E6%9C%80%E7%BB%88%E6%95%99%E7%A8%8B</guid><description>文章转自https://toalan.com/archives/71/</description><pubDate>Thu, 20 Nov 2025 13:26:16 GMT</pubDate><content:encoded>&lt;h1&gt;华为设备 microG 安装教程（鸿蒙 4.2 / EMUI 13.2+）&lt;/h1&gt;
&lt;hr&gt;
&lt;p&gt;因为华为手机现在很难安装原生谷歌框架，&lt;strong&gt;microG 这一替代方案&lt;/strong&gt; 已逐渐被大众接受。&lt;br&gt;
本教程会长期更新，&lt;strong&gt;同步 microG 的开发进度&lt;/strong&gt;，持续优化教程内容。&lt;/p&gt;
&lt;p&gt;本教程主要解决：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;华为手机无法使用海外应用&lt;/li&gt;
&lt;li&gt;无法安装谷歌全家桶&lt;/li&gt;
&lt;li&gt;无法使用依赖 GMS 服务的软件&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;🔍 microG 是什么？&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;microG&lt;/strong&gt; 是 Google 专有库的自由、开源实现，&lt;br&gt;
由德国开发者 Marvi…（原文略）开发。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本教程支持 &lt;strong&gt;纯手机操作&lt;/strong&gt;，无需电脑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;✅ 支持设备范围&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;鸿蒙 4.2 版本&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;EMUI 13.2 版本及以上&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⚠️ 只要您的设备符合上述条件，&lt;br&gt;
&lt;strong&gt;无论平板或手机都可以使用本方法安装&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;❌ &lt;strong&gt;非华为手机或平板&lt;/strong&gt;：&lt;br&gt;
一律不要尝试本教程，不适用。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;🚨 声明与版权&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;本教程 &lt;strong&gt;全网首发&lt;/strong&gt; 于 &lt;strong&gt;Alan&apos;s Blog&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;任何形式的 &lt;strong&gt;转载 / 扩散 / 复制 / 下载&lt;/strong&gt;&lt;br&gt;
必须注明原文链接，以尊重原创作者&lt;/li&gt;
&lt;li&gt;本教程内容 &lt;strong&gt;完全免费&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;严禁任何形式的 &lt;strong&gt;收费行为&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;请广大网友提高警惕，谨防上当受骗&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;📊 功能对比表（更新时间：2025-07-25）&lt;/h2&gt;
&lt;p&gt;| 功能 / 方法                    | 原生谷歌三件套                           | microG + Play 商店 / 第三方商店             |
| ----------------------------- | ---------------------------------------- | ------------------------------------------- |
| 解决认证弹窗                  | 🟢                                      | 🟢                                          |
| 正常使用 GMS 应用             | 🟢                                      | 🟢                                          |
| 应用分身                      | 🟢                                      | 🟢                                          |
| ChatGPT                       | 🟢                                      | 🟡 暂时不稳定，但能用                       |
| 更新系统                      | 🟢 能用就别更新*                        | 🟢                                          |
| 稳定程度                      | 🟢                                      | 🟢                                          |
| Google Play 商店              | 🟢                                      | 🟢 Play 商店 / 第三方商店                   |
| Google Play 服务              | 🟢 原生服务                              | 🟢 microG 代替*                             |
| 玩谷歌游戏                    | 🟢 少部分不支持                          | 🟢 部分不支持                               |
| 消息后台推送                  | 🟢 FCM 推送*（支持国内直连）             | 🟢 GCM 云端推送（不支持国内直连）           |
| 应用内购                      | 🟢 4.3 不支持*                           | 🟢 部分不支持、4.3 不支持*                  |
| 继续添加谷歌账号              | ⛔️                                      | 🟢                                          |
| 登录提示验证                  | 🟢                                      | 🟢                                          |
| 谷歌 Passkey 密码填充         | 🟢                                      | ⛔️                                         |
| 附近分享                      | 🟢                                      | ⛔️                                         |
| Google location services      | ⛔️                                      | 🟢                                          |
| 设备定位                      | ⛔️                                      | 🟢 部分应用不精准，地图精准                 |
| 谷歌企业服务                  | ⛔️                                      | ⛔️                                         |
| Google Pay                    | ⛔️                                      | ⛔️                                         |
| 支持型号？                    | 仅手机（国行版）且 4.2 及以下           | 手机、平板且系统 4.2 及以上                 |&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注：🟢 全支持、🟡 不稳定、⛔️ 不支持、❔ 未测试&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;ℹ️ 重要说明与补充&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;microG 并不是所有原生功能都支持，原生框架体验整体优于 microG。&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;如果你的平板 / 手机是 2019 年之前，且是老麒麟芯片，可以直接安装谷歌三件套，获得原生 GMS 体验。&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;关于消息推送（非常重要）&lt;/h3&gt;
&lt;p&gt;消息推送是一个非常重要的功能，例如使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;WeChat&lt;/li&gt;
&lt;li&gt;WhatsApp&lt;/li&gt;
&lt;li&gt;Telegram&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果应用不在后台运行，就不会有消息通知。&lt;br&gt;
锁屏或清理后台后，很容易错过消息。&lt;/p&gt;
&lt;p&gt;为了解决这个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;谷歌通过 &lt;strong&gt;GMS&lt;/strong&gt; 在后台常驻&lt;/li&gt;
&lt;li&gt;华为通过 &lt;strong&gt;HMS&lt;/strong&gt; 在后台常驻&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们作为中间服务器：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;收到消息&lt;/li&gt;
&lt;li&gt;再拉起对应应用&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样既不会错过重要信息，又能降低电量消耗。&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;📥 下载链接&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;不知道怎么下载？继续往下看安装教程，会有提示。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://1drv.ms/f/s!Atp0UM8FnZB_j5lJQJHiCvmHZIXlkw?e=OyKNKu&quot;&gt;MicroG相关安装包以及备份文件&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h1&gt;✅ 安装教程总览&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;无论之前装过本教程还是其他教程，&lt;br&gt;
&lt;strong&gt;准备工作和清理一定要做好。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;注意：本教程需要电脑辅助&lt;/strong&gt;&lt;br&gt;
如果没有电脑，无法使用最新版 Google 服务。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tips：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用分身记得关闭&lt;/li&gt;
&lt;li&gt;微信建议用电脑登录备份，后续可迁移回来&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;0. 安装前准备&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;如果你不能访问谷歌，请先解决网络问题，否则不要继续。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在系统中做以下设置：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关闭悬浮导航&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 系统和更新 → 系统导航方式 → 更多 → 悬浮导航：关闭&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关闭纯净模式增强防护&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 系统和更新 → 纯净模式：关闭增强防护&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关闭隐私空间&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 隐私 → 隐私空间：右上角关闭隐私空间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关闭应用分身&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 应用和服务 → 应用分身：关闭&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;关闭省电模式&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 电池 → 省电模式：关闭&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;以上功能在安装完成后都可以重新开启。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;1. 清除谷歌应用数据 / 卸载相关组件&lt;/h2&gt;
&lt;p&gt;需要清除 / 卸载以下应用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Play 商店&lt;/li&gt;
&lt;li&gt;Google 通讯录同步&lt;/li&gt;
&lt;li&gt;Google Play 服务&lt;/li&gt;
&lt;li&gt;Google 服务框架&lt;/li&gt;
&lt;li&gt;Google 账户管理程序&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.google.android.gms.policy_sidecar_aps&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SharedLibrary&lt;/strong&gt;（就是这个名字，其他不用管）&lt;/li&gt;
&lt;li&gt;谷歌服务助手&lt;/li&gt;
&lt;li&gt;microG（只要名字中包含 microg 的都卸载）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;操作路径：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;方式一：系统应用管理&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 应用和服务 → 应用管理&lt;/li&gt;
&lt;li&gt;右上角「三个点」→ 勾选「显示系统程序」&lt;/li&gt;
&lt;li&gt;搜索上述应用 → 清除数据或卸载（为所有用户卸载）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;方式二：通过应用市场&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开「应用市场」→ 我的 → 卸载管理&lt;/li&gt;
&lt;li&gt;搜索以上软件 → 有则卸载（为所有用户卸载）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;2. 激活谷歌服务助手（仅手机操作，平板忽略）&lt;/h2&gt;
&lt;h3&gt;2.1 下载并导入备份&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;下载下方的 backup 压缩包（解压后放到 Huawei 目录）：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://1drv.ms/u/c/7f909d05cf5074da/Edp0UM8FnZAggH-W2gMAAAABfZsVnlluru3YgfOe_iI3iA?e=G7dx9L&quot;&gt;backup文件&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;解压后，将 &lt;strong&gt;Backup 文件夹&lt;/strong&gt; 复制到：&lt;/p&gt;
&lt;p&gt;内部存储根目录 / Huawei / Backup&lt;/p&gt;
&lt;p&gt;建议先删除原来的 &lt;code&gt;Backup&lt;/code&gt; 文件夹，再复制新的进去。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;⚠️ 常见错误：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;复制错位置（必须是 &lt;strong&gt;内部存储根目录&lt;/strong&gt; 下的 &lt;code&gt;Huawei&lt;/code&gt; 文件夹）&lt;/li&gt;
&lt;li&gt;若没有 &lt;code&gt;Huawei&lt;/code&gt; 文件夹，可手动创建一个&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2.2 修改系统日期到 2019 年&lt;/h3&gt;
&lt;p&gt;路径：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 系统和更新 → 日期和时间&lt;/li&gt;
&lt;li&gt;关闭「自动设置」&lt;/li&gt;
&lt;li&gt;手动将日期改为 &lt;strong&gt;2019 年&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2.3 从内部存储恢复备份&lt;/h3&gt;
&lt;p&gt;路径：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;设置 → 系统和更新 → 备份和恢复&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果没有此项：
&lt;ul&gt;
&lt;li&gt;打开应用市场 → 搜索「备份」→ 下载并安装官方备份应用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;打开「备份与恢复」：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;点击右上角四个点 → 选择「从内部存储恢复」&lt;/li&gt;
&lt;li&gt;选择 &lt;code&gt;toalan.com&lt;/code&gt; 备份&lt;/li&gt;
&lt;li&gt;输入密码：&lt;code&gt;a12345678&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;执行恢复&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;如果没有「从内部存储恢复」选项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以先点「从外部存储」，把权限全部打开&lt;/li&gt;
&lt;li&gt;返回再进入「备份和恢复」，检查是否出现&lt;/li&gt;
&lt;li&gt;仍然没有，则说明 &lt;code&gt;Backup&lt;/code&gt; 目录导入不成功（请回到 0 步检查路径）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;2.4 激活谷歌服务助手&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;恢复完成后，先把时间调回正常：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;设置 → 系统和更新 → 日期和时间 → 打开「自动设置」&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在桌面找到 &lt;strong&gt;谷歌服务助手&lt;/strong&gt; 并打开：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;点击「激活」&lt;/li&gt;
&lt;li&gt;授权所有需要的权限&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;⚠️ 提示说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若提示：&lt;strong&gt;当前设备不支持&lt;/strong&gt; → 基本无解，可以放弃&lt;/li&gt;
&lt;li&gt;若提示：&lt;strong&gt;网络连接异常&lt;/strong&gt; → 表示仍有希望
&lt;ul&gt;
&lt;li&gt;需要通过 ADB 降级「备份和恢复」版本，具体教程参考原站说明&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;激活后 &lt;strong&gt;不需要&lt;/strong&gt; 点击「开始下载」，直接返回桌面即可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;3. 安装下载好的软件（核心三件）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;平板和手机安装包不完全相同，请仔细看文件名。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为减少维护，&lt;strong&gt;文章中不再写版本号后缀&lt;/strong&gt;，&lt;br&gt;
实际文件名中会带有版本后缀，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文章写：&lt;code&gt;com.google.android.gms&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;文件实际为：&lt;code&gt;com.google.android.gms-250932018-hw.apk&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;必装的 3 个 APK（缺一不可）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Google Play Store（Play 商店）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;com. google. android. gms（microG 服务）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;com. google. android. gsf-8（microG 框架）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;如果系统版本过低或为非国行手机，&lt;br&gt;
通常会在文件夹中提供其他版本可供选择。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;4. 授予 microG 权限&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;打开：设置 → 应用和服务 → 应用管理&lt;/li&gt;
&lt;li&gt;搜索：&lt;strong&gt;microG 服务&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;打开应用 → 权限&lt;/li&gt;
&lt;li&gt;将所有权限全部允许，确保全部打勾 ✅&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;特别注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定位权限必须为：&lt;strong&gt;始终允许&lt;/strong&gt;&lt;br&gt;
否则无法实现精准定位&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;5. 检测 microG 是否支持&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;打开 microG 设置界面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 microG 中点击右上角齿轮 ⚙️&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;点击最上方的「自我检查」&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;检查每一项是否全部 ✔️&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;🚨 如果全部通过，可以继续下一步。&lt;br&gt;
若有未通过项，请根据提示逐项排查。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2&gt;7. 登录谷歌账号&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;返回 microG 设置&lt;/li&gt;
&lt;li&gt;点击「Google 账号」&lt;/li&gt;
&lt;li&gt;选择「添加 Google 账号」&lt;/li&gt;
&lt;li&gt;登录自己的谷歌账号&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2&gt;8. 使用 Play 商店或第三方商店&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;手机：&lt;/strong&gt;&lt;br&gt;
直接使用 &lt;strong&gt;Play 商店&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;若打开报错，可先清理后台，再重新进入应用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;平板：&lt;/strong&gt;&lt;br&gt;
推荐使用第三方应用商店，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Aurora 最新版&lt;/li&gt;
&lt;li&gt;uptodown&lt;/li&gt;
&lt;li&gt;GBox 虚拟 Play 商店（可在其中的 Play 商店下载原生应用）&lt;/li&gt;
&lt;li&gt;APKPure&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;9. ChatGPT 怎么用？&lt;/h2&gt;
&lt;p&gt;建议通过 &lt;strong&gt;Aurora&lt;/strong&gt; 或 &lt;strong&gt;uptodown&lt;/strong&gt; 获取 ChatGPT。&lt;/p&gt;
&lt;h3&gt;步骤一：安装 Chrome 浏览器&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;安装 Chrome（一般会装两次，第一次是依赖库）&lt;/li&gt;
&lt;li&gt;打开设置 → 搜索「默认应用」&lt;/li&gt;
&lt;li&gt;将默认浏览器设置为：&lt;strong&gt;Chrome&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;教程提供了最新版 Chrome 安装包，在「其他 APP」文件夹里。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;步骤二：安装 ChatGPT&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;在第三方应用商店中搜索并安装 &lt;strong&gt;ChatGPT&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用 ChatGPT 时的一些小知识&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;香港节点通常无法使用 ChatGPT（DNS 解锁除外）&lt;/li&gt;
&lt;li&gt;即使使用其他地区节点，也不一定能解锁 APP：
&lt;ul&gt;
&lt;li&gt;有可能只解锁了网页版，并不支持 APP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;请尽量确保：
&lt;ul&gt;
&lt;li&gt;使用 &lt;strong&gt;非香港地区&lt;/strong&gt; 节点&lt;/li&gt;
&lt;li&gt;该节点是 &lt;strong&gt;完整解锁&lt;/strong&gt; 的&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;打开 ChatGPT 提示错误：
&lt;ul&gt;
&lt;li&gt;可以点击「注销」，再重新登录&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;加载时间较长：
&lt;ul&gt;
&lt;li&gt;属正常现象，耐心等待即可&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;无法进入：
&lt;ul&gt;
&lt;li&gt;可多试几次，或更换节点&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;🎉 教程结束 &amp;#x26; 致谢&lt;/h2&gt;
&lt;p&gt;🎉🎉🎉 至此，本教程全部步骤结束！&lt;/p&gt;
&lt;p&gt;研究技术和整理图文教程非常耗费时间与精力——&lt;br&gt;
如果本教程对你有所帮助：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;欢迎真金白银支持一下（原文底部通常会有打赏按钮）&lt;/li&gt;
&lt;li&gt;白嫖也完全没问题，但推荐你：
&lt;ul&gt;
&lt;li&gt;评论分享你的 &lt;strong&gt;成功时间&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;标注 &lt;strong&gt;设备型号 + 系统版本&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你有任何优化建议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;非常欢迎在评论区提出&lt;/li&gt;
&lt;li&gt;你的每一次互动，都可以帮助后来者获得更好的教程体验&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1&gt;✨ 常见问题（FAQ）&lt;/h1&gt;
&lt;h3&gt;1. 可以使用 Google 钱包和 Android Auto / Auto Car 吗？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;❌ 不可以。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3&gt;2. 手机上可以用 Play 商店替代第三方商店吗？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;✅ 可以。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;具体做法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;卸载 &lt;strong&gt;microG Companion&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;安装 &lt;strong&gt;Google Play 商店&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Play 商店版本可以从类似 &lt;code&gt;Google Play Store 44.1.17&lt;/code&gt; 的资源中获取&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;如果 Play 商店打开闪退：
&lt;ul&gt;
&lt;li&gt;很可能是 &lt;strong&gt;谷歌服务助手没有激活&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;请重新检查激活步骤（参考手机相关教程）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;之后你可以：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装 &lt;strong&gt;Chrome&lt;/strong&gt; 和 &lt;strong&gt;ChatGPT&lt;/strong&gt; 即可正常使用&lt;/li&gt;
&lt;li&gt;并可尝试把 Play 商店设置为「手动管理」，&lt;br&gt;
因为 ChatGPT 依赖于 Play 商店的后台运行&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>高统期末复习</title><link>https://blog.arronhc.cyou/blog/%E9%AB%98%E7%BB%9F%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E9%AB%98%E7%BB%9F%E6%9C%9F%E6%9C%AB%E5%A4%8D%E4%B9%A0</guid><description>高统期末复习</description><pubDate>Wed, 19 Nov 2025 14:59:14 GMT</pubDate><content:encoded>&lt;h2&gt;一些常见术语的解释&lt;/h2&gt;
&lt;h3&gt;方差&lt;/h3&gt;
&lt;p&gt;$$
E(\sum_{i=1}^n(X-\mu)^2)
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于根据训练数据集训练出的模型，其方差越大，对于训练集的微小变化就越敏感，泛化能力越弱&lt;/li&gt;
&lt;li&gt;方差体现的是所设定模型自身的性质（对训练集数据的敏感性）&lt;/li&gt;
&lt;li&gt;自由度越高的模型其方差越高，偏差越小&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;残差平方和 RSS (Residual Sum of Squares)&lt;/h3&gt;
&lt;p&gt;$$
\sum_{i=1}^n (y_i - \hat{y}_i)^2
$$
用来衡量误差的指标&lt;/p&gt;
&lt;h3&gt;最小二乘法&lt;/h3&gt;
&lt;p&gt;通过&lt;strong&gt;寻找最小的 RSS&lt;/strong&gt;，寻找拟合度最高的线&lt;/p&gt;
&lt;h3&gt;均方误差 MSE (mean squared error)&lt;/h3&gt;
&lt;p&gt;对于数据集 Tr：
$$
MSE_{T_r}=Ave_{i∈{T_r}}[y_i-\hat{f}(x_i)]=\frac{RSS}{n}
$$
是回归中最常用的评价准则&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]
为什么不用 RSS？因为数据集越大，RSS 就会越大，不能跨数据集，相比之下 MSE 更直观、不用考虑数据集大小&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;方差 vs 偏差&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;偏差是不可消除的，体现的是“假设”的模型与“真实”模型之间的本质差异&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;方差越大，说明我们拟合的模型对输入数据越敏感，细微的变化就会得到不同的拟合输出结果&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模型太简单 → &lt;strong&gt;高偏差、低方差&lt;/strong&gt; → 拟合不够&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模型太复杂 → &lt;strong&gt;低偏差、高方差&lt;/strong&gt; → 过拟合
所以&lt;strong&gt;平衡偏差/误差&lt;/strong&gt;就是一个重要问题&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;标准误差 SE (Standard Error)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;估计量的标准差，表示这个估计在&lt;strong&gt;重复抽样时会晃动的多厉害&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;实际上也就是重复抽样得到的每次结果的标准差&lt;/li&gt;
&lt;li&gt;标准误差越小，说明估计的“抖动”越小，越精准
$$
SE=\frac{s}{\sqrt{n}}
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;残差标准误差 RSE (Residual Standard Error)&lt;/h3&gt;
&lt;p&gt;在线性回归中：
$$
y=X\beta+\epsilon,\epsilon\sim
N(0,\sigma^2)
$$
RSE 是对误差项标准差 $\sigma$ 的估计：
$$
RSE=\hat{\sigma}=\sqrt{\frac{RSS}{df}}=\sqrt{\frac{RSS}{n-p-1}}
$$
其中 p+1 是参数个数（含截距）
RSE 统计的是误差项的标准差 $\sigma$，体现的是&lt;strong&gt;模型的整体噪声大小&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;标准差（SD）反应的是&lt;strong&gt;数据的波动程度&lt;/strong&gt;，是方差开根号的结果&lt;/li&gt;
&lt;li&gt;标准误差（SE）是在数据中随机抽 n 个样本，n 个样本的估计量的标准差，反应的是&lt;strong&gt;估计的稳定程度&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;残差是估计值与实际值的差值&lt;/li&gt;
&lt;li&gt;残差标准误差（RSE）是对误差项 $\epsilon$ 标准差（SD）的估计
总结：
&lt;ul&gt;
&lt;li&gt;标准差(SD)：数据的乱度&lt;/li&gt;
&lt;li&gt;标准误差(SE)：估计的晃动程度&lt;/li&gt;
&lt;li&gt;残差标准误差(RSE)：回归模型噪声的典型大小&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;t 统计量&lt;/h3&gt;
&lt;p&gt;$$
t=\frac{\bar{X}-\mu_0}{SE}
$$
比较样本均值与总体均值的差异&lt;/p&gt;
&lt;h3&gt;t 值（判断H0 假设，绝对值越大，假设越不成立）&lt;/h3&gt;
&lt;p&gt;线性回归中，我们要判断某一个系数究竟是不是 0（有没有关系），常采用零假设 $H_0$，而 t 值就是用来判断这个假设检验的。
公式：
$$
t_j=\frac{\hat{\beta}&lt;em&gt;j-\beta&lt;/em&gt;{j,0}}{SE(\hat{\beta}_j)}
$$
其中：
$\hat{\beta}&lt;em&gt;j$ 表示估计值
$\beta&lt;/em&gt;{j, 0}$ 表示在 $H_0$ 假设下的系数值（通常为 0）&lt;/p&gt;
&lt;p&gt;那么 t 值的含义实际上就是看看&lt;strong&gt;我这个估计值距离零假设的位置有几个标准误差的距离&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]
绝对值越大，认为系数越不可能为 0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;p 值&lt;/h3&gt;
&lt;p&gt;假如某个系数真的按 $H_0$ 这样等于 0，那么出现这组数据的概率有多大？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;p 很小：$H_0$ 不对&lt;/li&gt;
&lt;li&gt;p 不小：不拒绝 $H_0$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;F 统计量&lt;/h3&gt;
&lt;p&gt;$H_0$ 变成所有自变量都没用，用来&lt;strong&gt;一次性检验整体是否显著&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]
p 值越小，证明参数越显著，系数越不可能为 0&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;t 分布&lt;/h3&gt;
&lt;p&gt;假如零假设真的成立，那么 t 统计量应当服从 t 分布
我们可以用他计算 p 值，查临界值，做显著性检验，画置信区间&lt;/p&gt;
&lt;h3&gt;$R^2$ 统计量&lt;/h3&gt;
&lt;p&gt;公式：
$$
R^2=\frac{TSS-RSS}{TSS}=1-\frac{RSS}{TSS}
$$
其中总平方和 TSS（Total Sum of Squares） :
$$
TSS=\Sigma(y_i-\bar{y})
$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TSS 表示的是原数据的变异性&lt;/li&gt;
&lt;li&gt;RSS 表示的是回归后仍无法解释的变异性
那么无法解释的变异性占比越小，证明回归的越好&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]
$R^2$ 越接近 1，回归效果越好&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;调整 $R^2$&lt;/h3&gt;
&lt;p&gt;$R^2$ 的缺点就是，假如我们引入一个无关变量，就算这个变量对响应变量无关，但是由于自由度增加，相应的残差平方和 RSS 会降低，$R^2$ 会增大。&lt;/p&gt;
&lt;p&gt;因此引入调整 $R^2$
$$
R_{adj}^2=1-(\frac{(1-R^2)\cdot(n-1)}{n-p-1})
$$&lt;/p&gt;
&lt;h3&gt;95%置信区间&lt;/h3&gt;
&lt;p&gt;公式：
$$
估计值±2\cdot SE(估计值)
$$
表示在这个区间内，有 95%的可能性包含真实值
区间越窄，参数估得越准&lt;/p&gt;
&lt;h3&gt;预测区间&lt;/h3&gt;
&lt;p&gt;未来一个具体的 y（新样本）会落在哪里
预测区间一定比置信区间宽，因为要考虑两个因素：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;回归模型的准确性&lt;/li&gt;
&lt;li&gt;未来 y 的变异性（存在 $\epsilon$ 的波动）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;线性回归的假设&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可加性&lt;/strong&gt;：预测变量 $x_j$ 的变化对相应变量 $Y$ 产生的影响&lt;strong&gt;与其他预测变量的取值无关&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线性&lt;/strong&gt;：无论 $x_j$ 取什么值，$x_j$ 变化一个单位引起相应变量的变化&lt;strong&gt;是恒定的&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;误差项不相关&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;学生化残差&lt;/h3&gt;
&lt;p&gt;公式：
$$
\frac{e_i}{RSE}
$$&lt;/p&gt;
&lt;h3&gt;离群点&lt;/h3&gt;
&lt;p&gt;离群点是指对于给定的预测值 $x_i$ 来说，$y_i$ 远离模型预测点的点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;离群点通常对最小二乘拟合几乎没有影响&lt;/li&gt;
&lt;li&gt;会影响 RSE、置信区间、p 值、$R^2$
判断离群点：&lt;/li&gt;
&lt;li&gt;绘制学生化残差图，学生化残差大于 3 的观测点可能是离群点&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]
x 正常，y 很怪&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;高杠杆点&lt;/h3&gt;
&lt;p&gt;通过计算&lt;strong&gt;杠杆统计量&lt;/strong&gt;，来量化杠杆作用
公式：
$$
h_i=\frac{1}{n}+\frac{(x_i-\bar{x})^2}{\Sigma_{i&apos;=1}^n(x_{i&apos;}-\bar{x})^2}
$$&lt;/p&gt;
&lt;p&gt;$h_i$ 的取值在 1/n 到 1 之间，所有观测的平均杠杆值总是等于 $\frac{p+1}{n}$，假如平均值远大于这个值，怀疑这个点具有较高的杠杆作用&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]
x 脱离大部队&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;误差项自相关&lt;/h3&gt;
&lt;p&gt;常出现在时序序列数据，也就是说昨天的误差会影响今天的误差，出现&lt;strong&gt;跟踪&lt;/strong&gt;现象&lt;/p&gt;
&lt;h3&gt;共线性&lt;/h3&gt;
&lt;p&gt;预测变量之间高度相关
解决方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从回归中剔除一个问题变量&lt;/li&gt;
&lt;li&gt;把共线性的变量们组成一个单一的预测变量&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;K 最近邻法（KNN）&lt;/h3&gt;
&lt;p&gt;现在有一堆带标签的数据 ($x_i$，$y_i$)，现在要预测一个新样本 $x_{new}$ 的值 $y_{new}$，KNN 这么做：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在训练样本中，找出离 $x_{new}$ 最近的 K 个点&lt;/li&gt;
&lt;li&gt;把它们的 y 值取出来，做平均或者加权平均，这个结果作为 $y_{new}$ 的值&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;如何选择 K？&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;小的 K 值提供了更灵活的拟合，导致低偏差和高方差&lt;/li&gt;
&lt;li&gt;大的 K 值自由度更低，比较稳定，方差较小，但可能会隐藏 f（X）的部分结构导致偏差&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!warning]
存在维度灾难的问题，即在高维环境中找不到邻居
所以我们优先选择线性回归，即使在低维问题，因为这虽然会损失精度，但是模型简单，p 值清晰，可解释性强&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;逻辑斯蒂回归&lt;/h3&gt;
&lt;p&gt;$$
p(X)=\frac{e^{\beta_0+\beta_1X}}{1+e^{\beta_0+\beta_1X}}
$$
其范围在 0~1 之间，将分类问题转换为了概率建模的回归问题
我们采用&lt;strong&gt;最大似然方法估计系数&lt;/strong&gt;，即求可以最大化某个式子的 $\beta$ 值&lt;/p&gt;
&lt;p&gt;变换形式：
$$
log{\frac{p(X)}{1-p(X)}}=\beta_0+\beta_1X
$$
转换为了&lt;strong&gt;分对数&lt;/strong&gt;变换下关于 X 的一个线性模型&lt;/p&gt;
&lt;h3&gt;z 统计量&lt;/h3&gt;
&lt;p&gt;衡量某个观测值与总体均值之间的偏差
$$
z=\frac{X-\mu}{\sigma}
$$&lt;/p&gt;
&lt;h2&gt;稍后再看&lt;/h2&gt;
&lt;h3&gt;贝叶斯分类器&lt;/h3&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>想看的电影们</title><link>https://blog.arronhc.cyou/blog/%E6%83%B3%E7%9C%8B%E7%9A%84%E7%94%B5%E5%BD%B1</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E6%83%B3%E7%9C%8B%E7%9A%84%E7%94%B5%E5%BD%B1</guid><description>观影计划</description><pubDate>Mon, 03 Nov 2025 22:28:13 GMT</pubDate><content:encoded>&lt;h2&gt;芳华&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/file-20251103223040688.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;星际穿越&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/file-20251113104524788.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;盗梦空间&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/file-20251113104555456.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>滑动窗口</title><link>https://blog.arronhc.cyou/blog/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3</guid><description>滑动窗口，一种致力于解决子串问题的算法</description><pubDate>Fri, 31 Oct 2025 14:34:01 GMT</pubDate><content:encoded>&lt;h2&gt;什么是滑动窗口？&lt;/h2&gt;
&lt;p&gt;滑动窗口，致力于解决数组、字符串中的连续子串相关的问题，通过&lt;strong&gt;左右指针&lt;/strong&gt;框住一个窗口，维护窗口中的相关数值，&lt;strong&gt;每个元素最多被访问两遍&lt;/strong&gt;，因此时间复杂度能从 $O(n^2)$ 降低到 $O(n)$&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;使用情形&lt;/h2&gt;
&lt;p&gt;滑动窗口，常用来在 O(N) 时间复杂度上处理子串相关的问题，常见有一下三种情形：&lt;/p&gt;
&lt;h3&gt;1. 固定长度的窗口&lt;/h3&gt;
&lt;p&gt;首先框住第一个固定长度的窗口，再通过同时移动左右指针来移动窗口，每次移动会导致最左侧元素被删除、最右侧元素被加入，然后再根据变化维护窗口内的数值&lt;/p&gt;
&lt;p&gt;以求固定长度区间最大值为例：
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/%E5%9B%BA%E5%AE%9A%E9%95%BF%E5%BA%A6.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3&gt;2 . 可变长度的窗口&lt;/h3&gt;
&lt;p&gt;与上一个不同的是，我们可以扩大窗口，那么我们就可以单独右移右指针，以扩大窗口，举个例子来看吧：&lt;/p&gt;
&lt;p&gt;比方说我们想在一个字符串中，找到最大长度的一个子串，保证这个子串中的字符不重复，那么就要用到滑动窗口了：
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/%E9%95%BF%E5%BA%A6%E4%B8%8D%E5%9B%BA%E5%AE%9A.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;例题&lt;/h2&gt;
&lt;p&gt;咱们以&lt;a href=&quot;https://leetcode.cn/problems/longest-substring-without-repeating-characters/?envType=study-plan-v2&amp;#x26;envId=top-100-liked&quot;&gt;无重复字符的最长子串&lt;/a&gt;来举例子：&lt;/p&gt;
&lt;p&gt;这道题实际上也就是上面所说的第二种情况，可以以 $O(s.size())$ 的时间复杂度来解决问题&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Solution {

public:

    int lengthOfLongestSubstring(string s) {

        int n=s.size(),ans=0;

        unordered_set&amp;#x3C;char&gt; Set;

        int r=-1;

        for(int i=0;i&amp;#x3C;n;i++) {

            if(i!=0) {

                Set.erase(s[i-1]);

            }

            while(r&amp;#x3C;n-1&amp;#x26;&amp;#x26;!Set.count(s[r+1])) {

                Set.insert(s[r+1]);

                r++;

            }

            ans=max(ans,r-i+1);

        }

        return ans;

    }

};
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>每日一题：盛最多水的容器</title><link>https://blog.arronhc.cyou/blog/%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8</guid><description>每日一题：盛最多水的容器</description><pubDate>Thu, 25 Sep 2025 11:09:46 GMT</pubDate><content:encoded>&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;给定一个长度为 &lt;code&gt;n&lt;/code&gt; 的整数数组 &lt;code&gt;height&lt;/code&gt; 。有 &lt;code&gt;n&lt;/code&gt; 条垂线，第 &lt;code&gt;i&lt;/code&gt; 条线的两个端点是 &lt;code&gt;(i, 0)&lt;/code&gt; 和 &lt;code&gt;(i, height[i])&lt;/code&gt; 。&lt;/p&gt;
&lt;p&gt;找出其中的两条线，使得它们与 &lt;code&gt;x&lt;/code&gt; 轴共同构成的容器可以容纳最多的水。&lt;/p&gt;
&lt;p&gt;返回容器可以储存的最大水量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;说明：&lt;/strong&gt; 你不能倾斜容器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1：&lt;/strong&gt;
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8-1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt;[1,8,6,2,5,4,8,3,7]
&lt;strong&gt;输出：&lt;/strong&gt; 49
&lt;strong&gt;解释：&lt;/strong&gt; 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下，容器能够容纳水（表示为蓝色部分）的最大值为 49。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 2：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt; height = [1,1]
&lt;strong&gt;输出：&lt;/strong&gt; 1&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;n == height.length&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2 &amp;#x3C;= n &amp;#x3C;= 105&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0 &amp;#x3C;= height[i] &amp;#x3C;= 10^4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;存水量取决于两根柱子中短的那一根，那么我们现在要做的就是来挑柱子&lt;/p&gt;
&lt;p&gt;首先，我们当然希望 x 轴的距离越大越好，所以我们很自然地想到用&lt;strong&gt;双指针&lt;/strong&gt;来解决&lt;/p&gt;
&lt;p&gt;把两个指针分别放在最左端和最右端，我们一次挪动一步来找柱子，那么我们挪哪边的柱子呢？&lt;/p&gt;
&lt;p&gt;假如我们移动其中较高的那根柱子：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;距离缩短了&lt;/li&gt;
&lt;li&gt;高度只低不高
假如我们移动其中较矮的那根柱子：&lt;/li&gt;
&lt;li&gt;距离缩短了&lt;/li&gt;
&lt;li&gt;高度只高不低
所以我们已经得到了策略：移动较矮的那根柱子
&lt;img src=&quot;https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/%E7%9B%9B%E6%9C%80%E5%A4%9A%E6%B0%B4%E7%9A%84%E5%AE%B9%E5%99%A8-2.png&quot; alt=&quot;&quot;&gt;
每次移动较矮的那一根，找到容量最大的即可&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;完整代码&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Solution {

public:

    int maxArea(vector&amp;#x3C;int&gt;&amp;#x26; height) {

        int l = 0, r = height.size()-1;

        int max_volume=-1;

        while(l&amp;#x3C;r) {

            int h = min(height[l],height[r]);

            int vol = h*(r-l);

            max_volume = max(max_volume,vol);

            if(height[l]&amp;#x3C;height[r]) l++;

            else r--;

        }

        return max_volume;

    }

};
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>每日一题：分数转小数</title><link>https://blog.arronhc.cyou/blog/%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98%E5%88%86%E6%95%B0%E8%BD%AC%E5%B0%8F%E6%95%B0</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98%E5%88%86%E6%95%B0%E8%BD%AC%E5%B0%8F%E6%95%B0</guid><description>每日一题：分数转小数</description><pubDate>Thu, 25 Sep 2025 10:53:39 GMT</pubDate><content:encoded>&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;给定两个整数，分别表示分数的分子 &lt;code&gt;numerator&lt;/code&gt; 和分母 &lt;code&gt;denominator&lt;/code&gt;，以 &lt;strong&gt;字符串形式返回小数&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;如果小数部分为循环小数，则将循环的部分括在括号内。&lt;/p&gt;
&lt;p&gt;如果存在多个答案，只需返回 &lt;strong&gt;任意一个&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;对于所有给定的输入，&lt;strong&gt;保证&lt;/strong&gt; 答案字符串的长度小于 $10^4$ 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt; numerator = 1, denominator = 2
&lt;strong&gt;输出：&lt;/strong&gt; &quot;0.5&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 2：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt; numerator = 2, denominator = 1
&lt;strong&gt;输出：&lt;/strong&gt; &quot;2&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 3：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt; numerator = 4, denominator = 333
&lt;strong&gt;输出：&lt;/strong&gt; &quot;0.(012)&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-2^31 &amp;#x3C;= numerator, denominator &amp;#x3C;= 2^31 - 1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;denominator != 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;这道题我们采用&lt;strong&gt;长除法&lt;/strong&gt;的思路来做，这样我们不直接通过浮点数计算，而是一步步将小数部分转换到整数部分，依次得到小数的每一位。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例如：&lt;/strong&gt;
计算 7 / 4 = 1.75&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先是整数部分：7 / 4 = 1 余 3
&lt;ul&gt;
&lt;li&gt;记录余数 3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第一位小数：3 * 10=30 , 30/4 = 7 余 2
&lt;ul&gt;
&lt;li&gt;记录余数 2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第二位小数：2 * 10=20 , 20/4 = 5 余 0
得到结果为 1.75&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;再例如：&lt;/strong&gt;
计算 1 / 6 = 0.1666666...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先是整数部分：1 / 6 = 0 余 1
&lt;ul&gt;
&lt;li&gt;记录余数 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第一位小数： 1 * 10 = 10 , 10 / 6 = 1 余 4
&lt;ul&gt;
&lt;li&gt;记录余数 4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;第二位小数： 4 * 10 = 40 , 40 / 6 = 6 余 4
&lt;ul&gt;
&lt;li&gt;余数 4 出现过，6 是循环节&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note]
这种方法相当于每次将小数点往右移一位，以此来得到小数的下一位&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此我们可以总结出以下解题步骤：&lt;/p&gt;
&lt;h3&gt;1. 整数部分&lt;/h3&gt;
&lt;p&gt;这里会有两种情况，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;除得尽：则答案就是结果的字符串形式&lt;/li&gt;
&lt;li&gt;除不尽：需要继续处理小数部分&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 小数部分&lt;/h3&gt;
&lt;p&gt;同样是两种情况，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过长除法最终能结束：有限小数，直接把小数部分接上就行&lt;/li&gt;
&lt;li&gt;通过长除法结束不了：无限循环小数，需要找循环节&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 怎么找循环节&lt;/h3&gt;
&lt;p&gt;我们来看余数部分，假如说余数在某一次和这一次一样的，后面的结果也一定是一样的，也就是发生了循环。
我们通过哈希表，映射一下余数和对应的位置，从而得到循环节。&lt;/p&gt;
&lt;h4&gt;4. 需要注意的点&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;需要先判断分子分母的正负，看看答案是否需要加负号&lt;/li&gt;
&lt;li&gt;因为负数最小到 $-2^{31}$，取正会导致溢出，所以需要 long long&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!tip]
在判断结果的正负时，需要讨论分子分母的正负，这里我们可以直接使用&lt;strong&gt;异或&lt;/strong&gt;来解决，异或，同 0 异 1，所以可以这样写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;if(x &amp;#x3C; 0 ^ y &amp;#x3C; 0) ans.push_back(&apos;-&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;h2&gt;纠结部分&lt;/h2&gt;
&lt;p&gt;在判断循环节部分的代码，我之前是这样写的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;//小数部分
        unordered_map&amp;#x3C;ll,int&gt; index;
        int cnt=0;
        string decimal;
        while(remainder!=0) {
            remainder *= 10;
            int cur = remainder/y;
            remainder %= y;
            if(index.count(remainder)) {//之前有过
                ans += decimal.substr(0,index[remainder]) + &quot;(&quot; + decimal.substr(index[remainder]) + &quot;)&quot;;
                return ans;
            }
            index[remainder] = cnt++;
            decimal += (to_string(cur));
        }
        ans += decimal;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果在解决 1/6 时发现了问题，因为余数依次为 1、4、4，结果在第二次发现 4 时就终止了，这时 ans 是 0.(1)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!error]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先是整数部分：1 / 6 = 0 余 1&lt;/li&gt;
&lt;li&gt;记录余数 4&lt;/li&gt;
&lt;li&gt;第一位小数： 1 * 10 = 10 , 10 / 6 = 1 余 4&lt;/li&gt;
&lt;li&gt;余数 4 出现过，1 是循环节&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!why]
因为实际上第一个余数 1 对应的应该是第一位小数，但是这版代码误将下一次的余数和当前的小数点做了对应，自然就不对了&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;完整代码&lt;/h2&gt;
&lt;hr&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#define ll long long

class Solution {

public:

    string fractionToDecimal(int numerator, int denominator) {

        ll x = numerator;

        ll y = denominator;

        if(x==0) return to_string(0);

        string ans;

        if(x &amp;#x3C; 0 ^ y &amp;#x3C; 0) ans.push_back(&apos;-&apos;);

        x = abs(x),y = abs(y);

        //整数部分

        ll remainder = x % y;

        if(remainder==0) {

            ans += to_string(x/y);

            return ans;

        }else {

            ans += to_string(x/y);

        }

        ans.push_back(&apos;.&apos;);

  

        //小数部分

        unordered_map&amp;#x3C;ll,int&gt; index;

        int cnt=0;

        string decimal;

        while(remainder!=0) {

            if(index.count(remainder)) {//之前有过

                ans += decimal.substr(0,index[remainder]) + &quot;(&quot; + decimal.substr(index[remainder]) + &quot;)&quot;;

                return ans;

            }

            index[remainder] = cnt++;

            remainder *= 10;

            int cur = remainder/y;

            remainder %= y;

            decimal += (to_string(cur));

        }

        ans += decimal;

        return ans;

    }

};
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C++中常用的STL</title><link>https://blog.arronhc.cyou/blog/stl</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/stl</guid><description>C++中常用的 STL</description><pubDate>Thu, 25 Sep 2025 10:35:27 GMT</pubDate><content:encoded>&lt;h2&gt;string&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;字符串，有一些很好用的内置函数，下面介绍一些好用但之前不了解的：&lt;/p&gt;
&lt;h3&gt;一、构造函数&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;string s(num ,c)//生成num个c字符的字符串

例如：

string s(5,&apos;3&apos;) =&gt; &quot;33333&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;二、比较函数&lt;/h3&gt;
&lt;p&gt;string 可以直接使用比较符进行大小比较，string 所谓的“大小”是这样来判断的：
从左到右一位一位比较，按照字典序进行大小比较
同时，string (&quot;aaaa&quot;) &amp;#x3C; string (&quot;aaaaa&quot;)&lt;/p&gt;
&lt;h3&gt;三、插入&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pushback&lt;/code&gt; 在末尾插入一个字符&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;s.push_back(&apos;c&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;code&gt;insert&lt;/code&gt; 在位置 pos 前插入字符&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;s.insert(3,&apos;+&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;四、子串&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;//返回从第三位往后的子串
s.substr(3);
//返回2~5位的子串
s.substr(2,5);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;五、&lt;code&gt;to_string&lt;/code&gt; 和 &lt;code&gt;stoi&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;to_string&lt;/code&gt; 可以将十进制类型 int、long、longlong、double 等转化为 string 类型&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;s = to_string(1234); =&gt; &quot;1234&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;stoi&lt;/code&gt; 可以将 string 类型转换为十进制&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int x = stoi(&quot;1234&quot;,0,3); =&gt; 1234
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;vector&lt;/h2&gt;
&lt;p&gt;动态数组，然后有一个很好用的初始化：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;vector&amp;#x3C;int&gt; a(10,0);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;意味着开一个长度至少为 10 的动态数组，每一项初始值为 0&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>专辑推荐</title><link>https://blog.arronhc.cyou/blog/%E4%B8%93%E8%BE%91%E6%8E%A8%E8%8D%90</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E4%B8%93%E8%BE%91%E6%8E%A8%E8%8D%90</guid><description>推荐一些好听的专辑</description><pubDate>Tue, 23 Sep 2025 22:55:51 GMT</pubDate><content:encoded>&lt;hr&gt;
&lt;hr&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>入门Node.js</title><link>https://blog.arronhc.cyou/blog/nodejs</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/nodejs</guid><description>Node.js是什么，怎么用？</description><pubDate>Mon, 22 Sep 2025 17:49:51 GMT</pubDate><content:encoded>&lt;h2&gt;Node. js 是什么&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Node.js&lt;/strong&gt; 是一个基于Chrome V8 JavaScript引擎的JavaScript运行时环境，让JavaScript可以在服务器端运行。&lt;/p&gt;
&lt;p&gt;假如没有 Node. js，JavaScript 只能在浏览器上运行，而 Node. js 可以让 JavaScript 像 Python、Java 一样，能够直接在电脑上运行&lt;/p&gt;
&lt;h2&gt;如何使用 Node. js&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;npm 介绍&lt;/h3&gt;
&lt;p&gt;我们一般使用 npm（Node Package manager）进行 Node. js 的包管理，实现以下功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安装和管理 JS 包、模块&lt;/li&gt;
&lt;li&gt;管理项目依赖&lt;/li&gt;
&lt;li&gt;发布自己的包到 npm 仓库&lt;/li&gt;
&lt;li&gt;运行脚本命令
npm 随 Node. js 一同安装&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;npm 常用命令&lt;/h2&gt;
&lt;hr&gt;
&lt;h4&gt;0. 检查版本&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;node --version
npm --version
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1. 初始化项目&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建package.json文件
npm init
# 快速创建package.json文件
npm init -y 
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. 安装包&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 安装到生产依赖
npm install &amp;#x3C;包名&gt;
npm i &amp;#x3C;包名&gt;  # 简写

# 安装到开发依赖
npm install &amp;#x3C;包名&gt; --save-dev
npm i &amp;#x3C;包名&gt; -D  # 简写

# 全局安装
npm install &amp;#x3C;包名&gt; -g

# 安装指定版本
npm install &amp;#x3C;包名&gt;@版本号

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 卸载包&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 卸载本地包
npm uninstall &amp;#x3C;包名&gt;

# 卸载全局包
npm uninstall &amp;#x3C;包名&gt; -g

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 查看包信息&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 查看已安装的包
npm list
npm ls

# 查看全局包
npm list -g

# 查看包信息
npm info &amp;#x3C;包名&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. 更新包&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 更新所有包
npm update

# 更新指定包
npm update &amp;#x3C;包名&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;package. json 结构&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;这是项目的配置文件, 结构如下:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;my-project&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;项目描述&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node index.js&quot;,
    &quot;test&quot;: &quot;jest&quot;,
    &quot;build&quot;: &quot;webpack&quot;
  },
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^4.18.0&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;jest&quot;: &quot;^28.0.0&quot;
  }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;依赖类型&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;dependencies&lt;/strong&gt;: 生产环境需要的包&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;devDependencies&lt;/strong&gt;: 开发环境需要的包（如测试工具、构建工具）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;peerDependencies&lt;/strong&gt;: 同伴依赖&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;optionalDependencies&lt;/strong&gt;: 可选依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;版本号规则&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;&quot;^1.2.3&quot;  # 兼容版本：&gt;=1.2.3 &amp;#x3C;2.0.0
&quot;~1.2.3&quot;  # 近似版本：&gt;=1.2.3 &amp;#x3C;1.3.0
&quot;1.2.3&quot;   # 精确版本
&quot;*&quot;       # 最新版本
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;常用 npm 脚本&lt;/h4&gt;
&lt;p&gt;在 package. json 中定义脚本:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
&quot;scripts&quot;: {
    &quot;start&quot;: &quot;node index.js&quot;,
    &quot;test&quot;: &quot;jest&quot;,
    &quot;build&quot;: &quot;webpack&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行脚本:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run start
npm run test
npm test # npm run test的缩写
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用 Node. js 创建项目的基本流程&lt;/h2&gt;
&lt;h4&gt;1.创建项目目录&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir my-project
cd my-project
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.初始化项目&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm init -y
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 安装一些包&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;npm install ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 创建入口文件&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;console.log(&apos;Hello npm!&apos;)&quot; &gt; index.js
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="https://tse1.mm.bing.net/th/id/OIP.aqk7aOsCh5urrV00hAbbEQHaEc?rs=1&amp;pid=ImgDetMain&amp;o=7&amp;rm=3"/><enclosure url="https://tse1.mm.bing.net/th/id/OIP.aqk7aOsCh5urrV00hAbbEQHaEc?rs=1&amp;pid=ImgDetMain&amp;o=7&amp;rm=3"/></item><item><title>每日一题：最长连续序列</title><link>https://blog.arronhc.cyou/blog/%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%BA%8F%E5%88%97</guid><description>每日一题：最长连续序列</description><pubDate>Mon, 22 Sep 2025 09:24:59 GMT</pubDate><content:encoded>&lt;h2&gt;题目描述&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;给定一个未排序的整数数组 &lt;code&gt;nums&lt;/code&gt; ，找出数字连续的最长序列（不要求序列元素在原数组中连续）的长度。&lt;/p&gt;
&lt;p&gt;请你设计并实现时间复杂度为 &lt;code&gt;O(n)&lt;/code&gt; 的算法解决此问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例 1：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt; nums = [100,4,200,1,3,2]
&lt;strong&gt;输出：&lt;/strong&gt; 4
&lt;strong&gt;解释：&lt;/strong&gt; 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 2：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt; nums = [0,3,7,2,5,8,4,6,0,1]
&lt;strong&gt;输出：&lt;/strong&gt; 9&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;示例 3：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;输入：&lt;/strong&gt; nums = [1,0,1,2]
&lt;strong&gt;输出：&lt;/strong&gt; 3&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0 &amp;#x3C;= nums.length &amp;#x3C;= 105&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-109 &amp;#x3C;= nums[i] &amp;#x3C;= 109&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解题思路&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;朴素解-&gt;哈希表&lt;/h3&gt;
&lt;p&gt;首先来看最朴素的思想，当我们拿到一个数 x 时，以他作为连续序列的头元素，紧接着去寻找列表中有没有 x+1，直到找不到下一个 x+1，更新最长连续长度&lt;/p&gt;
&lt;p&gt;这样的时间复杂度是 $O(n^2)$ 的，显然不符合题目要求，我们尝试减小时间复杂度。&lt;/p&gt;
&lt;p&gt;我们知道，在哈希表中查询数据的时间复杂度是 $O(1)$ 的，因此，我们可以先将列表中的数据都存进哈希表中，这样找 x+1 的时间复杂度就从 $O(n)$ 变成了 $O(1)$，总时间复杂度降到了 $O(n)$&lt;/p&gt;
&lt;p&gt;当我们拿到一个数 x 时，首先判断列表中有无 x-1，假如有，那么以 x 打头的列表必然不可能是最长序列，可以直接略过。假如没有，那么就依次寻找 x+1，更新序列长度。&lt;/p&gt;
&lt;p&gt;至于 c++中如何使用哈希表，请见&lt;a href=&quot;%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%8E%E5%93%88%E5%B8%8C%E8%A1%A8&quot;&gt;哈希值与哈希表&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;最后可以很顺利地写出代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Solution {

public:

    int longestConsecutive(vector&amp;#x3C;int&gt;&amp;#x26; nums) {

        unordered_map&amp;#x3C;int,bool&gt; HashTable;

        for(int i=0;i&amp;#x3C;nums.size();i++) {

            HashTable[nums[i]] = true;

        }

        int max_length=1;

        for(int i=0;i&amp;#x3C;nums.size();i++) {

            int length=1,num=nums[i];

            if(HashTable[num-1]) continue;

            while(HashTable[num+1]) {

                length++;

                num++;

            }

            max_length = max(max_length,length);

        }

        return max_length;

    }

};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果发现通过不了，分析原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;边界情况，当数组为空时应特判&lt;/li&gt;
&lt;li&gt;考虑去重，当数据中存在很多重复数时，会极大增加时间复杂度&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于去重，我们采用 &lt;code&gt;unordered_set&lt;/code&gt;，这样一举两得，在去重的同时能够以 $O(1)$ 的时间复杂度查询键值&lt;/p&gt;
&lt;p&gt;具体用法请见&lt;a href=&quot;%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%8E%E5%93%88%E5%B8%8C%E8%A1%A8&quot;&gt;哈希值与哈希表&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;改进后代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;class Solution {

public:

    int longestConsecutive(vector&amp;#x3C;int&gt;&amp;#x26; nums) {

        if(nums.size()==0) return 0;

        unordered_set&amp;#x3C;int&gt; num_set;

        for(const int&amp;#x26; num : nums) {

            num_set.insert(num);

        }

        int max_length=1;

        for(const int&amp;#x26; num : num_set) {

            int length=1,cur_num=num;

            if(num_set.count(cur_num-1)) continue;

            while(num_set.count(cur_num+1)) {

                length++;

                cur_num++;

            }

            max_length = max(max_length,length);

        }

        return max_length;

    }

};
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>并查集</title><link>https://blog.arronhc.cyou/blog/%E5%B9%B6%E6%9F%A5%E9%9B%86</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E5%B9%B6%E6%9F%A5%E9%9B%86</guid><description>并查集，一种处理不相交集合的合并以及查找成员的集合归属的算法**</description><pubDate>Thu, 18 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h2&gt;一、并查集是什么&lt;/h2&gt;
&lt;p&gt;并查集用来“拉帮结派”，学术点来说，就是&lt;strong&gt;用来处理不相交集合的合并以及查找成员的集合归属的算法&lt;/strong&gt;。主要有 &lt;code&gt;union&lt;/code&gt; 和 &lt;code&gt;find&lt;/code&gt; 两个操作，分别用来合并两个集合、查找成员集合归属。&lt;/p&gt;
&lt;h2&gt;二、什么思想&lt;/h2&gt;
&lt;p&gt;首先，怎么区分各个集合？找集合中某个人作为老大，他就是这个集合的代表，想知道某个人在哪个集合中，&lt;strong&gt;找他的老大&lt;/strong&gt;就好了。至于怎么找，我们定义一个上级数组 &lt;code&gt;parent[]&lt;/code&gt;，用来存储每个对象的上级，一级一级往上找就好。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;find&lt;/code&gt; 操作&lt;/h3&gt;
&lt;p&gt;找成员 x 的老大，一级一级往上找，直到找到一个人，他的上级就是他自己。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int find(int x) {
	while(x != parent[x]) {
		x = parent[x];
	}
	return x;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;union&lt;/code&gt; 操作&lt;/h3&gt;
&lt;p&gt;将集合 A 和集合 B 合并，那么只需要让 A 的老大变成 B，或者 B 的老大变成 A 即可。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;void union(int x,int y) { //让x和y变成一伙的
	int rootX = find(x);
	int rootY = find(y);
	parent[rootX] = rootY;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以发现，这其实就是棵树
不过现在有了新问题：当树的高度过高，会导致 &lt;code&gt;find&lt;/code&gt; 操作的时间成本很大，所以我们可以采用&lt;strong&gt;路径压缩&lt;/strong&gt;的方法——当我们进行 &lt;code&gt;find&lt;/code&gt; 操作时，会从下到上遍历每一个上级，那么我们可以&lt;strong&gt;边遍历，边让每个节点的上级直接变成老大&lt;/strong&gt;，这样会使得树的高度直线下降。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;int find(int x) {
	while(x != parent[x]) {
		x = find(parent[x]);
	}
	return x;
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="https://pic1.zhimg.com/70/v2-6362d8b13705d5ba17b19cdeee453022_1440w.avis?source=172ae18b&amp;biz_tag=Post"/><enclosure url="https://pic1.zhimg.com/70/v2-6362d8b13705d5ba17b19cdeee453022_1440w.avis?source=172ae18b&amp;biz_tag=Post"/></item><item><title>哈希值与哈希表</title><link>https://blog.arronhc.cyou/blog/%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%8E%E5%93%88%E5%B8%8C%E8%A1%A8</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E5%93%88%E5%B8%8C%E5%80%BC%E4%B8%8E%E5%93%88%E5%B8%8C%E8%A1%A8</guid><description>学习哈希值与哈希表</description><pubDate>Wed, 17 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h2&gt;哈希值&lt;/h2&gt;
&lt;p&gt;将任何值通过&lt;strong&gt;哈希函数&lt;/strong&gt;转换为哈希值
&lt;strong&gt;具有以下特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同一值转换的&lt;strong&gt;哈希值是固定的&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;不论输入值长度大小，&lt;strong&gt;哈希值的长度是固定的&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;无法通过哈希值反推原值&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;简单总结：哈希值就是数据的“指纹”或“身份证号”。它独一无二（理想情况下），能代表原始数据，但又隐藏了原始数据的具体内容。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;哈希表&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;哈希表&lt;/strong&gt;是一种数据结构，它利用哈希值的思想来实现&lt;strong&gt;极速的增、删、查、改&lt;/strong&gt;。
&lt;strong&gt;分为以下步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将输入值转换为哈希值&lt;/li&gt;
&lt;li&gt;假如要对数据进行存储操作，查询哈希值是否发生冲突，若有，进行&lt;strong&gt;冲突处理&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;哈希值对容器大小取模得到容器中数据的索引，从而对数据进行增删改查&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;哈希冲突&lt;/h2&gt;
&lt;p&gt;在实际操作中，通过哈希函数转换，&lt;code&gt;张三&lt;/code&gt; 和 &lt;code&gt;李四&lt;/code&gt; 可能得到相同的哈希值，例如 &lt;code&gt;47&lt;/code&gt;，这样会导致两个人共用同一个“47号柜子”。&lt;/p&gt;
&lt;p&gt;对于这样的冲突，提供两种解决方法：&lt;/p&gt;
&lt;h3&gt;一. 链表法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;通过哈希函数得到柜子号 47&lt;/li&gt;
&lt;li&gt;这次我们给柜子挂上一条链表，链表上存储着所有哈希值等于柜子号的数据&lt;/li&gt;
&lt;li&gt;这样一来，用户得到哈希值对应到柜号 47后，只需要顺着链表就能找到自己的数据&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二. 开放空间法（线性搜索法）&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;通过哈希函数得到柜子号 47&lt;/li&gt;
&lt;li&gt;李四发现张三已经占用了 47 柜子，则往后搜索，看看 48、49... 找到第一个空柜子存自己的数据&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;哈希函数的选择&lt;/h2&gt;
&lt;p&gt;我们的核心目标是：追求&lt;strong&gt;速度和均匀性&lt;/strong&gt;、追求&lt;strong&gt;安全性&lt;/strong&gt;
&lt;strong&gt;均匀分布是为了降低冲突概率&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;一、用于数据结构的增删改查&lt;/h3&gt;
&lt;p&gt;使用&lt;strong&gt;非加密哈希函数&lt;/strong&gt;
&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;速度快&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;散列均匀&lt;/strong&gt;：能最大限度地避免冲突&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;对小输入友好&lt;/strong&gt;：即使数据只有微小变化，仍然会导致哈希值变化很大
&lt;strong&gt;常见选择&lt;/strong&gt;：&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MurmurHash&lt;/strong&gt;：性能极高，分布性非常好，是业界非常流行的非加密哈希算法。Redis, Hadoop 等很多知名项目都在使用它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FNV (Fowler-Noll-Vo)&lt;/strong&gt;：一个非常简单且快速的算法，在很多场景下表现良好，易于实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;xxHash&lt;/strong&gt;：以其惊人的速度而闻名，在许多基准测试中都是最快的哈希算法之一，同时保持了良好的分布性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CityHash / FarmHash&lt;/strong&gt;：由 Google 开发，专为 64 位架构优化，性能卓越。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] 黄金法则：
&lt;strong&gt;黄金法则：不要自己造轮子！&lt;/strong&gt;
绝大多数情况下，你不需要自己去实现这些算法。&lt;strong&gt;直接使用你所用编程语言内置的哈希功能即可。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 &lt;strong&gt;Java&lt;/strong&gt; 中，每个对象都有 &lt;code&gt;hashCode()&lt;/code&gt; 方法。&lt;code&gt;HashMap&lt;/code&gt; 就依赖这个方法。&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;Python&lt;/strong&gt; 中，有内置的 &lt;code&gt;hash()&lt;/code&gt; 函数。&lt;code&gt;dict&lt;/code&gt; 和 &lt;code&gt;set&lt;/code&gt; 都依赖它。&lt;/li&gt;
&lt;li&gt;在 &lt;strong&gt;C++&lt;/strong&gt; 中，标准库提供了 &lt;code&gt;std::hash&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;语言标准库的实现通常是经过深度优化的、可靠的非加密哈希函数，它们足以应对绝大多数哈希表的需求。只有在对性能有极致要求的特定场景下，才需要考虑引入 MurmurHash、xxHash 等第三方库。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;二. 用于数据加密&lt;/h3&gt;
&lt;p&gt;此时, 速度不再是第一要义, 算法的安全性最重要
所以我们使用&lt;strong&gt;加密哈稀函数&lt;/strong&gt;
&lt;strong&gt;评估标准&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;符合基础特点&lt;/li&gt;
&lt;li&gt;原像抗性: 不可逆性&lt;/li&gt;
&lt;li&gt;第二原像抗性: 抗篡改性&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!example]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;举个例子, 网站在发给你一个文件的同时, 像你发送了一串 SHA-256 哈希值, 让你检验文件是否被篡改&lt;/li&gt;
&lt;li&gt;一个黑客想攻击你, 制作了病毒文件, 并通过技术让他的文件哈希值也恰好等于原哈希值&lt;/li&gt;
&lt;li&gt;这样一来, 你一比对并无偏差, 安安心心的下载了病毒文件, 招致了困惑&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;碰撞抗性: 几乎不可能找到任意两个不同的 &lt;code&gt;A&lt;/code&gt; 和 &lt;code&gt;B&lt;/code&gt;, 使得 &lt;code&gt;H(A)&lt;/code&gt; 等于 &lt;code&gt;H(B)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;哈希函数在 c++中的实现&lt;/h2&gt;
&lt;p&gt;在 c++中，我们无需导入库，可直接使用 &lt;code&gt;hash&lt;/code&gt; 类实现&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;hash&amp;#x3C;string&gt; stringHasher;//创建哈希函数
string s = &quot;abc&quot;;
cout&amp;#x3C;&amp;#x3C;stringHasher(s);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;哈希表在 c++中的实现&lt;/h2&gt;
&lt;p&gt;在 c++中，哈希表通常采用 &lt;code&gt;#include&amp;#x3C;unordered_map&gt;&lt;/code&gt; 实现，&lt;code&gt;unordered_map&lt;/code&gt; 用于存储键值对，正好符合哈希表的功能。
以下是利用哈希表在列表中以 &lt;code&gt;O(n)&lt;/code&gt; 实现数据查找&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include&amp;#x3C;unordered_map&gt;

unordered_map&amp;#x3C;int,int&gt; hashTable;
for(int i=1;i&amp;#x3C;=n;i++) {
	hashTable[a[i]] = i;
}
cin&gt;&gt;x;
auto it = hashTable.find(x);
if(it != hashTable.end()) {
	return it-&gt;second;
}
return NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此外，还有 &lt;code&gt;unordered_set&lt;/code&gt; 用来实现常数级的数据查找，它是一种使用哈希表实现的无序关联容器，其中键被哈希到哈希表的索引位置，因此插入操作总是随机的。无序集合上的所有操作在平均情况下都具有常数时间复杂度 O (1)，但在最坏情况下，时间复杂度可以达到线性时间 O (n)，这取决于内部使用的哈希函数，但实际上它们表现非常出色，通常提供常数时间的查找操作。
具体用法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;unordered_set&amp;#x3C;type&gt; name;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;size()&lt;/code&gt; &lt;code&gt;empty()&lt;/code&gt; 用于获取大小和是否为空
&lt;code&gt;find()&lt;/code&gt; 用于查找键
&lt;code&gt;insert()&lt;/code&gt; &lt;code&gt;erase()&lt;/code&gt; 用于插入和删除元素
&lt;code&gt;count()&lt;/code&gt; 用于查找某个键出现的次数&lt;/p&gt;</content:encoded><h:img src="https://th.bing.com/th/id/R.043a2cf2c9096d1742b6840347ef24db?rik=3gvXArgyxhvvOQ&amp;riu=http%3a%2f%2fpic.baike.soso.com%2fp%2f20140127%2f20140127170337-1131872964.jpg&amp;ehk=CTCzw36%2fa0tUeqFiGr9K%2btUfGd%2bpfnBQC441FicmFj0%3d&amp;risl=&amp;pid=ImgRaw&amp;r=0"/><enclosure url="https://th.bing.com/th/id/R.043a2cf2c9096d1742b6840347ef24db?rik=3gvXArgyxhvvOQ&amp;riu=http%3a%2f%2fpic.baike.soso.com%2fp%2f20140127%2f20140127170337-1131872964.jpg&amp;ehk=CTCzw36%2fa0tUeqFiGr9K%2btUfGd%2bpfnBQC441FicmFj0%3d&amp;risl=&amp;pid=ImgRaw&amp;r=0"/></item><item><title>苏州行</title><link>https://blog.arronhc.cyou/blog/%E8%8B%8F%E5%B7%9E%E8%A1%8C</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E8%8B%8F%E5%B7%9E%E8%A1%8C</guid><description>25年国庆，去苏州玩！</description><pubDate>Tue, 16 Sep 2025 22:08:24 GMT</pubDate><content:encoded>&lt;h2&gt;出行&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;9.30&lt;/strong&gt; 飞机&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;周水子-天津滨海 10:15-11:30&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;9.30&lt;/strong&gt; 火车 (9.16 抢票下午 4:15)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;天津西-苏州 20:40-07:23&lt;/li&gt;
&lt;li&gt;苏州-昆山南 07:47-07:59&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;站内换乘 24 分钟&lt;/strong&gt;(来得及)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;10.3&lt;/strong&gt; 高铁 (9:21抢票上午 8:30)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;昆山南-上海 08:56-09:15&lt;/li&gt;
&lt;li&gt;上海-昆山 22:09-22:30&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;10.5&lt;/strong&gt; 火车（9.21 抢票上午 8:30）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;苏州-天津 20:26-07:33&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;10.8&lt;/strong&gt; 高铁（9.24抢票下午 4:15）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;天津西-大连北 17:26-22:23&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;住宿&lt;/h2&gt;
&lt;p&gt;10.1-10.5 (共 4 晚) 下榻全季昆山开发区前进东路酒店
10.6-10.8 (共 2 晚) 下榻天津&lt;/p&gt;
&lt;h2&gt;安排&lt;/h2&gt;
&lt;h3&gt;10.1&lt;/h3&gt;
&lt;p&gt;简单逛逛
看看金鸡湖、东方之门&lt;/p&gt;
&lt;h3&gt;10.2&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;拙政园&lt;/li&gt;
&lt;li&gt;苏州博物馆&lt;/li&gt;
&lt;li&gt;狮子林&lt;/li&gt;
&lt;li&gt;平江路&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10.3&lt;/h3&gt;
&lt;p&gt;前往上海&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;武康大楼&lt;/li&gt;
&lt;li&gt;HAUS NOWHERE&lt;/li&gt;
&lt;li&gt;南京路&lt;/li&gt;
&lt;li&gt;外滩&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10.4&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;留园&lt;/li&gt;
&lt;li&gt;虎丘山&lt;/li&gt;
&lt;li&gt;山塘街&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;要带的东西&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;手机&lt;/li&gt;
&lt;li&gt;身份证&lt;/li&gt;
&lt;li&gt;充电器（手机、手表）、充电宝&lt;/li&gt;
&lt;li&gt;剃须刀&lt;/li&gt;
&lt;li&gt;小鲨鱼书包&lt;/li&gt;
&lt;li&gt;精油&lt;/li&gt;
&lt;li&gt;面膜&lt;/li&gt;
&lt;li&gt;发箍&lt;/li&gt;
&lt;li&gt;洗面奶&lt;/li&gt;
&lt;li&gt;护肤品&lt;/li&gt;
&lt;li&gt;拖鞋&lt;/li&gt;
&lt;li&gt;pocket3&lt;/li&gt;
&lt;li&gt;唇膏&lt;/li&gt;
&lt;li&gt;药膏&lt;/li&gt;
&lt;li&gt;水杯（大小都带）&lt;/li&gt;
&lt;li&gt;卫生纸、湿纸巾&lt;/li&gt;
&lt;li&gt;毛巾、浴巾&lt;/li&gt;
&lt;li&gt;床上一次性用品&lt;/li&gt;
&lt;li&gt;鼻炎药、过敏药、感冒药&lt;/li&gt;
&lt;li&gt;夏衣 3 身，外套，防晒衣、鞋、睡衣&lt;/li&gt;
&lt;li&gt;备点吃的&lt;/li&gt;
&lt;li&gt;餐具&lt;/li&gt;
&lt;li&gt;礼物&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="https://pic2.zhimg.com/v2-906e0fe7a1ace8be912b1d3ea8cf546f_r.jpg"/><enclosure url="https://pic2.zhimg.com/v2-906e0fe7a1ace8be912b1d3ea8cf546f_r.jpg"/></item><item><title>链表</title><link>https://blog.arronhc.cyou/blog/%E9%93%BE%E8%A1%A8</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E9%93%BE%E8%A1%A8</guid><description>有关链表的一些思路</description><pubDate>Fri, 12 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h1&gt;一些思路：&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;快慢指针：fast 指针一次走两步，slow 指针一次走一步，那么当 fast 走完时，slow 指针会走到整条链表的一半位置&lt;/li&gt;
&lt;li&gt;判断两链表交点：
&lt;ul&gt;
&lt;li&gt;设链表 A 相交前长度为 a&lt;/li&gt;
&lt;li&gt;设链表 B 相交前长度为 b&lt;/li&gt;
&lt;li&gt;相交长度为 c&lt;/li&gt;
&lt;li&gt;因为有 a + c + b == b + c + a&lt;/li&gt;
&lt;li&gt;所以让两个指针一起走，走到头就跳转到对方的头指针接着走&lt;/li&gt;
&lt;li&gt;两个指针就会在交点处相遇&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;ListNode *fast = head,*slow =head;

while(fast != nullptr &amp;#x26;&amp;#x26; fast-&gt;next != nullptr) {

	fast = fast-&gt;next-&gt;next;

	slow = slow-&gt;next;

}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="https://pic2.zhimg.com/v2-6d8f7d01b3b74ce646e73b76ee414c40_r.jpg"/><enclosure url="https://pic2.zhimg.com/v2-6d8f7d01b3b74ce646e73b76ee414c40_r.jpg"/></item><item><title>粒子群算法</title><link>https://blog.arronhc.cyou/blog/%E7%B2%92%E5%AD%90%E7%BE%A4%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E7%B2%92%E5%AD%90%E7%BE%A4%E7%AE%97%E6%B3%95</guid><description>一种用于寻找最优解的算法，通过无数粒子进行自身和全局迭代，向最优解靠近</description><pubDate>Sat, 06 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h2&gt;粒子群算法（PSO）&lt;/h2&gt;
&lt;h3&gt;1.介绍&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;以一群蜜蜂寻找最甜的花为例
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;他们很盲目&lt;/strong&gt;：不知哪里最甜&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有记忆&lt;/strong&gt;：知道自己到过最甜的位置&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有沟通&lt;/strong&gt;：知道群体最甜的位置
于是，对于每只蜜蜂，它下次飞行会综合考虑以下三个方面：&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;按照惯性，沿着上一次的方向再飞一会儿&lt;/li&gt;
&lt;li&gt;自己的最甜处，即&lt;strong&gt;个体最优解&lt;/strong&gt;(pbest, Personal Best)&lt;/li&gt;
&lt;li&gt;群体的最甜处，即&lt;strong&gt;全局最优解&lt;/strong&gt;(gbest, Global Best)
然后在迭代后，所有蜜蜂都会找到最甜处&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;2.算法讲解&lt;/h3&gt;
&lt;p&gt;现在，我们把上面的故事翻译成算法的语言：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;粒子 (Particle)&lt;/strong&gt;：就是一只蜜蜂。在算法里，它代表一个潜在的解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;种群/粒子群 (Swarm)&lt;/strong&gt;：就是整个蜂群。它是一组潜在的解。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位置 (Position, &lt;code&gt;x&lt;/code&gt;)&lt;/strong&gt;：就是蜜蜂所在的位置坐标。在算法里，它就是这个潜在解的具体数值。如果我们要优化函数 &lt;code&gt;f(x, y)&lt;/code&gt;，那么一个粒子的位置就是 &lt;code&gt;(x, y)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;速度 (Velocity, &lt;code&gt;v&lt;/code&gt;)&lt;/strong&gt;：就是蜜蜂飞行的方向和距离。它决定了粒子下一次移动的方向和步长。速度越大，粒子移动得越快、越远。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适应度 (Fitness)&lt;/strong&gt;：就是蜜蜂闻到的花香浓度。在算法里，通常由一个&lt;strong&gt;目标函数&lt;/strong&gt;（也叫适应度函数）来计算。我们的目标就是找到适应度最高（或最低）的位置。例如，寻找函数的最小值时，函数值就是适应度，越小越好。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;个体最优解 (pbest, Personal Best)&lt;/strong&gt;：就是某只蜜蜂自己历史上去过的最香的位置。每个粒子都有自己的 &lt;code&gt;pbest&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全局最优解 (gbest, Global Best)&lt;/strong&gt;：就是整个蜂群目前发现的最香的位置。所有粒子共享同一个 &lt;code&gt;gbest&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;3.算法流程&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Step1&lt;/strong&gt;：初始化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;随机一群粒子&lt;/li&gt;
&lt;li&gt;为每个粒子随机分配参数（对于蜜蜂，就是初始位置 &lt;code&gt;x&lt;/code&gt; 和初始速度 &lt;code&gt;v&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;计算每个粒子的适应度 (适应度函数就是目标函数)&lt;/li&gt;
&lt;li&gt;将当前适应度设置为每个个体的 &lt;code&gt;pbest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;从所有 &lt;code&gt;pbest&lt;/code&gt; 中选出 &lt;code&gt;gbest&lt;/code&gt;
&lt;strong&gt;Step2&lt;/strong&gt;：迭代&lt;/li&gt;
&lt;li&gt;步骤与 Step1 相同，每次迭代会更新每个粒子的 &lt;code&gt;x&lt;/code&gt; 和 &lt;code&gt;v&lt;/code&gt;，从而得到更好的 &lt;code&gt;pbest&lt;/code&gt; 和 &lt;code&gt;gbest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3&gt;4.PSO 的灵魂：迭代函数&lt;/h3&gt;
&lt;p&gt;速度更新公式：
$$
v(t+1)=w&lt;em&gt;v(t)+c_1&lt;/em&gt;rand_1*(pbest-x(t))+c_2&lt;em&gt;rand_2&lt;/em&gt;(gbest-x(t))
$$
其中，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$v(t+1)$：粒子下一刻速度&lt;/li&gt;
&lt;li&gt;$w$：惯性权重&lt;/li&gt;
&lt;li&gt;$c_1$：&lt;strong&gt;自我学习部分&lt;/strong&gt;的学习因子&lt;/li&gt;
&lt;li&gt;$rand_1$：0~1 的随机数&lt;/li&gt;
&lt;li&gt;$c_2$：&lt;strong&gt;群体学习部分&lt;/strong&gt;的学习因子&lt;/li&gt;
&lt;li&gt;$rand_2$：0~1 的随机数&lt;/li&gt;
&lt;li&gt;$c_1&lt;em&gt;rand_1&lt;/em&gt;(pbest-x(t))$ 表示粒子向 &lt;code&gt;pbest&lt;/code&gt; 飞行的趋势&lt;/li&gt;
&lt;li&gt;$c_2&lt;em&gt;rand_2&lt;/em&gt;(pbest-x(t))$ 表示粒子向 &lt;code&gt;gbest&lt;/code&gt; 飞行的趋势&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] 值得注意的是
在实际应用是，&lt;code&gt;v&lt;/code&gt;, &lt;code&gt;x&lt;/code&gt; 是多维的向量，每个维度就代表一个的参数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;位置更新公式：
$$
x(t+1)=x(t)+v(t+1)
$$&lt;/h2&gt;
&lt;h3&gt;5. 代码实战&lt;/h3&gt;
&lt;p&gt;理论说完了，我们来点实际的。用 Python 实现 PSO，求解函数 &lt;code&gt;f(x, y) = x² + y²&lt;/code&gt; 在 &lt;code&gt;[-10, 10]&lt;/code&gt; 区间内的最小值。我们都知道，最小值在 &lt;code&gt;(0, 0)&lt;/code&gt;，值为 &lt;code&gt;0&lt;/code&gt;。看看 PSO 能不能找到它。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import numpy as np

# --- 1. 定义目标函数 (适应度函数) ---
def objective_function(position):
    # position 是一个包含 x 和 y 的数组/列表，例如 [x, y]
    return position[0]**2 + position[1]**2

# --- 2. PSO 参数设置 ---
w = 0.5         # 惯性权重
c1 = 1.5        # 个体学习因子
c2 = 1.5        # 社会学习因子
n_particles = 50 # 粒子数量
n_dimensions = 2 # 问题维度 (x, y)
max_iter = 100   # 最大迭代次数
bounds = [(-10, 10), (-10, 10)] # 搜索范围

# --- 3. 初始化粒子群 ---
# 初始化粒子位置
particles_pos = np.random.uniform(bounds[0][0], bounds[0][1], (n_particles, n_dimensions))
# 初始化粒子速度
particles_vel = np.random.rand(n_particles, n_dimensions)

# 初始化个体最优解 (pbest) 和其适应度
pbest_pos = particles_pos.copy()
pbest_fitness = np.array([objective_function(p) for p in pbest_pos])

# 初始化全局最优解 (gbest) 和其适应度
gbest_idx = np.argmin(pbest_fitness)
gbest_pos = pbest_pos[gbest_idx].copy()
gbest_fitness = pbest_fitness[gbest_idx]

# --- 4. 迭代优化 ---
print(f&quot;开始优化，共 {max_iter} 代...&quot;)

for i in range(max_iter):
    for j in range(n_particles):
        # --- 更新速度 ---
        r1 = np.random.rand(n_dimensions)
        r2 = np.random.rand(n_dimensions)
        
        cognitive_vel = c1 * r1 * (pbest_pos[j] - particles_pos[j])
        social_vel = c2 * r2 * (gbest_pos - particles_pos[j])
        particles_vel[j] = w * particles_vel[j] + cognitive_vel + social_vel

        # --- 更新位置 ---
        particles_pos[j] = particles_pos[j] + particles_vel[j]
        
        # --- 边界处理 ---
        for k in range(n_dimensions):
            if particles_pos[j, k] &amp;#x3C; bounds[k][0]:
                particles_pos[j, k] = bounds[k][0]
            elif particles_pos[j, k] &gt; bounds[k][1]:
                particles_pos[j, k] = bounds[k][1]

        current_fitness = objective_function(particles_pos[j])
        
        # --- 更新 pbest 和 gbest ---
        # 更新个体最优
        if current_fitness &amp;#x3C; pbest_fitness[j]:
            pbest_fitness[j] = current_fitness
            pbest_pos[j] = particles_pos[j].copy()

        # 更新全局最优
        if current_fitness &amp;#x3C; gbest_fitness:
            gbest_fitness = current_fitness
            gbest_pos = particles_pos[j].copy()

    # 每一代打印一次最优结果
    if (i + 1) % 10 == 0:
        print(f&quot;第 {i+1} 代: 最优位置 = {gbest_pos}, 最优值 = {gbest_fitness:.6f}&quot;)

# --- 5. 输出最终结果 ---
print(&quot;\n优化结束!&quot;)
print(f&quot;找到的最优位置 (gbest): {gbest_pos}&quot;)
print(f&quot;找到的最优值: {gbest_fitness}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你可以运行这段代码，会看到 &lt;code&gt;gbest&lt;/code&gt; 的值随着迭代次数的增加，越来越接近 &lt;code&gt;(0, 0)&lt;/code&gt;，最优值也越来越接近 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;</content:encoded><h:img src="https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/%E7%B2%92%E5%AD%90%E7%BE%A4%E7%AE%97%E6%B3%95.webp"/><enclosure url="https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/%E7%B2%92%E5%AD%90%E7%BE%A4%E7%AE%97%E6%B3%95.webp"/></item><item><title>网站汇总</title><link>https://blog.arronhc.cyou/blog/%E7%BD%91%E7%AB%99%E6%B1%87%E6%80%BB</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/%E7%BD%91%E7%AB%99%E6%B1%87%E6%80%BB</guid><description>收集一些好玩的、有用的网站</description><pubDate>Fri, 05 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h2&gt;知识类&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://linux.do/t/topic/543216&quot;&gt;简单说说：你的电脑是如何访问网页的&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://linux.do/t/topic/544944&quot;&gt;简单说说：IPV4已耗尽，为什么你仍然可以访问互联网? &lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;捣鼓类&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://linux.do/t/topic/747597&quot;&gt;实现了一个便捷美观的聚合影视站，支持 vercel 和 docker 部署&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;资源站&lt;/h2&gt;
&lt;p&gt;gemini生图：&lt;a href=&quot;https://gemini-image.smnet.studio/&quot;&gt;Banana Nano&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;影视站&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://video.6667000.xyz/video/search&quot;&gt;免费在线观看电影电视剧 - 高清流畅无广告&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;公益机场&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://uuuuuu.vvvv.ee/#/dashboard&quot;&gt;仪表盘 | W&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;音乐 api&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://music-api.gdstudio.xyz/api.php&quot;&gt;GD Studio&apos;s Online Music Platform API&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;AI-api&lt;/h2&gt;
&lt;p&gt;https://openrouter.ai/ （github 登录）&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>LaTeX的学习笔记</title><link>https://blog.arronhc.cyou/blog/latex</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/latex</guid><description>LaTeX，最好用的数学排版工具</description><pubDate>Wed, 03 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h2&gt;介绍&lt;/h2&gt;
&lt;p&gt;LaTex（读作 &quot;Lay-tek&quot;）是一个排版工具，相比 Word 中用鼠标调整格式，LaTeX 全部采用代码来描述文档的结构和内容，再将其编译为 pdf 文档。&lt;/p&gt;
&lt;h2&gt;数学公式的输入方式&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;$..$&lt;/code&gt; 来框住数学表达式&lt;/p&gt;
&lt;h3&gt;1. 行内公式&lt;/h3&gt;
&lt;p&gt;行内公式使用 &lt;code&gt;$..$&lt;/code&gt; 包裹，数学表达式嵌入在语句中，不会打断语句
&lt;strong&gt;例如&lt;/strong&gt;：
当变量 $x$ 趋近于无穷时，表达式 $\dfrac{1}{x}$ 的极限为 0.
&lt;strong&gt;其源码为&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Markdown&quot;&gt;当变量 $x$ 趋近于无穷时，表达式 $\dfrac{1}{x}$ 的极限为 0.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 行间公式&lt;/h3&gt;
&lt;p&gt;行间公式使用 &lt;code&gt;$$..$$&lt;/code&gt; 包裹，数学表达式独占一行，并会居中显示，公式内部的元素也会完整展开显示。
&lt;strong&gt;例如&lt;/strong&gt;：
计算下面这个极限：
$$
\lim_{x\to\infty}\dfrac{1}{x}=0
$$
&lt;strong&gt;其源码为&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Markdown&quot;&gt;计算下面这个极限：
$$
\lim_{x\to\infty}\dfrac{1}{x}=0
$$
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 上下标&lt;/h3&gt;
&lt;p&gt;上标用 &lt;code&gt;^&lt;/code&gt; 实现，比方 $e^x$，下标用 &lt;code&gt;_&lt;/code&gt; 实现，比方 $a_1$。假如上下标很长，要用 &lt;code&gt;{}&lt;/code&gt; 框起来，比如 $e^{x+1}$&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;$x^{10}$ 要写作 &lt;code&gt;$x^{10}$&lt;/code&gt; 而不是 &lt;code&gt;$x^10$&lt;/code&gt;, $x^10$&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;4. 字符&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;希腊字母&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;小写： &lt;code&gt;\alpha&lt;/code&gt;，&lt;code&gt;\beta&lt;/code&gt;, &lt;code&gt;\gamma&lt;/code&gt;, &lt;code&gt;\delta&lt;/code&gt;....&lt;/li&gt;
&lt;li&gt;大写：&lt;code&gt;\Alpha&lt;/code&gt;，&lt;code&gt;\Beta&lt;/code&gt;, &lt;code&gt;\Gamma&lt;/code&gt;, &lt;code&gt;\Delta&lt;/code&gt;....（首字母大写即可）
&lt;strong&gt;例如&lt;/strong&gt;：
$$
\alpha^2+\beta^2=\gamma
$$
&lt;strong&gt;其他字符&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;| 功能           | 示例代码                                                                                          | 显示效果                                                                      | 备注                 |
| :----------- | :-------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------ | :----------------- |
| &lt;strong&gt;积分、求和、极限&lt;/strong&gt; | &lt;code&gt;\int_a^b f(x)dx&lt;/code&gt;  &lt;code&gt;\sum_{i=1}^n a_i&lt;/code&gt;  &lt;code&gt;\lim_{x \to \infty} f(x)&lt;/code&gt;                     | $\int_a^b f(x)dx$  $\sum_{i=1}^n a_i$  $\lim_{x \to \infty} f(x)$ | 在行内模式会压缩           |
| &lt;strong&gt;关系运算符&lt;/strong&gt;    | &lt;code&gt;\leq&lt;/code&gt;, &lt;code&gt;\geq&lt;/code&gt;, &lt;code&gt;\neq&lt;/code&gt;, &lt;code&gt;\approx&lt;/code&gt;, &lt;code&gt;\equiv&lt;/code&gt;                                                   | $\leq, \geq, \neq, \approx, \equiv$                                       | 小于等于、大于等于、不等、约等、恒等 |
| &lt;strong&gt;集合运算符&lt;/strong&gt;    | &lt;code&gt;\in&lt;/code&gt;, &lt;code&gt;\notin&lt;/code&gt;, &lt;code&gt;\subset&lt;/code&gt;, &lt;code&gt;\cup&lt;/code&gt;, &lt;code&gt;\cap&lt;/code&gt;                                                    | $\in, \notin, \subset, \cup, \cap$                                        | 属于、不属于、子集、并集、交集    |
| &lt;strong&gt;箭头&lt;/strong&gt;       | &lt;code&gt;\to&lt;/code&gt;, &lt;code&gt;\rightarrow&lt;/code&gt;, &lt;code&gt;\Rightarrow&lt;/code&gt;, &lt;code&gt;\leftrightarrow&lt;/code&gt;                                        | $\to, \rightarrow, \Rightarrow, \leftrightarrow$                          |                    |
| &lt;strong&gt;其他常用符号&lt;/strong&gt;   | &lt;code&gt;\infty&lt;/code&gt; (无穷)  &lt;code&gt;\nabla&lt;/code&gt; (梯度)  &lt;code&gt;\partial&lt;/code&gt; (偏导)  &lt;code&gt;\forall&lt;/code&gt; (任意)  &lt;code&gt;\exists&lt;/code&gt; (存在) | $\infty$  $\nabla$  $\partial$  $\forall$  $\exists$      |                    |
| &lt;strong&gt;向量&lt;/strong&gt;       | &lt;code&gt;\vec{a}&lt;/code&gt;  &lt;code&gt;\overrightarrow{AB}&lt;/code&gt;                                                          | $\vec{a}$  $\overrightarrow{AB}$                                      |                    |
| &lt;strong&gt;上下括号&lt;/strong&gt;     | &lt;code&gt;\underbrace{a+b+\cdots+z}_{26}&lt;/code&gt;  &lt;code&gt;\overbrace{a+b+\cdots+z}^{26}&lt;/code&gt;                         | $\underbrace{a+b+\cdots+z}_{26}$  $\overbrace{a+b+\cdots+z}^{26}$     |                    |
|              |                                                                                               |                                                                           |                    |&lt;/p&gt;
&lt;p&gt;希腊字母对照表：&lt;/p&gt;
&lt;p&gt;| 希腊字母 (大写)  | LaTeX 命令   | 希腊字母 (小写)  | LaTeX 命令   | 备注                                              |
| :--------- | :--------- | :--------- | :--------- | :---------------------------------------------- |
| $\Alpha$   | &lt;code&gt;\Alpha&lt;/code&gt;   | $\alpha$   | &lt;code&gt;\alpha&lt;/code&gt;   | &lt;code&gt;\Alpha&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母A)   |
| $\Beta$    | &lt;code&gt;\Beta&lt;/code&gt;    | $\beta$    | &lt;code&gt;\beta&lt;/code&gt;    | &lt;code&gt;\Beta&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母B)    |
| $\Gamma$   | &lt;code&gt;\Gamma&lt;/code&gt;   | $\gamma$   | &lt;code&gt;\gamma&lt;/code&gt;   |                                                 |
| $\Delta$   | &lt;code&gt;\Delta&lt;/code&gt;   | $\delta$   | &lt;code&gt;\delta&lt;/code&gt;   |                                                 |
| $\Epsilon$ | &lt;code&gt;\Epsilon&lt;/code&gt; | $\epsilon$ | &lt;code&gt;\epsilon&lt;/code&gt; | 另一种写法：$\varepsilon$ (&lt;code&gt;\varepsilon&lt;/code&gt;)             |
| $\Zeta$    | &lt;code&gt;\Zeta&lt;/code&gt;    | $\zeta$    | &lt;code&gt;\zeta&lt;/code&gt;    | &lt;code&gt;\Zeta&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母Z)    |
| $\Eta$     | &lt;code&gt;\Eta&lt;/code&gt;     | $\eta$     | &lt;code&gt;\eta&lt;/code&gt;     | &lt;code&gt;\Eta&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母H)     |
| $\Theta$   | &lt;code&gt;\Theta&lt;/code&gt;   | $\theta$   | &lt;code&gt;\theta&lt;/code&gt;   | 另一种写法：$\vartheta$ (&lt;code&gt;\vartheta&lt;/code&gt;)                 |
| $\Iota$    | &lt;code&gt;\Iota&lt;/code&gt;    | $\iota$    | &lt;code&gt;\iota&lt;/code&gt;    | &lt;code&gt;\Iota&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母I)    |
| $\Kappa$   | &lt;code&gt;\Kappa&lt;/code&gt;   | $\kappa$   | &lt;code&gt;\kappa&lt;/code&gt;   | &lt;code&gt;\Kappa&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母K)   |
| $\Lambda$  | &lt;code&gt;\Lambda&lt;/code&gt;  | $\lambda$  | &lt;code&gt;\lambda&lt;/code&gt;  |                                                 |
| $\Mu$      | &lt;code&gt;\Mu&lt;/code&gt;      | $\mu$      | &lt;code&gt;\mu&lt;/code&gt;      | &lt;code&gt;\Mu&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母M)      |
| $\Nu$      | &lt;code&gt;\Nu&lt;/code&gt;      | $\nu$      | &lt;code&gt;\nu&lt;/code&gt;      | &lt;code&gt;\Nu&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母N)      |
| $\Xi$      | &lt;code&gt;\Xi&lt;/code&gt;      | $\xi$      | &lt;code&gt;\xi&lt;/code&gt;      |                                                 |
| $\Omicron$ | &lt;code&gt;\Omicron&lt;/code&gt; | $\omicron$ | &lt;code&gt;\omicron&lt;/code&gt; | &lt;code&gt;\Omicron&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母O) |
| $\Pi$      | &lt;code&gt;\Pi&lt;/code&gt;      | $\pi$      | &lt;code&gt;\pi&lt;/code&gt;      | 另一种写法：$\varpi$ (&lt;code&gt;\varpi&lt;/code&gt;)                       |
| $\Rho$     | &lt;code&gt;\Rho&lt;/code&gt;     | $\rho$     | &lt;code&gt;\rho&lt;/code&gt;     | 另一种写法：$\varrho$ (&lt;code&gt;\varrho&lt;/code&gt;)                     |
| $\Sigma$   | &lt;code&gt;\Sigma&lt;/code&gt;   | $\sigma$   | &lt;code&gt;\sigma&lt;/code&gt;   | 另一种写法：$\varsigma$ (&lt;code&gt;\varsigma&lt;/code&gt;)                 |
| $\Tau$     | &lt;code&gt;\Tau&lt;/code&gt;     | $\tau$     | &lt;code&gt;\tau&lt;/code&gt;     | &lt;code&gt;\Tau&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母T)     |
| $\Upsilon$ | &lt;code&gt;\Upsilon&lt;/code&gt; | $\upsilon$ | &lt;code&gt;\upsilon&lt;/code&gt; |                                                 |
| $\Phi$     | &lt;code&gt;\Phi&lt;/code&gt;     | $\phi$     | &lt;code&gt;\phi&lt;/code&gt;     | 另一种写法：$\varphi$ (&lt;code&gt;\varphi&lt;/code&gt;)                     |
| $\Chi$     | &lt;code&gt;\Chi&lt;/code&gt;     | $\chi$     | &lt;code&gt;\chi&lt;/code&gt;     | &lt;code&gt;\Chi&lt;/code&gt; 需要 &lt;code&gt;upgreek&lt;/code&gt; 或 &lt;code&gt;amsmath&lt;/code&gt; (但通常用英文字母X)     |
| $\Psi$     | &lt;code&gt;\Psi&lt;/code&gt;     | $\psi$     | &lt;code&gt;\psi&lt;/code&gt;     |                                                 |
| $\Omega$   | &lt;code&gt;\Omega&lt;/code&gt;   | $\omega$   | &lt;code&gt;\omega&lt;/code&gt;   |                                                 |&lt;/p&gt;
&lt;h3&gt;5. 分数&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;命令&lt;/strong&gt;：&lt;code&gt;\frac{分母}{分子}&lt;/code&gt;
&lt;strong&gt;例如&lt;/strong&gt;：
$$
\frac{x^2+x}{y+1}
$$
正如前文所提到的，只有在写为行间代码时，分式才能完整展开。&lt;/p&gt;
&lt;h3&gt;6. 函数&lt;/h3&gt;
&lt;p&gt;LaTeX 预设了基本函数，如 &lt;code&gt;\sin&lt;/code&gt;, &lt;code&gt;\cos&lt;/code&gt;, &lt;code&gt;\tan&lt;/code&gt;, &lt;code&gt;\log&lt;/code&gt; 等&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] 注意：
&lt;strong&gt;重点&lt;/strong&gt;：&lt;strong&gt;不要&lt;/strong&gt;直接输入 &lt;code&gt;sin(x)&lt;/code&gt;，而要使用 &lt;code&gt;\sin(x)&lt;/code&gt;。前者会被渲染成 $sin(x)$ (变量 s, i, n 相乘)，而后者是正确的函数格式 $\sin(x)$ (字体为正体)。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;7. 根号&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;平方根： &lt;code&gt;\sqrt{表达式}&lt;/code&gt; &lt;strong&gt;例如&lt;/strong&gt;：$\sqrt{x}$&lt;/li&gt;
&lt;li&gt;N 次平方根：&lt;code&gt;\sqrt[N]{表达式}&lt;/code&gt; &lt;strong&gt;例如&lt;/strong&gt;：$\sqrt[3]{x}$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;8. 省略号&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\ldots&lt;/code&gt;：与基线对齐的省略号，如 $a_1,a_2,\ldots,a_n$&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\cdots&lt;/code&gt;：居中的省略号，如 $a_1+a_2+\cdots+a_n$&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;9. 括号&lt;/h3&gt;
&lt;p&gt;使用 &lt;code&gt;\left(...\right)&lt;/code&gt;，获得一个可以自动调整大小的括号，而不是单纯用 &lt;code&gt;()&lt;/code&gt;，区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;直接使用 &lt;code&gt;()&lt;/code&gt;：
$$
f(x) = (\frac{\sqrt{x^2+x}+1}{x+1})^2
$$&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;\left(...\right)&lt;/code&gt;：
$$
f(x) = \left(\frac{\sqrt{x^2+x}+1}{x+1}\right)^2
$$
方括号 &lt;code&gt;[]&lt;/code&gt;、绝对值号 &lt;code&gt;||&lt;/code&gt; 同理&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;10. 矩阵（matrix）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;矩阵&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pmatrix&lt;/code&gt;：圆括号矩阵&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bmatrix&lt;/code&gt;：方括号矩阵&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vmatrix&lt;/code&gt;：竖线行列式&lt;/li&gt;
&lt;li&gt;&lt;code&gt;matrix&lt;/code&gt;：无括号&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;语法：&lt;code&gt;&amp;#x26;&lt;/code&gt; 用于分隔列，&lt;code&gt;\\&lt;/code&gt; 用于分隔行&lt;/li&gt;
&lt;li&gt;示例：
$$
A = \begin{pmatrix}
a &amp;#x26; b \
c &amp;#x26; d
\end{pmatrix}
\quad
I = \begin{bmatrix}
1 &amp;#x26; 0 \
0 &amp;#x26; 1
\end{bmatrix}
$$
&lt;strong&gt;源代码&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-Markdown&quot;&gt;$$
A = \begin{pmatrix}
a &amp;#x26; b \\
c &amp;#x26; d
\end{pmatrix}
\quad
I = \begin{bmatrix}
1 &amp;#x26; 0 \\
0 &amp;#x26; 1
\end{bmatrix}
$$
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;11. 分段函数 (cases)&lt;/h3&gt;
&lt;p&gt;$$
f(x) = \begin{cases}
x^2, &amp;#x26;\text{如果 } x \ge 0 \
-x , &amp;#x26;\text{如果 } x &amp;#x3C; 0
\end{cases}
$$
&lt;strong&gt;源代码&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Markdown&quot;&gt;$$
f(x) = \begin{cases}
	x^2, &amp;#x26;\text{如果 } x \ge 0 \\
	-x , &amp;#x26;\text{如果 } x &amp;#x3C; 0
\end{cases}
$$
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;12. 多行公式对齐：align&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;align&lt;/code&gt; &lt;strong&gt;环境（带编号）&lt;/strong&gt;/ &lt;code&gt;align*&lt;/code&gt; &lt;strong&gt;环境（不带编号）&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;语法&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;在需要对齐的符号前用 &lt;code&gt;&amp;#x26;&lt;/code&gt; 标注&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;\\&lt;/code&gt; 换行&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：
$$
\begin{align*}
f(x) &amp;#x26;= (x+y)(x-y) \
&amp;#x26;= x^2-xy+yx-y^2 \
&amp;#x26;= x^2-y^2
\end{align*}
$$&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-Markdown&quot;&gt;$$
\begin{align*}
f(x) &amp;#x26;= (x+y)(x-y) \\
	 &amp;#x26;= x^2-xy+yx-y^2 \\
	 &amp;#x26;= x^2-y^2
\end{align*}
$$
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="https://picx.zhimg.com/v2-2e9b7ef09c2c67fbd3c9a30b9a764a79_r.webp?source=172ae18b&amp;consumer=ZHI_MENG"/><enclosure url="https://picx.zhimg.com/v2-2e9b7ef09c2c67fbd3c9a30b9a764a79_r.webp?source=172ae18b&amp;consumer=ZHI_MENG"/></item><item><title>Callout怎么用</title><link>https://blog.arronhc.cyou/blog/callout</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/callout</guid><description>callout，用于在文档中创建醒目、突出的标签块所设计的语法</description><pubDate>Wed, 03 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h2&gt;语法&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-Markdown&quot;&gt;&gt; [!关键字]
&gt; 语句
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键字&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NOTE&lt;/code&gt;：注意&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INFO&lt;/code&gt;：信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TODO&lt;/code&gt;：代办&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIP / HINT&lt;/code&gt;：提示/技巧&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SUCCESS / CHECK / DONE&lt;/code&gt;：成功 / 完成&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QUESTION / HELP / FAQ&lt;/code&gt;：问题&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WARNING / CAUTION&lt;/code&gt;: 警告 / 注意&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DANGER / ERROR&lt;/code&gt;: 危险 / 错误&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EXAMPLE&lt;/code&gt; : 示例&lt;/li&gt;
&lt;li&gt;&lt;code&gt;QUOTE / CITE&lt;/code&gt;：引用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!note] 注意事项
注意啦！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!info]
这是一条信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!warning]
警告！不要阅读这条消息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;[!example]&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;print(&quot;Hello World!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;</content:encoded><h:img src="https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/callout.png"/><enclosure url="https://blogppics.oss-cn-beijing.aliyuncs.com/blogpics/callout.png"/></item><item><title>Mermaid图表</title><link>https://blog.arronhc.cyou/blog/mermaid%E5%9B%BE%E8%A1%A8</link><guid isPermaLink="true">https://blog.arronhc.cyou/blog/mermaid%E5%9B%BE%E8%A1%A8</guid><description>mermaid语法的介绍</description><pubDate>Tue, 02 Sep 2025 18:11:51 GMT</pubDate><content:encoded>&lt;h2&gt;介绍：&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Mermaid 是用于&lt;strong&gt;流程图绘制&lt;/strong&gt;的 Markdown&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;示例&lt;/strong&gt;：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;flowchart TB
	A[开始] --&gt; B{判断}
	B -.是.-&gt; C[执行操作1]
	B -.否.-&gt; D[执行操作2]
	C --&gt; E[结束]
	D --&gt; E
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;代码如下 ：&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;flowchart TB
	A[开始] --&gt; B{判断}
	B -.是.-&gt; C[执行操作1]
	B -.否.-&gt; D[执行操作2]
	C --&gt; E[结束]
	D --&gt; E
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码块使用 &lt;code&gt;```mermaid```&lt;/code&gt; 包裹
&lt;strong&gt;解释:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;graph TD&lt;/code&gt;: 定义了一个图表，方向是从上到下 (Top-Down)。你也可以使用 &lt;code&gt;LR&lt;/code&gt; (Left-Right) 从左到右。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A[开始]&lt;/code&gt;: 定义了一个节点 A，文本为“开始”，使用方括号 &lt;code&gt;[]&lt;/code&gt; 表示矩形（默认）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;B{判断}&lt;/code&gt;: 定义了一个节点 B，文本为“判断”，使用花括号 &lt;code&gt;{}&lt;/code&gt; 表示菱形。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C[执行操作1]&lt;/code&gt;: 定义了一个节点 C，文本为“执行操作 1”。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A --&gt; B&lt;/code&gt;: 定义了一个从节点 A 到节点 B 的箭头。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;B --&gt;|是| C&lt;/code&gt;: 定义了一个从节点 B 到节点 C 的箭头，带有标签“是”。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常用节点形状:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[]&lt;/code&gt;: 矩形 (默认)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt;: 圆角矩形&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{}&lt;/code&gt;: 菱形&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(())&lt;/code&gt;: 圆形&lt;/li&gt;
&lt;li&gt;&lt;code&gt;([text])&lt;/code&gt;: 体育场形状&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&gt;&lt;/code&gt;: 旗形&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{{text}}&lt;/code&gt;: 子例程形状&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="https://mermaid.nodejs.cn/hero-chart-dark.svg"/><enclosure url="https://mermaid.nodejs.cn/hero-chart-dark.svg"/></item></channel></rss>