帮你提升Python的27种编程语言

27 种语言

过程型编程语言: C, Rust, Cython
面向对象数据建模语言: Java, C#, Eiffel
C 的面向对象衍生语言: C++, D
面向数组的数据处理: MATLAB/Octave, Julia
统计数数据分析: R
计算型管道建模: Haskell, Scala, Clojure, F#
事件驱动编程: JavaScript, Go, Erlang, Elixir
渐变类型: TypeScript
动态元编程: Hy, Ruby
实用问题解决: Lua, PHP, Perl
计算型思维: Scratch, Logo
作为这个世界上最流行的编程语言之一的合作设计者,我经常遇到一种令人非常沮丧的行为( Python 社区和其它领域中都存在)就是社区中有影响力的人尝试去在其它开源社区中灌输对于”缺失“的恐惧感,并以此驱动别人对本社区做出贡献(我自己偶尔也会做出这样不当的行为,当别人掉进这个陷阱时我也更容易觉察出来)。

虽然借鉴其他编程语言社区的经验是一件好事,但用基于恐惧的方法来推动行动有很大问题,社区成员为了吸引代码贡献者的注意,容易把其他社区的成员视为竞争对手,而不是作为潜在的盟友共同迎接挑战,推动软件开发技术的进步。还会导致社区排斥那些喜欢其他编程语言的人,把他们当做敌人。

事实上,我们希望有更丰富的跨平台开源编程语言可供选择,编程语言是最重要的思维工具,可以把我们的想法转换成计算机能理解的明确的条款。如果人们发现了某种语言既适合他们的大脑又能直接解决问题,这就很好了,不必关心他们到底选的是那种语言。

因此我要给 Python 社区提三个具体的要求和一个更广泛的建议,首先说这些要求:

1. 当我们想要激励部落成员,提高社区吸引力时,我们不应该使用恐惧法,相反我们应该使用自豪法。当使用恐惧法激励时,比如说这样的话:“如果我们不解决问题 X,那么 Python 开发者将会减少,他们将会转而去使用语言 Y”,那么我们就在故意地向全世界自由贡献代码的人传递负能量。但如果我们使用自豪法,说出来的话将是这样的:“Python 中的问题 X 的确难以解决,看看语言 Y 的社区,他们用一个极好的方法解决了这个问题,我们也可以在 Python 中尝试这个解决方案,以同样优雅的方式解决 Python 中的问题”。积极地强调“以我们自己的努力为自豪”,有利于促进 Python 社区中不断学习的文化,还会不断促进与其他社区关系的发展。

2. 克制对其他编程语言社区蔑视的态度,特别是对那些有大牛存在,能自己解决遇到的问题,不必等待商业软件供应商屈尊解决问题的社区。世界上的大多数重要的问题都不是利益驱动的问题(那些人想要解决问题,不是为了谋取财富,也没有机构出钱资助他们),所以我们应该鼓励和赞扬那些加紧尝试解决问题的人。不必管他们使用的是什么技术。

3. 如果我们认识的人刚开始学习编程,即使他们所选的编程语言是我们自己所不喜欢的。我们也要支持他们,因为他们比我们清楚什么更适合他们的大脑。所以对我们合适的语言不一定适合他们。如果他们开始对当初的选择感到沮丧,以至于完全打消了他们学习编程的积极性,这时给他们推荐编程语言才更有意义。这个建议甚至也对我们当中做过改进网络安全方面工作的人有用,对于原本不安全的语言,我们的解决方式是,通过改善操作系统中的沙盒功能,渐进地破除这个语言选择障碍,用原生系统的安全属性,改善了编程语言默认行为,不应该从应用安全的角度评价编程语言的好坏,迷惑初学者。(如果有人请编程新手写了一个未经审计的软件,却部署为处理安全敏感的问题,这不是程序员的问题,而应该怪那个部署的人没有尽职调查软件的来源和安全属性。)

我更多的建议是针对人开始遭遇 Python 的核心程序集的境界,也因此开始探索更多的 Python 自身的“思维工具”。

我们做 Python 核心开发过程的一部分事情是由于领会的特点具有在其他语言我们具有优势,是否能够用一种合适的方式让 Python 代码容易读写。这意味着学习别的程序语言能够明确特定的风格,并且在 Python 中编程提高人对软件开发的认识。

为了有助于这样的努力, 我已经列出了下面可供探索的领域,还有一些对这些领域可能提供额外见解的语言。我尽可能的链接到 Wikipedia 页面而不是直接转向相关的首页,因为维基百科经常会给出一些有趣的历史背景,当选择一门新的编程言作为一项学术型的练习而不是被立即用来实践性使用的时候,这些背景值得去探索。

然而就我个人而言, 我熟悉许多的编程语言(并且已经使用了其中的一些进行开发生产系统) 。所有的推荐包括我间接了解的语言(或通过阅读一些材料和设计文档,又或者是从和我信任的朋友之间的交流中得知一门语言的优势和劣势)。

本应该有很多的与沿出现在名单之中,但是对于列出来的这些特定的语言只是随意的一些子集,它们从某些程度来讲是基于我自己的兴趣而选出来的(比如说:我主要的兴趣点在于占支配地位的 Linux,Android 和 Windows 生态系统,所以在这里我就忽略了封闭但是更加有利可图的以 Apple 为中心的 Objective-C 和 Swift 编程语言,而且我对于专注于艺术环境的编程语言就不太了解,如 Processing,我甚至需要猜测从其中学到的东西怎么样可以指导一个 Python 开发者)。对于一个更全面的编程语言名单而言需要考虑很多因素,除了考虑一个编程语言能够指导你成为一个开发者外,IEEE Spectrum 的一年一度关于编程语言流行度和增长的排名也非常值得了解一下。

面向过程编程语言 C,Rust,Cython

Python 默认的运行模型是过程型的:我们从主模块的顶部开始然后一句一句执行。所有的 Phthon 对于其他数据和计算模型的方法支持都是基于它是过程型的这一特性。

C 语言毫无疑问仍然是底层编程语言的***者. 它是实现 Python 解释器的核心语言,同样也是实现 Linux 操作系统内核的核心语言。作为软件开发人员,学习 C 语言是学习更多关于软件所运行的底层硬件的最好起点 - C 语言经常被描述为“可移植的汇编语言”,通常使用 C 语言编译器作为交叉编译器,为新的 CPU 体系结构编译出第一个应用程序。

Rust,相比之下, 是一个由 Mozilla 创建的比较新的语言。它能够进入这个名单的原因是,Rust 吸取了工业界已知的关于不能在 C 语言中做什么的教训,并且被设计成可以与 C 库互操作的语言,它对硬件的控制达到了和低级系统编程语言相同的精度,但它使用不同的编译时方法进行数据建模和内存管理,在结构上消除了许多常见的困扰 C 程序的的缺陷(比如缓存溢出、重复释放内存错误、空指针访问以及线程同步问题)。我是一名嵌入式系统工程师,通过培训具备了最初的专业经验,我已经看到,当前被 C 语言和定制汇编代码***的各个领域很有可能会被 Rust 取代。

Cython 也是一种默认的更底层的语言,但是与通用目标语言 C,Rust 不同,Cython 主要用于书写 CPython 扩展模块。Cython 被设计作为一个 Python 的超集,让程序员选择何时支持纯 Python 语法的灵活性,当 Cython 语法支持的扩展使其可以生成的代码相当于本地C代码的速度和内存效率。

学习这些语言之一是以实用的角度增强其对内存管理,算法效率,二进制接口兼容性,软件可移植性,将源代码转换成运行系统的深刻理解。

面向对象的数据建模: Java, C#, Eiffel

编程中最主要的任务之一是为现实世界的状态建模,这方面最通常的方法是面向对象语言所提供的那些原生的语法支持:把数据结构、操作这些数据结构的方法组合成类。

Python 原生设计上就可以直接使用面向对象的特性,而不需要一上来先学习如何编写自己的类。不是每种语言都才有这样的方式 - 对于本节列出的这些语言,学习面向对象的设计思想是使用这些语言的前提。

得益于 Sun Microsystems 在 20 世纪 90 年代中后期对 Java 语言的市场推广,Java 成了很多大专院校计算机科学入门课程的默认语言。虽然现在在很多教育领域它正被 Python 淘汰,但它在商业应用程序开发领域仍然是一种最受欢迎的语言。有一系列其它语言针对公共的 JVM(Java 虚拟机)运行时的实现,包括 Python 的 Jython 实现。Android 系统的 Dalvik 和 ART 环境是基于 Java 编程 API 实现的。

C# 在很多方面与 Java 相似,在 Sun 和 Microsoft 解决关于 J++(微软实现的 Java 语言)和标准 JAVA 不一致的问题失败后,它是作为J++的替代语言出现的。像 Java 一样,它也是一个受欢迎的商业应用开发语言,有一系列其它语言针对共享 .NET CLR(公共语言运行库)的实现,包括 Python 的 IronPython 实现(原始的 IronPython 1.0 中的核心组件被抽取出来创建 .NET 动态语言运行库的中间层)。在很长的一段时间里,.NET 是只能在 Windows 系统中使用的专有技术,有一个跨平台的开源代码 mono 重新实现了 .NET,但在 2015 年初,微软宣布了 .NET 开源计划。

与列表里大多数语言不同,我并不推荐在日常使用 Eiffel。它之所以在推荐列表里,是因为这门语言有着大量优良的面向对象设计思想,包括以“正确可信”作为程序的设计目标。(同时,Eiffel 也告诉我对于大多数的软件开发,并没有以“正确可信”为设计目标,这是因为正确可信的软件确实无法妥善处理不确定的情况。当很多相关约束还不清楚,需要在不断迭代的过程中去逐步完善的时候,这种设计理念就完全不适合了)

学习这类编程语言,就需要去熟悉继承模型、契约设计、类不变项、前置条件、后置条件、协变(covariance)、逆变(contravariance)、方法查找路径、泛型编程,以及其他各种在 Python 的类型系统上也支持的特性。此外还有很多标准库模块和第三方框架,会用到“显示的面向对象”设计风格,例如 unittest 和 loggingmodules,以及 Django 框架里基于类的 view。

面向对象的 C:C++, D

使用 CPython 的一种方式,是把它的内核当做一种“包含对象的 C 语言”的编程环境 – CPython 是通过 C 语言的风格去实现面向对象编程,也就是用 C 的结构体描述数据,然后把结构的实例指针作为第一个参数传给那些数据处理函数(也就是 CPython 的 C 里面的 omnipresentPyObject* 指针)。这种设计模式被有意复制到 Python 里,在实例方法和类方法需要显示的指定 self 或者 cls 参数。

C++ 的目标是在源码级完全兼容 C 语言,在此之上增加了一些高级特性,例如原生的面向对象编程支持和基于模板的元程序开发。C++ 的晦涩和复杂是臭名昭著的(即使 2011 年的语言标准的更新解决了大量最糟糕的问题),不过就算这样,C++ 仍然是很多场景下的选择,包括 3D 建模图形引擎和跨平台应用开发框架,如 Qt。

D 编程语言也很有趣,因为它和 C++ 的关系很像 Rust 与 C 语言之间的关系:设计 D 语言的目的是,既要保留 C++ 的大部分优点,也要避免 C++ 中存在的很多缺陷(比如缺乏存储安全性)。与 Rust 不同,D 语言不是一个从零开始设计的全新编程语言,相反,它是直接从 C++ 衍生出来的语言,尽管它不像 C++ 那样是 C 语言的严格超集,但它遵守一个设计原则,任何落入 C 语言和 D 语言公共子集中的代码,在两种语言中的行为必须相同。

学习这些语言有利于深刻理解把高级语言特征和底层 C 运行时模型相结合的复杂性。学习 C++ 也有助于使用 Python 操作已有的用 C++ 编写的库和工具包。

面向数组的数据处理: MATLAB/Octave, Julia

面向数组的编程用于数值编程模型:基于矩阵代数和相关的数值方法。

虽然 Python 的标准库并没有直接支持,不过在语言设计上已经做了考虑,一系列语法和语义上的功能支持,有助于第三方库 NumPy 以及类似的面向数组的工具。

在很多情况下,Python 科学计算 软件系列都被当做专用的 MATLAB 编程环境的替代者,被广泛用于科学和工程上的建模、仿真和数值分析。开源项目 GNU Octave 的目标是在语法上与 MATLAB 代码兼容,让人可以比较面向对象编程的这两种方式。

Julia 是另一个相对较新的语言, 它的主要特点是支持面向数组编程和基于类型的函数重载.

学习一种这样的语言有助于深入理解 Python 科学计算工具包的威力,同时,学习这样的语言有助于研究如何利用 OpenCL 和 Nvidia 的 CUDA 等类似的技术实现硬件级并发执行,也有助于研究如何使用 Apache Spark 和 Blaze 等数据处理框架实现分布式数据处理。

统计数据分析语言: R

由于有越来越多的大数据集需要处理。因此需要一种免费的能处理这样的数据集的分析工具,编程语言 R 就是一种这样的工具,它特别注重统计数据分析和可视化。

学习 R 语言有助于深入理解 Python 科学计算工具包的统计功能,特别是其中的数据分析库 pandas 和统计可视化库 seaborn 。

计算管道建模语言:Haskell, Scala, Clojure, F#

面向对象数据建模和面向数组数据建模主要用于对数据进行静态建模,有两种建模方式,一种是把数据保存在对象的各个属性中,另一种是把结构化的数据保存为数组。

相比之下,函数式编程语言更强调以计算流的形式对数据进行动态建模。只要学一下函数式编程基础,就会显著提高使用数据转换操作对数据建模的能力,这对于使用其他范式的编程语言(比如面向过程、面向对象、面向数组的编程语言)开发应用程序也是有帮助的。

Haskell 是一个函数式编程语言,对 Python 的设计产生过重大的影响, 最明显的就是 Python 2.0 引入的列表解析。

Scala 毫无疑问是基于 JVM 的函数式编程语言,与 Java, Python 和R一样,是 Apache Spark 数据分析平台的四门主要编程语言之一。在设计上支持函数式编程方式的同时,Scala 的语法、数据模型和执行模型在设计上尽量避免为原有的 Java 程序员带了太大的障碍(从这个角度上看,Scala 更恰当的分类应该是有着强函数式语言支持的面向对象的编程语言)。

Clojure 是另一门基于 JVM 的函数式编程语言,被看作是 Lisp 的一个变种。它在我们的清单中具有一席之地,是因为它为 Python 的函数式编程工具箱 toolz 的实现带来灵感。

我自己对F# 并不熟悉,不过由于它是 .NET CLR 推荐的语言,所以还是值得关注的。

学习这些编程语言,有助于了解 Python 自身的计算管道建模工具,包括容器推导表达式、生成器、生成器表达式、functools 和 itertools 标准库模块,和第三方函数式 Python 工具如 toolz。

事件驱动编程语言:JavaScript, Go, Erlang, Elixir

计算管道是处理数据转换和分析问题的一种很好的方法,不过很多问题需要程序以持久的方式运行,等待事件发生,然后处理这些事件。对这类服务,通常可以并发的处理多个事件,来实现同时为多个用户(或者至少多个行为)提供服务。

JavaScript 最初是为浏览器开发的事件处理编程语言,可以让 web 开发人员处理客户端本地的用户行为(例如鼠标移动和按键)和事件(例如页面渲染结束)。所有现代的浏览器都支持 JavaScript,与 HTML5 的 DOM 一起,已经成为用户界面的外观和行为事实上的标准。

Go 是 Google 设计出来的,设计这个语言的目的是为了创建高度可扩展的网络服务,Go 语言非常适合开发命令行程序。从设计编程语言的角度看,最引人注目的是 Go 语言在它的核心并发模型中使用了“顺序通信过程(Communicating Sequential Processes)”这一概念。

Erlang 是爱立信设计出来的,设计这个语言的目的是为了制造高度可靠的电话交换机以及类似的设备,著名的开源框架 RabbitMQ 的消息服务器就是用 Erlang 实现的。Erlang 使用 Actor 模型实现了核心并发原语,不允许不同线程直接共享数据,线程间的通信只能靠传递消息。尽管我自己从来没有使用过 Erlang 语言, 但我的第一份工作涉及到了一个基于 Actor 模型开发的并发框架,它是一个前爱立信工程师用 C++ 开发的,我自己也基于 TSK(任务)和 MBX(邮箱)原语开发过这样的框架,是在德州仪器的轻量级 DSP/BIOS 运行时(现在叫 TI-TROS)中实现的。

Elixir 能够出现在这个名单中的理由是,虽然它运行在 Erlang 虚拟机中,与编程语言 Erlang 具有相同的并发语义,但它也包含了一系列额外的语言级特征,提供了更全面的开发环境,更容易吸引从其他编程语言(比如 Python、Java 或 Ruby)转过来的开发者。

学习一种这样的语言有助于深入理解 Python 本身是如何支持并发和并行的,包括原生协程、基于生成器的协程、concurrent.futures 和 asyncio 标准库模块、第三方网络服务开发框架(比如 Twisted 和 Tornado)、Django 中新引入的 channels 概念和 GUI 框架中的事件处理循环。

动静混合类型: TypeScript

Python 3.5 引入的特性里,最有争议的一项是新的类型模块,为 Python 体系加上了混合类型的支持。

对于那些接触过的静态类型编程语言主要是 C, C++ 和 Java 的开发者来说,这简直就是一个及其可怕的想法。

Microsoft 的 TypeScript 为 JavaScript 应用提供动静混合类型支持,让你对这个概念会有好一些的看法。TypeScript 代码会编译成 JavaScript 代码(编译后不包含任何运行时类型检查),主流的 JavaScript 库的 TypeScript 注释(annotations)在 DefinitelyTyped 代码库里可以找到。

正如 Chris Neugebauer 在澳大利亚 PyCon 大会的报告上指出的那样,这很像是 Python 与类型提示库 typeshed 以及类似 mypy 那样的类型推导和分析工具之间的关系。

本质上,TypeScript 和 Python 的类型提示都是实现特定测试程序的方式,不管是独立文件(常规的测试程序),还是内嵌在主代码里(类似静态编程语言的类型声明)。不管哪种情况,你都可以运行单独的命令检查剩余的代码是否符合已知的类型约束(对于 JavaScript 和 TypeScript,在编译阶段会隐式的去完成,对于 Python 的类型提示,则是可选的静态分析任务)。

动态元程序设计: Hy,Ruby

像 C、C++、C#、Java 这样的编程语言给 Python 带来的一个有点让人不安特性是“代码即数据”:类似函数和类都是运行时对象,可以被其他对象操作。

Hy 是一个 Lisp 的变种,可以在 CPython 虚拟机和 PyPy 虚拟机上运行。Lisp 在“代码即数据”上做到了极致,Lisp 代码本身就是由描述需要实现的操作的嵌套的列表组成的(这门语言的名字就源自”LISt Processor”)。Lisp 风格的语言,最强大的一点是它们可以很轻松的实现自己的领域特定语言(DSL),不过这有时候也为阅读其他人的代码带来困难。

Ruby 在很多方面都与 Python 很类似,但是作为更为开放的社区,Ruby 更接受动态元程序设计,而对于 Python,这方面只是“支持,但不鼓励”。这方面的功能包括重定义类加入一些方法,用闭包实现语言核心结构如迭代器。

学习这些语言可以帮助深入了解 Python 自身的动态元程序设计的支持,包括函数和类装饰、monkeypatching(动态修改代码)、unittest.mock 标准库模块以及第三方对象***模块入如 wrapt(我还没找到什么编程语言有助于了解 Python 的元类(metaclass),如果有人有好的建议,可以在评论里告诉我。元类的高级特性包括核心类型、抽象基类、枚举类型和混合类型(动态类型和静态类型混合)表达式的运行时执行)。

实用***者:Lua, PHP, Perl

流行的编程语言通常并不是孤立的 —— 他们属于庞大的生态系统的一部分(商业和社区都是这样),此外还有终端用户、框架开发者、工具开发者、教育人员等等。

Lua 是一门流行的编程语言,主要作为脚本引擎内嵌于大型的程序里。值得一提的例子有,为魔兽争霸游戏客户端编写的插件,在很多 Linux 发行版存在的 RPM 工具也内置了 Ruby。与 CPython 相比,Lua 运行时大小只有其十分之一,并且它的弱反省(weaker introspection)的能力也能让它更容易独立于应用程序的其他部分和宿主操作系统。一个值得提到的来自 Lua 社区对 Python 生态系统的贡献是,CPython 和 PyPy 采用 LuaJit FFI(Foreign Function Interface)作为其 JIT 友好的 cffi 接口库的基础。

PHP 是另一个受欢迎的编程语言,由于 PHP 擅长生成 HTML 页面,被早期的虚拟服务器主机提供商广泛使用,因此它作为 LAMP stack(Linux-Apache-MySQL-PHP)的组成部分被广为人知。尽管 PHP 在设计中存在很多令人苦恼的概念上的缺陷,它仍然成了很多著名的开源 web 服务的基础,包括 Drupal 内容管理系统、WordPress 博客引擎和支撑 Wikipedia 的 MediaWiki 引擎。PHP 也能支撑很多重要的服务,比如众包社区所使用的分布式事件报告平台 Ushahidi。

和 PHP 一样,Perl 也是 Linux 系统上的一个受欢迎的语言,与 PHP 不同,Perl 不是作为网站开发平台被人熟知的,它更常见的用途是作为系统管理员管理系统的工具,它既能使用正则表达式处理字符串又能处理基于文本的 Linux 操作系统命令的输出结果。只使用 Perl 就能处理所有的任务,不需要再使用 Whensh、awk 和 sed 等工具了。

学习其中的一门语言并不能提供任何好的见解在审美上漂亮或者在理念上简洁的程序语言设计。可能的结果是在实践中提供一些编程语言的结构和采纳的知识,以及了解关于偶然的机会、历史的积累和降低入门门槛(通过重新分配使缺省变得可能)所起到的作用,这些都强于语言本身固有的能力。

特别是,它可以提供一些见解关于 CKAN、OpenStack NFV、Blender、SciPy、OpenMDAO、PyGMO、PyCUDA、 Raspberry Pi Foundation 和 Python 的项目意义,通过广泛的商业组织,确保 Python 生态系统的机构投资持续进行。

数值计算的思想:Scratch,Logo

最后想说的是,我常常陷入这样的讨论,即结构化编程和面向对象倡导者的争论。后者自称面向对象编程语言和结构化编程语言一样易学。

当我们谈论的是通过具体的数值实验来教学(机器人学),研究对象在仿真软件中的模型有着直接现实世界的参照物时,比如学生们可以接触到传感器,发动机,继电器等。我认为支持面向对象的小伙伴们有一定的道理。

然而对于其他人来讲,我遇到的一个典型的挑战是:拿起一本食谱,将其中一个菜谱转换成你认为易学的面向对象编程语言,然后找到一个理解这门编程语言的学生,沿着我的思路,来继续转换这个菜谱。(我期待着看到学术研究人员真正践行这样的学习过程,——我会发自内心的为这样的情况感到欣慰。)大多数的情况下,小伙伴们不必遵循这样的流程——仅仅需要在头脑中进行思维的实验就足以让他们感受到要想学会这“易学的”编程语言需要多少预备知识。

然而另外一个解决此问题的方法是学习那些用于教育小孩子数值计算的编程语言。

其中一种最流行的莫过于 Scrach,它是一种让学生利用拖动的方式来操作封闭的图形化环境,从而可以看到图形化界面中相应的移动和反应的编程接口。像 Scrach 这样的图形化环境是一种类似于利用连环画帮助孩子们逐步学习读书认字的方式的程序设计方式。

然而,这种利用一种特殊教育目的编程语言来操作一个图形化界面的想法并不新奇,随着的早期最经典环境之一的 Logo 环境在 2 0 世纪 60 年代的创建(类似于 Python 自己的海龟模块),那时候,你所接触的主要的东西是一个“海龟”,你可以用命令它的移动来画线,从而改变图形环境。通过这种方式,像命令行、迭代、状态(例如:向上划、向下划)都以一种建立在人们的自然直观的思维方式(想象一下,假如你是一只海龟,如果向右旋转 90 度将会发生什么?)的基础上来介绍。

回归本源,作为一名富有经验的程序员,重新学习以上的任何一门编程语言是最有效的方式来忘掉所学(抛弃一些轮子):这些语言工具所涵盖的概念帮助我们回想起那些我们曾经认为理所当然的概念,但是需要以初学者的眼光重新学习。当我们这么做的时候,因为我们更加愿意回想起整个的逻辑链条,包括那些我们之前认为理所当然而省略的思维步骤,我们会更加有效地和学生以及其他的初学者一起工作。
全部评论

相关推荐

CrazyBucket:我今天下午也做梦在招聘会上面试一家小厂,给自己气笑了
点赞 评论 收藏
分享
10-29 15:38
门头沟学院 Java
榕城小榕树:难道你简历里写了配送路径优化算法?
点赞 评论 收藏
分享
6 15 评论
分享
牛客网
牛客企业服务