0%

不能承受的生命之轻

从C++入TS

“白海豚”在大地上淫威着,兵荒马乱般的,将狼藉留作了给人们的恶作剧。黑暗的空调房成为我暂时的庇护所。为了理解和学习最入门的ai agent的mcp服务的搭建和微调,在C++数据结构学习之余,我开始着手速通TS。
首先从TS的特性说起,它是一门语言:

1
2
3
4
5
6
7

**1.** javascript的超集,
**2.** 静态类型,在编译期进行类型判断,不用等运行时;
**3.** 类型推断,TS 编译器可以从赋值或返回值推断变量类型,不用手写类型;
**4.** 结构化类型:看”形状”不看名义,只要结构匹配就能够赋值,不需要显式继承;
**5.** 类型仅存在于编译器,编译后即为纯JS,运行时零开销。

也许是因为学习进度的问题,我在TS接触到了C++中没有接触过的内容(我在开始接触数据结构的学习也是速通的C转的cpp,没有系统学过cpp,可能对于类和继承这些并没有涉及,本着用什么就学什么的实用主义,倒也省了许多时间),也可以算是一种新的特性,就是类的封装和接口。这个在平时刷题的时候都被Leetcode巧妙地隐藏了,所以即使是刷算法题,也感知不深。

首先我们看其中TS最标志性的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

type Gender = 'man'|'woman';

/* const gender: Gender = 'woman'; (本条仅供调试使用) */

interface User{
name: string;
age: number;
id: number | string /*这里还可以填自定义的类型,“|”表示联合类型*/;
gender: Gender;
}
class UserImpl implements User{
constructor(
public name:string,
public age:number,
public id:number | string,
public gender: Gender,
)/*
这一段函数体可以留空,TS会帮你填好
{
this.name = name;
this.age = age;
this.id = id;
this.gender = gender;
}*/
}

首先我们看最表面的一层,就是变量类型声明。与C++不同,TS的类型声明在变量之后,使用 “:” 隔开。常见的变量类型有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

基础类型

  number / string / boolean / null / undefined / symbol / bigint / void

 特殊类型

      1.any       → 放弃检查(应急逃生口,应少用)

      2.unknown   → 安全版 any,使用前必须窄化

      3.never     → 永不返回(抛异常/死代码),也做穷尽性检查

字面量类型

        'man' | 'woman'   → 值只能是列举出的那几样(见"类型组合")

其中字面量类型包括:联合类型和交叉类型,这个可以联想到我们在高中学习过的集合交并运算。联合运算符 “|” 等价于数学上的 “∪∪” ,交叉运算符 “&” 等价于数学上的 “∩∩” ,通过这个我们完成了对 “Gender” 的定义。

然后我通过 “interface” 规定了 “User” 应该有哪些内容:姓名是字符串,年龄是数字,性别只能从前面定义的两种值里选。这样以后用到用户信息时,就有了一份可以对照的要求,少写了字段、把类型填错了,编辑器通常就会提醒。不过它只负责检查代码,后端实际传来的数据是不是符合要求,还需要另外检查。

刚接触的时候,我很容易把它和C++里的 “struct” 联系起来,毕竟看上去都是把几个成员放在一起。但仔细想想,两者做的事情并不一样:C++的struct可以用来创建对象,里面也可以写方法;TS的interface只是把要求列出来,本身不能拿来创建对象,编译成JS之后也就消失了。真正存放用户信息的,可以是直接写出来的一个普通对象,也可以是通过class创建的对象。普通对象里面同样可以放函数,并不是非得写一个类才能有方法。

那么有了interface,为什么还要写class呢?拿上面的例子来说,User只规定用户信息应该是什么样子,UserImpl则负责把这些信息存进创建出来的对象里。如果以后需要给用户增加一些方法,也可以在类里实现。这里的 “implements” 就是让编译器检查这个类有没有满足接口的要求,具体怎么做,还是要自己写。接口也能规定一个方法接收什么参数、返回什么类型,只是不会替我们把方法写好。

对我来说,比较新鲜的一点是:一个对象只要具备User要求的那些字段,而且类型也对得上,就可以在需要User的地方使用,不一定非得通过UserImpl创建。这也就是前面说的看“形状”。所以如果只是保存一份姓名、年龄之类的信息,直接写一个对象就够了;需要把数据和操作它的方法放在一起管理时,再考虑使用class,也不必每次都照着例子把两者写一遍。

这样再看interface的用处,就容易理解了。比如写一个接收User的注册函数,调用它的人就知道要提供哪些信息,编辑器也能帮忙补全和检查。以后修改了接口,那些不再符合要求的地方也更容易找出来。对于我这种用到哪里学到哪里的初学者,至少不用一边写,一边回头翻“这里到底叫什么、应该填什么类型”;多人一起写代码时,也有一份共同遵守的要求。

理解了这些,我们就可以进入mcp服务搭建的学习了。

云卷云舒

“卧看满天云不动,不知云与我俱东。”悠然恣意,像是卧在船上,任凭扁舟弄发,鸟雀呼晴。恰日落橘黄,草木弄晴,风儿潜入夜,阑阑珊珊,卷走了湿气。没有桨叶的小舟骀荡着,漫无目的,仿佛寄蜉蝣于天地,渺沧海之一粟,仅仅是予此一念,便已是沁出一层冷汗——思考杀死了意义,存在背叛了永恒————我于此间徜徉。

—