编程语言杂谈

大家好啊,今天来点大家想看的东西啊。

世界上有很多门编程语言,每门语言都有着自己的优缺点,使用场景也各不相同。国内教学一般会选择 C 语言作为入门语言,后面再教 Java、Python 等。自己写了这么久的代码,接触过不少语言,总归是有一些想法的,在此分享一下。注意,本文属于杂谈,行文较为随意。

我是初中开始对编程感兴趣的,于是购买了《C 语言从入门到精通》这本书 —— 然后写完 Hello World 就再也没看过了。只能说还是太小吧,思考能力还是不够,以及教材本身的落后。落后在哪我真的很难说,国内的大多数教材总是严重滞后于生产实际,这大家有目共睹。就开发工具来说,书中推荐的是 Visual Studio 6.0,我嘞个老东西啊。

回到 C 语言本身,现在来谈一些缺点。是的,我其实并不喜欢 C 语言,虽说是高级语言,但它离底层实在是太近了,我觉得有点不够“高级”。

字符串

C 语言中字符串的定义是这样的:

char s[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 定义长度为 6 的字符串
char s2[5] = {'H', 'e', 'l', 'l', 'o'};      // 定义长度为 5 的字符串,未以 '\0' 结尾

在学 C 的时候,我就觉得字符串的处理实在是太麻烦了。C 语言中没有专门的字符串类型,字符串其实就是字符数组,结尾用一个特殊的空字符 \0 来标识结束。为什么?因为 C 语言是支持越界访问数组的,没有 \0 就不知道字符串在哪结束。

我们来对比一下 Python 中用 list 来模拟字符串的例子:

s = ['H', 'e', 'l', 'l', 'o']                # 定义长度为 5 的字符串

如果我们访问第六个元素 s[5],Python 会抛出一个 IndexError 异常,告诉我们索引超出了范围。而在 C 语言中,访问 s[5] 我们能知道字符串该结束了,而访问 s2[5] 直接就是 未定义行为 了,很可能出现安全漏洞和崩溃。

YouTube 视频 The worst programming language of all time 喷了两个小时的 C++,我要笑死了。

其实,正如 Rust 程序设计语言 所说,字符串并不简单。你还需要去处理编码、Unicode 等问题,我想很少有人没有见到一堆乱码。

修改传入的参数

先说结论:我坚决反对修改传进来的值。这种做法既不直观,也不安全,更违背了函数式编程的基本原则。

export const authOptions: NextAuthOptions = {
  callbacks: {
    jwt({ token, user }) {
      // The arguments user, account, profile and isNewUser are only passed the first time this callback is called on a new session,
      // after the user signs in. In subsequent calls, only token will be available.
      // token : The current JSON Web Token
      // user : The user object returned by the callback for the session (only available on first sign in)
      if (user) {
        token = { ...token, ...user };
      }
      return token;
    },

    session({ session, token }) {
      if (session.user == undefined) {
        session.user = {};
        session.user.name = token.name;
      } else {
        session.user.name = token.name;
      }
      session.step = token.step;
      session.secretCode = token.secretCode;
      session.accessToken = token.accessToken;
      session.refreshToken = token.refreshToken;
      session.mfaSession = token.mfaSession;
      return session;
    },
  },
};

在 C 语言中,参数传递有两种方式:按值传递和按指针传递。按值传递还好,至少不会影响到原始数据。但是按指针传递就麻烦了,你永远不知道一个函数会不会偷偷地修改你传进去的参数。

void mysterious_function(int *arr, int size) {
    // 这个函数做了什么?
    // 它会修改数组内容吗?
    // 只有看实现才知道!
}

这种设计带来的问题是显而易见的:

  1. 可读性差:仅从函数调用无法判断参数是否会被修改
  2. 调试困难:当数据被意外修改时,很难追踪到是哪个函数干的
  3. 副作用泛滥:函数不再是纯函数,而是充满了副作用的“黑盒”

更要命的是,C 语言没有 const 关键字的严格限制(早期版本),也没有明确的输入输出参数标记。你拿到一个函数,完全不知道它会对你的数据做什么。

undefined 还是 null?

在 JavaScript/TypeScript 中,我们有 undefinednull,他们到底有什么区别?我从接触到 JavaScript/TypeScript 开始就一直搞不清楚。

直到我认识到 Map 这个数据结构,才在获取值时发现了两者的区别:

糟糕的 NullPointerException

Java 语言中,NullPointerException(简称 NPE)是最常见的运行时错误之一。NPE 通常发生在你试图访问一个为 null 的对象的成员或方法时。

String str = null;
int length = str.length(); // 这里会抛出 NullPointerException

NPE 的问题在于它通常是在运行时才被发现的,这使得调试变得非常困难。更糟糕的是,NPE 可能会在程序的任何地方发生,导致程序崩溃或产生不可预期的行为。