news 2026/10/23 5:04:52

ESP32S3红白机模拟器实战:从选型到自制掌机全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32S3红白机模拟器实战:从选型到自制掌机全记录

这事儿的开头特别简单:我抽屉里躺着两片ESP32S3开发板,一片是拿来调试传感器用的,另一片是因为前一版没调好又买的替补,结果两块一起吃灰。直到有天翻出一台坏了很久的小霸王学习机,才突然冒出一个念头——普通单片机能不能把红白机模拟器跑起来,顺便做成一台能握着玩的掌机?折腾了大概一周多,答案是能,而且效果比我预期好不少。

这篇内容我尽量按“从选型到落地”的顺序写,不堆术语,但该讲清楚的原理一个不落。适合手里正好有ESP32S3开发板、想试一把怀旧游戏的嵌入式玩家,也适合准备做掌机类小项目的朋友。如果你只是想玩NES游戏,那直接装个RetroPie或者网页模拟器更省事,这篇帮不了你;但如果你想在MCU上真正理解模拟器是怎么运作的、想把它做成实体掌机,这篇应该有点用。

1. 一个吃灰开发板,和一整个童年的游戏清单

1.1 这项目是怎么被“逼”出来的

那台小霸王基板坏得挺彻底:电源烧了,RF盒也没了,只剩手柄和拆下来的CPU/PPU芯片还能当纪念品。看着那些老芯片我突然意识到,它们本质上就是一台当时很便宜的计算机,而我现在手头的ESP32S3,主频是那颗老CPU的一百多倍,内存更是天壤之别。理论上没有理由跑不动一个红白机模拟器。

于是我把项目目标定成三件事:能跑常见红白机游戏、能出声、能用手柄操作。第一版用了面包板加杜邦线,屏幕是一块240x240的ST7789,音频用I2S功放小板,按键先拿8个轻触开关凑合。跑通之后才决定做成一个像样点的掌机。

这个顺序我想多说一句。很多人做这类项目容易一步到位,一上来就设计外壳、画PCB、搞锂电池管理,结果核心功能还没通,热情就耗在建模和焊接上了。我的建议永远是:先让游戏跑起来,再谈“产品化”。哪怕画面糊、声音炸、按键像踩键盘,只要PC端模拟器的执行逻辑能在S3上转起来,整个项目就成功了一大半。

1.2 为什么偏要ESP32S3,不拿树莓派或者网页模拟器解决

有人一听“红白机模拟器”,第一反应是“这不就装个RetroPie吗”。确实,树莓派跑全套模拟器是流水线作业,但那不是我这台设备要走的路线。我想做的是“单片机上的模拟器”,而不是“Linux小主机上的模拟器”,这两件事的复杂度和趣味性完全不在一个层级。

回到硬件选型。ESP32S3在我眼里干掉其他选择的原因有这么几条:

  • 双核Xtensa LX7跑到240MHz。红白机模拟器的主战场不是“CPU有多快”,而是“能不能稳定把每帧算完”,双核天然适合把6502模拟和音频/显示刷新拆开。
  • 片内512KB SRAM的底子。虽然跑模拟器仍然紧,但比老ESP32从容。更重要的是它支持大容量外部PSRAM(Quad/Octal),可以把帧缓冲和ROM缓冲放到外扩内存里。
  • 原生USB OTG。这个优势很多人忽略:ESP32S3可以直接接USB手柄,旧的ESP32没有这么干净的支持。
  • SIMD指令和位操作指令对像素处理和音频混音友好,虽然用C写不一定吃到红利,但优化空间摆在那。

我也拿它和几个常见候选对比过,整理成表方便大家对照:

芯片核心规模RAM外部存储原生USB模拟器适配难度
ESP32双核LX6 240MHz520KBPSRAM无中等,可跑但音频I2S驱动旧
ESP32C3单核LX6 160MHz400KB部分型号支持有偏高,单核忙不过来
RP2040双核M0+ 133MHz264KB无官方PSRAM有高,内存太小
ESP32S3双核LX7 240MHz512KBOctal PSRAMUSB OTG顺畅,这篇的主题

RP2040近年在社区也很火,玩模拟器的也不少,但264KB内存想同时放帧缓冲、音频缓冲、ROM缓存是真的捉襟见肘,硬做只能牺牲兼容性。ESP32S3靠着PSRAM一下子把“容器”做大了,这才是它能跑得舒服的根本原因。

1.3 这篇对谁最有用

我给它的定位是:有MCU开发基础但没怎么接触过模拟器原理的人,以及想做掌机硬件但不知道从哪里下手的嵌入式爱好者。如果你完全没写过ESP-IDF代码,建议先花半天把官方hello world点灯跑通再回来,不然中间会有大量“环境问题”和“模拟器问题”混在一起,很难排查。

2. 模拟器不是“跑个压缩包”:FC那套老底子必须门儿清

2.1 FC/NES的根本:CPU、PPU、APU、Mapper

红白机虽然老,但它的架构比很多人想象中复杂。主板上最核心的四块东西是CPU、PPU、APU和Mapper。如果拿人体打比方:CPU是大脑,PPU是视觉皮层,APU是声带,Mapper是大脑加载外部记忆时的“页表开关”。

CPU用的是Ricoh 2A03,本质是一颗6502变种,NTSC制式下主频只有1.7897725MHz,地址空间16位,内部带2KB SRAM,程序放在卡带ROM里,CPU像“读书”一样按地址读指令执行。PPU则是独立的2C02,负责画面生成,它跑在CPU三倍的时钟上,对外输出NTSC视频信号,分辨率256x240。这里有个关键点:CPU和PPU不是共享时钟同步工作的,它们各自跑各自的节奏,靠一组寄存器互相通信。模拟器要处理的就是这种“异步协作”。

APU是2A03内部集成的音频单元,五个声道:两路脉冲波、一路三角波、一路噪声、一路DPCM采样。很多经典游戏BOSS脚步声、爆炸声就是用DPCM播放的采样。Mapper则是卡带上的电路芯片,比如MMC1、MMC3,负责在CPU眼中“切换”卡带里的ROM页。没有Mapper,容量只能卡在极小的范围内;有了Mapper,游戏才能不断换页来加载更多代码和美术素材。模拟器支持得好不好,很大程度取决于Mapper支持得多全。

2.2 一个游戏帧周期里,模拟器到底算了什么

NTSC红白机每秒跑60.0988帧,换算过来每帧大约是29780个CPU周期。模拟器要干的事情,就是在每一帧里把“该发生的都发生一遍”:

  1. 执行6502指令,累计到大约29780周期为止;
  2. 模拟CPU通过寄存器对PPU和APU发控制命令;
  3. PPU侧按扫描线渲染。PPU在每一帧会有262条扫描线,每条扫描线341个周期,其中既有图像输出也有回扫,模拟器要逐像素地决定屏幕上每个点显示哪个色号;
  4. APU侧按目标采样率生成声音,比如44100Hz下每帧要产生大约735个样本,再把五个声道混在一起。

这四件事不是顺次完成的,而是在时间轴上互相交织。这也是为什么模拟器代码看起来“到处都是状态机”:当前执行到第几个周期、PPU扫描到第几行、APU接下来该生成哪个样本,全都耦合在一起。

在PC上写这种模拟器,随便怎么笨办法都能跑满帧。但在MCU上就不一样了:每帧几十万次函数调用、每一次内存访问都要走内存映射、音频样本还得实时计算,这些都是实打实的CPU开销和内存带宽消耗。所以一个“看起来简单”的红白机模拟器,在单片机上其实是性能和内存管理的双重挑战。

2.3 为什么6502核是移植成败的关键

6502虽然指令不算复杂,但寻址模式丰富,流控里还充斥着“跳转”“间接寻址”这些操作。成熟的模拟器通常会把256个操作码做成一张函数指针表,每条指令对应一个处理函数,执行时查表跳转。这种结构在PC上没问题,在MCU上就有函数指针间接跳转导致分支预测失效、缓存不友好的问题。

更耗性能的是内存映射。6502访问不同地址区间时,代码要判断“这个地址是RAM、ROM、寄存器还是外设”,每读一个字节都要走一遍。优化方法无非是页表映射和热点路径直连:比如把Zeropage和内部RAM直接映射到一个快速数组,访问它们时跳过复杂的查表流程。我用“翻译官”来形容这部分:内存和CPU之间隔着一道翻译层,翻译得好不好,直接决定一局《魂斗罗》会不会卡成幻灯片。

我建议想深入研究的读者,先用PC上现成的模拟器源码(比如InfoNES这一卦老项目)把6502执行一遍,加打印日志跟踪几条指令,比直接啃芯片手册快得多。理解了6502内核,后面看任何模拟器工程都不会一脸懵。

3. 用ESP-IDF把模拟器搬到板子上

3.1 移植起点:InfoNES与Retro-Go这类成熟方案

刚接触这个方向的人容易一个猛子扎进“从零写一个NES模拟器”的深坑,我劝你冷静。社区里已经有很好的起点,我这次是从InfoNES下手的:它本来就是嵌入式友好的方向,C代码量不大、结构直接,很容易看懂几个关键文件分别干什么。Retro-Go也是好选择,它已经做了GUI菜单、屏幕驱动和音频适配,只是工程规模大一些,适合想直接“跑通再改”的人。

先说实用结论:想快速出成果,选Retro-Go,编译完基本能进菜单;想学习原理,选InfoNES自己动手接显示和声音。如果拿InfoNES起步,你基本要自己补这几块:把它的输出画到SPI屏幕上、把APU输出数据用I2S丢给功放、把按键状态填到它预留的内存地址里。

这里容易踩的第一个坑就是“老工程的接口适配”。很多经典模拟器源码当年是基于ESP32老SDK写的,API还是老版I2S驱动那一套。拿到ESP32S3 + ESP-IDF v5.x上,编译报错一大堆很正常,别急着怀疑工具链,多数是接口变了。

3.2 构建环境:IDF版本、PSRAM、分区表

我这次用的是ESP-IDF v5.2,官方最新版本之一,驱动和组件库相对干净。安装好环境后,把工程clone下来,第一件事还不是编译,是设置芯片和内存参数。下面这几个菜单项是我每次都会核对的:

  • idf.py set-target esp32s3,把目标芯片设为S3;
  • 开启PSRAM支持:在menuconfig里找ESP32S3-Specific → Support for external, SPI-connected RAM,打开;
  • 如果外挂PSRAM需要缓存访问,打开Cache for external SPI RAM选项。注意这会消耗一点片内总线带宽,但对模拟器来说利远大于弊;
  • 检查flash大小和分区表。至少要保证app分区有2MB以上,否则固件都塞不下;
  • 如果屏幕是SPI接口,在组件配置里把对应的LCD驱动勾上,省得自己重复造轮子。

为什么要这么在意PSRAM?因为S3片内512KB SRAM看着不小,但你的固件代码、堆栈、音频缓冲、PPU状态、帧缓冲全挤在一起,分分钟爆掉。把RGB565格式的帧缓冲放到PSRAM里,屏幕数据有了着落,游戏ROM的载入缓冲也能放松很多。这个决定能救你一整天的命。

3.3 烧录与验证:该看哪些日志

S3烧录有两条主流路径:一条是板载USB转串口,接入后设备名类似/dev/ttyACM0或串口号,另一条是走ESP-PROG之类的调试器。普通玩家用USB口就够了。烧录命令就三行:

idf.py build idf.py -p /dev/ttyACM0 flash idf.py -p /dev/ttyACM0 monitor

烧录时需要注意:如果电脑没识别到芯片,按住开发板的BOOT键再上电多半能解决。进入monitor后我习惯先看两行日志:一行是模拟器初始化的提示,类似NES init OK;另一行是文件系统挂载情况。如果启动直接崩溃,先用idf.py monitor抓backtrace,绝大多数情况下不是栈溢出就是内存越界,看到这几个字先查帧缓冲分配。

4. 双核240MHz的性能账:优化路径与真实帧率

4.1 双核分工:哪条核算CPU,哪条核画图

ESP32S3双核是我觉得它适合做模拟器的最大原因。我的做法是把模拟器主循环固定钉在一个核上,另一个核专门处理外设。分工大概是:

  • Core1:跑6502主循环和PPU渲染,生成一帧画面;
  • Core0:跑音频采样生成、输入轮询、屏幕刷新调度这些杂活。

用xTaskCreatePinnedToCore把两个任务分别钉到0核和1核,避免FreeRTOS调度器把它们来回搬。音频任务和模拟器主核之间会共享一个缓冲区,我用了简单的环形缓冲加互斥锁,模拟器每生成一批音频样本塞进队列,音频任务按固定节拍取走送往I2S。这个缓冲设计是容易出问题的地方:缓冲区太大,延迟感明显;缓冲区太小,爆音不断。我最终在44100Hz采样率下试了512样本一Buffer,折中效果挺好。

4.2 显示通路:色彩表、缩放与SPI推送

红白机的画面本质是色号索引,不是RGB值。PPU输出的是调色板索引号,模拟器拿到后要转成屏幕能显示的颜色。这里有个实用优化:建一张把FC标准64色调色板映射到RGB565的查找表,直接在渲染时套表,而不是每次算颜色。虽然听起来是个很小的优化,但在逐像素循环里能省下不少乘法和条件判断。

缩放方面,常见目标屏是240x240,原画面256x240。最简单的方案是最近邻缩放到240x240,也就是砍掉左右各8像素的边。我知道有人会觉得“砍画面是不是亏了”,但实际玩起来,被砍掉的部分大多是电视时代的安全区域边缘,对游戏性影响很小。如果实在介意,也可以上320x240的ILI9341屏,做整数倍点对点放大。

SPI推屏是另一个关键点。不要用单字节轮询方式往SPI屏写数据,一定要用DMA。ESP-IDF的SPI驱动支持在事务里挂发送缓冲区,让数据在后台搬运,CPU可以继续干模拟器的活。实测在40MHz SPI时钟下,240x240全屏刷新大约能到50到60帧,刚好压在主机帧率附近。有的屏支持80MHz时钟,效果更好,但我建议先稳定跑满60,再尝试拉高。

4.3 ROM兼容性大概在什么水平

这个问题大概率是你最关心的。我实测下来,凡是用NROM、Mapper 0、Mapper 1、Mapper 2、Mapper 3这些常见方式做的游戏,基本都能流畅跑。魂斗罗、超级马里奥、坦克大战、冒险岛,这些不用特别复杂的Mapper,跑起来很稳。支持MMC3(Mapper 4)的话,兼容面能扩大一大截,很多后期大作都依赖MMC3。

但不要抱有过高期望:VRC6、VRC7这类带额外音源芯片的卡带,很多轻量模拟器是不支持或支持不全的,声音会缺失或错乱。还有部分是网络流传的修改版ROM,很多作者为了加功能改了Mapper设置,跑不动多半不是模拟器的问题。

我自己的做法是建了一个“兼容性清单”表格:游戏名、Mapper号、帧率、有无声音异常、有无花屏,每次改完代码跑一遍清单。这个习惯帮我节省了大量“上次还能玩这次怎么花屏”的定位时间。

5. 声音和按键:让童年完整的另外一半

5.1 I2S音频输出选型与接线

ESP32S3片内没有传统DAC,输出声音最省心的方案就是外接I2S音频设备。我试过几种,最终稳定用的是MAX98357A,这是一颗I2S数字输入功放芯片,直接接喇叭就行,无需额外DAC和功放电路。接线也简单:BCLK接GPIO、LRCLK接GPIO、DIN接GPIO,再接好电源和喇叭。如果只想接耳机,也可以换用PCM5102这类I2S DAC,把输出接到耳机插座。

要特别提醒的是:ESP-IDF v5.x换了新的I2S驱动,接口名字都改了,以前i2s_driver_install那套老API已经淡出主流程。现在用的是driver/i2s_std.h里的一套标准I2S接口,你先用官方example把正弦波放出来,再接入模拟器的APU数据。很多人一上来就把模拟器声音糊上去,结果没声,排查半天发现是驱动接口没写对。

5.2 一次“数据全是峰值”的排查:I2S配置错误怎么找

我印象很深的一件事,是有个群友在调INMP441接ESP32S3时,发现读回来的音频数据“一直都是峰值1”,也就是样本值打满。这种问题其实不是麦克风坏了,绝大多数是I2S配置错了。虽然他是录音方向,我是播放方向,但排查思路完全通用。

I2S的数据峰值问题,常见原因就这几类:

  • L/R声道选择脚悬空。INMP441这类芯片用L/R引脚的电平决定输出到左声道还是右声道,悬空会导致电平不定,数据错乱;
  • slot配置不匹配。驱动里如果按双声道去读,而麦克风实际只输出单声道,会让无数据的那一路读到全1之类;
  • BCLK和WS极性问题。不同芯片对时钟极性约定不同,极性和数据错位后读出来的值自然不对。

排查链路我自己总结为“先时钟,后数据,最后才怀疑软件”。先用逻辑分析仪看BCLK和WS有没有正常波形,再通过驱动把原始数据打印出来观察,最后用官方example的收发环回验证接线。放到模拟器音频输出上同理:先放正弦波、再放混音数据,能迅速锁定是硬件通路还是软件逻辑的问题。这个方法我建议你直接记下来,以后做任何I2S音频项目都用得上。

5.3 按键映射:FC手柄的移位寄存器逻辑与直连实现

FC手柄内部是一颗CD4021并转串移位寄存器,8个按键对应8位数据,主机用轮询方式从数据线上一位一位读出来。模拟器在做的事情其实很简单:把8个按键状态塞到内存地址0x4016和0x4017对应的寄存器位里,游戏自己会去读。

实现在S3上,我试过几种方案:

  • GPIO直连:8个按键接8个IO,死区时间和去抖都在代码里做,最简单可靠;
  • 行列矩阵扫描:适合按键超过8个的情况,但扫描会占用一点CPU时间;
  • I2C扩展IO:省引脚但延迟大,手感容易发闷;
  • USB手柄:S3原生USB OTG,可以接USB HID手柄,这个体验很爽,适合做成品。

去抖逻辑我习惯用10到20毫秒的软件策略:每2到5毫秒采一次,连续两次一致才认为键状态稳定。模拟器读取按键的时机也很重要,最好每帧开始时就采样一次当前状态,然后整个帧内用同一份键值。千万不要把按键做成“按下触发一次事件”的模型,FC游戏要的是“状态”,不是“事件”。

6. 从裸板到能握在手里的掌机

6.1 屏幕怎么选:分辨率、接口、刷新

屏幕是掌机体验的胜负手。我的建议是优先选1.3寸或1.54寸的240x240 IPS屏,驱动芯片多半是ST7789,接口是4线SPI。这个尺寸在逻辑上正好接近NES的4:3视觉比例,装在自制外壳里也不突兀。2.4寸320x240分辨率看起来更爽,但整机尺寸和功耗都会上去,建议第二版再考虑。

刷新率方面,ST7789的SPI屏在40MHz时钟下表现不错,驱动做好DMA就能维持稳定。屏幕初始化序列目前各家驱动板子基本通用,如果你用的是现成的模块,厂商给的初始化序列先跑通,再谈优化。千万别一上来就挑剔“扫描方向不对”这类小问题,那是最后打磨阶段的事。

6.2 供电与续航:从USB线到18650

原型阶段用USB线供电完全没问题,一根线连着电脑,刷机调试也方便。要做到手持阶段,就得考虑电池。我推荐的组合是18650电池加TP4056充电板,再通过一颗升压模块稳定输出5V给屏幕和功放。ESP32S3本身能接受3.7V直接供电,但屏幕背光和MAX98357A功放需要稳定电压,所以升压是必要的。

IP5306这块集成充电和升压的芯片我也试过,它确实能把电路做得紧凑,但它的寄存器控制逻辑有点绕,不适合赶进度。如果你像我一样想快速出成品,老老实实TP4056加独立升压模块,少跟那些“智能”电源芯片较劲。实测整机满亮度打游戏大概在300到400毫安,一颗2000毫安时的18650能撑四五小时,续航完全够用。

6.3 ROM管理与游戏列表

掌机不能只有一个游戏,所以文件系统管理是躲不开的一环。两种路线:SD卡用SPI模式挂FAT文件系统,或者在外部Flash里划一个FAT分区。个人推荐SD卡,换游戏方便,还能直接插电脑管理ROM。

我的实现步骤是:启动时挂载SD卡,扫描/roms目录,构建游戏列表;进入菜单选择游戏后,读出文件头里的Mapper信息,再做CRC校验,最后装载到PSRAM里运行。这里有个小细节:ROM文件名最好用英文或拼音,中文字符在FAT编码和菜单字体渲染上都会引入一堆麻烦。另外每个游戏运行时分配的内存要记得在退出时完整释放,我第一版没做干净,玩了三四个游戏后内存碎片化导致随机崩溃。

7. 这些坑,我帮你提前踩过了

7.1 只信自己的抓取:ROM文件与校验的坑

网上流传的ROM质量参差不齐,很多是改版、汉化版、Bad Dump。我一开始随便下了几个,结果有的花屏、有的打到一半死机,差点让我以为S3性能不够。后来用工具仔细看了文件头才发现,有些文件连NES\x1a开头都错了,PRG/CHR容量也和实际不符。从这以后我养成了习惯:ROM拿到手先校验文件头,再核对Mapper配置。开发调试期间,我基本只用自己从正版卡带里导出的文件或者公开的测试ROM,这既是对版权的尊重,也省去“坏文件导致误判”的麻烦。

7.2 从“能跑”到“好玩”:两步走路线

项目做到最后,我想强调的还是节奏问题。第一步,面包板上把游戏跑起来,哪怕声音是从小喇叭里“嘶嘶”出来的,你已经赢了;第二步,才考虑建模、做板、电电池、调手柄布局。很多人在第一步和第二步之间卡住,不是技术不行,是想一口气吃成胖子。我在这个项目里也画废了两版外壳,最后是用瓦楞纸加热熔胶先凑出一个能用按键的轮廓,确认了按钮位置手感没问题才去重新建模。

另外一个小经验:如果你的目标是“掌机”,不要在同一份固件里顺手加麦克风录音、传感器云上报这些额外功能。功能拆开做,每个模块独立验证,再合到一起。我自己就试过把模拟器和I2S麦克风录音放在同一个工程里折腾,结果两边互相干扰,排查到头大。后来学乖了,模拟器归模拟器,其他项目归其他项目,一个工程只做一件事。

说到底,ESP32S3红白机模拟器这个项目,本质上是在“资源刚刚够用”的边界上做一套实时的多任务系统:CPU模拟、PPU渲染、音频生成、输入扫描、屏幕刷新全都要挤在每一帧的时间里。跑通不是终点,把帧率稳定住、声音调干净、手感调舒服,才是这个项目真正值回票价的地方。如果看完你也翻出了吃灰的S3开发板,别犹豫,先让《魂斗罗》跑起来再说。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 19:03:57

Superpowers:让AI编程助手从聊天到动手干活的技能扩展框架

1. 从“superpowers”这个标题说起:它到底是什么第一次看到“superpowers”这个词,很多人脑子里蹦出来的可能是超级英雄电影里的超能力,或者某个游戏里的技能系统。但如果你最近在开发者社区、技术群或者代码托管平台上频繁刷到这个词&#x…

作者头像 李华
网站建设 2026/10/18 19:03:56

基于微信小程序的学生信息管理系统开发指南

很多计算机专业的学生大四上学期就开始头疼毕业设计选题了,找了一圈发现题目不是太简单(比如做个静态网页)就是太难(比如搞什么深度学习平台)。如果你正在找“既能过盲审、又好实现、还能在答辩时拿得出手”的题目&…

作者头像 李华
网站建设 2026/10/18 19:03:56

从零搭建分布式日志系统:EFK架构设计、部署与实战排查

日志排查这摊事儿,我入行前几年一直靠三招硬扛:SSH连服务器、grep关键词、肉眼翻文件。服务少的时候这套组合拳够用,等服务规模上来,业务实例分散到几十台机器甚至多个机房,再想查一个问题,就得一台一台跳上…

作者头像 李华
网站建设 2026/10/18 19:03:57

大模型分布式训练:并行策略、通信优化与工程实践

如果你亲手训过大模型,一定经历过那种半夜盯屏的焦虑:显存又爆了,显卡利用率忽高忽低,日志里NCCL直接给你一个超时错误。单张H100的显存和算力在普通场景下已经很强,但放到7B、13B甚至70B参数面前,真的就像…

作者头像 李华
网站建设 2026/10/18 20:58:07

Python中可变对象与不可变对象详细教程

前言 "可变"与"不可变"是理解 Python 数据模型的第一道坎,也是最容易产生诡异 bug 的地方:明明只是把列表传进函数"改一下",函数外面的列表却也跟着变了;明明写了 a b 然后改 a,b 却纹…

作者头像 李华
网站建设 2026/10/18 20:58:07

Python中函数的创建及调用

前言 函数在 Python 里有两种诞生方式:用 def 起名创建,或用 lambda 匿名创建。创建之后,它就是一个普通的对象——可以赋值、可以放进列表、可以当参数传给别的函数、也可以被函数返回。调用则更丰富:按位置调、按关键字调、用 *…

作者头像 李华