跳转至

如何接触到编程的

DOS 批处理脚本

最初接触到编程这个概念是从小学期间订阅的一本杂志里, 当中讲述了如何编写一个简单的 DOS 批处理脚本.
但当时只是对计算机编程产生了一点印象并没有打算实际上手操作.

后来玩了款名为 Unturned 的游戏, 因为其不具备云存档功能所以上网查找本地导出存档的方法, 下载了一个可以帮助自动导出存档的程序.
在使用时意外发现这个程序的右键菜单中多了一个 "编辑" 选项, 打开后发现里面有运行时出现的内容, 便尝试进行修改, 发现重新运行程序后修改会生效.
后面才知道这个程序就是一个 DOS 批处理脚本, 大概从这时起开始查询一些命令, 并尝试编写 DOS 批处理脚本.

用 DOS 批处理脚本实现了一些自己感兴趣的程序, 以下列出几个印象较深的程序:

  • 助手: 可以打字与其对话, 程序会调用 Windows 的 TTS 功能使用语音回答. 但由于实现分词等功能较为复杂, 难度超出预期, 最后开发终止.
  • 扫雷: 在编写非数字时自动揭开周围方块的时候使用到了一个递归算法, 后来才知道这个算法叫做深度优先搜索.
  • 远程 Shell: 利用了杀毒软件对 DOS 脚本不敏感的缺陷, 实现了可以绕过杀毒软件的远程 Shell. 为了不依赖第三方可执行文件, 通讯使用的是系统自带的 FTP 命令.

通过精妙的设计和使用一些高级的语法, DOS 批处理脚本也能完成许多复杂的操作. 当时有两个 DOS 批处理脚本给我留下了深刻的印象:

  • 使用 ASCII 字符显示一个会旋转的 3D 球体.

    @echo off & setlocal & title Sphere 3D & set /a cols=62, lines=62 & goto :Init_system
    
    :: By einstein1969. Dedicated to jeb, dbenham, penpen, carlos, aGerman, Aacini, EdDyreen, 
    :: npocmaka_, Liviu, Sponge Belly, Magialisk, the users and the staff of Dostips forum.
    
    :: Use raster font 8x8. 
    
    :Main
    
    set /a ar=100, rt=0, ds=0, cx=cols/2, cz=4000, cY=lines/2
    
    (
        set SIN=
        set _PLOT$_=
        set _$PLOT_=
        set _empty=
        set lines=
        set cols=
    
        for /L %%\ in (1000,-1,0) do (
            set /a "rt+=31416/60"
    
            if !ds! lss 1000 set /a ds+=10
    
            setlocal
    
            set /a "a=(15708-rt) %% 62832, c=(a>>31|1)*a"
    
            if !c! gtr 47124 (set /a "a=a-(a>>31|1)*62832, b=%SIN%, a=rt %% 62832, c=(a>>31|1)*a")  else (if !c! gtr 15708 (set /a "a=(a>>31|1)*31416-a, b=%SIN%, a=rt %% 62832, c=(a>>31|1)*a") else set /a "b=%SIN%, a=rt %% 62832, c=(a>>31|1)*a")
            if !c! gtr 47124 (set /a "a=a-(a>>31|1)*62832, a=%SIN%")  else (if !c! gtr 15708 (set /a "a=(a>>31|1)*31416-a, a=%SIN%") else set /a "a=%SIN%")
    
            for %%f in ("0 9999" "-5000 8661" "-8661 5000" "-9999 0" "-8661 -5000" "-5000 -8661" "0 -9999" "5000 -8661" "8661 -5000" "9999 0" "8661 5000" "5000 8661") do for /f "tokens=1,2" %%g in (%%f) do (
                for %%t in ("3827 9239" "7071 7071" "9239 3827" "9999 0" "9239 -3827" "7071 -7071" "3827 -9239") do for /f "tokens=1,2" %%u in (%%t) do (
                    set /a "ax=ar*%%h/10000*%%u/10000, az=ar*%%g/10000*%%u/10000, ay=ar*%%v/10000, aax=(ax*b/10000-(ay*a/10000+az*b/10000)*a/10000)*b/10000-(ay*b/10000-az*a/10000)*a/10000, aay=(ax*b/10000-(ay*a/10000+az*b/10000)*a/10000)*a/10000+(ay*b/10000-az*a/10000)*b/10000, e=ax*a/10000+(ay*a/10000+az*b/10000)*b/10000, c=ds*aax/(e-cz)+cx, d=ds*aay/(e-cz)+cy"   
    
                    if not defined L!d! set L!d!=%_empty%
    
                    if !e! lss 0 (%_$PLOT_% !c! !d! 1 %_PLOT$_%) else %_$PLOT_% !c! !d! 4 %_PLOT$_%
                )   
            )
    
            if not "!OT!"=="!time:~-1!" (
                cls & (For /L %%l in (1,1,%lines%) do if not defined L%%l (echo() else echo( !L%%l!)>CON
                if "!OT!"=="0" title Sphere 3D [%%\]
                endlocal
                set OT=!time:~-1!
            ) else endlocal
        )
    )
    goto :eof
    
    :Init_system
    
    setlocal DisableDelayedExpansion
    
    set /a cc=cols+2, ll=lines+2
    (
        mode %cc%,%ll% & cls
        for /F "Tokens=1 delims==" %%v in ('set') do set "%%v="
        set /a cols=%cols%, lines=%lines%
    )
    set "_$PLOT_=For /F usebackq^ tokens^=1-3 %%x in ('"
    set "_PLOT$_=') do set /a f=%%x+1 & For %%w in (!f!) do set L%%y=!L%%y:~0,%%x!!g:~%%z,1!!L%%y:~%%w!"
    
    set "SIN=(a-a*a/1920*a/312500+a*a/1920*a/15625*a/15625*a/2560000-a*a/1875*a/15360*a/15625*a/15625*a/16000*a/44800000)"
    
    setlocal EnableDelayedExpansion
    
    For /L %%l in (1,1,%cols%) do set "_empty=!_empty! "
    
    set g= .±²@"
    
    Goto :Main
    
  • 逐渐绘制一个由曲线组成的看上去像 3D 的物体, 绘制需要一段时间.

这两个程序都比较简短, 大概在 100 行以内.

在网上认识了一位热心的网友, 帮忙解决了许多 DOS 批处理脚本编写的问题, 他平时主要使用的是 C 语言, 他说过学习编程的话 C 语言是一个更好的选择.
但我当时只是觉得 DOS 批处理脚本够用, 便没有去了解 C 语言, 对实际生产中所使用的编程语言没有概念.

C 语言

大概初三的时候, 开始学习 C 语言. 最初的目的和信息竞赛有关, 但最终并未参加相关比赛.
入门时主要的学习方式是通过一套 Bilibili 上找到的教学视频, 现在对视频的开头和结尾还有印象.
视频是使用PPT+实践的方式来进行教学的, PPT讲解完就编写代码并展示执行结果, 内容十分紧凑, 毫不拖沓.

C++ 语言

后来我打算学习一门新的编程语言, 当时只知道 Java 和 C++. 最终选择了后者, 原因有:

  1. 听说 C++ 很难, 我想要挑战.
  2. 我有 C 语言基础.

对 C++ 的最初印象来自一款手机应用程序, 展示各种语言的 Hello world 代码并让用户判断是什么语言.
和其他语言对比起来, C++ 使用的输入输出流看上去十分特殊, 无论是当时还是现在我都觉得这语法十分的丑陋, 好在 C++23 引入了 std::print, 此后 C++ 的 Hello World 将使用更加现代的语法编写.

C++ 的标准库提供了更丰富的功能, 在处理各种操作时 (如字符串处理) 远比 C 更简单和安全.
在 C 语言里, 许多简单的操作都需要自己实现, 这会在增加工作量的同时引入更多的错误.

在编程范式方面, C++ 是多范式的编程语言, 而 C 是面向过程的编程语言. 其中 C++ 的面向对象编程范式正是我在使用 C 语言时求之不得的东西.

C++ 这门语言最早出现于 1985, 有着沉重的历史包袱.

  • n + 1 问题:

    C++ 与时俱进, 不断引入新的特性. 但却难以抛弃旧的特性.
    虽然在独立项目中, 开发者可以选择自己喜欢的特性, 但在多个合作的项目里, 难以避免旧的特性依然被使用.
    这意味着开发者不能只学习最新的语言标准和特性, 还需要学习旧标准和旧特性.

    C++ 兼容大部分 C 代码本身一件好事, 但如却导致部分代码 C 和 C++ 代码混用的情况, 使得代码风格迥异.

  • 未定义行为 (undefined behavior, UB):

    int f(bool b)
    {
        int x;               // OK: the value of x is indeterminate
        int y = x;           // undefined behavior
        unsigned char c;     // OK: the value of c is indeterminate
        unsigned char d = c; // OK: the value of d is indeterminate
        int e = d;           // undefined behavior
        return b ? d : 0;    // undefined behavior if b is true
    }
    
  • 整数类型:

    C++ 中整数类型 (除 .?int\d+_t) 的大小的不确定的. 这意味着在编写跨平台的代码时 int 的最大值和最小值不是一个常量. 为了获得大小固定的整数类型还需要包含头文件 <cstdint>.

  • 没有标准的构建系统:

    调用编译器编译代码是最直接的方式, 但这种方法难以跨编译器和平台, 而且只适用于简单的小项目. 被广泛使用的构建系统有很多, 甚至出现了跨构建系统的 CMake.
    可以生成不同构建系统的文件, 然后再通过构建系统来调用编译器和链接器, 最终生成可执行文件.
    CMake 本身不附带各种编译器和构建系统, 因此需要用户自己安装. 部分项目的 CMakeLists.txt 甚至具有时效性, 导致年久失修的项目无法构建.

  • 依赖管理困难:

    与其他更现代的编程语言相比, C++ 缺少自带的包管理器, 这使得库之间的依赖管理处理变得尤为棘手.
    库之间的调用更像是直接混合代码, 这导致了在添加依赖项时必须涉及到构建系统的配置.
    虽然存在诸如 vcpkgconan 这样的第三方包管理器, 但在使用过程中我仍然频繁的遇到依赖项构建错误, 导致项目无法继续编译.
    尽管我曾尝试向包管理器提交问题, 并最终得到了修复, 但这个过程往往耗时较长. 依赖项的构建错误经常中断游戏引擎的开发进程,我甚至尝试过混合使用 vcpkg 和 conan.
    因为这样可以把单个依赖项在单个包管理器中出故障的概率降低为单个依赖项在两个包管理器中出故障的概率, 这几乎可以解决包管理器自身导致的错误, 但对于依赖项本身存在的问题却无法解决.

    大量的时间被耗费在了构建上, 使我无法专注于项目本身的开发, 这是我最终选择放弃将 C++ 作为首选语言的主要原因.

寻找新的语言

放弃 C++ 意味着我需要学习一门新的语言.

  • Python: 相比 C 语言, 我觉得 Python 是一门语法十分复杂的语言. 因为之前运行 Python 脚本经常遇到报错, 我对 Python 的印象并不好. 在加上我之前使用 C++ 开发过的项目, 很多无法使用 Python 来实现, 因此我没有选择这个语言.
  • Java: 学习了 C++ 后再回头看 Java, 我觉得 Java 的代码有大量的冗余, 并不美观. 比如喜欢使用单词全屏而非缩写, C++ 则倾向用符号来表示, 十分简洁. 在我看来, 对于经验丰富的开发者来说, 使用冗长的全拼单词是多余的.
  • Carbon: 虽然它声称兼容 C++ 代码, 但由于其尚处于实验阶段且缺乏可用的编译器. 而且它的代码风格与 C++ 迥异.

TODO

Rust 语言

TODO

评论