Das ist ein großes Thema! Man kann ein langes Buch darüber schreiben, und mehrere Leute haben das auch getan (suche bei Amazon nach „unicode book“, um ein paar Beispiele zu sehen).
In früheren Jahrzehnten war der Umgang mit Zeichen in Computern viel einfacher, weil Programmierer annahmen, dass Englisch die einzige wichtige Sprache sei. Also: 26 Buchstaben, Groß- und Kleinschreibung, 10 Ziffern, ein paar Satzzeichen, dazu ein Code (0x07) zum Klingelnlassen, und das alles passte in 7 Bit: der ASCII-Zeichensatz.
Natürlich fingen die Leute an zu fragen, was mit à, ä und Ł ist, dann fragten andere nach ऄ, ஹ und ญ, und die Jugend wollte Emojis 😱. Was tun?
Um es kurz zu machen: Viele kluge und geduldige Leute mussten jahrelang in Ausschüssen sitzen und die Details des Unicode-Zeichensatzes und von Kodierungen wie UTF-8 ausarbeiten, und jede Menge Software musste sehr aufwendig umgeschrieben werden. Außerdem wurden jede Menge neue Bugs eingebaut.
Damit nicht alles kaputtgeht, sorgt das Unicode/UTF-8-Design dafür, dass die ersten 127 Codes mit ASCII identisch sind (sogar die Klingel).
Sprachen, die nach etwa 2005 entworfen wurden, haben den großen Vorteil, dass damals bereits ein einigermaßen stabiler Unicode-Standard existierte.
Julia (erstmals veröffentlicht 2012) konnte davon ausgehen, dass alles Unicode sein würde: Zeichen, Strings, Variablen- und Funktionsnamen, mathematische Operatoren …
Um das Handbuch zu zitieren: „Julia macht den Umgang mit reinem ASCII-Text einfach und effizient, und der Umgang mit Unicode ist so einfach und effizient wie möglich.“ Beachte das „wie möglich“, das ist ein wichtiger Teil der Aussage.
Zeichenliterale werden in einfache Anführungszeichen gesetzt und sind etwas anderes als Strings in doppelten Anführungszeichen.
Für Leute aus der C/C++-Welt ist das selbstverständlich, für Python- und JavaScript-Programmierer aber möglicherweise verwirrend.
julia> a = 'a' # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
julia> typeof(a)
Char
julia> jha = 'झ' # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)
julia> typeof(jha)
Char
julia> '❤' # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)
An den Beispielen sehen wir, dass der Typ Char ist, und Julia hat weitere Informationen über die Kategorie des Zeichens.
Bei genauerem Hinsehen zeigt sich, dass diese Zeichen mit 4 Hexadezimalziffern dargestellt werden können. Der vollständige Zeichensatz braucht bis zu 6 Hex-Ziffern.
Diese Zahlen heißen „Codepunkte“ und reichen derzeit von U+0000 bis U+10FFFF.
Sie werden in der REPL angezeigt, aber im Code verwendest du codepoints(), um sie zu erhalten.
Die Umwandlung zwischen Char und Int ist einfach:
julia> Int('a')
97
julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
Der Compiler erlaubt einige Formen der Ganzzahl-Arithmetik mit Chars:
julia> 'b' - 'a' # interval, in alphabetic order
1
julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)
julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)
julia> 'f' + ('A' - 'a') # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)
Eine Teilmenge der Funktionen zur String-Verarbeitung kann auch mit Char-Eingaben arbeiten.
uppercase() und lowercase().isuppercase() und islowercase().isletter(), deckt viele Alphabete abisdigit(), prüft streng auf 0:9isnumeric(), weiter gefasst als isdigit, also true für ¾ und verschiedene nicht-europäische Schriftenisxdigit(), Hexadezimalziffernisascii(), Zeichen aus der Zeit vor Unicodeispunct(), Satzzeichenisspace(), jedes Whitespace-Zeichenisprint(), druckbare Zeichen (das Gegenteil ist iscntrl())islowercase('A') # false
uppercase('γ') # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@') # true
isdigit('A') # false
isxdigit('A') # true
Um zu prüfen, ob ein Zeichen in einem String vorkommt, haben wir in.
Beachte, dass das etwas anderes ist als Teilstrings:
# char search
julia> 'a' ∈ "xabcahliazlnkiw"
true
# substring search
julia> "a" ∈ "xabcahliazlnkiw"
use occursin(needle, haystack) for string containment
Außerdem ermöglichen reguläre Ausdrücke (Thema eines anderen Konzepts) eine mächtige Suche und Bearbeitung.
Um einen String in einen Char-Vektor umzuwandeln, können wir collect() verwenden.
Für die Umwandlung eines Char-Vektors in einen String gibt es den Konstruktor String().
julia> s = "abcde"
"abcde"
julia> cv = collect(s)
5-element Vector{Char}:
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
julia> String(cv)
"abcde"
Das funktioniert mit beliebigen Zeichen, nicht nur mit ASCII.
julia> collect("❤,😱")
3-element Vector{Char}:
'❤': Unicode U+2764 (category So: Symbol, other)
',': ASCII/Unicode U+002C (category Po: Punctuation, other)
'😱': Unicode U+1F631 (category So: Symbol, other)
Beachte, dass der Konstruktor String() auf einen Vektor wirkt.
Um ein einzelnes Char in einen String aus einem Zeichen zu casten, ist die Funktion string() zuständig, mit kleinem s.
julia> string('a')
"a"
Bisher wirkt alles in diesem Text relativ einfach. Gibt es also wirklich nicht viel, worüber man sich Sorgen machen müsste?
Leider ist das zu optimistisch!
Eine Schwierigkeit ergibt sich aus der Notwendigkeit von „bis zu“ 6 Hex-Ziffern pro Codepunkt. Das bedeutet, dass verschiedene Zeichen unterschiedlich viel Speicherplatz brauchen, wenn sie UTF-8-kodiert sind.
Ein Byte kann nur (vorzeichenlose) Zahlen bis 255 speichern, also zwei Hex-Ziffern, deshalb verwendet UTF-8 eine variable Anzahl von Bytes (1 bis 4), um ein Char zu speichern.
Diese heißen „Codeeinheiten“, und die Funktion ncodeunits() gibt die Anzahl zurück, die für ein gegebenes Zeichen nötig ist.
julia> codepoint(jha) # jha 'झ' is defined in an earlier example
0x0000091d
julia> ncodeunits(jha)
3
julia> ncodeunits('a') # ASCII character
1
julia> ncodeunits('😱') # emoji
4
Außerdem hat nicht alles, was auf dem Bildschirm dargestellt werden kann, einen eigenen eindeutigen Codepunkt. Einige visuell unterscheidbare Zeichen gelten als von anderen abgeleitet, deshalb behandelt Unicode sie als Grundzeichen plus Modifikator.