为什么像Java这样的语言在其他语言不区分string和char?

7
我注意到像Java这样的语言有一个char基元和一个string类。其他像Python和Ruby这样的语言只有一个字符串类。这些语言使用长度为1的字符串来表示字符。
我想知道这种区别是否是由于历史原因造成的。我知道直接影响Java的语言有一个char类型,但没有字符串。相反,使用char*或char[]形成字符串。
但我不确定是否有实际目的这样做。我也很好奇在某些情况下一种方法是否比另一种方法更具优势。
为什么像Java这样的语言区分char基元和string类,而像Ruby和Python这样的语言却不区分呢?
肯定有某种设计上的考虑,无论是约定、效率、清晰度、易于实现等方面。语言设计师真的只是随机选择一个字符表示吗?

2
性能。为什么要有单独的“byte”类型,当你可以只存储“int”时? - Matt Ball
1
同样适用于C++,以保持与C的兼容性。 - John Carter
1
char通常是一个字节(通常 翻白眼 - 是的,我在看你,Java)。这并不总是代表可读字符。另一方面,注意到类形式的string通常是一组可读字符的集合(但不一定)。但无论如何,提供“字符串”功能需要额外开销。 - RageD
1
@RageD 实际上,Java 设计者的决定是非常一致的。他们说 char 是用于字符而不是存储整数值的。因此,他们引入了 byte。这导致了字符(您可能想要存储 Unicode 数据)和从 -128 到 127 的整数值之间的一致分离。如果他们不这样做,他们就必须引入像 wchar_t urgs 这样的东西。所以更多的是要看其他语言 ;-) - junix
我不太清楚你所说的“基于C语言”的意思。如果你指的是语法上的相似(即所谓的“花括号语言”),那么这可能包括编译型、虚拟机和脚本语言,但除了语法上的相似之外,它们并不一定在架构上相似。C、C++和Objective-C直接相关,并且通常被包含在同一个基础编译器中。 - Clifford
显示剩余7条评论
5个回答

4
底线是这就是语言设计者决定的方式。很难再深入探讨了。
然而,关于C语言,它通常被认为是一种更低级的语言,因为其语法更准确地反映了正在执行的数据和任务的本质。将字符视为字符串将是一个不符合C语言特点的抽象层次。这会使得在底层看起来数据的样子不够清晰,同时还会增加开销,而实际上你只需要一个字符。
请注意,C类型的语言支持单个字符的字符串,因此,在我看来,你真正拥有了两全其美的最佳选择。

1
我认为这更多是“必须如此”而不是任意的设计决策。尽管名字叫做“char”,但它并不仅仅是一个字符表示。作为一种系统级语言,你需要一个能够寻址单个内存位置的类型。你可能会想知道为什么没有像C#中那样有一个与char不同的byte类型,甚至是short short int,但考虑到70年代早期计算机的能力,那将是一种多余的浪费。 - Clifford
我并不是要表明决定是随意的,只是那是原因,我们可能并不知道做出那个决定时发生了什么。 - Jonathan Wood
1
这是正确的,给出的答案,包括我的都只能是假设和(也许是有根据的)猜测。总的来说,C语言的编写旨在使编译器尽可能简单轻便,它的设计的许多方面都表现出这一点。 - Clifford

4

编辑1:添加了一些来源链接;改进了关于Lisp的历史故事;解答了为什么Java有原始类型。

编辑2:解释了现代脚本语言的效率不再是如此重要的原因。

早期,内存非常昂贵 - 即使是简单的计算机也只有几千字节。您必须同意的典型服务条款将超过整个系统的RAM。这意味着数据结构必须比今天设计的数据结构小得多。

计算机起源于20世纪40年代的英国和美国,那时工程师所需的最小字符集是没有任何特殊字符的西欧字母表。0-9、A-Z和a-z总共62个字符。加上31个控制字符、空格和一些标点符号,就可以将它们全部放入7位中。完美地适用于电传打字机。

现在,这7位在不同的体系结构上可以以不同的方式布局。如果使用IBM,您必须知道EBCDIC,这与ASCII完全不同。

'60年代和'70年代的编程语言反映了这些问题,并将字符串压缩到最小的空间中:
- Pascal:一组打包的字节 - 固定长度且不以 null 结尾 - C:以 null 结尾的字节序列(通常被认为是使用疯狂的黑客思想来处理数组下标的数组) - Fortran 66:字符串?你不需要它们。在整数中存储一对字符,然后使用 READ、WRITE 和 FORMAT
作为这些语言的程序员,我可以说这很糟糕。特别是大多数商业程序都需要大量的文本输入和操作。随着内存变得更便宜,程序员倾向于先编写字符串工具,以便能够做任何有意义的事情。
固定长度的字符串(例如 Pascal)很高效,但如果您需要扩展或缩小甚至一个字符,它们会很笨拙。
C语言的以空字符结尾的方法有一个缺点,就是字符串的长度没有与字符串一起存储,因此很容易覆盖缓冲区并导致应用程序崩溃。这种错误仍然是计算机不安全性的主要原因。解决这个问题有两种方法:
  • 每次写入时检查字符串长度:这只是扫描内存直到找到空字符。丑陋。
  • malloc新内存并将字符串复制到新内存中,然后使用free释放。
在80年代,标准库被引入来处理字符串 - 这些由工具供应商和操作系统提供。有重大的标准化运动,但各方争夺控制标准,这是非常丑陋的。

随着国际化的增加,也带来了另一个问题 - 国际字符集。首先,ASCII被扩展到8位,作为ISO 8859-1适用于不同的欧洲语言(重音,希腊文,西里尔文),然后Unicode完全将计算机带到了世界的各个角落。这带来了字符编码的问题,例如UTF-8UTF-16以及如何在这些不同的方法之间进行转换。

我还应该指出Lisp引入了垃圾回收。这解决了C语言中malloc/free的复杂性。Lisp的强大的数组和序列库自然地处理字符串。

第一个将这些趋势结合在一起的主要流行语言是Java。它结合了语言中的三个改进:

  1. 国际化和Unicode: 一个独立的数据类型Character和原始类型char
  2. 封装: 固定长度和以null结尾的问题被消除了:
    1. 不可变
    2. 虚拟机和垃圾回收中的巧妙优化
  3. 库: 所有基本的字符串操作功能都已在语言中标准化。

现在有些语言中每个值都是对象。然而,当Java在90年代末提出时,GC、JIT/Hotspot技术远没有现在快(至少部分原因是因为RAM限制,但算法也改进了)。Gosling很关心性能,所以保留了原始数据类型。

还有一点:在Java中,存在一个Character类是自然的 - 它是许多操作和实用方法的自然归属地,如isWhiteSpace()isLetter(),后者由于日语、韩语和印度语而有些复杂。

Python在早期做出了一个糟糕的决定,将字符定义为8位ASCII;通过引入另一种微妙不同且不兼容的数据类型(unicode),可以看到随之而来的问题,并且现在只能通过复杂的迁移到Python 3.x来解决这个问题。
现代语言(包括脚本语言)遵循Java和Python所展示的关于字符串库的广泛共识。
每种语言都是为特定目的而设计的,因此以不同的方式平衡竞争性设计考虑因素。现代语言受益于过去60年中性能和内存方面的巨大改进,因此它们可以在CPU和RAM的效率上优先考虑泛化、纯度和实用性,尤其是脚本语言,由于脚本的性质已经做出了该决定。因此,现代语言倾向于只有高级字符串类型。 TL/DR 早期计算机在内存方面非常有限,因此强制采用最简单的实现方法。现代语言受益于GCs,认识到国际化(8位->16位)字符并封装字符串数据类型,使字符串操作变得安全和容易。

2

现在,我对这个问题的看法可能与其他答案类似,但我还是要说一下:

是的,(像其他人提到的那样)像C这样的低级语言比像Perl、Ruby或Python这样的脚本语言更注重优化、性能和机器级细节。由于这种“全控制”思想的结果是,与脚本语言相比,你通常需要考虑更多的事情。

那么我想说什么呢?嗯,SO的一个成员曾经给我传递了“Python之禅”,其中有一些核心的Python哲学,比如“可读性很重要”、“简单比复杂好”,以及“做一件事应该有一种明显的方法,最好只有一种方法”。我强调了最后一句话。

接下来,这里有一个抽象编程语言的例子,它确实有一个char类型:SML。例如,我在交互模式下进行了两个语句的演示:

- val a = "a"
val a = "a" : string    #interpreter type feedback

- val a = #"a"
val a = #"a" : char     #interpreter type feedback

在上述两个例子中,我有两种方式来表示一个可读的字符,尽管它们是根本不同的类型。虽然SML在今天的标准下是一种相对抽象的语言,但它的核心理念在于计算、数学表达式语法和错误安全性。对于纯函数语言(SML不是纯函数语言),最后一点更加重要。因此,尽管抽象化强调远离低级语言的可怕细节,但它仍然没有像Python这样的语言那样非常强调“可读性”和“易用性”。
事实上,脚本语言通常强调快速生成代码和易于学习和使用的语法。至于Ruby,松本本人甚至宣称该语言应该“好玩”。从我的最谦虚的估计来看,我认为Python等语言中不区分char和string数据类型的原因包含在简单性的概念中。冗长和复杂似乎是脚本语言的敌人。此外,作为最后一点,如果有人倾向于使用C兼容的数据类型,则可以使用Python的ctypes库。

遗憾的是,这个问题已经关闭了,但我仍然认为它很有用,你的答案也很棒。如果您不介意,能否编辑您的答案,省略关于C字符串(空终止符、memset等)问题的结尾部分?这并不一定适用于Java。 - Eva
@Eva,当然可以,发布后不久我也想到了这一点。很高兴它对你有用。 - eazar001

1
我不确定这种区别是由于历史原因(C只有字符,字符串由char *或char[]组成),还是因为有实际目的。 我也很好奇在某些情况下一种方式是否比另一种方式具有优势。 在C中,“字符串”的概念是以字符数组/字符序列的形式呈现的,并以结束字符\0终止。否则,“字符串”就像C中的任何其他数组一样。 例如,在C#和其他几种语言中,字符串被视为抽象,字符串更像是不透明的对象。该对象包含可处理字符串的方法,但字符串的存储方式对程序员来说是“隐藏”的。 这样做的原因是C是一种比较老的语言,比新语言更接近硬件。 如何在语言中定义字符串(使用单引号还是双引号)实际上只是设计语言的人员认为当时是一个好主意的实现细节。

脚本语言可以使用单引号表示字符串,因为它们没有单独的 char 类型。我不是在问 C 和新语言之间的区别,而是在问基于 C 的语言和脚本语言之间的区别。我知道字符串对象是一种抽象概念,但我不明白的是为什么脚本语言没有单独的 char 类型。 - Eva
C语言处理字符串的方式与其年龄无关,而是与其应用有关。您总是需要一种可以直接处理内存、寄存器和硬件架构的系统级语言,而C(和C++)提供了这种功能。很少有“新”的语言是“系统级”的,因为C和C++几乎已经覆盖了所有方面。 - Clifford

1
在C和C++中,char只是一个“小”的整数。虽然它用于字符编码,但在至少支持各种语言和字母表的桌面系统或任何需要支持多种语言和字母表的系统面前,它的使用正在减少。然而,由于这些是能够直接访问硬件的“系统级”语言,因此还需要一种能够寻址特定架构上最小可寻址内存单元的数据类型;这就是为什么需要char的原因。
C#区分用于字符编码的类型char(实际上是16位)和最小可寻址单元类型byte(8位)。这种清晰度可能是后来加入该领域的优势。
当然,C语言实际上根本没有字符串数据类型,它只是有一个以空字符结尾的字符数组约定和使用该约定的函数库(顺便说一句,这是一个简单但低效的约定,如此处所述)。在C++中,string类带来了真正字符串类型的优势,并且可以避免一些低效和危险 - 虽然缓解危险的同时也会产生不同的性能损失。

脚本语言怎么样? - Eva
他们怎么样?这个问题是关于基于C的语言,而C是一种编译语言。此外,我的观点是关于C作为系统级语言的要求 - 在阐述我的观点时,我可能没有回答你所有的问题。脚本语言根据定义不是系统级别的 - 你通常不能用脚本语言(或者虚拟机语言)编写操作系统、引导程序或设备驱动程序,因为它们依赖于运行时环境,而这个环境就是系统本身。另一方面,C具有最小的运行时环境要求,并且能够在没有操作系统的情况下运行“裸机”。 - Clifford
不,问题是关于为什么基于C的语言将char与string分开,而脚本语言则不会。我的问题与C语言本身无关。我要编辑我的问题,以结束这种混淆。 - Eva
好的,那么从我的回答中可以得出结论,非系统级别的语言不需要处理基本机器类型,而是抽象类型。 - Clifford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接