连接号是一种标点符号。和只有一种形式的间隔号不同,在中国大陆它有三种形式:短横线“–”、一字线“—”和浪纹线“~”。

麻烦的地方在于,国标只规定了这三种形式的长相和宽度,一个字也没提对应的码位。今天通行的U+2013U+2014U+FF5E,其实是W3C事后替它推断出来的,推断站得住,却不是国标白纸黑字写的规定,所以误用一直不少。

用途

短横线

  • 化合物的名称或表格、插图的编号,例如:
    • 3–戊酮
    • 表 2–8
  • 门牌号码、电话号码,以及用阿拉伯数字表示的年月日,例如:
    • 安宁里东路 26 号院 3–2–11 室
    • 010–88842603
    • 2011–02–15
  • 在复合名词中起连接作用,例如:
    • 吐鲁番–哈密盆地
  • 某些产品的名称和型号,例如:
    • WZ–10 直升机
  • 汉语拼音、外来语内部的分合,例如:
    • shuōshuō–xiàoxiào(说说笑笑)
    • 盎格鲁–撒克逊人
    • 让–雅克·卢梭(“让–雅克”为双名)
    • 皮埃尔·孟戴斯–弗朗斯(“孟戴斯–弗朗斯”为复姓)

最后两个例子里,短横线和间隔号同时出现,分工正好相反:间隔号分开姓和名,短横线把双名或复姓连成一体。

以上说的都是排版意义上的写法。程序里的日期字段、电话号码字段是另一回事,那里2011-02-15就该是ASCII。

一字线和浪纹线

这两种一般可以互换。

  • 标示相关项目(如时间、地域等)的起止,例如:
    • 沈括(1031—1095),宋朝人。
    • 2011 年 2 月 3 日—10 日
    • 北京—上海特别旅客快车
  • 标示数值范围(由阿拉伯数字或汉字数字构成)的起止,例如:
    • 25~30 g
    • 第五~八课

相关标准

《标点符号用法》(GB/T 15834–2011)

第5.1.6节说:

连接号中的短横线比汉字“一”略短,占半个字位置;一字线比汉字“一”略长,占一个字位置;浪纹线占一个字位置。连接号上下居中,不出现在一行之首。

注意这一节从头到尾说的都是“比汉字‘一’略短”“占半个字位置”这种排版意义上的长相,没有出现任何码位。后面的混乱,根子就在这里。

附录A.11“连接号用法补充规则”说:

浪纹线连接号用于标示数值范围时,在不引起歧义的情况下,前一数值附加符号或计量单位可省略。

示例:5公斤~100公斤(正)

5~100 公斤(正)

台湾“教育部”《重订标点符号手册》(修订版)

修订说明说:

“连接号”为新增。

也就是说,1987年的旧版根本没有连接号这一条。修订版第21页给出两种形式:

连接号 甲式:— 乙式:~

位置 占一个字的位置,居正中。

说明 用于连接时空的起止或数量的多寡等。

两式可自由选用,例如“西元1811—1872 年”和“西元1811~1872 年”都算对。

讽刺的是,同一页的用法举例里,“那篓黄鱼每条重约1-1.6 公斤”用的却是U+FF0D FULLWIDTH HYPHEN-MINUS,和上面自己定义的甲式并不是同一个字符。这和间隔号那篇里国标自己把间隔号印成全宽是同一类事故。

W3C《中文排版需求》

第5.1节“连接号”把两岸的规定并列了一遍,然后加了一条注:

《标点符号用法》(GB/T 15834—2011)中没有指定这三个符号的码位,但是基本上可以推断一字线是U+2014 EM DASH [—],浪纹线是U+FF5E FULLWIDTH TILDE [~]。但是对于“短横线”,该标准5.1.6节规定“短横线比汉字『一』略短,占半个字位置”,因此可以是U+2013 EN DASH [–]。这些连接号的实际长短根据所用处理系统和使用字体会有区别。

字宽实测

U+002D HYPHEN-MINUS,它确实老老实实半宽,但这不构成用它的理由。Unicode在字符表里给它的注释是:

used generically for hyphen, minus sign or en dash, all of which have dedicated alternatives

U+2013最常挨的一句批评是:Unicode给它的东亚宽度属性是AAmbiguous,歧义),根本没承诺过半宽。

1
2
3
4
5
6
7
8
>>> import unicodedata as u
>>> for c in '-–—~':
...     print(repr(u.east_asian_width(c)), u.name(c))
...
'Na' HYPHEN-MINUS
'A' EN DASH
'A' EM DASH
'F' FULLWIDTH TILDE

这话没错,但证明不了什么。A的意思是Unicode替字体做主,把宽度留给语境和字形设计:U+2014AU+2015A间隔号U+00B7也是A。想知道“半个字”有没有落地,不能查属性表,得去量字体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import os, unicodedata
from fontTools.ttLib import TTFont, TTCollection

FONTS = [('宋体',     'simsun.ttc',           0), ('黑体',     'simhei.ttf',        None),
         ('楷体',     'simkai.ttf',        None), ('仿宋',     'simfang.ttf',       None),
         ('标楷体',   'kaiu.ttf',          None), ('微软雅黑', 'msyh.ttc',             0),
         ('微软正黑', 'msjh.ttc',             0), ('等线',     'Deng.ttf',          None),
         ('思源黑体', 'NotoSansSC-VF.ttf', None), ('思源宋体', 'NotoSerifSC-VF.ttf', None)]
CHARS = [0x002D, 0x2013, 0x2014, 0x2015, 0xFF5E]

def pad(s, n):                       # 按显示宽度补空格,汉字算两格
    w = sum(2 if unicodedata.east_asian_width(c) in 'WF' else 1 for c in s)
    return s + ' ' * (n - w)

def width(font, cp):                 # 字符宽度,以一个全角字(em)为单位
    g = font.getBestCmap().get(cp)
    return font['hmtx'][g][0] / font['head'].unitsPerEm

print(pad('字体', 10) + ''.join(f'{"U+%04X" % c:>9}' for c in CHARS))
for name, fn, i in FONTS:
    path = os.path.join(r'C:\Windows\Fonts', fn)
    f = TTCollection(path).fonts[i] if i is not None else TTFont(path)
    print(pad(name, 10) + ''.join(f'{width(f, c):>9.2f}' for c in CHARS))

在我这台Windows 11上跑出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
字体         U+002D   U+2013   U+2014   U+2015   U+FF5E
宋体           0.50     1.00     1.00     1.00     1.00
黑体           0.50     1.00     1.00     1.00     1.00
楷体           0.50     1.00     1.00     1.00     1.00
仿宋           0.50     1.00     1.00     1.00     1.00
标楷体         0.50     1.00     1.00     1.00     1.00
微软雅黑       0.43     0.54     1.08     0.45     1.00
微软正黑       0.43     0.54     1.08     0.45     1.00
等线           0.50     0.50     1.00     1.00     1.00
思源黑体       0.32     0.53     0.88     1.00     1.00
思源宋体       0.34     0.55     0.87     1.00     1.00

数字不够直观,把“吐鲁番–哈密盆地”在四款字体里排出来,短横线占掉的字宽用底色标出来:

“吐鲁番–哈密盆地”分别以宋体、黑体、微软雅黑、思源黑体排版,中间短横线的字宽依次为一个字、一个字、半个字、半个字

U+2013画成全宽的,全是宋体、黑体、楷体、仿宋、标楷体这一路从GBK字库继承下来的老字体。它们的码位表里U+2013是双字节的A843,双字节就往全宽方格里塞,一刀切,跟这个字符本来该多宽毫无关系。

而微软雅黑、微软正黑、等线、思源黑体、思源宋体(Windows这些年新配的中文字体,加上开源界用得最多的那套)U+2013都落在0.5个字上下,正是国标要的“比汉字‘一’略短,占半个字位置”。

所以“U+2013在中文字体里就是全宽”这个说法已经过期了:半宽做不到,是二十年前字库留下的包袱,该修的是字体,不是这个码位。

U+2014一字线是三个里字体做得最不齐的:宋体一路刚好一个字,微软那两款做到1.08(国标说“比汉字‘一’略长”,只有它们按字面执行了),思源却只有0.87,比“一”还短。只有浪纹线U+FF5E所有字体都精确1.00,因为它是全宽形式(FULLWIDTH TILDE),宽度写在名字里,没有含糊的余地。

上面量的都是中文字体,对着Word、WPS这类排版场景。网页是另一回事:CSS字体栈里西文字体通常排在前面,U+2013U+2014在西文字体里也有,根本轮不到中文字体出场。这未必是坏事:Segoe UI的U+2013是0.50 em、U+2014是1.00 em,U+FF5E它没有,才落回中文字体拿到1.00,三个宽度全部正中国标。你现在看到的这一页就是这么排的。

与破折号的区别

破折号和一字线长得几乎一样,是最高发的误用。第4.10.2节说:

破折号的形式是“——”。

第5.1.4节说:

破折号标在相应项目之间,占两个字位置,上下居中,不能中间断开分处上行之末和下行之首。

所以两者的区别是:一字线是一个U+2014,破折号是两个U+2014。前者连接起止,后者注释、补充说明或标示语意转折。“沈括(1031—1095)”用一个,“一个矮小而结实的日本中年人——内山老板走了过来”用两个。

如何输入

在微软拼音、搜狗输入法、百度输入法的中文标点状态下:

  • Shift+`Esc下面那个键)得到浪纹线“~”,即U+FF5E
  • Shift+-得到破折号“——”,即两个U+2014。一字线没有直接对应的按键,把破折号删掉一半就是;
  • 短横线同样没有按键,-键给的是ASCII的U+002D

后两个只能靠别的办法:在Word里输入20132014再按Alt+X;或者在输入法里自定义两个短语,一劳永逸。

在HTML中,–得到短横线U+2013—得到一字线U+2014,两个都有好记的命名实体。U+FF5E没有命名实体,只能写~。这里有个坑:˜不是浪纹线,而是U+02DC SMALL TILDE,一个抬得很高的小波浪;∼(大写T)也不是,它是U+223C TILDE OPERATOR

在LaTeX中,--得到短横线U+2013---得到一字线U+2014。坑更大:~在LaTeX里是不折行空格,不会输出波浪线,要用\textasciitilde或者数学模式的$\sim$;中文文档用xeCJK直接写“~”最省心。

几种常见误用

减号键

U+002D HYPHEN-MINUS [-],键盘上直接打出来的那个,也是所有误用里最普遍的一种,-键就在那儿,谁都懒得多想。前面引的那份国标电子版,短横线打的也是它。

它半宽,所以在“3-戊酮”这种地方看着还算过得去,代价是把一个身份含混的ASCII字符塞进了中文标点的位置;拿它当一字线用,“1031-1095”就短得不像话了。短横线该用U+2013

横线

U+2015 HORIZONTAL BAR [―],Unicode给它的别名是quotation dash,用途是引出整段引语,不是连接。

它的问题是长度不稳:回头看那张表,宋体、黑体、思源都把它画成1.00,跟一字线一模一样,用错了根本看不出来;可微软雅黑和微软正黑给的是0.45,一换字体就缩成半截。网上流传的那份GB/T 15834–2011电子版,第4.13.2节的一字线用的正是U+2015,在宋体里看着没毛病,大概这就是它能一直流传下来的原因。

全宽减号

U+FF0D FULLWIDTH HYPHEN-MINUS [-]。宽度对了,可它在字体里通常被设计成算术减号的样子,比一字线短、比短横线粗,位置也偏高。台湾“教育部”那本手册就误用了它。

ASCII波浪号

U+007E TILDE [~]。半宽,且在多数字体里贴着字符上沿,和居中的浪纹线完全不是一回事。程序员写惯了~/,很容易顺手打出来。

数学符号

U+223C TILDE OPERATOR [∼],表示“相似于”“近似”;U+2212 MINUS SIGN [−],真正的减号。两者在数学排版里各有本分,都不该出现在标点位置上。

日文波浪号

U+301C WAVE DASH [〜]。日文语境里的浪纹线,来自JIS编码,中文不建议使用。理由和间隔号那篇里U+30FB KATAKANA MIDDLE DOT不建议用是一样的。