Programujeme ve Visual Basic .NET - 24. díl - práce s textovými řetězci

V tomto dílu seriálu si přiblížíme základní operace s textem a možnosti, které VB.NET nabízí pro spojování a řazení textových řetězců.

Na rozdíl od typu System.Array je prostředí BASIC-u tradičně dobře vybaveno pro práci s textovými řetězci. Je to vidět i na tom, že některé textové operace má VB.NET (na rozdíl třeba od C#) implementovány jako součást jazyka, což zjednodušuje a zpřehledňuje např. porovnávání řetězců. Druhá stránkou tohoto faktu je jistá dvoukolejnost prostředí VB.NET pro práci s texty, jelikož pro řadu textových operací VB.NET podporuje kromě objektově orientovaných metod prostředí .NET (které jsou zapouzdřené do třídy System.String a tříd jmenného prostoru System.Text) i původní funkce (které jsou součástí modulu Microsoft.VisualBasic.String). Klasické funkce výhodně využijeme především tehdy, pokud pracujeme s kódem, odladěným v některé ze starších verzí VB.

Protože jsou tyto funkce ve vývojovém prostředí jako je Microsoft Visual Studio, nebo Visual Basic 2005 Express Edition automaticky importovány, lze je zde používat stejně snadno, jako ve starších verzích VB - znamená to ovšem, že pro práci s textovými řetězci máme k dispozici dvě navzájem odlišné skupiny funkcí. S ohledem na předpokládaný vývoj jazyka VB.NET a snazší orientaci ve zdrojových kódech ostatních jazyků .NET (především C#) budou v následujícím výkladu upřednostňovány nativní metody .NET Framework. O nejužitečnějších funkcích z klasické skupiny se ale zmíníme rovněž, protože se běžně vyskytují v ukázkách kódu na internetu a jejich použití je často pro začátečníka intuitivnější a pohodlnější. Občas se tedy budeme muset při výkladu rozhodovat mezi tím, kterou metodu pro daný účel použijeme. Z hlediska výkonu jde o nepodstatnou záležitost, protože většina specializovaných funkcí modulu Microsoft.VisualBasic.String je po překladu bezprostředně mapována na odpovídající funkce .NET Framework.

Spojování řetězců operátory

Operaci spojování dvou či více textových řetězců do jednoho se odborně říká konkatenace a můžeme pro ni používat stejný operátor, jako pro sčítání čísel, čili znaménko "+" (plus). Musíme však mít na paměti, že tento operátor je primárně určen pro sčítání čísel a tak, pokud vypneme silnou typovou kontrolu s použitím direktivy Option Strict Off (viz 16. díl seriálu), VB.NET výsledek přetypuje na číslo, kdykoliv to bude možné, např. ukázka

Option Strict Off
. . . .
Dim a As String = "4", b As Integer = 4
System.Console.WriteLine(a + b)
System.Console.WriteLine(b + a)

vypíše v obou případech číslo 8, zatímco při zapnutí typové kontroly dojde k chybě a pro spojování řetězcových proměnných načítaných z příkazového řádku např. metodou ReadLine() je tento operátor nepoužitelný. Z tohoto důvodu občas bývá výhodnější použít speciální operátor "&" (znak ampersand), který současně zajistí i potřebné přetypování svých operandů na typ System.String(). Upravená ukázka:

Option Strict Off
. . . .
Dim a As String = "4", b As Integer = 4
System.Console.WriteLine(a & b)

potom vypíše řetězec "44" (řetězec b je připojen na konec řetězce a). Podobným způsobem lze spojovat i literály (řetězcové konstanty) a výsledek pak přiřadit do řetězcové proměnné, přičemž jednou operací lze spojit několik řetězců současně:

Dim s As String = "Hello" & ", " & "world" & "!"

V případě spojování delších literálů lze výše uvedený zápis lze často  zpřehlednit s použitím znaku oddělovače řádku, který tvoří kombinace mezery a znaku podtržítka (" _").

Délka příkazů v čitelně formátovaném kódu by totiž neměla přesáhnout cca 80 znaků, což je standardní šířka řádku textové konzole:

Dim s As String = "Hello" & _
    ", "    & _
    "world" & _
    "!"

Složený operátor přiřazení

V případě, že výsledek spojování řetězců zapisujeme v jednom z operandů, lze operátor konkatenace ("&") lze výhodně spojit s operátorem přiřazení ("=") do složeného operátoru "=&".

Namísto zápisu

s = a & b

lze o něco stručneji tutéž operaci zapsat jako

s &= b

Stejný způsob zápisu můžeme využít i pro připojování literálu (řetězcové konstanty) k obsahu řetězcové proměnné a tímto způsobem skládat dohromady libovolně dlouhé řetězce (délka příkazu i počet znaků oddělovače řádků je překladačem VB.NET omezen). Další výhodou tohoto způsobu spojování řetězců oproti použití oddělovače řádku je, že umožňuje jednotlivé řádky doplnit komentářem.

Dim s As String  ` deklarace řetězce
s  = "Hello"  ` inicializace řetězce
s &= ", "  ` připojení řetězce
s &= "world"  ` 
s &= "!"  ` 

Tento způsob zápisu s běžně používá v jazycích odvozených od C, ale pro VB.NET je nový a ve starších verzích VB se s ním nesetkáme. Lze jej uplatnit i pro další složené operátory, používající matematické operace, např. "+=" (přičtení s přiřazením), "-=" (odčítání s přiřazením), "*=" (násobení s přiřazením), logické operátory atd.

Metody Concat() a Join()

Pro spojování většího počtu drobných řetězců do jednoho nemusí být ani shora uvedený zkrácený zápis přehledný a efektivní. Pro tyto situace nám prostředí .NET nabízí metodu System.String.Concat(), která může přebrat až čtyři řetězce současně jako individuální parametry, pro větší počet řetězců současně pak pole parametrů, nebo pole řetězců. Několik možných způsobů použití metody Concat(), představujících ekvivalent shora uvedené ukázky spojující čtyři textové fragmenty do jednoho v rámci jediného příkazu jsou demonstrovány dále:

Dim  aTest As String() = {"Hello", ", ", "world", "!"}
System.Console.WriteLine(String.Concat(aTest))
System.Console.WriteLine(String.Concat("Hello", ", ", "world", "!"))
System.Console.WriteLine(String.Concat(New String(){"Hello", ", ", "world", "!"}))

Možnosti spojování řetězců v prostředí .NET tím nejsou zdaleka vyčerpány. Pro spojení pole řetězců do jediného můžeme použít metodu System.String.Join(), která kromě toho umožní specifikovat textový řetězec (tzv. separátor), kterými budou jednotlivé textové řetězce po spojení prokládány (separovány). Kompletní dokumentace metody System.String.Join() zahrnuje další kombinace parametrů. V případě, že zadáme prázdný řetězec, je výsledek totožný s metodou System.String.Concat(). Metoda System.String.Join() se mj. hodí pro vypsání obsahu pole do konzole jediným příkazem:

Dim  aTest$() = {"Hello", ", ", "world", "!"}
System.Console.WriteLine(String.Join("", aTest))
System.Console.WriteLine(String.Join(System.Environment.NewLine, aTest))

Shora uvedený fragment kódu do konzole postupně vypíše:

Hello, world!
Hello
,
world
!

Jak již bylo zmíněno v předchozím dílu seriálu, při přiřazení řetězcové proměnné do proměnné se s paměti vyhrazuje (alokuje) paměť pro novou kopii, což činí opakované manipulace s řetězci značně neefektivní. Prostředí .NET tento nedostatek řeší pomocí třídy System.Text.StringBuilder, která je optimalizována na rychlou práci, např. spojování a záměny delších řetězců. Jelikož práce  třídou StringBuilder je poněkud složitější a vzhledem k tomu, že VB.NET pracuje s řetězci i na základní úrovni podstatně efektivněji, než předchozí verze VB, nemusí být přínos třídy StringBuilder při běžné práci nijak zásadní. Proto se s třídou StringBuilder  seznámíme později v rámci popisu pokročilejších postupů pro práci s řetězci a tříd jmenného prostoru System.Text.

Porovnávání řetězců, znaková sada ASCII

Řetězce lze v zásadě porovnávat jako libovolné jiné objekty pomocí zabudované metody Equals každého objektu. Problém je, že řetězce jsou pomocí této metody porovnávány na nejnižší úrovni, tj. bajt po bajtu, což může v praxi vést k záludným chybám, jelikož v závislosti na použitém kódování mohou být textové řetězce tvořeny stejnými znaky, ačkoliv je reprezentují různé bajty a naopak.

Dim a$ = "Tеst", b$ = "Test"
System.Console.Write(a = b)
System.Console.Write(a.Equals(b))

Zkopírujeme-li tuto ukázku do editoru jako je SnippetCompiler přímo z této stránky, ihned zjistíme, že se řetězce nerovnají, protože písmeno "e" v obou případech reprezentují různé znaky.

Alfanumerické znaky latinky bez diakritiky s kódem 0 - 128 (7-bitů) náleží do tzv. sady ASCII znaků (ASCII = "American Standard Code for Information Interchange"). Jelikož se tabulka ASCII znaků vyskytuje na mnoha místech internetu, není nutné ji zde citovat. Protože znaky s kódem vyšším než 31 jsou přímo zobrazitelné a tisknutelné, můžeme si sady ASCII v případě potřeby snadno vypsat do konzole např. pomocí ukázky níže:

Module Main
  Sub Main
    For i As Integer = 32 To 128
      System.Console.WriteLine("ASCII({0}) = ""{1}""", i, System.Convert.ToChar(i))
    Next
  End Sub
End Module

Porovnávací operátory

Jelikož sada znaků ASCII je podmnožina znakové sady Unicode, rozšířením horní meze intervalu na 216 - 1  (65535) vypíšeme do konzole kompletní znakovou sadu Unicode, většina znaků však nebude výchozím písmem systémové konzole Windows zobrazitelná a vypíše se jako otazníky. Z výsledku vidíme, že pořadí znaků v uzavřených intervalech <65, 90> a <97, 122> odpovídá přirozenému řazení znaků velké a malé abecedy. Pořadí znaků podle kódu znaků (číselné hodnoty bajtů, které je tvoří) určuje přirozené řazení řetězců. Ten řetězec, který obsahuje na dané pozici znak s nižší hodnotou v bytech je řazen jako první a lze jej považovat za menší.  K porovnání znaků a textových řetězců tedy můžeme použít skupinu porovnávacích (relačních) operátorů "=" (rovná se...), "<>" (nerovná se...), ""<" (menší než...), ">" (větší než...), "<=" (menší nebo rovno než...) a ">=" (větší nebo rovno než...) podobně, jako pro číselné datové typy. Proto také výsledek porovnání literálů  "a"  <  "b" bude True (znak "a" leží v tabulce znaků níže, než znak "b", má nižší hodnotu v bytech - je proto "menší"):

Dim a As String = "a", b As String = "b"
System.Console.WriteLine(a = b)   ` False
System.Console.WriteLine(a < b)   ` True
System.Console.WriteLine(a > b)   ` False
System.Console.WriteLine(a <> b)   ` True

Porovnání řetězců tvořených více znaky probíhá tak, že případě, že jsou první znaky řetězců shodné, použije se pro porovnání dvojice znaků na druhé, třetím místě, atd. dokud není nalezen rozdílná dvojice znaků. Pokud je přitom předčasně dosaženo konce kratšího řetězce, dostává nižší hodnotu - jinak shodný kratší řetězec je považován za "menší".

System.Console.WriteLine("aaaaaa" > "aaaaa")  ` True

Kromě standardních relačních operátorů VB.NET podporuje pro rozšířené porovnání řetězců operátor Like, který slouží ke stanovení, zda daný řetězec obsahuje danou posloupnost znaků, tzv. vzor (pattern) a může tedy posloužit například k zjištění, zda je jeden řetězec obsažen uvnitř druhého. Použití tohoto operátoru je poněkud složitější a proto se s ním seznámíme později při výkladu tzv. regulárních výrazů.

Příkaz Option Compare

Pro řazení může mít v praxi význam ignorovat velikost písmen, odpovídající znaky velké a malé abecedy se tak při řazení dostanou k sobě: a-A-b-B-c-C.... Způsob porovnávání řetězců můžeme nastavit pomocí parametru překladače vbc.exe a na úrovni modulu přepnout použitím speciálního příkazu (tzv. direktivy překladače) Option Compare. Direktiva Option Compare může nabývat celkem tří hodnot: Option Compare Binary (výchozí), Option Compare Text a Option Compare Database a podobně jako direktivy Option Strict a Option Explicit ji lze uvádět pouze jednou v záhlaví zdrojového souboru před všemi ostatními příkazy. Pro porovnání řetězců nezávislé na velikosti znaků (tzv. "case insensitive") použijeme příkaz Option Compare Text:

Option Compare Text
Module Main
  Sub Main
    Dim s1 As String = "A", s2 As String = "a"
    System.Console.WriteLine(s1 = s2)   ` True
  End Sub
End Module

Výsledkem použití direktivy Option Compare Text je, že se původní řazení znaků

A < B < E < Z < a < b < e < z < À < Ê < Ø < à < ê < ø

změní na

(A=a) < (À = à) < (B=b) < (E=e) < (Ê = ê) < (Z=z) < (Ø = ø)

Je důležité si uvědomit, že textové řazení řetězců na rozdíl od binárního pracuje s napevno definovanými dvojicemi znaků, které jsou párovány ve znakové sadě. Proto je tento způsob závislý na použité znakové sadě, popř. národním prostředí, ve kterém je tato sada znaků používána jako výchozí a ve kterém naše aplikace běží  (přesněji třídě System.Globalization.CultureInfo, která je přiřazena kontextu instrukční fronty procesoru - tzv. vlákna, nebo-li threadu, spojeného s procesem aplikace. Práce se znakovými sadami a národním prostředím je předmětem tvorby jazykově nezávislých aplikací a jelikož přesahuje rozsah úvodního kursu VB.NET, nebudeme se jí prozatím věnovat.

Metoda Compare(), CompareOrdinal()

Jak uvidíme dále, nastavení příkazu Option Compare ovlivní současně celou skupinu metod pracujících s pořadím řetězců, např. funkce pro prohledávání, třídění a řazení textových řetězců. V případě, že potřebujeme využít tento způsob jen na úrovni jednotlivého příkazu, můžeme využít metodu System.String.Compare(retezec1, retezec2, volba_compare_text), která pomocí třetího parametru umožňuje výslovně určit , zda porovnání řetězců bude probíhat s přihlédnutím k velikosti znaků, nebo ne:

Dim s1 As String = "A", s2 As String = "a"
System.Console.WriteLine(System.String.Compare(s1, s2, True))   ` vrátí 0, Compare Text zapnuto
System.Console.WriteLine(System.String.Compare(s1, s2, False))   ` vrátí 1, Compare Text vypnuto

Metoda System.String.Compare() nevrací pravdivostní hodnotu True / False, ale hodnotu typu System.Int32 se znaménkem v rozsahu <-1 - 1> podle nastavení třetího parametru a toho, zda je první z operandů menší nebo větší než druhý. Jedině v případě, že jsou si oba parametry rovny, vrací nulu.

V případě, že chceme naopak ignorovat nastavení překladače nebo direktivy Option Compare Text, můžeme použít metodu System.String.CompareOrdinal(retezec1, retezec2), která za všech okolností provádí "case sensitive" porovnání řetězců - tedy s rozlišením velikosti písmen a nevyžaduje tudíž třetí parametr:

Dim s1 As String = "A", s2 As String = "a"
System.Console.WriteLine(System.String.CompareOrdinal(s1, s2))   ` vrátí -32, bytová hodnota "A" je o 32 nižší, než hodnota znaku "a"

Metoda System.String.CompareOrdinal() vrací hodnotu z intevalu <-65535 - 65355>, udávající vzájemný rozdíl bytové hodnoty řetězců v bajtech. Jak metoda Compare(), tak metoda CompareOrdinal() podporují několik dalších parametrů, se kterými je vhodné se blíže seznámit v off-line, nebo on-line dokumentaci VB.NET.

Stručné shrnutí

Jelikož prostředí VB.NET tradičně podporuje řadu funkcí pro práci s řetězci, setkáme se při textových operacích porovnávání a spojování řetězců jak s oeprátory a funkcemi VB.NET, tak s metodami prostředí .NET Framework. Jejich výsledek často závisí na nastavení prostředí pomocí direktivy Option Compare, nebo na parametru, který určuje, zda se má při řazení přihlédnout ke dvojicím znaků, tvořících páry velkých a malých písmen národní abecedy. Výsledek řazení pak závisí na výchozí znakové sadě národního prostředí, ve kterém právě běží naše aplikace.

V příštím dílu seriálu dokončíme úvod do základních funkcí VB.NET pro práci s textem přehledem zbývajících metod třídy System.String.

Diskuze (58) Další článek: Autor stránek Napster.no odsouzen za odkazy na MP3

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,